1 /*
2  *  Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "KoShapeGradientHandles.h"
20 
21 #include <QGradient>
22 #include <KoShape.h>
23 #include <KoGradientBackground.h>
24 #include <KoShapeBackgroundCommand.h>
25 #include <KoShapeFillWrapper.h>
26 #include <kis_assert.h>
27 #include "kis_algebra_2d.h"
28 
KoShapeGradientHandles(KoFlake::FillVariant fillVariant,KoShape * shape)29 KoShapeGradientHandles::KoShapeGradientHandles(KoFlake::FillVariant fillVariant, KoShape *shape)
30     : m_fillVariant(fillVariant),
31       m_shape(shape)
32 {
33 }
34 
handles() const35 QVector<KoShapeGradientHandles::Handle> KoShapeGradientHandles::handles() const {
36     QVector<Handle> result;
37 
38     const QGradient *g = gradient();
39     if (!g) return result;
40 
41     switch (g->type()) {
42     case QGradient::LinearGradient: {
43         const QLinearGradient *lgradient = static_cast<const QLinearGradient*>(g);
44         result << Handle(Handle::LinearStart, lgradient->start());
45         result << Handle(Handle::LinearEnd, lgradient->finalStop());
46         break;
47     }
48     case QGradient::RadialGradient: {
49         const QRadialGradient *rgradient = static_cast<const QRadialGradient*>(g);
50 
51         result << Handle(Handle::RadialCenter, rgradient->center());
52 
53         if (rgradient->center() != rgradient->focalPoint()) {
54             result << Handle(Handle::RadialFocalPoint, rgradient->focalPoint());
55         }
56 
57         result << Handle(Handle::RadialRadius,
58                          rgradient->center() + QPointF(rgradient->centerRadius(), 0));
59         break;
60     }
61     case QGradient::ConicalGradient:
62         // not supported
63         break;
64     case QGradient::NoGradient:
65         // not supported
66         break;
67     }
68 
69     if (g->coordinateMode() == QGradient::ObjectBoundingMode) {
70         const QRectF boundingRect = m_shape->outlineRect();
71         const QTransform gradientToUser(boundingRect.width(), 0, 0, boundingRect.height(),
72                                         boundingRect.x(), boundingRect.y());
73         const QTransform t = gradientToUser * m_shape->absoluteTransformation();
74 
75         QVector<Handle>::iterator it = result.begin();
76 
77 
78 
79         for (; it != result.end(); ++it) {
80             it->pos = t.map(it->pos);
81         }
82     }
83 
84     return result;
85 }
86 
type() const87 QGradient::Type KoShapeGradientHandles::type() const
88 {
89     const QGradient *g = gradient();
90     return g ? g->type() : QGradient::NoGradient;
91 }
92 
moveGradientHandle(KoShapeGradientHandles::Handle::Type handleType,const QPointF & absoluteOffset)93 KUndo2Command *KoShapeGradientHandles::moveGradientHandle(KoShapeGradientHandles::Handle::Type handleType, const QPointF &absoluteOffset)
94 {
95     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(handleType != Handle::None, 0);
96 
97     KoShapeFillWrapper wrapper(m_shape, m_fillVariant);
98     const QGradient *originalGradient = wrapper.gradient();
99     QTransform originalTransform = wrapper.gradientTransform();
100     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(originalGradient, 0);
101 
102     QScopedPointer<QGradient> newGradient;
103 
104     switch (originalGradient->type()) {
105     case QGradient::LinearGradient: {
106         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(handleType == Handle::LinearStart ||
107                                              handleType == Handle::LinearEnd, 0);
108 
109         newGradient.reset(KoFlake::cloneGradient(originalGradient));
110         QLinearGradient *lgradient = static_cast<QLinearGradient*>(newGradient.data());
111 
112         if (handleType == Handle::LinearStart) {
113             lgradient->setStart(getNewHandlePos(lgradient->start(), absoluteOffset, newGradient->coordinateMode()));
114         } else if (handleType == Handle::LinearEnd) {
115             lgradient->setFinalStop(getNewHandlePos(lgradient->finalStop(), absoluteOffset, newGradient->coordinateMode()));
116 
117         }
118         break;
119     }
120     case QGradient::RadialGradient: {
121         newGradient.reset(KoFlake::cloneGradient(originalGradient));
122         QRadialGradient *rgradient = static_cast<QRadialGradient*>(newGradient.data());
123 
124         if (handleType == Handle::RadialCenter) {
125             rgradient->setCenter(getNewHandlePos(rgradient->center(), absoluteOffset, newGradient->coordinateMode()));
126         } else if (handleType == Handle::RadialFocalPoint) {
127             rgradient->setFocalPoint(getNewHandlePos(rgradient->focalPoint(), absoluteOffset, newGradient->coordinateMode()));
128         } else if (handleType == Handle::RadialRadius) {
129             QPointF radiusPos = rgradient->center() + QPointF(QPointF(rgradient->radius(), 0));
130             radiusPos = getNewHandlePos(radiusPos, absoluteOffset, newGradient->coordinateMode());
131             rgradient->setRadius(radiusPos.x() - rgradient->center().x());
132         }
133         break;
134     }
135     case QGradient::ConicalGradient:
136         // not supported
137         break;
138     case QGradient::NoGradient:
139         // not supported
140         break;
141     }
142 
143     return wrapper.setGradient(newGradient.data(), originalTransform);
144 }
145 
getHandle(KoShapeGradientHandles::Handle::Type handleType)146 KoShapeGradientHandles::Handle KoShapeGradientHandles::getHandle(KoShapeGradientHandles::Handle::Type handleType)
147 {
148     Handle result;
149 
150     Q_FOREACH (const Handle &h, handles()) {
151         if (h.type == handleType) {
152             result = h;
153             break;
154         }
155     }
156 
157     return result;
158 }
159 
gradient() const160 const QGradient *KoShapeGradientHandles::gradient() const {
161     KoShapeFillWrapper wrapper(m_shape, m_fillVariant);
162     return wrapper.gradient();
163 }
164 
getNewHandlePos(const QPointF & oldPos,const QPointF & absoluteOffset,QGradient::CoordinateMode mode)165 QPointF KoShapeGradientHandles::getNewHandlePos(const QPointF &oldPos, const QPointF &absoluteOffset, QGradient::CoordinateMode mode)
166 {
167     const QTransform offset = QTransform::fromTranslate(absoluteOffset.x(), absoluteOffset.y());
168     QTransform localToAbsolute = m_shape->absoluteTransformation();
169     QTransform absoluteToLocal = localToAbsolute.inverted();
170 
171     if (mode == QGradient::ObjectBoundingMode) {
172         const QRectF rect = m_shape->outlineRect();
173         const QTransform gradientToUser = KisAlgebra2D::mapToRect(rect);
174         localToAbsolute = gradientToUser * localToAbsolute;
175 
176         /// Some shapes may have zero-width/height, then inverted transform will not
177         /// exist. Therefore we should use a special method for that.
178         const QTransform userToGradient = KisAlgebra2D::mapToRectInverse(rect);
179         absoluteToLocal = absoluteToLocal * userToGradient;
180     }
181 
182     return (localToAbsolute * offset * absoluteToLocal).map(oldPos);
183 }
184