1 /* This file is part of the KDE project
2  * Copyright (C) 2009 Jos van den Oever <jos@vandenoever.info>
3  * Copyright (C) 2009 Thomas Zander <zander@kde.org>
4  * Copyright (C) 2008 Jan Hambrecht <jaham@gmx.net>
5  * Copyright (C) 2010 Thorsten Zachmann <zachmann@kde.org>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 #include "KoFlake.h"
24 #include "KoShape.h"
25 
26 #include <QGradient>
27 #include <math.h>
28 #include "kis_global.h"
29 
cloneGradient(const QGradient * gradient)30 QGradient *KoFlake::cloneGradient(const QGradient *gradient)
31 {
32     if (! gradient)
33         return 0;
34 
35     QGradient *clone = 0;
36 
37     switch (gradient->type()) {
38     case QGradient::LinearGradient:
39     {
40         const QLinearGradient *lg = static_cast<const QLinearGradient*>(gradient);
41         clone = new QLinearGradient(lg->start(), lg->finalStop());
42         break;
43     }
44     case QGradient::RadialGradient:
45     {
46         const QRadialGradient *rg = static_cast<const QRadialGradient*>(gradient);
47         clone = new QRadialGradient(rg->center(), rg->radius(), rg->focalPoint());
48         break;
49     }
50     case QGradient::ConicalGradient:
51     {
52         const QConicalGradient *cg = static_cast<const QConicalGradient*>(gradient);
53         clone = new QConicalGradient(cg->center(), cg->angle());
54         break;
55     }
56     default:
57         return 0;
58     }
59 
60     clone->setCoordinateMode(gradient->coordinateMode());
61     clone->setSpread(gradient->spread());
62     clone->setStops(gradient->stops());
63 
64     return clone;
65 }
66 
mergeGradient(const QGradient * coordsSource,const QGradient * fillSource)67 QGradient *KoFlake::mergeGradient(const QGradient *coordsSource, const QGradient *fillSource)
68 {
69     QPointF start;
70     QPointF end;
71     QPointF focalPoint;
72 
73     switch (coordsSource->type()) {
74     case QGradient::LinearGradient: {
75         const QLinearGradient *lg = static_cast<const QLinearGradient*>(coordsSource);
76         start = lg->start();
77         focalPoint = start;
78         end = lg->finalStop();
79         break;
80     }
81     case QGradient::RadialGradient: {
82         const QRadialGradient *rg = static_cast<const QRadialGradient*>(coordsSource);
83         start = rg->center();
84         end = start + QPointF(rg->radius(), 0);
85         focalPoint = rg->focalPoint();
86         break;
87     }
88     case QGradient::ConicalGradient: {
89         const QConicalGradient *cg = static_cast<const QConicalGradient*>(coordsSource);
90 
91         start = cg->center();
92         focalPoint = start;
93 
94         QLineF l (start, start + QPointF(1.0, 0));
95         l.setAngle(cg->angle());
96         end = l.p2();
97         break;
98     }
99     default:
100         return 0;
101     }
102 
103     QGradient *clone = 0;
104 
105     switch (fillSource->type()) {
106     case QGradient::LinearGradient:
107         clone = new QLinearGradient(start, end);
108         break;
109     case QGradient::RadialGradient:
110         clone = new QRadialGradient(start, kisDistance(start, end), focalPoint);
111         break;
112     case QGradient::ConicalGradient: {
113         QLineF l(start, end);
114         clone = new QConicalGradient(l.p1(), l.angle());
115         break;
116     }
117     default:
118         return 0;
119     }
120 
121     clone->setCoordinateMode(fillSource->coordinateMode());
122     clone->setSpread(fillSource->spread());
123     clone->setStops(fillSource->stops());
124 
125     return clone;
126 }
127 
toRelative(const QPointF & absolute,const QSizeF & size)128 QPointF KoFlake::toRelative(const QPointF &absolute, const QSizeF &size)
129 {
130     return QPointF(size.width() == 0 ? 0: absolute.x() / size.width(),
131                    size.height() == 0 ? 0: absolute.y() / size.height());
132 }
133 
toAbsolute(const QPointF & relative,const QSizeF & size)134 QPointF KoFlake::toAbsolute(const QPointF &relative, const QSizeF &size)
135 {
136     return QPointF(relative.x() * size.width(), relative.y() * size.height());
137 }
138 
139 #include <QTransform>
140 #include "kis_debug.h"
141 #include "kis_algebra_2d.h"
142 
143 namespace {
144 
getScaleByPointsPair(qreal x1,qreal x2,qreal expX1,qreal expX2)145 qreal getScaleByPointsPair(qreal x1, qreal x2, qreal expX1, qreal expX2)
146 {
147     static const qreal eps = 1e-10;
148 
149     const qreal diff = x2 - x1;
150     const qreal expDiff = expX2 - expX1;
151 
152     return qAbs(diff) > eps ? expDiff / diff : 1.0;
153 }
154 
findMinMaxPoints(const QPolygonF & poly,int * minPoint,int * maxPoint,std::function<qreal (const QPointF &)> dimension)155 void findMinMaxPoints(const QPolygonF &poly, int *minPoint, int *maxPoint, std::function<qreal(const QPointF&)> dimension)
156 {
157     KIS_ASSERT_RECOVER_RETURN(minPoint);
158     KIS_ASSERT_RECOVER_RETURN(maxPoint);
159 
160     qreal minValue = dimension(poly[*minPoint]);
161     qreal maxValue = dimension(poly[*maxPoint]);
162 
163     for (int i = 0; i < poly.size(); i++) {
164         const qreal value = dimension(poly[i]);
165 
166         if (value < minValue) {
167             *minPoint = i;
168             minValue = value;
169         }
170 
171         if (value > maxValue) {
172             *maxPoint = i;
173             maxValue = value;
174         }
175     }
176 }
177 
178 }
179 
180 
significantScaleOrientation(qreal scaleX,qreal scaleY)181 Qt::Orientation KoFlake::significantScaleOrientation(qreal scaleX, qreal scaleY)
182 {
183     const qreal scaleXDeviation = qAbs(1.0 - scaleX);
184     const qreal scaleYDeviation = qAbs(1.0 - scaleY);
185 
186     return scaleXDeviation > scaleYDeviation ? Qt::Horizontal : Qt::Vertical;
187 }
188 
scaleShape(KoShape * shape,qreal scaleX,qreal scaleY,const QPointF & absoluteStillPoint,const QTransform & postScalingCoveringTransform)189 void KoFlake::scaleShape(KoShape *shape, qreal scaleX, qreal scaleY,
190                           const QPointF &absoluteStillPoint,
191                           const QTransform &postScalingCoveringTransform)
192 {
193     const QTransform scale = QTransform::fromScale(scaleX, scaleY);
194     QPointF localStillPoint = postScalingCoveringTransform.inverted().map(absoluteStillPoint);
195     const QTransform localStillPointOffset = QTransform::fromTranslate(-localStillPoint.x(), -localStillPoint.y());
196 
197     shape->setTransformation( shape->transformation() *
198                 postScalingCoveringTransform.inverted() *
199                 localStillPointOffset *
200                 scale *
201                 localStillPointOffset.inverted() *
202                 postScalingCoveringTransform);
203 }
204 
scaleShapeGlobal(KoShape * shape,qreal scaleX,qreal scaleY,const QPointF & absoluteStillPoint)205 void KoFlake::scaleShapeGlobal(KoShape *shape, qreal scaleX, qreal scaleY,
206                                const QPointF &absoluteStillPoint)
207 {
208     const QTransform scale = QTransform::fromScale(scaleX, scaleY);
209     const QTransform absoluteStillPointOffset = QTransform::fromTranslate(-absoluteStillPoint.x(), -absoluteStillPoint.y());
210 
211     const QTransform uniformGlobalTransform =
212             shape->absoluteTransformation() *
213             absoluteStillPointOffset *
214             scale *
215             absoluteStillPointOffset.inverted() *
216             shape->absoluteTransformation().inverted() *
217             shape->transformation();
218 
219     shape->setTransformation(uniformGlobalTransform);
220 }
221 
resizeShape(KoShape * shape,qreal scaleX,qreal scaleY,const QPointF & absoluteStillPoint,bool useGlobalMode)222 void KoFlake::resizeShape(KoShape *shape, qreal scaleX, qreal scaleY,
223                           const QPointF &absoluteStillPoint,
224                           bool useGlobalMode)
225 {
226     using namespace KisAlgebra2D;
227 
228     if (useGlobalMode) {
229         const QTransform scale = QTransform::fromScale(scaleX, scaleY);
230         const QTransform uniformGlobalTransform =
231                 shape->absoluteTransformation() *
232                 scale *
233                 shape->absoluteTransformation().inverted();
234 
235         const QRectF rect = shape->outlineRect();
236 
237         /**
238          * The basic idea of such global scaling:
239          *
240          * 1) We choose two the most distant points of the original outline rect
241          * 2) Calculate their expected position if transformed using `uniformGlobalTransform`
242          * 3) NOTE1: we do not transform the entire shape using `uniformGlobalTransform`,
243          *           because it will cause massive shearing. We transform only two points
244          *           and adjust other points using dumb scaling.
245          * 4) NOTE2: given that `scale` transform is much more simpler than
246          *           `uniformGlobalTransform`, we cannot guarantee equivalent changes on
247          *           both globalScaleX and globalScaleY at the same time. We can guarantee
248          *           only one of them. Therefore we select the most "important" axis and
249          *           guarantee scael along it. The scale along the other direction is not
250          *           controlled.
251          * 5) After we have the two most distant points, we can just calculate the scale
252          *    by dividing difference between their expected and original positions. This
253          *    formula can be derived from equation:
254          *
255          *    localPoint_i * ScaleMatrix = localPoint_i * UniformGlobalTransform = expectedPoint_i
256          */
257 
258         // choose the most significant scale direction
259         Qt::Orientation significantOrientation = significantScaleOrientation(scaleX, scaleY);
260 
261         std::function<qreal(const QPointF&)> dimension;
262 
263         if (significantOrientation == Qt::Horizontal) {
264             dimension = [] (const QPointF &pt) {
265                 return pt.x();
266             };
267 
268         } else {
269             dimension = [] (const QPointF &pt) {
270                 return pt.y();
271             };
272         }
273 
274         // find min and max points (in absolute coordinates),
275         // by default use top-left and bottom-right
276         QPolygonF localPoints(rect);
277         QPolygonF globalPoints = shape->absoluteTransformation().map(localPoints);
278 
279         int minPointIndex = 0;
280         int maxPointIndex = 2;
281 
282         findMinMaxPoints(globalPoints, &minPointIndex, &maxPointIndex, dimension);
283 
284         // calculate the scale using the extremum points
285         const QPointF minPoint = localPoints[minPointIndex];
286         const QPointF maxPoint = localPoints[maxPointIndex];
287 
288         const QPointF minPointExpected = uniformGlobalTransform.map(minPoint);
289         const QPointF maxPointExpected = uniformGlobalTransform.map(maxPoint);
290 
291         scaleX = getScaleByPointsPair(minPoint.x(), maxPoint.x(),
292                                       minPointExpected.x(), maxPointExpected.x());
293         scaleY = getScaleByPointsPair(minPoint.y(), maxPoint.y(),
294                                       minPointExpected.y(), maxPointExpected.y());
295     }
296 
297     const QSizeF oldSize(shape->size());
298     const QSizeF newSize(oldSize.width() * qAbs(scaleX), oldSize.height() * qAbs(scaleY));
299 
300     const QTransform mirrorTransform = QTransform::fromScale(signPZ(scaleX), signPZ(scaleY));
301 
302     /**
303      * NOTE: when resizing a shape we expect top-left corner in parent's
304      *       coordinates to keep it's position.
305      */
306 
307     shape->setSize(newSize);
308 
309     QPointF localStillPoint = shape->absoluteTransformation().inverted().map(absoluteStillPoint);
310     const QTransform localStillPointOffset = QTransform::fromTranslate(-localStillPoint.x(), -localStillPoint.y());
311     const QSizeF realNewSize = shape->size();
312 
313     const QTransform realResizeTransform =
314         QTransform::fromScale(oldSize.width() > 0 ? realNewSize.width() / oldSize.width() : 1.0,
315                               oldSize.height() > 0 ? realNewSize.height() / oldSize.height() : 1.0);
316 
317     shape->setTransformation(realResizeTransform.inverted() *
318                              localStillPointOffset *
319                              realResizeTransform *
320                              mirrorTransform *
321                              localStillPointOffset.inverted() *
322                              shape->transformation()
323                              );
324 }
325 
resizeShapeCommon(KoShape * shape,qreal scaleX,qreal scaleY,const QPointF & absoluteStillPoint,bool useGlobalMode,bool usePostScaling,const QTransform & postScalingCoveringTransform)326 void KoFlake::resizeShapeCommon(KoShape *shape, qreal scaleX, qreal scaleY,
327                           const QPointF &absoluteStillPoint,
328                           bool useGlobalMode,
329                           bool usePostScaling, const QTransform &postScalingCoveringTransform)
330 {
331     if (usePostScaling) {
332         if (!useGlobalMode) {
333             scaleShape(shape, scaleX, scaleY, absoluteStillPoint, postScalingCoveringTransform);
334         } else {
335             scaleShapeGlobal(shape, scaleX, scaleY, absoluteStillPoint);
336         }
337     } else {
338         resizeShape(shape, scaleX, scaleY, absoluteStillPoint, useGlobalMode);
339     }
340 }
341 
anchorToPoint(AnchorPosition anchor,const QRectF rect,bool * valid)342 QPointF KoFlake::anchorToPoint(AnchorPosition anchor, const QRectF rect, bool *valid)
343 {
344     static QVector<QPointF> anchorTable;
345 
346     if (anchorTable.isEmpty()) {
347         anchorTable << QPointF(0.0,0.0);
348         anchorTable << QPointF(0.5,0.0);
349         anchorTable << QPointF(1.0,0.0);
350 
351         anchorTable << QPointF(0.0,0.5);
352         anchorTable << QPointF(0.5,0.5);
353         anchorTable << QPointF(1.0,0.5);
354 
355         anchorTable << QPointF(0.0,1.0);
356         anchorTable << QPointF(0.5,1.0);
357         anchorTable << QPointF(1.0,1.0);
358     }
359 
360     if (valid)
361         *valid = false;
362 
363     switch(anchor)
364     {
365         case AnchorPosition::TopLeft:
366         case AnchorPosition::Top:
367         case AnchorPosition::TopRight:
368         case AnchorPosition::Left:
369         case AnchorPosition::Center:
370         case AnchorPosition::Right:
371         case AnchorPosition::BottomLeft:
372         case AnchorPosition::Bottom:
373         case AnchorPosition::BottomRight:
374             if (valid)
375                 *valid = true;
376             return KisAlgebra2D::relativeToAbsolute(anchorTable[int(anchor)], rect);
377         default:
378             KIS_SAFE_ASSERT_RECOVER_NOOP(anchor >= AnchorPosition::TopLeft && anchor < AnchorPosition::NumAnchorPositions);
379             return rect.topLeft();
380     }
381 }
382