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 }