File indexing completed on 2024-05-12 15:58:36
0001 /* 0002 * This file is part of Krita 0003 * 0004 * SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger@cberger.net> 0005 * SPDX-FileCopyrightText: 2009 Edward Apap <schumifer@hotmail.com> 0006 * SPDX-FileCopyrightText: 2010 Marc Pegon <pe.marc@free.fr> 0007 * 0008 * SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 0012 #include "kis_perspectivetransform_worker.h" 0013 0014 #include <QMatrix4x4> 0015 #include <QTransform> 0016 #include <QVector3D> 0017 #include <QPolygonF> 0018 0019 #include <KoUpdater.h> 0020 #include <KoColor.h> 0021 #include <KoCompositeOpRegistry.h> 0022 0023 #include "kis_paint_device.h" 0024 #include "kis_perspective_math.h" 0025 #include "kis_random_accessor_ng.h" 0026 #include "kis_random_sub_accessor.h" 0027 #include "kis_selection.h" 0028 #include <kis_iterator_ng.h> 0029 #include "krita_utils.h" 0030 #include "kis_progress_update_helper.h" 0031 #include "kis_painter.h" 0032 #include "kis_image.h" 0033 #include "kis_algebra_2d.h" 0034 0035 0036 KisPerspectiveTransformWorker::KisPerspectiveTransformWorker(KisPaintDeviceSP dev, QPointF center, double aX, double aY, double distance, bool cropDst, KoUpdaterPtr progress) 0037 : m_dev(dev), m_progressUpdater(progress), m_cropDst(cropDst) 0038 0039 { 0040 QMatrix4x4 m; 0041 m.rotate(180. * aX / M_PI, QVector3D(1, 0, 0)); 0042 m.rotate(180. * aY / M_PI, QVector3D(0, 1, 0)); 0043 0044 QTransform project = m.toTransform(distance); 0045 QTransform t = QTransform::fromTranslate(center.x(), center.y()); 0046 0047 QTransform forwardTransform = t.inverted() * project * t; 0048 0049 init(forwardTransform); 0050 } 0051 0052 KisPerspectiveTransformWorker::KisPerspectiveTransformWorker(KisPaintDeviceSP dev, const QTransform &transform, bool cropDst, KoUpdaterPtr progress) 0053 : m_dev(dev), m_progressUpdater(progress), m_cropDst(cropDst) 0054 { 0055 init(transform); 0056 } 0057 0058 void KisPerspectiveTransformWorker::fillParams(const QRectF &srcRect, 0059 const QRect &dstBaseClipRect, 0060 KisRegion *dstRegion, 0061 QPolygonF *dstClipPolygon) 0062 { 0063 QPolygonF bounds = srcRect; 0064 QPolygonF newBounds = m_forwardTransform.map(bounds); 0065 0066 QRectF clipRect = dstBaseClipRect; 0067 0068 if (!m_cropDst) { 0069 clipRect |= srcRect; 0070 clipRect = KisAlgebra2D::blowRect(clipRect, 3.0); 0071 } 0072 0073 newBounds = newBounds.intersected(clipRect); 0074 QPainterPath path; 0075 path.addPolygon(newBounds); 0076 *dstRegion = KritaUtils::splitPath(path); 0077 *dstClipPolygon = newBounds; 0078 } 0079 0080 void KisPerspectiveTransformWorker::init(const QTransform &transform) 0081 { 0082 m_isIdentity = transform.isIdentity(); 0083 m_isTranslating = transform.type() == QTransform::TxTranslate; 0084 0085 m_forwardTransform = transform; 0086 m_backwardTransform = transform.inverted(); 0087 0088 if (m_dev) { 0089 m_srcRect = m_dev->exactBounds(); 0090 0091 QPolygonF dstClipPolygonUnused; 0092 0093 fillParams(m_srcRect, 0094 m_dev->defaultBounds()->bounds(), 0095 &m_dstRegion, 0096 &dstClipPolygonUnused); 0097 } 0098 } 0099 0100 KisPerspectiveTransformWorker::~KisPerspectiveTransformWorker() 0101 { 0102 } 0103 0104 void KisPerspectiveTransformWorker::setForwardTransform(const QTransform &transform) 0105 { 0106 init(transform); 0107 } 0108 0109 0110 struct BilinearWrapper 0111 { 0112 using SrcAccessorSP = KisRandomSubAccessorSP; 0113 0114 BilinearWrapper(KisPaintDeviceSP device) 0115 : m_accessor(device->createRandomSubAccessor()) 0116 { 0117 } 0118 0119 void samplePixel(const QPointF &pt, quint8 *dst) { 0120 m_accessor->moveTo(pt.x(), pt.y()); 0121 m_accessor->sampledOldRawData(dst); 0122 } 0123 0124 KisRandomSubAccessorSP m_accessor; 0125 }; 0126 0127 struct NearestNeighbourWrapper 0128 { 0129 using SrcAccessorSP = KisRandomAccessorSP; 0130 0131 NearestNeighbourWrapper(KisPaintDeviceSP device) 0132 : m_accessor(device->createRandomConstAccessorNG()), 0133 m_pixelSize(device->pixelSize()) 0134 { 0135 } 0136 0137 void samplePixel(const QPointF &pt, quint8 *dst) { 0138 m_accessor->moveTo(qRound(pt.x()), qRound(pt.y())); 0139 memcpy(dst, m_accessor->oldRawData(), m_pixelSize); 0140 } 0141 0142 KisRandomConstAccessorSP m_accessor; 0143 int m_pixelSize; 0144 }; 0145 0146 template <class SrcAccessorWrapper> 0147 void KisPerspectiveTransformWorker::runImpl() 0148 { 0149 KIS_ASSERT_RECOVER_RETURN(m_dev); 0150 0151 if (m_isIdentity) return; 0152 0153 // TODO: check if this optimization is possible. The only blocking issue might be if 0154 // some other thread also accesses this device (which should not be the case, 0155 // theoretically 0156 // 0157 // if (m_isTranslating) { 0158 // m_dev->moveTo(m_dev->offset() + QPoint(qRound(m_forwardTransform.dx()), qRound(m_forwardTransform.dy()))); 0159 // return; 0160 // } 0161 0162 KisPaintDeviceSP cloneDevice = new KisPaintDevice(*m_dev.data()); 0163 0164 // Clear the destination device, since all the tiles are already 0165 // shared with cloneDevice 0166 m_dev->clear(); 0167 0168 KIS_ASSERT_RECOVER_NOOP(!m_isIdentity); 0169 0170 KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, m_dstRegion.rectCount()); 0171 0172 SrcAccessorWrapper srcAcc(cloneDevice); 0173 KisRandomAccessorSP accessor = m_dev->createRandomAccessorNG(); 0174 0175 Q_FOREACH (const QRect &rect, m_dstRegion.rects()) { 0176 for (int y = rect.y(); y < rect.y() + rect.height(); ++y) { 0177 for (int x = rect.x(); x < rect.x() + rect.width(); ++x) { 0178 0179 QPointF dstPoint(x, y); 0180 QPointF srcPoint = m_backwardTransform.map(dstPoint); 0181 0182 if (m_srcRect.contains(srcPoint)) { 0183 accessor->moveTo(dstPoint.x(), dstPoint.y()); 0184 srcAcc.samplePixel(srcPoint, accessor->rawData()); 0185 } 0186 } 0187 } 0188 progressHelper.step(); 0189 } 0190 } 0191 0192 void KisPerspectiveTransformWorker::run(SampleType sampleType) 0193 { 0194 if (sampleType == Bilinear) { 0195 runImpl<BilinearWrapper>(); 0196 } else { 0197 runImpl<NearestNeighbourWrapper>(); 0198 } 0199 } 0200 0201 void KisPerspectiveTransformWorker::runPartialDst(KisPaintDeviceSP srcDev, 0202 KisPaintDeviceSP dstDev, 0203 const QRect &dstRect) 0204 { 0205 KIS_SAFE_ASSERT_RECOVER_RETURN(srcDev->pixelSize() == dstDev->pixelSize()); 0206 KIS_SAFE_ASSERT_RECOVER_NOOP(*srcDev->colorSpace() == *dstDev->colorSpace()); 0207 0208 QRectF srcClipRect = srcDev->exactBounds() | srcDev->defaultBounds()->imageBorderRect(); 0209 if (srcClipRect.isEmpty()) return; 0210 0211 if (m_isIdentity || m_isTranslating) { 0212 KisPainter gc(dstDev); 0213 gc.setCompositeOpId(COMPOSITE_COPY); 0214 gc.bitBlt(dstRect.topLeft(), srcDev, m_backwardTransform.mapRect(dstRect)); 0215 } else { 0216 KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, dstRect.height()); 0217 0218 KisRandomSubAccessorSP srcAcc = srcDev->createRandomSubAccessor(); 0219 KisRandomAccessorSP accessor = dstDev->createRandomAccessorNG(); 0220 0221 for (int y = dstRect.y(); y < dstRect.y() + dstRect.height(); ++y) { 0222 for (int x = dstRect.x(); x < dstRect.x() + dstRect.width(); ++x) { 0223 0224 QPointF dstPoint(x, y); 0225 QPointF srcPoint = m_backwardTransform.map(dstPoint); 0226 0227 if (srcClipRect.contains(srcPoint) || srcDev->defaultBounds()->wrapAroundMode()) { 0228 accessor->moveTo(dstPoint.x(), dstPoint.y()); 0229 srcAcc->moveTo(srcPoint.x(), srcPoint.y()); 0230 srcAcc->sampledOldRawData(accessor->rawData()); 0231 } 0232 } 0233 progressHelper.step(); 0234 } 0235 } 0236 } 0237 0238 QTransform KisPerspectiveTransformWorker::forwardTransform() const 0239 { 0240 return m_forwardTransform; 0241 } 0242 0243 QTransform KisPerspectiveTransformWorker::backwardTransform() const 0244 { 0245 return m_backwardTransform; 0246 }