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 }