File indexing completed on 2024-05-12 15:58:46

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