File indexing completed on 2024-05-19 04:26:38

0001 /*
0002  *  SPDX-FileCopyrightText: 2004 Michael Thaler <michael.thaler@physik.tu-muenchen.de> filters
0003  *  SPDX-FileCopyrightText: 2005-2007 C. Boemann <cbo@boemann.dk>
0004  *  SPDX-FileCopyrightText: 2005, 2010 Boudewijn Rempt <boud@valdyas.org>
0005  *  SPDX-FileCopyrightText: 2010 Marc Pegon <pe.marc@free.fr>
0006  *  SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73@gmail.com>
0007  *
0008  *  SPDX-License-Identifier: GPL-2.0-or-later
0009  */
0010 
0011 #include "kis_transform_worker.h"
0012 
0013 #include <qmath.h>
0014 #include <klocalizedstring.h>
0015 
0016 #include <QTransform>
0017 
0018 #include <KoColorSpace.h>
0019 #include <KoCompositeOpRegistry.h>
0020 #include <KoColor.h>
0021 
0022 #include "kis_paint_device.h"
0023 #include "kis_debug.h"
0024 #include "kis_selection.h"
0025 #include "kis_iterator_ng.h"
0026 #include "kis_random_accessor_ng.h"
0027 #include "kis_filter_strategy.h"
0028 #include "kis_painter.h"
0029 #include "kis_filter_weights_applicator.h"
0030 #include "kis_progress_update_helper.h"
0031 #include "kis_pixel_selection.h"
0032 #include "kis_image.h"
0033 
0034 
0035 KisTransformWorker::KisTransformWorker(KisPaintDeviceSP dev,
0036                                        double xscale, double yscale,
0037                                        double xshear, double yshear,
0038                                        double xshearOrigin, double yshearOrigin,
0039                                        double rotation,
0040                                        qreal xtranslate, qreal ytranslate,
0041                                        KoUpdaterPtr progress,
0042                                        KisFilterStrategy *filter)
0043 {
0044     m_dev = dev;
0045     m_xscale = xscale;
0046     m_yscale = yscale;
0047     m_xshear = xshear;
0048     m_yshear = yshear;
0049     m_xshearOrigin = xshearOrigin;
0050     m_yshearOrigin = yshearOrigin;
0051     m_rotation = rotation,
0052     m_xtranslate = xtranslate;
0053     m_ytranslate = ytranslate;
0054     m_progressUpdater = progress;
0055     m_filter = filter;
0056 }
0057 
0058 KisTransformWorker::~KisTransformWorker()
0059 {
0060 }
0061 
0062 QTransform KisTransformWorker::transform() const
0063 {
0064     QTransform TS = QTransform::fromTranslate(m_xshearOrigin, m_yshearOrigin);
0065     QTransform S; S.shear(0, m_yshear); S.shear(m_xshear, 0);
0066     QTransform SC = QTransform::fromScale(m_xscale, m_yscale);
0067     QTransform R; R.rotateRadians(m_rotation);
0068     QTransform T = QTransform::fromTranslate(m_xtranslate, m_ytranslate);
0069 
0070     return TS.inverted() * S * TS * SC * R * T;
0071 }
0072 
0073 void KisTransformWorker::transformPixelSelectionOutline(KisPixelSelectionSP pixelSelection) const
0074 {
0075     if (pixelSelection->outlineCacheValid()) {
0076         QPainterPath outlineCache = pixelSelection->outlineCache();
0077         pixelSelection->setOutlineCache(transform().map(outlineCache));
0078     }
0079 }
0080 
0081 QRect rotateWithTf(int rotation, KisPaintDeviceSP dev,
0082                    QRect boundRect,
0083                    KoUpdaterPtr progressUpdater,
0084                    int portion)
0085 {
0086     qint32 pixelSize = dev->pixelSize();
0087     QRect r(boundRect);
0088 
0089     KisPaintDeviceSP tmp = new KisPaintDevice(dev->colorSpace());
0090     tmp->prepareClone(dev);
0091 
0092     KisRandomAccessorSP devAcc = dev->createRandomAccessorNG();
0093     KisRandomAccessorSP tmpAcc = tmp->createRandomAccessorNG();
0094     KisProgressUpdateHelper progressHelper(progressUpdater, portion, r.height());
0095 
0096     QTransform tf;
0097     tf = tf.rotate(rotation);
0098 
0099     int ty = 0;
0100     int tx = 0;
0101 
0102     for (qint32 y = r.y(); y <= r.height() + r.y(); ++y) {
0103         for (qint32 x = r.x(); x <= r.width() + r.x(); ++x) {
0104             tf.map(x, y, &tx, &ty);
0105             devAcc->moveTo(x, y);
0106             tmpAcc->moveTo(tx, ty);
0107 
0108             memcpy(tmpAcc->rawData(), devAcc->rawData(), pixelSize);
0109         }
0110         progressHelper.step();
0111     }
0112 
0113     dev->makeCloneFrom(tmp, tmp->region().boundingRect());
0114     return r;
0115 }
0116 
0117 QRect KisTransformWorker::rotateRight90(KisPaintDeviceSP dev,
0118                                         QRect boundRect,
0119                                         KoUpdaterPtr progressUpdater,
0120                                         int portion)
0121 {
0122     QRect r = rotateWithTf(90, dev, boundRect, progressUpdater, portion);
0123     dev->moveTo(dev->x() - 1, dev->y());
0124     return QRect(- r.top() - r.height(), r.x(), r.height(), r.width());
0125 }
0126 
0127 QRect KisTransformWorker::rotateLeft90(KisPaintDeviceSP dev,
0128                                        QRect boundRect,
0129                                        KoUpdaterPtr progressUpdater,
0130                                        int portion)
0131 {
0132     QRect r = rotateWithTf(270, dev, boundRect, progressUpdater, portion);
0133     dev->moveTo(dev->x(), dev->y() - 1);
0134     return QRect(r.top(), - r.x() - r.width(), r.height(), r.width());
0135 }
0136 
0137 QRect KisTransformWorker::rotate180(KisPaintDeviceSP dev,
0138                                     QRect boundRect,
0139                                     KoUpdaterPtr progressUpdater,
0140                                     int portion)
0141 {
0142     QRect r = rotateWithTf(180, dev, boundRect, progressUpdater, portion);
0143     dev->moveTo(dev->x() - 1, dev->y() -1);
0144     return QRect(- r.x() - r.width(), - r.top() - r.height(), r.width(), r.height());
0145 }
0146 
0147 bool KisTransformWorker::forceSubPixelTranslation() const
0148 {
0149     return m_forceSubPixelTranslation;
0150 }
0151 
0152 void KisTransformWorker::setForceSubPixelTranslation(bool value)
0153 {
0154     m_forceSubPixelTranslation = value;
0155 }
0156 
0157 template <class iter> void calcDimensions(QRect rc, qint32 &srcStart, qint32 &srcLen, qint32 &firstLine, qint32 &numLines);
0158 
0159 template <> void calcDimensions <KisHLineIteratorSP>
0160 (QRect rc, qint32 &srcStart, qint32 &srcLen, qint32 &firstLine, qint32 &numLines)
0161 {
0162     srcStart = rc.x();
0163     srcLen = rc.width();
0164     firstLine = rc.y();
0165     numLines = rc.height();
0166 }
0167 
0168 template <> void calcDimensions <KisVLineIteratorSP>
0169 (QRect rc, qint32 &srcStart, qint32 &srcLen, qint32 &firstLine, qint32 &numLines)
0170 {
0171     srcStart = rc.y();
0172     srcLen = rc.height();
0173     firstLine = rc.x();
0174     numLines = rc.width();
0175 
0176 }
0177 
0178 template <class iter>
0179 void updateBounds(QRect &boundRect,
0180                   const KisFilterWeightsApplicator::LinePos &newBounds);
0181 
0182 template <>
0183 void updateBounds<KisHLineIteratorSP>(QRect &boundRect, const KisFilterWeightsApplicator::LinePos &newBounds)
0184 {
0185     boundRect.setLeft(newBounds.start());
0186     boundRect.setWidth(newBounds.size());
0187 }
0188 
0189 template <>
0190 void updateBounds<KisVLineIteratorSP>(QRect &boundRect, const KisFilterWeightsApplicator::LinePos &newBounds)
0191 {
0192     boundRect.setTop(newBounds.start());
0193     boundRect.setHeight(newBounds.size());
0194 }
0195 
0196 template <class T>
0197 void KisTransformWorker::transformPass(KisPaintDevice *src, KisPaintDevice *dst,
0198                                        double floatscale, double shear, double dx,
0199                                        KisFilterStrategy *filterStrategy,
0200                                        int portion)
0201 {
0202     bool clampToEdge = shear == 0.0;
0203 
0204     qint32 srcStart, srcLen, firstLine, numLines;
0205     calcDimensions<T>(m_boundRect, srcStart, srcLen, firstLine, numLines);
0206 
0207     KisProgressUpdateHelper progressHelper(m_progressUpdater, portion, numLines);
0208     KisFilterWeightsBuffer buf(filterStrategy, qAbs(floatscale));
0209     KisFilterWeightsApplicator applicator(src, dst, floatscale, shear, dx, clampToEdge);
0210 
0211     KisFilterWeightsApplicator::LinePos dstBounds;
0212 
0213     for (int i = firstLine; i < firstLine + numLines; i++) {
0214         KisFilterWeightsApplicator::LinePos dstPos;
0215         KisFilterWeightsApplicator::LinePos srcPos(srcStart, srcLen);
0216 
0217         dstPos = applicator.processLine<T>(srcPos, i, &buf, filterStrategy->support(buf.weightsPositionScale().toFloat()));
0218         dstBounds.unite(dstPos);
0219 
0220         progressHelper.step();
0221     }
0222 
0223     updateBounds<T>(m_boundRect, dstBounds);
0224 }
0225 
0226 template<typename T>
0227 void swapValues(T *a, T *b) {
0228     T c = *a;
0229     *a = *b;
0230     *b = c;
0231 }
0232 
0233 bool KisTransformWorker::run()
0234 {
0235     return runPartial(m_dev->exactBounds());
0236 }
0237 
0238 bool KisTransformWorker::runPartial(const QRect &processRect)
0239 {
0240     /* Check for nonsense and let the user know, this helps debugging.
0241     Otherwise the program will crash at a later point, in a very obscure way, probably by division by zero */
0242     /* Note that because KisFilterWeightsBuffer uses KisFixedPoint, we need to check the fixed point as well
0243      * since it has different precision than qreal or double */
0244     KisFixedPoint m_xscale_fixedPoint = KisFixedPoint(m_xscale);
0245     KisFixedPoint m_yscale_fixedPoint = KisFixedPoint(m_yscale);
0246     Q_ASSERT_X(m_xscale != 0 && m_xscale_fixedPoint != 0, "KisTransformer::run() validation step", "xscale == 0");
0247     Q_ASSERT_X(m_yscale != 0 && m_yscale_fixedPoint != 0, "KisTransformer::run() validation step", "yscale == 0");
0248 
0249     // Fallback safety line in case Krita is compiled without ASSERTS
0250     if (m_xscale == 0 || m_yscale == 0 || m_xscale_fixedPoint == 0 || m_yscale_fixedPoint == 0) return false;
0251 
0252     m_boundRect = processRect;
0253 
0254     if (m_boundRect.isNull()) {
0255         if (!m_progressUpdater.isNull()) {
0256             m_progressUpdater->setProgress(100);
0257         }
0258         return true;
0259     }
0260 
0261     double xscale = m_xscale;
0262     double yscale = m_yscale;
0263     double rotation = m_rotation;
0264     qreal xtranslate = m_xtranslate;
0265     qreal ytranslate = m_ytranslate;
0266 
0267     // Apply shearX/Y separately. In Krita it is demanded separately
0268     // most of the times.
0269     if (m_xshear != 0 || m_yshear != 0) {
0270         int portion = 50;
0271 
0272         int dx = - qRound(m_yshearOrigin * yscale * m_xshear);
0273         int dy = - qRound(m_xshearOrigin * xscale * m_yshear);
0274 
0275         bool scalePresent = !(qFuzzyCompare(xscale, 1.0) && qFuzzyCompare(yscale, 1.0));
0276         bool xShearPresent = !qFuzzyCompare(m_xshear, 0.0);
0277         bool yShearPresent = !qFuzzyCompare(m_yshear, 0.0);
0278 
0279         if (scalePresent || (xShearPresent && yShearPresent)) {
0280             transformPass <KisHLineIteratorSP>(m_dev.data(), m_dev.data(), xscale, yscale *  m_xshear, dx, m_filter, portion);
0281             transformPass <KisVLineIteratorSP>(m_dev.data(), m_dev.data(), yscale, m_yshear, dy, m_filter, portion);
0282         } else if (xShearPresent) {
0283             transformPass <KisHLineIteratorSP>(m_dev.data(), m_dev.data(), xscale, m_xshear, dx, m_filter, portion);
0284             m_boundRect.translate(0, dy);
0285             m_dev->moveTo(m_dev->x(), m_dev->y() + dy);
0286         } else if (yShearPresent) {
0287             transformPass <KisVLineIteratorSP>(m_dev.data(), m_dev.data(), yscale, m_yshear, dy, m_filter, portion);
0288             m_boundRect.translate(dx, 0);
0289             m_dev->moveTo(m_dev->x() + dx, m_dev->y());
0290         }
0291 
0292         yscale = 1.;
0293         xscale = 1.;
0294     }
0295 
0296     if (rotation < 0.0) {
0297         rotation = -fmod(-rotation, 2 * M_PI) + 2 * M_PI;
0298     } else {
0299         rotation = fmod(rotation, 2 * M_PI);
0300     }
0301 
0302     int rotQuadrant = int(rotation / (M_PI / 2) + 0.5) & 3;
0303     rotation -= rotQuadrant * M_PI / 2;
0304 
0305 
0306     /**
0307      * We don't check for xtranslate and ytranslate to be integers here, because
0308      * people expect the translation to be lossless, that is, not doing any resampling.
0309      *
0310      * Theoretically, we could implement a separate option to allow translations with
0311      * resampling in the transform tool, but I don't know how useful it would be.
0312      * People who wo pixel art can set scale to something like 99.99% and it should
0313      * do the trick.
0314      *
0315      * See: https://bugs.kde.org/show_bug.cgi?id=445714
0316      */
0317     const bool simpleTranslation =
0318         !m_forceSubPixelTranslation &&
0319         qFuzzyCompare(rotation, 0.0) &&
0320         qFuzzyCompare(xscale, 1.0) &&
0321         qFuzzyCompare(yscale, 1.0);
0322 
0323     int progressTotalSteps = qMax(1, 2 * (!simpleTranslation) + (rotQuadrant != 0));
0324     int progressPortion = 100 / progressTotalSteps;
0325 
0326     /**
0327      * Pre-rotate the image to ensure the actual resampling is done
0328      * for an angle -pi/4...pi/4. This is faster and produces better
0329      * quality.
0330      */
0331     switch (rotQuadrant) {
0332     case 1:
0333         swapValues(&xscale, &yscale);
0334         m_boundRect = rotateRight90(m_dev, m_boundRect, m_progressUpdater, progressPortion);
0335         break;
0336     case 2:
0337         m_boundRect = rotate180(m_dev, m_boundRect, m_progressUpdater, progressPortion);
0338         break;
0339     case 3:
0340         swapValues(&xscale, &yscale);
0341         m_boundRect = rotateLeft90(m_dev, m_boundRect, m_progressUpdater, progressPortion);
0342         break;
0343     default:
0344         /* do nothing */
0345         break;
0346     }
0347 
0348     if (simpleTranslation) {
0349         const int intXTranslate = qRound(xtranslate);
0350         const int intYTranslate = qRound(ytranslate);
0351 
0352         m_boundRect.translate(intXTranslate, intYTranslate);
0353         m_dev->moveTo(m_dev->x() + intXTranslate, m_dev->y() + intYTranslate);
0354     } else {
0355         QTransform SC = QTransform::fromScale(xscale, yscale);
0356         QTransform R; R.rotateRadians(rotation);
0357         QTransform T = QTransform::fromTranslate(xtranslate, ytranslate);
0358         QTransform m = SC * R * T;
0359 
0360         /**
0361          * First X-pass, then Y-pass
0362          *     | a 0 0 |   | 1 d 0 |
0363          * m = | b 1 0 | x | 0 e 0 | (matrices are in Qt's notation)
0364          *     | c 0 1 |   | 0 f 1 |
0365          */
0366         qreal a = m.m11();
0367         qreal b = m.m21();
0368         qreal c = m.m31();
0369         qreal d = m.m12() / m.m11();
0370         qreal e = m.m22() - m.m21() * m.m12() / m.m11();
0371         qreal f = m.m32() - m.m31() * m.m12() / m.m11();
0372 
0373         // First Pass (X)
0374         transformPass <KisHLineIteratorSP>(m_dev.data(), m_dev.data(), a, b, c, m_filter, progressPortion);
0375 
0376         // Second Pass (Y)
0377         transformPass <KisVLineIteratorSP>(m_dev.data(), m_dev.data(), e, d, f, m_filter, progressPortion);
0378 
0379 #if 0
0380         /************************************************************/
0381         /**
0382          * First Y-pass, then X-pass (for testing purposes)
0383          *     | 1 d 0 |   | a 0 0 |
0384          * m = | 0 e 0 | x | b 1 0 | (matrices are in Qt's notation)
0385          *     | 0 f 1 |   | c 0 1 |
0386          */
0387         qreal a = m.m11() - m.m21() * m.m12() / m.m22();
0388         qreal b = m.m21() / m.m22();
0389         qreal c = m.m31() - m.m21() * m.m32() / m.m22();
0390         qreal d = m.m12();
0391         qreal e = m.m22();
0392         qreal f = m.m32();
0393         // First Pass (X)
0394         transformPass <KisHLineIteratorSP>(m_dev.data(), m_dev.data(), a, b, c, m_filter, progressPortion);
0395         // Second Pass (Y)
0396         transformPass <KisVLineIteratorSP>(m_dev.data(), m_dev.data(), e, d, f, m_filter, progressPortion);
0397         /************************************************************/
0398 #endif /* 0 */
0399 
0400 #if 0
0401         /************************************************************/
0402         // Old three-pass implementation (for testing purposes)
0403         yshear = sin(rotation);
0404         xshear = -tan(rotation / 2);
0405         xtranslate -= int(xshear * ytranslate);
0406 
0407         transformPass <KisHLineIteratorSP>(m_dev.data(), m_dev.data(), xscale, yscale*xshear, 0, m_filter, 0);
0408         transformPass <KisVLineIteratorSP>(m_dev.data(), m_dev.data(), yscale, yshear, ytranslate, m_filter, 0);
0409         if (xshear != 0.0) {
0410             transformPass <KisHLineIteratorSP>(m_dev.data(), m_dev.data(), 1.0, xshear, xtranslate, m_filter, 0);
0411         } else {
0412             m_dev->move(m_dev->x() + xtranslate, m_dev->y());
0413             updateBounds <KisHLineIteratorSP>(m_boundRect, 1.0, 0, xtranslate);
0414         }
0415         /************************************************************/
0416 #endif /* 0 */
0417 
0418     }
0419 
0420     if (!m_progressUpdater.isNull()) {
0421         m_progressUpdater->setProgress(100);
0422     }
0423 
0424     /**
0425      * Purge the tiles which might be left after scaling down the
0426      * image
0427      */
0428     m_dev->purgeDefaultPixels();
0429 
0430     return true;
0431 }
0432 
0433 void mirror_impl(KisPaintDeviceSP dev, qreal axis, bool isHorizontal)
0434 {
0435     KIS_ASSERT_RECOVER_RETURN(qFloor(axis) == axis || (axis - qFloor(axis) == 0.5));
0436 
0437     QRect mirrorRect = dev->exactBounds();
0438     if (mirrorRect.width() <= 1) return;
0439 
0440     /**
0441      * We split the total mirror rect into two halves, which lay to
0442      * the 'left' and 'right' from the axis. Effectively, these halves
0443      * should be swapped, but there is a bit of optimization: some
0444      * parts of these portions overlap and some don't. So former ones
0445      * should be really swapped, but the latter ones just moved to the
0446      * other side.
0447      *
0448      * So the algorithm consists of two stages:
0449      *
0450      * 1) Move the non-overlapping portion of the mirror rect to the
0451      *    other side of the axis. The move may be either left-to-right or
0452      *    right-to-left.
0453      *
0454      * 2) Use slow 'swap' operation for the remaining portion of the
0455      *    mirrorRect.
0456      *
0457      * NOTE: the algorithm works with (column, row) coordinates which
0458      *       are mapped to the real (x, y) depending on the value of
0459      *       'isHorizontal' parameter.
0460      */
0461 
0462     int leftStart;
0463     int rightEnd;
0464 
0465     if (isHorizontal) {
0466         leftStart = mirrorRect.x();
0467         rightEnd = mirrorRect.x() + mirrorRect.width();
0468     } else {
0469         leftStart = mirrorRect.y();
0470         rightEnd = mirrorRect.y() + mirrorRect.height();
0471     }
0472 
0473     /**
0474      * If the axis is not aligned, that is crosses some pixel cell, we should just skip this
0475      * column and not process it. Actually, how can we mirror the central single-pixel column?
0476      */
0477     const bool axisNonAligned = qFloor(axis) < axis;
0478 
0479     int leftCenterPoint = qFloor(axis);
0480     int leftEnd = qMin(leftCenterPoint, rightEnd);
0481 
0482     int rightCenterPoint = axisNonAligned ? qCeil(axis) : qFloor(axis);
0483     int rightStart = qMax(rightCenterPoint, leftStart);
0484 
0485     int leftSize = qMax(0, leftEnd - leftStart);
0486     int rightSize = qMax(0, rightEnd - rightStart);
0487 
0488     int maxDistanceToAxis = qMax(leftCenterPoint - leftStart,
0489                            rightEnd - rightCenterPoint);
0490 
0491 
0492     // Main variables for controlling the stages of the algorithm
0493     bool moveLeftToRight = leftSize > rightSize;
0494     int moveAmount = qAbs(leftSize - rightSize);
0495     int swapAmount = qMin(leftSize, rightSize);
0496 
0497     // Initial position of 'left' and 'right' block iterators
0498     int initialLeftCol = leftCenterPoint - maxDistanceToAxis;
0499     int initialRightCol = rightCenterPoint + maxDistanceToAxis - 1;
0500 
0501 
0502     KisRandomAccessorSP leftIt = dev->createRandomAccessorNG();
0503     KisRandomAccessorSP rightIt = dev->createRandomAccessorNG();
0504     const KoColor defaultPixelObject = dev->defaultPixel();
0505     const quint8 *defaultPixel = defaultPixelObject.data();
0506 
0507     const int pixelSize = dev->pixelSize();
0508     QByteArray buf(pixelSize, 0);
0509 
0510     // Map (column, row) -> (x, y)
0511     int rowsRemaining;
0512     int row;
0513 
0514     if (isHorizontal) {
0515         rowsRemaining = mirrorRect.height();
0516         row = mirrorRect.y();
0517     } else {
0518         rowsRemaining = mirrorRect.width();
0519         row = mirrorRect.x();
0520     }
0521 
0522     int leftColPos = 0;
0523     int rightColPos = 0;
0524 
0525     const int &leftX = isHorizontal ? leftColPos : row;
0526     const int &leftY = isHorizontal ? row : leftColPos;
0527 
0528     const int &rightX = isHorizontal ? rightColPos : row;
0529     const int &rightY = isHorizontal ? row : rightColPos;
0530 
0531     while (rowsRemaining) {
0532         leftColPos = initialLeftCol;
0533         rightColPos = initialRightCol;
0534 
0535         int rows = qMin(rowsRemaining, isHorizontal ? leftIt->numContiguousRows(leftY) : leftIt->numContiguousColumns(leftX));
0536         int rowStride = isHorizontal ? leftIt->rowStride(leftX, leftY) : pixelSize;
0537 
0538         if (moveLeftToRight) {
0539             for (int i = 0; i < moveAmount; i++) {
0540                 leftIt->moveTo(leftX, leftY);
0541                 rightIt->moveTo(rightX, rightY);
0542 
0543                 quint8 *leftPtr = leftIt->rawData();
0544                 quint8 *rightPtr = rightIt->rawData();
0545 
0546                 for (int j = 0; j < rows; j++) {
0547                     // left-to-right move
0548                     memcpy(rightPtr, leftPtr, pixelSize);
0549                     memcpy(leftPtr, defaultPixel, pixelSize);
0550 
0551                     leftPtr += rowStride;
0552                     rightPtr += rowStride;
0553                 }
0554 
0555                 leftColPos++;
0556                 rightColPos--;
0557             }
0558         } else {
0559             for (int i = 0; i < moveAmount; i++) {
0560                 leftIt->moveTo(leftX, leftY);
0561                 rightIt->moveTo(rightX, rightY);
0562 
0563                 quint8 *leftPtr = leftIt->rawData();
0564                 quint8 *rightPtr = rightIt->rawData();
0565 
0566                 for (int j = 0; j < rows; j++) {
0567                     // right-to-left move
0568                     memcpy(leftPtr, rightPtr, pixelSize);
0569                     memcpy(rightPtr, defaultPixel, pixelSize);
0570 
0571                     leftPtr += rowStride;
0572                     rightPtr += rowStride;
0573                 }
0574 
0575                 leftColPos++;
0576                 rightColPos--;
0577             }
0578         }
0579 
0580         for (int i = 0; i < swapAmount; i++) {
0581             leftIt->moveTo(leftX, leftY);
0582             rightIt->moveTo(rightX, rightY);
0583 
0584             quint8 *leftPtr = leftIt->rawData();
0585             quint8 *rightPtr = rightIt->rawData();
0586 
0587             for (int j = 0; j < rows; j++) {
0588                 // swap operation
0589                 memcpy(buf.data(), leftPtr, pixelSize);
0590                 memcpy(leftPtr, rightPtr, pixelSize);
0591                 memcpy(rightPtr, buf.data(), pixelSize);
0592 
0593                 leftPtr += rowStride;
0594                 rightPtr += rowStride;
0595             }
0596 
0597             leftColPos++;
0598             rightColPos--;
0599         }
0600 
0601         rowsRemaining -= rows;
0602         row += rows;
0603     }
0604 }
0605 
0606 void KisTransformWorker::mirrorX(KisPaintDeviceSP dev, qreal axis)
0607 {
0608     mirror_impl(dev, axis, true);
0609 }
0610 
0611 void KisTransformWorker::mirrorY(KisPaintDeviceSP dev, qreal axis)
0612 {
0613     mirror_impl(dev, axis, false);
0614 }
0615 
0616 void KisTransformWorker::mirrorX(KisPaintDeviceSP dev)
0617 {
0618     QRect bounds = dev->exactBounds();
0619     mirrorX(dev, bounds.x() + 0.5 * bounds.width());
0620 }
0621 
0622 void KisTransformWorker::mirrorY(KisPaintDeviceSP dev)
0623 {
0624     QRect bounds = dev->exactBounds();
0625     mirrorY(dev, bounds.y() + 0.5 * bounds.height());
0626 }
0627 
0628 void KisTransformWorker::mirror(KisPaintDeviceSP dev, qreal axis, Qt::Orientation orientation)
0629 {
0630     mirror_impl(dev, axis, orientation == Qt::Horizontal);
0631 }
0632 
0633 void KisTransformWorker::offset(KisPaintDeviceSP device, const QPoint& offsetPosition, const QRect& wrapRect)
0634 {
0635     Q_ASSERT(wrapRect == wrapRect.normalized());
0636 
0637     // inspired by gimp offset code, only wrap mode supported
0638     int sx = wrapRect.x();
0639     int sy = wrapRect.y();
0640 
0641     int width = wrapRect.width();
0642     int height = wrapRect.height();
0643 
0644     // offset coords are relative to space wrapRect
0645     int offsetX = offsetPosition.x();
0646     int offsetY = offsetPosition.y();
0647 
0648     while (offsetX < 0)
0649     {
0650         offsetX += width;
0651     }
0652 
0653     while (offsetY < 0)
0654     {
0655         offsetY += height;
0656     }
0657 
0658     if ((offsetX == 0) && (offsetY == 0))
0659     {
0660         return;
0661     }
0662 
0663     KisPaintDeviceSP offsetDevice = new KisPaintDevice(device->colorSpace());
0664 
0665     int srcX = 0;
0666     int srcY = 0;
0667 
0668     int destX = offsetX;
0669     int destY = offsetY;
0670 
0671     width = qBound<int>(0, width - offsetX, width);
0672     height = qBound<int>(0, height - offsetY, height);
0673 
0674     if ((width != 0) && (height != 0)) {
0675         // convert back to paint device space
0676         KisPainter::copyAreaOptimized(QPoint(destX + sx, destY + sy), device, offsetDevice, QRect(srcX + sx, srcY + sy, width, height));
0677     }
0678 
0679     srcX = wrapRect.width() - offsetX;
0680     srcY = wrapRect.height() - offsetY;
0681 
0682     destX = (srcX + offsetX) % wrapRect.width();
0683     destY = (srcY + offsetY) % wrapRect.height();
0684 
0685     if (offsetX != 0 && offsetY != 0) {
0686           KisPainter::copyAreaOptimized(QPoint(destX + sx, destY + sy), device, offsetDevice, QRect(srcX + sx, srcY + sy, offsetX, offsetY));
0687     }
0688 
0689     if (offsetX != 0) {
0690         KisPainter::copyAreaOptimized(QPoint(destX + sx, (destY + offsetY) + sy), device, offsetDevice, QRect(srcX + sx, 0 + sy, offsetX, wrapRect.height() - offsetY));
0691     }
0692 
0693     if (offsetY != 0) {
0694         KisPainter::copyAreaOptimized(QPoint((destX + offsetX) + sx, destY + sy), device, offsetDevice, QRect(0 + sx, srcY + sy, wrapRect.width() - offsetX, offsetY));
0695     }
0696 
0697     // bitblt the result back
0698     QRect resultRect(sx, sy, wrapRect.width(), wrapRect.height());
0699     KisPainter::copyAreaOptimized(resultRect.topLeft(), offsetDevice, device, resultRect);
0700 }
0701 
0702