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