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