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

0001 /*
0002  *  SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_safe_transform.h"
0008 
0009 #include <QTransform>
0010 #include <QLineF>
0011 #include <QPolygonF>
0012 
0013 
0014 #include "kis_debug.h"
0015 #include "kis_algebra_2d.h"
0016 
0017 
0018 
0019 struct Q_DECL_HIDDEN KisSafeTransform::Private
0020 {
0021     bool needsClipping = true;
0022 
0023     QRect bounds;
0024     QTransform forwardTransform;
0025     QTransform backwardTransform;
0026 
0027     QPolygonF srcClipPolygon;
0028     QPolygonF dstClipPolygon;
0029 
0030     bool getHorizon(const QTransform &t, QLineF *horizon) {
0031         static const qreal eps = 1e-10;
0032 
0033         QPointF vanishingX(t.m11() / t.m13(), t.m12() / t.m13());
0034         QPointF vanishingY(t.m21() / t.m23(), t.m22() / t.m23());
0035 
0036         if (qAbs(t.m13()) < eps && qAbs(t.m23()) < eps) {
0037             *horizon = QLineF();
0038             return false;
0039         } else if (qAbs(t.m23()) < eps) {
0040             QPointF diff = t.map(QPointF(0.0, 10.0)) - t.map(QPointF());
0041             vanishingY = vanishingX + diff;
0042         } else if (qAbs(t.m13()) < eps) {
0043             QPointF diff = t.map(QPointF(10.0, 0.0)) - t.map(QPointF());
0044             vanishingX = vanishingY + diff;
0045         }
0046 
0047         *horizon = QLineF(vanishingX, vanishingY);
0048         return true;
0049     }
0050 
0051     qreal getCrossSign(const QLineF &horizon, const QRectF &rc) {
0052         if (rc.isEmpty()) return 1.0;
0053 
0054         QPointF diff = horizon.p2() - horizon.p1();
0055         return KisAlgebra2D::signPZ(KisAlgebra2D::crossProduct(diff, rc.center() - horizon.p1()));
0056     }
0057 
0058     QPolygonF getCroppedPolygon(const QLineF &baseHorizon, const QRect &rc, const qreal crossCoeff) {
0059         if (rc.isEmpty()) return QPolygonF();
0060 
0061         QRectF boundsRect(rc);
0062         QPolygonF polygon(boundsRect);
0063         QPolygonF result;
0064 
0065         // calculate new (offset) horizon to avoid infinity
0066         const qreal offsetLength = 10.0;
0067         const QPointF horizonOffset = offsetLength * crossCoeff *
0068             KisAlgebra2D::rightUnitNormal(baseHorizon.p2() - baseHorizon.p1());
0069 
0070         const QLineF horizon = baseHorizon.translated(horizonOffset);
0071 
0072         // base vectors to calculate the side of the horizon
0073         const QPointF &basePoint = horizon.p1();
0074         const QPointF horizonVec = horizon.p2() - basePoint;
0075 
0076 
0077         // iteration
0078         QPointF prevPoint = polygon[polygon.size() - 1];
0079         qreal prevCross = crossCoeff * KisAlgebra2D::crossProduct(horizonVec, prevPoint - basePoint);
0080 
0081         for (int i = 0; i < polygon.size(); i++) {
0082             const QPointF &pt = polygon[i];
0083 
0084             qreal cross = crossCoeff * KisAlgebra2D::crossProduct(horizonVec, pt - basePoint);
0085 
0086             if ((cross >= 0 && prevCross >= 0) || (cross == 0 && prevCross < 0)) {
0087                 result << pt;
0088             } else if (cross * prevCross < 0) {
0089                 QPointF intersection;
0090                 QLineF edge(prevPoint, pt);
0091                 QLineF::IntersectType intersectionType =
0092                     horizon.intersect(edge, &intersection);
0093 
0094                 KIS_ASSERT_RECOVER_NOOP(intersectionType != QLineF::NoIntersection);
0095 
0096                 result << intersection;
0097 
0098                 if (cross > 0) {
0099                     result << pt;
0100                 }
0101             }
0102 
0103             prevPoint = pt;
0104             prevCross = cross;
0105         }
0106 
0107         if (result.size() > 0 && !result.isClosed()) {
0108             result << result.first();
0109         }
0110 
0111         return result;
0112     }
0113 
0114 };
0115 
0116 KisSafeTransform::KisSafeTransform(const QTransform &transform,
0117                                    const QRect &bounds,
0118                                    const QRect &srcInterestRect)
0119     : m_d(new Private)
0120 {
0121     m_d->bounds = bounds;
0122 
0123     m_d->forwardTransform = transform;
0124     m_d->backwardTransform = transform.inverted();
0125 
0126     m_d->needsClipping = transform.type() > QTransform::TxShear;
0127 
0128     if (m_d->needsClipping) {
0129         m_d->srcClipPolygon = QPolygonF(QRectF(m_d->bounds));
0130         m_d->dstClipPolygon = QPolygonF(QRectF(m_d->bounds));
0131 
0132         qreal crossCoeff = 1.0;
0133 
0134         QLineF srcHorizon;
0135         if (m_d->getHorizon(m_d->backwardTransform, &srcHorizon)) {
0136             crossCoeff = m_d->getCrossSign(srcHorizon, srcInterestRect);
0137             m_d->srcClipPolygon = m_d->getCroppedPolygon(srcHorizon, m_d->bounds, crossCoeff);
0138         }
0139 
0140         QLineF dstHorizon;
0141         if (m_d->getHorizon(m_d->forwardTransform, &dstHorizon)) {
0142             crossCoeff = m_d->getCrossSign(dstHorizon, mapRectForward(srcInterestRect));
0143             m_d->dstClipPolygon = m_d->getCroppedPolygon(dstHorizon, m_d->bounds, crossCoeff);
0144         }
0145     }
0146 }
0147 
0148 KisSafeTransform::~KisSafeTransform()
0149 {
0150 }
0151 
0152 QPolygonF KisSafeTransform::srcClipPolygon() const
0153 {
0154     return m_d->srcClipPolygon;
0155 }
0156 
0157 QPolygonF KisSafeTransform::dstClipPolygon() const
0158 {
0159     return m_d->dstClipPolygon;
0160 }
0161 
0162 QPolygonF KisSafeTransform::mapForward(const QPolygonF &p)
0163 {
0164     QPolygonF poly;
0165 
0166     if (!m_d->needsClipping) {
0167         poly = m_d->forwardTransform.map(p);
0168     } else {
0169         poly = m_d->srcClipPolygon.intersected(p);
0170         poly = m_d->forwardTransform.map(poly).intersected(QRectF(m_d->bounds));
0171     }
0172 
0173     return poly;
0174 }
0175 
0176 QPolygonF KisSafeTransform::mapBackward(const QPolygonF &p)
0177 {
0178     QPolygonF poly;
0179 
0180     if (!m_d->needsClipping) {
0181         poly = m_d->backwardTransform.map(p);
0182     } else {
0183         poly = m_d->dstClipPolygon.intersected(p);
0184         poly = m_d->backwardTransform.map(poly).intersected(QRectF(m_d->bounds));
0185     }
0186 
0187     return poly;
0188 }
0189 
0190 QRectF KisSafeTransform::mapRectForward(const QRectF &rc)
0191 {
0192     return mapForward(rc).boundingRect();
0193 }
0194 
0195 QRectF KisSafeTransform::mapRectBackward(const QRectF &rc)
0196 {
0197     return mapBackward(rc).boundingRect();
0198 }
0199 
0200 QRect KisSafeTransform::mapRectForward(const QRect &rc)
0201 {
0202     return mapRectForward(QRectF(rc)).toAlignedRect();
0203 }
0204 
0205 QRect KisSafeTransform::mapRectBackward(const QRect &rc)
0206 {
0207     return mapRectBackward(QRectF(rc)).toAlignedRect();
0208 }
0209