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