1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2006-09-15
7 * Description : Exiv2 library interface.
8 * Tools for combining rotation operations.
9 *
10 * Copyright (C) 2006-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11 * Copyright (C) 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
12 *
13 * This program is free software; you can redistribute it
14 * and/or modify it under the terms of the GNU General
15 * Public License as published by the Free Software Foundation;
16 * either version 2, or (at your option)
17 * any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * ============================================================ */
25
26 // Local includes
27
28 #include "metaengine_rotation.h"
29
30 namespace Digikam
31 {
32
33 /**
34 * If the picture is displayed according to the exif orientation tag,
35 * the user will request rotating operations relative to what he sees,
36 * and that is the picture rotated according to the EXIF tag.
37 * So the operation requested and the given EXIF angle must be combined.
38 * E.g. if orientation is "6" (rotate 90 clockwiseto show correctly)
39 * and the user selects 180 clockwise, the operation is 270.
40 * If the user selected 270, the operation would be None (and clearing the exif tag).
41 *
42 * This requires to describe the transformations in a model which
43 * cares for both composing (180+90=270) and eliminating (180+180=no action),
44 * as well as the non-commutative nature of the operations (vflip+90 is not 90+vflip)
45 *
46 * All 2D transformations can be described by a 2x3 matrix, see QWMetaEngineRotation.
47 * All transformations needed here - rotate 90, 180, 270, flipV, flipH -
48 * can be described in a 2x2 matrix with the values 0,1,-1
49 * (because flipping is expressed by changing the sign only,
50 * and sine and cosine of 90, 180 and 270 are either 0,1 or -1).
51 *
52 * x' = m11 x + m12 y
53 * y' = m21 x + m22 y
54 *
55 * Moreover, all combinations of these rotate/flip operations result in one of the eight
56 * matrices defined below.
57 * (I did not proof that mathematically, but empirically)
58 *
59 * static const MetaEngineRotation identity; //( 1, 0, 0, 1)
60 * static const MetaEngineRotation rotate90; //( 0, 1, -1, 0)
61 * static const MetaEngineRotation rotate180; //(-1, 0, 0, -1)
62 * static const MetaEngineRotation rotate270; //( 0, -1, 1, 0)
63 * static const MetaEngineRotation flipHorizontal; //(-1, 0, 0, 1)
64 * static const MetaEngineRotation flipVertical; //( 1, 0, 0, -1)
65 * static const MetaEngineRotation rotate90flipHorizontal; //( 0, 1, 1, 0), first rotate, then flip
66 * static const MetaEngineRotation rotate90flipVertical; //( 0, -1, -1, 0), first rotate, then flip
67 */
68
69 namespace Matrix
70 {
71
72 static const MetaEngineRotation identity ( 1, 0, 0, 1);
73 static const MetaEngineRotation rotate90 ( 0, 1, -1, 0);
74 static const MetaEngineRotation rotate180 (-1, 0, 0, -1);
75 static const MetaEngineRotation rotate270 ( 0, -1, 1, 0);
76 static const MetaEngineRotation flipHorizontal (-1, 0, 0, 1);
77 static const MetaEngineRotation flipVertical ( 1, 0, 0, -1);
78 static const MetaEngineRotation rotate90flipHorizontal ( 0, 1, 1, 0);
79 static const MetaEngineRotation rotate90flipVertical ( 0, -1, -1, 0);
80
matrix(MetaEngineRotation::TransformationAction action)81 MetaEngineRotation matrix(MetaEngineRotation::TransformationAction action)
82 {
83 switch (action)
84 {
85 case MetaEngineRotation::NoTransformation:
86 {
87 return identity;
88 }
89
90 case MetaEngineRotation::FlipHorizontal:
91 {
92 return flipHorizontal;
93 }
94
95 case MetaEngineRotation::FlipVertical:
96 {
97 return flipVertical;
98 }
99
100 case MetaEngineRotation::Rotate90:
101 {
102 return rotate90;
103 }
104
105 case MetaEngineRotation::Rotate180:
106 {
107 return rotate180;
108 }
109
110 case MetaEngineRotation::Rotate270:
111 {
112 return rotate270;
113 }
114 }
115
116 return identity;
117 }
118
matrix(MetaEngine::ImageOrientation exifOrientation)119 MetaEngineRotation matrix(MetaEngine::ImageOrientation exifOrientation)
120 {
121 switch (exifOrientation)
122 {
123 case MetaEngine::ORIENTATION_NORMAL:
124 {
125 return identity;
126 }
127
128 case MetaEngine::ORIENTATION_HFLIP:
129 {
130 return flipHorizontal;
131 }
132
133 case MetaEngine::ORIENTATION_ROT_180:
134 {
135 return rotate180;
136 }
137
138 case MetaEngine::ORIENTATION_VFLIP:
139 {
140 return flipVertical;
141 }
142
143 case MetaEngine::ORIENTATION_ROT_90_HFLIP:
144 {
145 return rotate90flipHorizontal;
146 }
147
148 case MetaEngine::ORIENTATION_ROT_90:
149 {
150 return rotate90;
151 }
152
153 case MetaEngine::ORIENTATION_ROT_90_VFLIP:
154 {
155 return rotate90flipVertical;
156 }
157
158 case MetaEngine::ORIENTATION_ROT_270:
159 {
160 return rotate270;
161 }
162
163 case MetaEngine::ORIENTATION_UNSPECIFIED:
164 {
165 return identity;
166 }
167 }
168
169 return identity;
170 }
171
172 } // namespace Matrix
173
MetaEngineRotation()174 MetaEngineRotation::MetaEngineRotation()
175 {
176 set(1, 0, 0, 1);
177 }
178
MetaEngineRotation(TransformationAction action)179 MetaEngineRotation::MetaEngineRotation(TransformationAction action)
180 {
181 *this = Matrix::matrix(action);
182 }
183
MetaEngineRotation(MetaEngine::ImageOrientation exifOrientation)184 MetaEngineRotation::MetaEngineRotation(MetaEngine::ImageOrientation exifOrientation)
185 {
186 *this = Matrix::matrix(exifOrientation);
187 }
188
MetaEngineRotation(int m11,int m12,int m21,int m22)189 MetaEngineRotation::MetaEngineRotation(int m11, int m12, int m21, int m22)
190 {
191 set(m11, m12, m21, m22);
192 }
193
set(int m11,int m12,int m21,int m22)194 void MetaEngineRotation::set(int m11, int m12, int m21, int m22)
195 {
196 m[0][0] = m11;
197 m[0][1] = m12;
198 m[1][0] = m21;
199 m[1][1] = m22;
200 }
201
isNoTransform() const202 bool MetaEngineRotation::isNoTransform() const
203 {
204 return (*this == Matrix::identity);
205 }
206
operator *=(const MetaEngineRotation & ma)207 MetaEngineRotation& MetaEngineRotation::operator*=(const MetaEngineRotation& ma)
208 {
209 set(ma.m[0][0]*m[0][0] + ma.m[0][1]*m[1][0], ma.m[0][0]*m[0][1] + ma.m[0][1]*m[1][1],
210 ma.m[1][0]*m[0][0] + ma.m[1][1]*m[1][0], ma.m[1][0]*m[0][1] + ma.m[1][1]*m[1][1]);
211
212 return *this;
213 }
214
operator ==(const MetaEngineRotation & ma) const215 bool MetaEngineRotation::operator==(const MetaEngineRotation& ma) const
216 {
217 return (
218 (m[0][0] == ma.m[0][0]) &&
219 (m[0][1] == ma.m[0][1]) &&
220 (m[1][0] == ma.m[1][0]) &&
221 (m[1][1] == ma.m[1][1])
222 );
223 }
224
operator !=(const MetaEngineRotation & ma) const225 bool MetaEngineRotation::operator!=(const MetaEngineRotation& ma) const
226 {
227 return !(*this==ma);
228 }
229
operator *=(TransformationAction action)230 MetaEngineRotation& MetaEngineRotation::operator*=(TransformationAction action)
231 {
232 return (*this *= Matrix::matrix(action));
233 }
234
operator *=(QList<TransformationAction> actions)235 MetaEngineRotation& MetaEngineRotation::operator*=(QList<TransformationAction> actions)
236 {
237 foreach (const TransformationAction& action, actions)
238 {
239 *this *= Matrix::matrix(action);
240 }
241
242 return *this;
243 }
244
operator *=(MetaEngine::ImageOrientation exifOrientation)245 MetaEngineRotation& MetaEngineRotation::operator*=(MetaEngine::ImageOrientation exifOrientation)
246 {
247 return (*this *= Matrix::matrix(exifOrientation));
248 }
249
250 /**
251 * Converts the mathematically correct description
252 * into the primitive operations that can be carried out losslessly.
253 */
transformations() const254 QList<MetaEngineRotation::TransformationAction> MetaEngineRotation::transformations() const
255 {
256 QList<TransformationAction> transforms;
257
258 if (*this == Matrix::rotate90)
259 {
260 transforms << Rotate90;
261 }
262 else if (*this == Matrix::rotate180)
263 {
264 transforms << Rotate180;
265 }
266 else if (*this == Matrix::rotate270)
267 {
268 transforms << Rotate270;
269 }
270 else if (*this == Matrix::flipHorizontal)
271 {
272 transforms << FlipHorizontal;
273 }
274 else if (*this == Matrix::flipVertical)
275 {
276 transforms << FlipVertical;
277 }
278 else if (*this == Matrix::rotate90flipHorizontal)
279 {
280 // first rotate, then flip!
281
282 transforms << Rotate90;
283 transforms << FlipHorizontal;
284 }
285 else if (*this == Matrix::rotate90flipVertical)
286 {
287 // first rotate, then flip!
288
289 transforms << Rotate90;
290 transforms << FlipVertical;
291 }
292
293 return transforms;
294 }
295
exifOrientation() const296 MetaEngine::ImageOrientation MetaEngineRotation::exifOrientation() const
297 {
298 if (*this == Matrix::identity)
299 {
300 return MetaEngine::ORIENTATION_NORMAL;
301 }
302
303 if (*this == Matrix::rotate90)
304 {
305 return MetaEngine::ORIENTATION_ROT_90;
306 }
307 else if (*this == Matrix::rotate180)
308 {
309 return MetaEngine::ORIENTATION_ROT_180;
310 }
311 else if (*this == Matrix::rotate270)
312 {
313 return MetaEngine::ORIENTATION_ROT_270;
314 }
315 else if (*this == Matrix::flipHorizontal)
316 {
317 return MetaEngine::ORIENTATION_HFLIP;
318 }
319 else if (*this == Matrix::flipVertical)
320 {
321 return MetaEngine::ORIENTATION_VFLIP;
322 }
323 else if (*this == Matrix::rotate90flipHorizontal)
324 {
325 return MetaEngine::ORIENTATION_ROT_90_HFLIP;
326 }
327 else if (*this == Matrix::rotate90flipVertical)
328 {
329 return MetaEngine::ORIENTATION_ROT_90_VFLIP;
330 }
331
332 return MetaEngine::ORIENTATION_UNSPECIFIED;
333 }
334
toMatrix() const335 QMatrix MetaEngineRotation::toMatrix() const
336 {
337 return toMatrix(exifOrientation());
338 }
339
toMatrix(MetaEngine::ImageOrientation orientation)340 QMatrix MetaEngineRotation::toMatrix(MetaEngine::ImageOrientation orientation)
341 {
342 QMatrix matrix;
343
344 switch (orientation)
345 {
346 case MetaEngine::ORIENTATION_NORMAL:
347 case MetaEngine::ORIENTATION_UNSPECIFIED:
348 {
349 break;
350 }
351
352 case MetaEngine::ORIENTATION_HFLIP:
353 {
354 matrix.scale(-1, 1);
355 break;
356 }
357
358 case MetaEngine::ORIENTATION_ROT_180:
359 {
360 matrix.rotate(180);
361 break;
362 }
363
364 case MetaEngine::ORIENTATION_VFLIP:
365 {
366 matrix.scale(1, -1);
367 break;
368 }
369
370 case MetaEngine::ORIENTATION_ROT_90_HFLIP:
371 {
372 matrix.scale(-1, 1);
373 matrix.rotate(90);
374 break;
375 }
376
377 case MetaEngine::ORIENTATION_ROT_90:
378 {
379 matrix.rotate(90);
380 break;
381 }
382
383 case MetaEngine::ORIENTATION_ROT_90_VFLIP:
384 {
385 matrix.scale(1, -1);
386 matrix.rotate(90);
387 break;
388 }
389
390 case MetaEngine::ORIENTATION_ROT_270:
391 {
392 matrix.rotate(270);
393 break;
394 }
395 }
396
397 return matrix;
398 }
399
400 } // namespace Digikam
401