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 }