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