1
2 /*
3 Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
4 All rights reserved.
5
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
9
10 1. Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in the
14 documentation and/or other materials provided with the distribution.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 #define DEBUG_KP_TOOL_ROTATE 0
29
30
31 #include "kpTransformRotateCommand.h"
32
33 #include "layers/selections/image/kpAbstractImageSelection.h"
34 #include "environments/commands/kpCommandEnvironment.h"
35 #include "kpDefs.h"
36 #include "document/kpDocument.h"
37 #include "layers/selections/image/kpFreeFormImageSelection.h"
38 #include "pixmapfx/kpPixmapFX.h"
39 #include "layers/selections/image/kpRectangularImageSelection.h"
40 #include "views/manager/kpViewManager.h"
41 #include "kpLogCategories.h"
42
43 #include <QApplication>
44 #include <QPolygon>
45 #include <QTransform>
46
47 #include <KLocalizedString>
48
49 //--------------------------------------------------------------------------------
50
kpTransformRotateCommand(bool actOnSelection,double angle,kpCommandEnvironment * environ)51 kpTransformRotateCommand::kpTransformRotateCommand (bool actOnSelection,
52 double angle,
53 kpCommandEnvironment *environ)
54 : kpCommand (environ),
55 m_actOnSelection (actOnSelection),
56 m_angle (angle),
57 m_backgroundColor (environ->backgroundColor (actOnSelection)),
58 m_losslessRotation (kpPixmapFX::isLosslessRotation (angle)),
59 m_oldSelectionPtr (nullptr)
60 {
61 }
62
~kpTransformRotateCommand()63 kpTransformRotateCommand::~kpTransformRotateCommand ()
64 {
65 delete m_oldSelectionPtr;
66 }
67
68
69 // public virtual [base kpCommand]
name() const70 QString kpTransformRotateCommand::name () const
71 {
72 QString opName = i18n ("Rotate");
73
74 return (m_actOnSelection) ? i18n ("Selection: %1", opName) : opName;
75 }
76
77
78 // public virtual [base kpCommand]
size() const79 kpCommandSize::SizeType kpTransformRotateCommand::size () const
80 {
81 return ImageSize (m_oldImage) +
82 SelectionSize (m_oldSelectionPtr);
83 }
84
85
86 // public virtual [base kpCommand]
execute()87 void kpTransformRotateCommand::execute ()
88 {
89 kpDocument *doc = document ();
90 Q_ASSERT (doc);
91
92
93 QApplication::setOverrideCursor (Qt::WaitCursor);
94
95
96 if (!m_losslessRotation) {
97 m_oldImage = doc->image (m_actOnSelection);
98 }
99
100
101 kpImage newImage = kpPixmapFX::rotate (doc->image (m_actOnSelection),
102 m_angle,
103 m_backgroundColor);
104
105 if (!m_actOnSelection) {
106 doc->setImage (newImage);
107 }
108 else {
109 kpAbstractImageSelection *sel = doc->imageSelection ();
110 Q_ASSERT (sel);
111
112 // Save old selection
113 m_oldSelectionPtr = sel->clone ();
114
115 // Conserve memmory:
116 //
117 // 1. If it's a lossless rotation, we don't need to the store old
118 // image anywhere at all, as we can reconstruct it by rotating in
119 // reverse.
120 // 2. If it's not a lossless rotation, "m_oldImage" already holds
121 // a copy of the old image. In this case, we actually save very
122 // little with this line (just, the computed transparency mask) since
123 // kpImage is copy-on-write.
124 m_oldSelectionPtr->setBaseImage (kpImage ());
125
126
127 // Calculate new top left (so selection rotates about center)
128 // (the Times2 trickery is used to reduce integer division error without
129 // resorting to the troublesome world of floating point)
130 QPoint oldCenterTimes2 (sel->x () * 2 + sel->width (),
131 sel->y () * 2 + sel->height ());
132 QPoint newTopLeftTimes2 (oldCenterTimes2 - QPoint (newImage.width (), newImage.height ()));
133 QPoint newTopLeft (newTopLeftTimes2.x () / 2, newTopLeftTimes2.y () / 2);
134
135
136 // Calculate rotated points
137 QPolygon currentPoints = sel->calculatePoints ();
138 currentPoints.translate (-currentPoints.boundingRect ().x (),
139 -currentPoints.boundingRect ().y ());
140 QTransform rotateMatrix = kpPixmapFX::rotateMatrix (doc->image (m_actOnSelection), m_angle);
141 currentPoints = rotateMatrix.map (currentPoints);
142 currentPoints.translate (-currentPoints.boundingRect ().x () + newTopLeft.x (),
143 -currentPoints.boundingRect ().y () + newTopLeft.y ());
144
145
146 if (currentPoints.boundingRect ().width () == newImage.width () &&
147 currentPoints.boundingRect ().height () == newImage.height ())
148 {
149 doc->setSelection (
150 kpFreeFormImageSelection (
151 currentPoints, newImage,
152 m_oldSelectionPtr->transparency ()));
153 }
154 else
155 {
156 // TODO: fix the latter "victim of" problem in kpAbstractImageSelection by
157 // allowing the border width & height != pixmap width & height
158 // Or maybe autocrop?
159 #if DEBUG_KP_TOOL_ROTATE
160 qCDebug(kpLogCommands) << "kpTransformRotateCommand::execute() currentPoints.boundingRect="
161 << currentPoints.boundingRect ()
162 << " newPixmap: w=" << newImage.width ()
163 << " h=" << newImage.height ()
164 << " (victim of rounding error and/or rotated-a-(rectangular)-pixmap-that-was-transparent-in-the-corners-making-sel-uselessly-bigger-than-needs-be)";
165 #endif
166 doc->setSelection (
167 kpRectangularImageSelection (
168 QRect (newTopLeft.x (), newTopLeft.y (),
169 newImage.width (), newImage.height ()),
170 newImage,
171 m_oldSelectionPtr->transparency ()));
172 }
173
174 environ ()->somethingBelowTheCursorChanged ();
175 }
176
177
178 QApplication::restoreOverrideCursor ();
179 }
180
181 // public virtual [base kpCommand]
unexecute()182 void kpTransformRotateCommand::unexecute ()
183 {
184 kpDocument *doc = document ();
185 Q_ASSERT (doc);
186
187
188 QApplication::setOverrideCursor (Qt::WaitCursor);
189
190
191 kpImage oldImage;
192
193 if (!m_losslessRotation)
194 {
195 oldImage = m_oldImage;
196 m_oldImage = kpImage ();
197 }
198 else
199 {
200 oldImage = kpPixmapFX::rotate (doc->image (m_actOnSelection),
201 360 - m_angle,
202 m_backgroundColor);
203 }
204
205
206 if (!m_actOnSelection) {
207 doc->setImage (oldImage);
208 }
209 else {
210 m_oldSelectionPtr->setBaseImage (oldImage);
211 doc->setSelection (*m_oldSelectionPtr);
212 delete m_oldSelectionPtr; m_oldSelectionPtr = nullptr;
213
214 environ ()->somethingBelowTheCursorChanged ();
215 }
216
217
218 QApplication::restoreOverrideCursor ();
219 }
220
221