File indexing completed on 2025-02-02 04:15:57

0001 /*
0002  *  SPDX-FileCopyrightText: 2022 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisOptimizedBrushOutline.h"
0008 
0009 #include <QPainterPath>
0010 #include <QTransform>
0011 #include <kis_algebra_2d.h>
0012 
0013 KisOptimizedBrushOutline::KisOptimizedBrushOutline()
0014 {
0015 }
0016 
0017 KisOptimizedBrushOutline::KisOptimizedBrushOutline(const QPainterPath &path)
0018     : m_subpaths(path.toSubpathPolygons().toVector())
0019 {
0020     // storing in a form of a QVector is much more efficient
0021     // than in a QList
0022 }
0023 
0024 KisOptimizedBrushOutline::KisOptimizedBrushOutline(const QVector<QPolygonF> &subpaths)
0025     : m_subpaths(subpaths)
0026 {
0027 }
0028 
0029 void KisOptimizedBrushOutline::map(const QTransform &t)
0030 {
0031     m_transform *= t;
0032     m_cachedBoundingRect = QRectF();
0033 }
0034 
0035 KisOptimizedBrushOutline KisOptimizedBrushOutline::mapped(const QTransform &t) const
0036 {
0037     KisOptimizedBrushOutline result(*this);
0038     result.map(t);
0039     return result;
0040 }
0041 
0042 KisOptimizedBrushOutline::const_iterator KisOptimizedBrushOutline::begin() const
0043 {
0044     return const_iterator(this, 0);
0045 }
0046 
0047 KisOptimizedBrushOutline::const_iterator KisOptimizedBrushOutline::end() const
0048 {
0049     return const_iterator(this, m_subpaths.size() + m_additionalDecorations.size());
0050 }
0051 
0052 QRectF KisOptimizedBrushOutline::boundingRect() const
0053 {
0054     if (!m_cachedBoundingRect.isNull()) return m_cachedBoundingRect;
0055 
0056     /**
0057      * We don't use normal begin()/end() iteration here,
0058      * because it makes too many allocations for the polygons.
0059      * Instead we calculate the bounding rect by mere iteration
0060      * over points.
0061      */
0062 
0063     QRectF result;
0064     bool resultInitialized = false;
0065 
0066     for (auto polyIt = m_subpaths.cbegin(); polyIt != m_subpaths.cend(); ++polyIt) {
0067         /**
0068          * This is a highly optimized way to accumulate a rect from a
0069          * set of points:
0070          *
0071          * 1) `QRectF::isEmpty()` is expensive, so use `resultInitialized` instead
0072          * 2) Use `KisAlgebra2D::accumulateBoundsNonEmpty` to avoid calling `isEmpty()`
0073          */
0074 
0075 
0076         auto it = polyIt->cbegin();
0077 
0078         if (!resultInitialized && it != polyIt->cend()) {
0079             KisAlgebra2D::Private::resetEmptyRectangle(m_transform.map(*it), &result);
0080             resultInitialized = true;
0081             ++it;
0082         }
0083 
0084         for (; it != polyIt->cend(); ++it) {
0085             KisAlgebra2D::accumulateBoundsNonEmpty(m_transform.map(*it), &result);
0086         }
0087     }
0088 
0089     for (auto polyIt = m_additionalDecorations.cbegin(); polyIt != m_additionalDecorations.cend(); ++polyIt) {
0090         auto it = polyIt->cbegin();
0091 
0092         if (!resultInitialized && it != polyIt->cend()) {
0093             KisAlgebra2D::Private::resetEmptyRectangle(m_transform.map(*it), &result);
0094             resultInitialized = true;
0095             ++it;
0096         }
0097 
0098         for (; it != polyIt->cend(); ++it) {
0099             KisAlgebra2D::accumulateBoundsNonEmpty(m_transform.map(*it), &result);
0100         }
0101     }
0102 
0103     m_cachedBoundingRect = result;
0104 
0105     return result;
0106 }
0107 
0108 bool KisOptimizedBrushOutline::isEmpty() const
0109 {
0110     return begin() == end();
0111 }
0112 
0113 void KisOptimizedBrushOutline::addRect(const QRectF &rc)
0114 {
0115     QPainterPath path;
0116     path.addRect(rc);
0117     addPath(path);
0118 }
0119 
0120 void KisOptimizedBrushOutline::addEllipse(const QPointF &center, qreal rx, qreal ry)
0121 {
0122     QPainterPath path;
0123     path.addEllipse(center, rx, ry);
0124     addPath(path);
0125 }
0126 
0127 void KisOptimizedBrushOutline::addPath(const QPainterPath &path)
0128 {
0129     addPath(KisOptimizedBrushOutline(path));
0130 }
0131 
0132 void KisOptimizedBrushOutline::addPath(const KisOptimizedBrushOutline &path)
0133 {
0134     const QTransform invertedTransform = path.m_transform * m_transform.inverted();
0135 
0136     m_additionalDecorations.reserve(m_additionalDecorations.size() +
0137                                     path.m_subpaths.size() +
0138                                     path.m_additionalDecorations.size());
0139 
0140     for (auto it = path.m_subpaths.cbegin(); it != path.m_subpaths.cend(); ++it) {
0141         m_additionalDecorations.append(invertedTransform.map(*it));
0142     }
0143 
0144     for (auto it = path.m_additionalDecorations.cbegin(); it != path.m_additionalDecorations.cend(); ++it) {
0145         m_additionalDecorations.append(invertedTransform.map(*it));
0146     }
0147 
0148     m_cachedBoundingRect = QRectF();
0149 }
0150 
0151 void KisOptimizedBrushOutline::translate(qreal tx, qreal ty)
0152 {
0153     map(QTransform::fromTranslate(tx, ty));
0154 }
0155 
0156 void KisOptimizedBrushOutline::translate(const QPointF &offset)
0157 {
0158     translate(offset.x(), offset.y());
0159 }
0160 
0161 QPolygonF KisOptimizedBrushOutline::const_iterator::dereference() const
0162 {
0163     int index = m_index;
0164 
0165     if (index < m_outline->m_subpaths.size()) {
0166         return m_outline->m_transform.map(m_outline->m_subpaths.at(index));
0167     }
0168 
0169     index -= m_outline->m_subpaths.size();
0170     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(index >= 0, QPolygonF());
0171     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(index < m_outline->m_additionalDecorations.size(), QPolygonF());
0172 
0173     return m_outline->m_transform.map(m_outline->m_additionalDecorations.at(index));
0174 }