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 }