File indexing completed on 2024-05-12 15:56:41
0001 /* This file is part of the KDE project 0002 * SPDX-FileCopyrightText: 2009 Jos van den Oever <jos@vandenoever.info> 0003 * SPDX-FileCopyrightText: 2009 Thomas Zander <zander@kde.org> 0004 * SPDX-FileCopyrightText: 2008 Jan Hambrecht <jaham@gmx.net> 0005 * SPDX-FileCopyrightText: 2010 Thorsten Zachmann <zachmann@kde.org> 0006 * 0007 * SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "KoFlake.h" 0011 #include "KoShape.h" 0012 0013 #include <QGradient> 0014 #include <math.h> 0015 #include "kis_global.h" 0016 0017 QGradient *KoFlake::cloneGradient(const QGradient *gradient) 0018 { 0019 if (! gradient) 0020 return 0; 0021 0022 QGradient *clone = 0; 0023 0024 switch (gradient->type()) { 0025 case QGradient::LinearGradient: 0026 { 0027 const QLinearGradient *lg = static_cast<const QLinearGradient*>(gradient); 0028 clone = new QLinearGradient(lg->start(), lg->finalStop()); 0029 break; 0030 } 0031 case QGradient::RadialGradient: 0032 { 0033 const QRadialGradient *rg = static_cast<const QRadialGradient*>(gradient); 0034 clone = new QRadialGradient(rg->center(), rg->radius(), rg->focalPoint()); 0035 break; 0036 } 0037 case QGradient::ConicalGradient: 0038 { 0039 const QConicalGradient *cg = static_cast<const QConicalGradient*>(gradient); 0040 clone = new QConicalGradient(cg->center(), cg->angle()); 0041 break; 0042 } 0043 default: 0044 return 0; 0045 } 0046 0047 clone->setCoordinateMode(gradient->coordinateMode()); 0048 clone->setSpread(gradient->spread()); 0049 clone->setStops(gradient->stops()); 0050 0051 return clone; 0052 } 0053 0054 QGradient *KoFlake::mergeGradient(const QGradient *coordsSource, const QGradient *fillSource) 0055 { 0056 QPointF start; 0057 QPointF end; 0058 QPointF focalPoint; 0059 0060 switch (coordsSource->type()) { 0061 case QGradient::LinearGradient: { 0062 const QLinearGradient *lg = static_cast<const QLinearGradient*>(coordsSource); 0063 start = lg->start(); 0064 focalPoint = start; 0065 end = lg->finalStop(); 0066 break; 0067 } 0068 case QGradient::RadialGradient: { 0069 const QRadialGradient *rg = static_cast<const QRadialGradient*>(coordsSource); 0070 start = rg->center(); 0071 end = start + QPointF(rg->radius(), 0); 0072 focalPoint = rg->focalPoint(); 0073 break; 0074 } 0075 case QGradient::ConicalGradient: { 0076 const QConicalGradient *cg = static_cast<const QConicalGradient*>(coordsSource); 0077 0078 start = cg->center(); 0079 focalPoint = start; 0080 0081 QLineF l (start, start + QPointF(1.0, 0)); 0082 l.setAngle(cg->angle()); 0083 end = l.p2(); 0084 break; 0085 } 0086 default: 0087 return 0; 0088 } 0089 0090 QGradient *clone = 0; 0091 0092 switch (fillSource->type()) { 0093 case QGradient::LinearGradient: 0094 clone = new QLinearGradient(start, end); 0095 break; 0096 case QGradient::RadialGradient: 0097 clone = new QRadialGradient(start, kisDistance(start, end), focalPoint); 0098 break; 0099 case QGradient::ConicalGradient: { 0100 QLineF l(start, end); 0101 clone = new QConicalGradient(l.p1(), l.angle()); 0102 break; 0103 } 0104 default: 0105 return 0; 0106 } 0107 0108 clone->setCoordinateMode(fillSource->coordinateMode()); 0109 clone->setSpread(fillSource->spread()); 0110 clone->setStops(fillSource->stops()); 0111 0112 return clone; 0113 } 0114 0115 QPointF KoFlake::toRelative(const QPointF &absolute, const QSizeF &size) 0116 { 0117 return QPointF(size.width() == 0 ? 0: absolute.x() / size.width(), 0118 size.height() == 0 ? 0: absolute.y() / size.height()); 0119 } 0120 0121 QPointF KoFlake::toAbsolute(const QPointF &relative, const QSizeF &size) 0122 { 0123 return QPointF(relative.x() * size.width(), relative.y() * size.height()); 0124 } 0125 0126 #include <QTransform> 0127 #include "kis_debug.h" 0128 #include "kis_algebra_2d.h" 0129 0130 namespace { 0131 0132 qreal getScaleByPointsPair(qreal x1, qreal x2, qreal expX1, qreal expX2) 0133 { 0134 static const qreal eps = 1e-10; 0135 0136 const qreal diff = x2 - x1; 0137 const qreal expDiff = expX2 - expX1; 0138 0139 return qAbs(diff) > eps ? expDiff / diff : 1.0; 0140 } 0141 0142 void findMinMaxPoints(const QPolygonF &poly, int *minPoint, int *maxPoint, std::function<qreal(const QPointF&)> dimension) 0143 { 0144 KIS_ASSERT_RECOVER_RETURN(minPoint); 0145 KIS_ASSERT_RECOVER_RETURN(maxPoint); 0146 0147 qreal minValue = dimension(poly[*minPoint]); 0148 qreal maxValue = dimension(poly[*maxPoint]); 0149 0150 for (int i = 0; i < poly.size(); i++) { 0151 const qreal value = dimension(poly[i]); 0152 0153 if (value < minValue) { 0154 *minPoint = i; 0155 minValue = value; 0156 } 0157 0158 if (value > maxValue) { 0159 *maxPoint = i; 0160 maxValue = value; 0161 } 0162 } 0163 } 0164 0165 } 0166 0167 0168 Qt::Orientation KoFlake::significantScaleOrientation(qreal scaleX, qreal scaleY) 0169 { 0170 const qreal scaleXDeviation = qAbs(1.0 - scaleX); 0171 const qreal scaleYDeviation = qAbs(1.0 - scaleY); 0172 0173 return scaleXDeviation > scaleYDeviation ? Qt::Horizontal : Qt::Vertical; 0174 } 0175 0176 void KoFlake::scaleShape(KoShape *shape, qreal scaleX, qreal scaleY, 0177 const QPointF &absoluteStillPoint, 0178 const QTransform &postScalingCoveringTransform) 0179 { 0180 const QTransform scale = QTransform::fromScale(scaleX, scaleY); 0181 QPointF localStillPoint = postScalingCoveringTransform.inverted().map(absoluteStillPoint); 0182 const QTransform localStillPointOffset = QTransform::fromTranslate(-localStillPoint.x(), -localStillPoint.y()); 0183 0184 shape->setTransformation( shape->transformation() * 0185 postScalingCoveringTransform.inverted() * 0186 localStillPointOffset * 0187 scale * 0188 localStillPointOffset.inverted() * 0189 postScalingCoveringTransform); 0190 } 0191 0192 void KoFlake::scaleShapeGlobal(KoShape *shape, qreal scaleX, qreal scaleY, 0193 const QPointF &absoluteStillPoint) 0194 { 0195 const QTransform scale = QTransform::fromScale(scaleX, scaleY); 0196 const QTransform absoluteStillPointOffset = QTransform::fromTranslate(-absoluteStillPoint.x(), -absoluteStillPoint.y()); 0197 0198 const QTransform uniformGlobalTransform = 0199 shape->absoluteTransformation() * 0200 absoluteStillPointOffset * 0201 scale * 0202 absoluteStillPointOffset.inverted() * 0203 shape->absoluteTransformation().inverted() * 0204 shape->transformation(); 0205 0206 shape->setTransformation(uniformGlobalTransform); 0207 } 0208 0209 void KoFlake::resizeShape(KoShape *shape, qreal scaleX, qreal scaleY, 0210 const QPointF &absoluteStillPoint, 0211 bool useGlobalMode) 0212 { 0213 using namespace KisAlgebra2D; 0214 0215 if (useGlobalMode) { 0216 const QTransform scale = QTransform::fromScale(scaleX, scaleY); 0217 const QTransform uniformGlobalTransform = 0218 shape->absoluteTransformation() * 0219 scale * 0220 shape->absoluteTransformation().inverted(); 0221 0222 const QRectF rect = shape->outlineRect(); 0223 0224 /** 0225 * The basic idea of such global scaling: 0226 * 0227 * 1) We choose two the most distant points of the original outline rect 0228 * 2) Calculate their expected position if transformed using `uniformGlobalTransform` 0229 * 3) NOTE1: we do not transform the entire shape using `uniformGlobalTransform`, 0230 * because it will cause massive shearing. We transform only two points 0231 * and adjust other points using dumb scaling. 0232 * 4) NOTE2: given that `scale` transform is much more simpler than 0233 * `uniformGlobalTransform`, we cannot guarantee equivalent changes on 0234 * both globalScaleX and globalScaleY at the same time. We can guarantee 0235 * only one of them. Therefore we select the most "important" axis and 0236 * guarantee scael along it. The scale along the other direction is not 0237 * controlled. 0238 * 5) After we have the two most distant points, we can just calculate the scale 0239 * by dividing difference between their expected and original positions. This 0240 * formula can be derived from equation: 0241 * 0242 * localPoint_i * ScaleMatrix = localPoint_i * UniformGlobalTransform = expectedPoint_i 0243 */ 0244 0245 // choose the most significant scale direction 0246 Qt::Orientation significantOrientation = significantScaleOrientation(scaleX, scaleY); 0247 0248 std::function<qreal(const QPointF&)> dimension; 0249 0250 if (significantOrientation == Qt::Horizontal) { 0251 dimension = [] (const QPointF &pt) { 0252 return pt.x(); 0253 }; 0254 0255 } else { 0256 dimension = [] (const QPointF &pt) { 0257 return pt.y(); 0258 }; 0259 } 0260 0261 // find min and max points (in absolute coordinates), 0262 // by default use top-left and bottom-right 0263 QPolygonF localPoints(rect); 0264 QPolygonF globalPoints = shape->absoluteTransformation().map(localPoints); 0265 0266 int minPointIndex = 0; 0267 int maxPointIndex = 2; 0268 0269 findMinMaxPoints(globalPoints, &minPointIndex, &maxPointIndex, dimension); 0270 0271 // calculate the scale using the extremum points 0272 const QPointF minPoint = localPoints[minPointIndex]; 0273 const QPointF maxPoint = localPoints[maxPointIndex]; 0274 0275 const QPointF minPointExpected = uniformGlobalTransform.map(minPoint); 0276 const QPointF maxPointExpected = uniformGlobalTransform.map(maxPoint); 0277 0278 scaleX = getScaleByPointsPair(minPoint.x(), maxPoint.x(), 0279 minPointExpected.x(), maxPointExpected.x()); 0280 scaleY = getScaleByPointsPair(minPoint.y(), maxPoint.y(), 0281 minPointExpected.y(), maxPointExpected.y()); 0282 } 0283 0284 const QSizeF oldSize(shape->size()); 0285 const QSizeF newSize(oldSize.width() * qAbs(scaleX), oldSize.height() * qAbs(scaleY)); 0286 0287 const QTransform mirrorTransform = QTransform::fromScale(signPZ(scaleX), signPZ(scaleY)); 0288 0289 /** 0290 * NOTE: when resizing a shape we expect top-left corner in parent's 0291 * coordinates to keep it's position. 0292 */ 0293 0294 shape->setSize(newSize); 0295 0296 QPointF localStillPoint = shape->absoluteTransformation().inverted().map(absoluteStillPoint); 0297 const QTransform localStillPointOffset = QTransform::fromTranslate(-localStillPoint.x(), -localStillPoint.y()); 0298 const QSizeF realNewSize = shape->size(); 0299 0300 const QTransform realResizeTransform = 0301 QTransform::fromScale(oldSize.width() > 0 ? realNewSize.width() / oldSize.width() : 1.0, 0302 oldSize.height() > 0 ? realNewSize.height() / oldSize.height() : 1.0); 0303 0304 shape->setTransformation(realResizeTransform.inverted() * 0305 localStillPointOffset * 0306 realResizeTransform * 0307 mirrorTransform * 0308 localStillPointOffset.inverted() * 0309 shape->transformation() 0310 ); 0311 } 0312 0313 void KoFlake::resizeShapeCommon(KoShape *shape, qreal scaleX, qreal scaleY, 0314 const QPointF &absoluteStillPoint, 0315 bool useGlobalMode, 0316 bool usePostScaling, const QTransform &postScalingCoveringTransform) 0317 { 0318 if (usePostScaling) { 0319 if (!useGlobalMode) { 0320 scaleShape(shape, scaleX, scaleY, absoluteStillPoint, postScalingCoveringTransform); 0321 } else { 0322 scaleShapeGlobal(shape, scaleX, scaleY, absoluteStillPoint); 0323 } 0324 } else { 0325 resizeShape(shape, scaleX, scaleY, absoluteStillPoint, useGlobalMode); 0326 } 0327 } 0328 0329 QPointF KoFlake::anchorToPoint(AnchorPosition anchor, const QRectF rect, bool *valid) 0330 { 0331 static QVector<QPointF> anchorTable; 0332 0333 if (anchorTable.isEmpty()) { 0334 anchorTable << QPointF(0.0,0.0); 0335 anchorTable << QPointF(0.5,0.0); 0336 anchorTable << QPointF(1.0,0.0); 0337 0338 anchorTable << QPointF(0.0,0.5); 0339 anchorTable << QPointF(0.5,0.5); 0340 anchorTable << QPointF(1.0,0.5); 0341 0342 anchorTable << QPointF(0.0,1.0); 0343 anchorTable << QPointF(0.5,1.0); 0344 anchorTable << QPointF(1.0,1.0); 0345 } 0346 0347 if (valid) 0348 *valid = false; 0349 0350 switch(anchor) 0351 { 0352 case AnchorPosition::TopLeft: 0353 case AnchorPosition::Top: 0354 case AnchorPosition::TopRight: 0355 case AnchorPosition::Left: 0356 case AnchorPosition::Center: 0357 case AnchorPosition::Right: 0358 case AnchorPosition::BottomLeft: 0359 case AnchorPosition::Bottom: 0360 case AnchorPosition::BottomRight: 0361 if (valid) 0362 *valid = true; 0363 return KisAlgebra2D::relativeToAbsolute(anchorTable[int(anchor)], rect); 0364 default: 0365 KIS_SAFE_ASSERT_RECOVER_NOOP(anchor >= AnchorPosition::TopLeft && anchor < AnchorPosition::NumAnchorPositions); 0366 return rect.topLeft(); 0367 } 0368 }