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 ¢er, 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 }