File indexing completed on 2024-05-12 15:56:48

0001 /*
0002  *  SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KoShapeFillWrapper.h"
0008 
0009 #include <KoShape.h>
0010 #include <QList>
0011 #include <QBrush>
0012 #include <KoColorBackground.h>
0013 #include <KoGradientBackground.h>
0014 #include <KoPatternBackground.h>
0015 #include <KoMeshGradientBackground.h>
0016 #include <KoShapeStroke.h>
0017 #include <KoShapeBackgroundCommand.h>
0018 #include <KoShapeStrokeCommand.h>
0019 #include <KoStopGradient.h>
0020 
0021 #include "kis_assert.h"
0022 #include "kis_debug.h"
0023 #include "kis_global.h"
0024 
0025 #include <KoFlakeUtils.h>
0026 
0027 struct ShapeBackgroundFetchPolicy
0028 {
0029     typedef KoFlake::FillType Type;
0030 
0031     typedef QSharedPointer<KoShapeBackground> PointerType;
0032     static PointerType getBackground(KoShape *shape) {
0033         return shape->background();
0034     }
0035     static Type type(KoShape *shape) {
0036         QSharedPointer<KoShapeBackground> background = shape->background();
0037         QSharedPointer<KoColorBackground> colorBackground = qSharedPointerDynamicCast<KoColorBackground>(background);
0038         QSharedPointer<KoGradientBackground> gradientBackground = qSharedPointerDynamicCast<KoGradientBackground>(background);
0039         QSharedPointer<KoPatternBackground> patternBackground = qSharedPointerDynamicCast<KoPatternBackground>(background);
0040         QSharedPointer<KoMeshGradientBackground> meshgradientBackground = qSharedPointerDynamicCast<KoMeshGradientBackground>(background);
0041 
0042 
0043         if(gradientBackground) {
0044             return Type::Gradient;
0045         }
0046 
0047         if (patternBackground) {
0048             return Type::Pattern;
0049         }
0050 
0051         if (colorBackground) {
0052             return Type::Solid;
0053         }
0054 
0055         if (meshgradientBackground) {
0056             return Type::MeshGradient;
0057         }
0058 
0059         return Type::None;
0060     }
0061 
0062     static QColor color(KoShape *shape) {
0063         QSharedPointer<KoColorBackground> colorBackground = qSharedPointerDynamicCast<KoColorBackground>(shape->background());
0064         return colorBackground ? colorBackground->color() : QColor();
0065     }
0066 
0067     static const QGradient* gradient(KoShape *shape) {
0068         QSharedPointer<KoGradientBackground> gradientBackground = qSharedPointerDynamicCast<KoGradientBackground>(shape->background());
0069         return gradientBackground ? gradientBackground->gradient() : 0;
0070     }
0071 
0072     static QTransform gradientTransform(KoShape *shape) {
0073         QSharedPointer<KoGradientBackground> gradientBackground = qSharedPointerDynamicCast<KoGradientBackground>(shape->background());
0074         return gradientBackground ? gradientBackground->transform() : QTransform();
0075     }
0076 
0077     static const SvgMeshGradient* meshgradient(KoShape *shape) {
0078         QSharedPointer<KoMeshGradientBackground> meshgradientBackground = qSharedPointerDynamicCast<KoMeshGradientBackground>(shape->background());
0079         return meshgradientBackground ? meshgradientBackground->gradient() : nullptr;
0080     }
0081 
0082     static QTransform meshgradientTransform(KoShape *shape) {
0083         QSharedPointer<KoMeshGradientBackground> meshgradientBackground = qSharedPointerDynamicCast<KoMeshGradientBackground>(shape->background());
0084         return meshgradientBackground ? meshgradientBackground->transform() : QTransform();
0085     }
0086 
0087     static bool compareTo(PointerType p1, PointerType p2) {
0088         return p1->compareTo(p2.data());
0089     }
0090 };
0091 
0092 struct ShapeStrokeFillFetchPolicy
0093 {
0094     typedef KoFlake::FillType Type;
0095 
0096     typedef KoShapeStrokeModelSP PointerType;
0097     static PointerType getBackground(KoShape *shape) {
0098         return shape->stroke();
0099     }
0100     static Type type(KoShape *shape) {
0101         KoShapeStrokeSP stroke = qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
0102         if (!stroke) return Type::None;
0103 
0104         // Pattern type not implemented yet, so that logic will have to be added here later
0105         if (stroke->lineBrush().gradient()) {
0106             return Type::Gradient;
0107         } else {
0108 
0109             // strokes without any width are none
0110             if (stroke->color().isValid() && stroke->lineWidth() != 0.0) {
0111                 return Type::Solid;
0112             }
0113 
0114             return Type::None;
0115         }
0116     }
0117 
0118     static QColor color(KoShape *shape) {
0119         KoShapeStrokeSP stroke = qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
0120         return stroke ? stroke->color() : QColor();
0121     }
0122 
0123     static const QGradient* gradient(KoShape *shape) {
0124         KoShapeStrokeSP stroke = qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
0125         return stroke ? stroke->lineBrush().gradient() : 0;
0126     }
0127 
0128     static QTransform gradientTransform(KoShape *shape) {
0129         KoShapeStrokeSP stroke = qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
0130         return stroke ? stroke->lineBrush().transform() : QTransform();
0131     }
0132 
0133     static bool compareTo(PointerType p1, PointerType p2) {
0134         return p1->compareFillTo(p2.data());
0135     }
0136 };
0137 
0138 
0139 template <class Policy>
0140 bool compareBackgrounds(const QList<KoShape*> shapes)
0141 {
0142     if (shapes.size() == 1) return true;
0143 
0144     typename Policy::PointerType bg =
0145         Policy::getBackground(shapes.first());
0146 
0147     Q_FOREACH (KoShape *shape, shapes) {
0148         if (
0149             !(
0150               (!bg && !Policy::getBackground(shape)) ||
0151               (bg && Policy::compareTo(bg, Policy::getBackground(shape)))
0152              )) {
0153 
0154             return false;
0155         }
0156     }
0157 
0158     return true;
0159 }
0160 
0161 /******************************************************************************/
0162 /*             KoShapeFillWrapper::Private                                    */
0163 /******************************************************************************/
0164 
0165 struct KoShapeFillWrapper::Private
0166 {
0167     QList<KoShape*> shapes;
0168     KoFlake::FillVariant fillVariant= KoFlake::Fill;
0169 
0170     QSharedPointer<KoShapeBackground> applyFillGradientStops(KoShape *shape, const QGradient *srcQGradient);
0171     void applyFillGradientStops(KoShapeStrokeSP shapeStroke, const QGradient *stopGradient);
0172 };
0173 
0174 QSharedPointer<KoShapeBackground> KoShapeFillWrapper::Private::applyFillGradientStops(KoShape *shape, const QGradient *stopGradient)
0175 {
0176     QGradientStops stops = stopGradient->stops();
0177 
0178     if (!shape || !stops.count()) {
0179         return QSharedPointer<KoShapeBackground>();
0180     }
0181 
0182     KoGradientBackground *newGradient = 0;
0183     QSharedPointer<KoGradientBackground> oldGradient = qSharedPointerDynamicCast<KoGradientBackground>(shape->background());
0184     if (oldGradient) {
0185         // just copy the gradient and set the new stops
0186         QGradient *g = KoFlake::mergeGradient(oldGradient->gradient(), stopGradient);
0187         newGradient = new KoGradientBackground(g);
0188         newGradient->setTransform(oldGradient->transform());
0189     }
0190     else {
0191         // No gradient yet, so create a new one.
0192         QScopedPointer<QLinearGradient> fakeShapeGradient(new QLinearGradient(QPointF(0, 0), QPointF(1, 1)));
0193         fakeShapeGradient->setCoordinateMode(QGradient::ObjectBoundingMode);
0194 
0195         QGradient *g = KoFlake::mergeGradient(fakeShapeGradient.data(), stopGradient);
0196         newGradient = new KoGradientBackground(g);
0197     }
0198     return QSharedPointer<KoGradientBackground>(newGradient);
0199 }
0200 
0201 void KoShapeFillWrapper::Private::applyFillGradientStops(KoShapeStrokeSP shapeStroke, const QGradient *stopGradient)
0202 {
0203     QGradientStops stops = stopGradient->stops();
0204     if (!stops.count()) return;
0205 
0206     QLinearGradient fakeShapeGradient(QPointF(0, 0), QPointF(1, 1));
0207     fakeShapeGradient.setCoordinateMode(QGradient::ObjectBoundingMode);
0208     QTransform gradientTransform;
0209     const QGradient *shapeGradient = 0;
0210 
0211     {
0212         QBrush brush = shapeStroke->lineBrush();
0213         gradientTransform = brush.transform();
0214         shapeGradient = brush.gradient() ? brush.gradient() : &fakeShapeGradient;
0215     }
0216 
0217     {
0218         QScopedPointer<QGradient> g(KoFlake::mergeGradient(shapeGradient, stopGradient));
0219         QBrush newBrush = *g;
0220         newBrush.setTransform(gradientTransform);
0221         shapeStroke->setLineBrush(newBrush);
0222     }
0223 }
0224 
0225 /******************************************************************************/
0226 /*             KoShapeFillWrapper                                             */
0227 /******************************************************************************/
0228 
0229 KoShapeFillWrapper::KoShapeFillWrapper(KoShape *shape, KoFlake::FillVariant fillVariant)
0230     : m_d(new Private())
0231 {
0232     KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
0233     m_d->shapes << shape;
0234     m_d->fillVariant= fillVariant;
0235 }
0236 
0237 
0238 KoShapeFillWrapper::KoShapeFillWrapper(QList<KoShape*> shapes, KoFlake::FillVariant fillVariant)
0239     : m_d(new Private())
0240 {
0241     KIS_SAFE_ASSERT_RECOVER_RETURN(!shapes.isEmpty());
0242     m_d->shapes = shapes;
0243     m_d->fillVariant= fillVariant;
0244 }
0245 
0246 KoShapeFillWrapper::~KoShapeFillWrapper()
0247 {
0248 }
0249 
0250 bool KoShapeFillWrapper::isMixedFill() const
0251 {
0252     if (m_d->shapes.isEmpty()) return false;
0253 
0254     return m_d->fillVariant == KoFlake::Fill ?
0255         !compareBackgrounds<ShapeBackgroundFetchPolicy>(m_d->shapes) :
0256         !compareBackgrounds<ShapeStrokeFillFetchPolicy>(m_d->shapes);
0257 }
0258 
0259 KoFlake::FillType KoShapeFillWrapper::type() const
0260 {
0261     if (m_d->shapes.isEmpty() || isMixedFill()) return KoFlake::None;
0262 
0263     KoShape *shape = m_d->shapes.first();
0264     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, KoFlake::None);
0265 
0266     KoFlake::FillType fillType;
0267     if (m_d->fillVariant == KoFlake::Fill) {
0268         // fill property of vector object
0269         fillType = ShapeBackgroundFetchPolicy::type(shape);
0270     } else {
0271         // stroke property of vector object
0272         fillType = ShapeStrokeFillFetchPolicy::type(shape);
0273     }
0274 
0275     return fillType;
0276 }
0277 
0278 QColor KoShapeFillWrapper::color() const
0279 {
0280     // this check guarantees that the shapes list is not empty and
0281     // the fill is not mixed!
0282     if (type() != KoFlake::Solid) return QColor();
0283 
0284     KoShape *shape = m_d->shapes.first();
0285     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, QColor());
0286 
0287     return m_d->fillVariant == KoFlake::Fill ?
0288         ShapeBackgroundFetchPolicy::color(shape) :
0289         ShapeStrokeFillFetchPolicy::color(shape);
0290 }
0291 
0292 const QGradient* KoShapeFillWrapper::gradient() const
0293 {
0294     // this check guarantees that the shapes list is not empty and
0295     // the fill is not mixed!
0296     if (type() != KoFlake::Gradient) return 0;
0297 
0298     KoShape *shape = m_d->shapes.first();
0299     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0);
0300 
0301     return m_d->fillVariant == KoFlake::Fill ?
0302         ShapeBackgroundFetchPolicy::gradient(shape) :
0303         ShapeStrokeFillFetchPolicy::gradient(shape);
0304 }
0305 
0306 QTransform KoShapeFillWrapper::gradientTransform() const
0307 {
0308     // this check guarantees that the shapes list is not empty and
0309     // the fill is not mixed!
0310     if (type() != KoFlake::Gradient) return QTransform();
0311 
0312     KoShape *shape = m_d->shapes.first();
0313     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, QTransform());
0314 
0315     return m_d->fillVariant == KoFlake::Fill ?
0316         ShapeBackgroundFetchPolicy::gradientTransform(shape) :
0317                 ShapeStrokeFillFetchPolicy::gradientTransform(shape);
0318 }
0319 
0320 const SvgMeshGradient* KoShapeFillWrapper::meshgradient() const
0321 {
0322     if (type() != KoFlake::MeshGradient) return nullptr;
0323 
0324     KoShape *shape = m_d->shapes.first();
0325     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0);
0326 
0327     return m_d->fillVariant == KoFlake::Fill ?
0328         ShapeBackgroundFetchPolicy::meshgradient(shape) :
0329         nullptr;
0330 }
0331 
0332 KUndo2Command *KoShapeFillWrapper::setColor(const QColor &color)
0333 {
0334     KUndo2Command *command = 0;
0335 
0336     if (m_d->fillVariant == KoFlake::Fill) {
0337          QSharedPointer<KoShapeBackground> bg;
0338 
0339         if (color.isValid()) {
0340             bg = toQShared(new KoColorBackground(color));
0341         }
0342 
0343         QSharedPointer<KoShapeBackground> fill(bg);
0344         command = new KoShapeBackgroundCommand(m_d->shapes, fill);
0345     } else {
0346         command = KoFlake::modifyShapesStrokes(m_d->shapes,
0347             [color] (KoShapeStrokeSP stroke) {
0348                 stroke->setLineBrush(Qt::NoBrush);
0349                 stroke->setColor(color);
0350 
0351             });
0352     }
0353 
0354     return command;
0355 }
0356 
0357 KUndo2Command *KoShapeFillWrapper::setLineWidth(const float &lineWidth)
0358 {
0359     KUndo2Command *command = 0;
0360 
0361     command = KoFlake::modifyShapesStrokes(m_d->shapes, [lineWidth](KoShapeStrokeSP stroke) {
0362             stroke->setColor(Qt::transparent);
0363             stroke->setLineWidth(lineWidth);
0364 
0365      });
0366 
0367    return command;
0368 }
0369 
0370 
0371 bool KoShapeFillWrapper::hasZeroLineWidth() const
0372 {
0373         KoShape *shape = m_d->shapes.first();
0374         if (!shape) return false;
0375         if (m_d->fillVariant == KoFlake::Fill)  return false;
0376 
0377         // this check is useful to determine if
0378         KoShapeStrokeSP stroke = qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
0379         if (!stroke) return false;
0380 
0381         if ( stroke->lineWidth() == 0.0) {
0382             return true;
0383         }
0384 
0385         return false;
0386 }
0387 
0388 
0389 KUndo2Command *KoShapeFillWrapper::setGradient(const QGradient *gradient, const QTransform &transform)
0390 {
0391     KUndo2Command *command = 0;
0392 
0393     if (m_d->fillVariant == KoFlake::Fill) {
0394         QList<QSharedPointer<KoShapeBackground>> newBackgrounds;
0395 
0396         foreach (KoShape *shape, m_d->shapes) {
0397             Q_UNUSED(shape);
0398 
0399             KoGradientBackground *newGradient = new KoGradientBackground(KoFlake::cloneGradient(gradient));
0400             newGradient->setTransform(transform);
0401             newBackgrounds << toQShared(newGradient);
0402         }
0403 
0404         command = new KoShapeBackgroundCommand(m_d->shapes, newBackgrounds);
0405 
0406     } else {
0407         command = KoFlake::modifyShapesStrokes(m_d->shapes,
0408             [gradient, transform] (KoShapeStrokeSP stroke) {
0409                 QBrush newBrush = *gradient;
0410                 newBrush.setTransform(transform);
0411 
0412                 stroke->setLineBrush(newBrush);
0413                 stroke->setColor(Qt::transparent);
0414             });
0415     }
0416 
0417     return command;
0418 }
0419 
0420 KUndo2Command* KoShapeFillWrapper::applyGradient(const QGradient *gradient)
0421 {
0422     return setGradient(gradient, gradientTransform());
0423 }
0424 
0425 KUndo2Command* KoShapeFillWrapper::applyGradientStopsOnly(const QGradient *gradient)
0426 {
0427     KUndo2Command *command = 0;
0428 
0429     if (m_d->fillVariant == KoFlake::Fill) {
0430         QList<QSharedPointer<KoShapeBackground>> newBackgrounds;
0431 
0432         foreach (KoShape *shape, m_d->shapes) {
0433             newBackgrounds <<  m_d->applyFillGradientStops(shape, gradient);
0434         }
0435 
0436         command = new KoShapeBackgroundCommand(m_d->shapes, newBackgrounds);
0437 
0438     } else {
0439         command = KoFlake::modifyShapesStrokes(m_d->shapes,
0440             [this, gradient] (KoShapeStrokeSP stroke) {
0441                 m_d->applyFillGradientStops(stroke, gradient);
0442             });
0443     }
0444 
0445     return command;
0446 }
0447 
0448 KUndo2Command* KoShapeFillWrapper::setMeshGradient(const SvgMeshGradient *gradient,
0449                                                    const QTransform &transform)
0450 {
0451     KUndo2Command *command = nullptr;
0452     if (m_d->fillVariant == KoFlake::Fill) {
0453         QList<QSharedPointer<KoShapeBackground>> newBackgrounds;
0454 
0455         for (const auto &shape: m_d->shapes) {
0456             Q_UNUSED(shape);
0457             KoMeshGradientBackground *newBackground =
0458                 new KoMeshGradientBackground(gradient, transform);
0459 
0460             newBackgrounds << toQShared(newBackground);
0461         }
0462         command = new KoShapeBackgroundCommand(m_d->shapes, newBackgrounds);
0463     }
0464     // TODO: for strokes!!
0465     return command;
0466 }