File indexing completed on 2024-05-12 15:56:39
0001 /* This file is part of the KDE project 0002 * SPDX-FileCopyrightText: 2011 Jan Hambrecht <jaham@gmx.net> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "KoClipPath.h" 0008 #include "KoPathShape.h" 0009 #include "KoShapeGroup.h" 0010 0011 #include <QTransform> 0012 #include <QPainterPath> 0013 #include <QPainter> 0014 #include <QVarLengthArray> 0015 #include <QSharedData> 0016 0017 #include <kis_algebra_2d.h> 0018 0019 0020 QTransform scaleToPercent(const QSizeF &size) 0021 { 0022 const qreal w = qMax(static_cast<qreal>(1e-5), size.width()); 0023 const qreal h = qMax(static_cast<qreal>(1e-5), size.height()); 0024 return QTransform().scale(1.0/w, 1.0/h); 0025 } 0026 0027 QTransform scaleFromPercent(const QSizeF &size) 0028 { 0029 const qreal w = qMax(static_cast<qreal>(1e-5), size.width()); 0030 const qreal h = qMax(static_cast<qreal>(1e-5), size.height()); 0031 return QTransform().scale(w/1.0, h/1.0); 0032 } 0033 0034 class Q_DECL_HIDDEN KoClipPath::Private : public QSharedData 0035 { 0036 public: 0037 Private() 0038 : QSharedData() 0039 {} 0040 0041 Private(const Private &rhs) 0042 : QSharedData() 0043 , clipPath(rhs.clipPath) 0044 , clipRule(rhs.clipRule) 0045 , coordinates(rhs.coordinates) 0046 , initialTransformToShape(rhs.initialTransformToShape) 0047 , initialShapeSize(rhs.initialShapeSize) 0048 { 0049 Q_FOREACH (KoShape *shape, rhs.shapes) { 0050 KoShape *clonedShape = shape->cloneShape(); 0051 KIS_ASSERT_RECOVER(clonedShape) { continue; } 0052 0053 shapes.append(clonedShape); 0054 } 0055 } 0056 0057 ~Private() 0058 { 0059 qDeleteAll(shapes); 0060 shapes.clear(); 0061 } 0062 0063 void collectShapePath(QPainterPath *result, const KoShape *shape) { 0064 if (const KoPathShape *pathShape = dynamic_cast<const KoPathShape*>(shape)) { 0065 // different shapes add up to the final path using Windind Fill rule (acc. to SVG 1.1) 0066 QTransform t = pathShape->absoluteTransformation(); 0067 result->addPath(t.map(pathShape->outline())); 0068 } else if (const KoShapeGroup *groupShape = dynamic_cast<const KoShapeGroup*>(shape)) { 0069 QList<KoShape*> shapes = groupShape->shapes(); 0070 std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); 0071 0072 Q_FOREACH (const KoShape *child, shapes) { 0073 collectShapePath(result, child); 0074 } 0075 } 0076 } 0077 0078 0079 void compileClipPath() 0080 { 0081 QList<KoShape*> clipShapes = this->shapes; 0082 if (clipShapes.isEmpty()) 0083 return; 0084 0085 clipPath = QPainterPath(); 0086 clipPath.setFillRule(Qt::WindingFill); 0087 0088 std::sort(clipShapes.begin(), clipShapes.end(), KoShape::compareShapeZIndex); 0089 0090 Q_FOREACH (KoShape *path, clipShapes) { 0091 if (!path) continue; 0092 0093 collectShapePath(&clipPath, path); 0094 } 0095 } 0096 0097 QList<KoShape*> shapes; 0098 QPainterPath clipPath; ///< the compiled clip path in shape coordinates of the clipped shape 0099 Qt::FillRule clipRule = Qt::WindingFill; 0100 KoFlake::CoordinateSystem coordinates = KoFlake::ObjectBoundingBox; 0101 QTransform initialTransformToShape; ///< initial transformation to shape coordinates of the clipped shape 0102 QSizeF initialShapeSize; ///< initial size of clipped shape 0103 }; 0104 0105 KoClipPath::KoClipPath(QList<KoShape*> clipShapes, KoFlake::CoordinateSystem coordinates) 0106 : d(new Private()) 0107 { 0108 d->shapes = clipShapes; 0109 d->coordinates = coordinates; 0110 d->compileClipPath(); 0111 } 0112 0113 KoClipPath::~KoClipPath() 0114 { 0115 } 0116 0117 KoClipPath::KoClipPath(const KoClipPath &rhs) 0118 : d(new Private(*rhs.d)) 0119 { 0120 } 0121 0122 KoClipPath &KoClipPath::operator=(const KoClipPath &rhs) 0123 { 0124 d = rhs.d; 0125 return *this; 0126 } 0127 0128 KoClipPath *KoClipPath::clone() const 0129 { 0130 return new KoClipPath(*this); 0131 } 0132 0133 void KoClipPath::setClipRule(Qt::FillRule clipRule) 0134 { 0135 d->clipRule = clipRule; 0136 } 0137 0138 Qt::FillRule KoClipPath::clipRule() const 0139 { 0140 return d->clipRule; 0141 } 0142 0143 KoFlake::CoordinateSystem KoClipPath::coordinates() const 0144 { 0145 return d->coordinates; 0146 } 0147 0148 void KoClipPath::applyClipping(KoShape *shape, QPainter &painter) 0149 { 0150 if (shape->clipPath()) { 0151 QPainterPath path = shape->clipPath()->path(); 0152 0153 if (shape->clipPath()->coordinates() == KoFlake::ObjectBoundingBox) { 0154 const QRectF shapeLocalBoundingRect = shape->outline().boundingRect(); 0155 path = KisAlgebra2D::mapToRect(shapeLocalBoundingRect).map(path); 0156 } 0157 0158 if (!path.isEmpty()) { 0159 painter.setClipPath(path, Qt::IntersectClip); 0160 } 0161 } 0162 } 0163 0164 QPainterPath KoClipPath::path() const 0165 { 0166 return d->clipPath; 0167 } 0168 0169 QPainterPath KoClipPath::pathForSize(const QSizeF &size) const 0170 { 0171 return scaleFromPercent(size).map(d->clipPath); 0172 } 0173 0174 QList<KoPathShape*> KoClipPath::clipPathShapes() const 0175 { 0176 // TODO: deprecate this method! 0177 0178 QList<KoPathShape*> shapes; 0179 0180 Q_FOREACH (KoShape *shape, d->shapes) { 0181 KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape); 0182 if (pathShape) { 0183 shapes << pathShape; 0184 } 0185 } 0186 0187 return shapes; 0188 } 0189 0190 QList<KoShape *> KoClipPath::clipShapes() const 0191 { 0192 return d->shapes; 0193 } 0194 0195 QTransform KoClipPath::clipDataTransformation(KoShape *clippedShape) const 0196 { 0197 if (!clippedShape) 0198 return d->initialTransformToShape; 0199 0200 // the current transformation of the clipped shape 0201 QTransform currentShapeTransform = clippedShape->absoluteTransformation(); 0202 0203 // calculate the transformation which represents any resizing of the clipped shape 0204 const QSizeF currentShapeSize = clippedShape->outline().boundingRect().size(); 0205 const qreal sx = currentShapeSize.width() / d->initialShapeSize.width(); 0206 const qreal sy = currentShapeSize.height() / d->initialShapeSize.height(); 0207 QTransform scaleTransform = QTransform().scale(sx, sy); 0208 0209 // 1. transform to initial clipped shape coordinates 0210 // 2. apply resizing transformation 0211 // 3. convert to current clipped shape document coordinates 0212 return d->initialTransformToShape * scaleTransform * currentShapeTransform; 0213 }