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