/src/libkexiv2/src/rotationmatrix.cpp
Line | Count | Source |
1 | | /* |
2 | | SPDX-FileCopyrightText: 2006-2015 Gilles Caulier <caulier dot gilles at gmail dot com> |
3 | | SPDX-FileCopyrightText: 2004-2012 Marcel Wiesweg <marcel dot wiesweg at gmx dot de> |
4 | | |
5 | | SPDX-License-Identifier: GPL-2.0-or-later |
6 | | */ |
7 | | |
8 | | // Local includes |
9 | | |
10 | | #include "rotationmatrix.h" |
11 | | |
12 | | namespace KExiv2Iface |
13 | | { |
14 | | |
15 | | /** |
16 | | If the picture is displayed according to the exif orientation tag, |
17 | | the user will request rotating operations relative to what he sees, |
18 | | and that is the picture rotated according to the EXIF tag. |
19 | | So the operation requested and the given EXIF angle must be combined. |
20 | | E.g. if orientation is "6" (rotate 90 clockwiseto show correctly) |
21 | | and the user selects 180 clockwise, the operation is 270. |
22 | | If the user selected 270, the operation would be None (and clearing the exif tag). |
23 | | |
24 | | This requires to describe the transformations in a model which |
25 | | cares for both composing (180+90=270) and eliminating (180+180=no action), |
26 | | as well as the non-commutative nature of the operations (vflip+90 is not 90+vflip) |
27 | | |
28 | | All 2D transformations can be described by a 2x3 matrix, see QWRotationMatrix. |
29 | | All transformations needed here - rotate 90, 180, 270, flipV, flipH - |
30 | | can be described in a 2x2 matrix with the values 0,1,-1 |
31 | | (because flipping is expressed by changing the sign only, |
32 | | and sine and cosine of 90, 180 and 270 are either 0,1 or -1). |
33 | | |
34 | | x' = m11 x + m12 y |
35 | | y' = m21 x + m22 y |
36 | | |
37 | | Moreover, all combinations of these rotate/flip operations result in one of the eight |
38 | | matrices defined below. |
39 | | (I did not proof that mathematically, but empirically) |
40 | | |
41 | | static const RotationMatrix identity; //( 1, 0, 0, 1) |
42 | | static const RotationMatrix rotate90; //( 0, 1, -1, 0) |
43 | | static const RotationMatrix rotate180; //(-1, 0, 0, -1) |
44 | | static const RotationMatrix rotate270; //( 0, -1, 1, 0) |
45 | | static const RotationMatrix flipHorizontal; //(-1, 0, 0, 1) |
46 | | static const RotationMatrix flipVertical; //( 1, 0, 0, -1) |
47 | | static const RotationMatrix rotate90flipHorizontal; //( 0, 1, 1, 0), first rotate, then flip |
48 | | static const RotationMatrix rotate90flipVertical; //( 0, -1, -1, 0), first rotate, then flip |
49 | | |
50 | | */ |
51 | | |
52 | | namespace Matrix |
53 | | { |
54 | | |
55 | | static const RotationMatrix identity ( 1, 0, 0, 1); |
56 | | static const RotationMatrix rotate90 ( 0, 1, -1, 0); |
57 | | static const RotationMatrix rotate180 (-1, 0, 0, -1); |
58 | | static const RotationMatrix rotate270 ( 0, -1, 1, 0); |
59 | | static const RotationMatrix flipHorizontal (-1, 0, 0, 1); |
60 | | static const RotationMatrix flipVertical ( 1, 0, 0, -1); |
61 | | static const RotationMatrix rotate90flipHorizontal ( 0, 1, 1, 0); |
62 | | static const RotationMatrix rotate90flipVertical ( 0, -1, -1, 0); |
63 | | |
64 | | RotationMatrix matrix(RotationMatrix::TransformationAction action) |
65 | 0 | { |
66 | 0 | switch (action) |
67 | 0 | { |
68 | 0 | case RotationMatrix::NoTransformation: |
69 | 0 | return identity; |
70 | 0 | case RotationMatrix::FlipHorizontal: |
71 | 0 | return flipHorizontal; |
72 | 0 | case RotationMatrix::FlipVertical: |
73 | 0 | return flipVertical; |
74 | 0 | case RotationMatrix::Rotate90: |
75 | 0 | return rotate90; |
76 | 0 | case RotationMatrix::Rotate180: |
77 | 0 | return rotate180; |
78 | 0 | case RotationMatrix::Rotate270: |
79 | 0 | return rotate270; |
80 | 0 | } |
81 | | |
82 | 0 | return identity; |
83 | 0 | } |
84 | | |
85 | | RotationMatrix matrix(KExiv2::ImageOrientation exifOrientation) |
86 | 0 | { |
87 | 0 | switch (exifOrientation) |
88 | 0 | { |
89 | 0 | case KExiv2::ORIENTATION_NORMAL: |
90 | 0 | return identity; |
91 | 0 | case KExiv2::ORIENTATION_HFLIP: |
92 | 0 | return flipHorizontal; |
93 | 0 | case KExiv2::ORIENTATION_ROT_180: |
94 | 0 | return rotate180; |
95 | 0 | case KExiv2::ORIENTATION_VFLIP: |
96 | 0 | return flipVertical; |
97 | 0 | case KExiv2::ORIENTATION_ROT_90_HFLIP: |
98 | 0 | return rotate90flipHorizontal; |
99 | 0 | case KExiv2::ORIENTATION_ROT_90: |
100 | 0 | return rotate90; |
101 | 0 | case KExiv2::ORIENTATION_ROT_90_VFLIP: |
102 | 0 | return rotate90flipVertical; |
103 | 0 | case KExiv2::ORIENTATION_ROT_270: |
104 | 0 | return rotate270; |
105 | 0 | case KExiv2::ORIENTATION_UNSPECIFIED: |
106 | 0 | return identity; |
107 | 0 | } |
108 | | |
109 | 0 | return identity; |
110 | 0 | } |
111 | | |
112 | | } // namespace Matrix |
113 | | |
114 | | RotationMatrix::RotationMatrix() |
115 | 0 | { |
116 | 0 | set( 1, 0, 0, 1 ); |
117 | 0 | } |
118 | | |
119 | | RotationMatrix::RotationMatrix(TransformationAction action) |
120 | 0 | { |
121 | 0 | *this = Matrix::matrix(action); |
122 | 0 | } |
123 | | |
124 | | RotationMatrix::RotationMatrix(KExiv2::ImageOrientation exifOrientation) |
125 | 0 | { |
126 | 0 | *this = Matrix::matrix(exifOrientation); |
127 | 0 | } |
128 | | |
129 | | RotationMatrix::RotationMatrix(int m11, int m12, int m21, int m22) |
130 | 0 | { |
131 | 0 | set(m11, m12, m21, m22); |
132 | 0 | } |
133 | | |
134 | | void RotationMatrix::set(int m11, int m12, int m21, int m22) |
135 | 0 | { |
136 | 0 | m[0][0]=m11; |
137 | 0 | m[0][1]=m12; |
138 | 0 | m[1][0]=m21; |
139 | 0 | m[1][1]=m22; |
140 | 0 | } |
141 | | |
142 | | bool RotationMatrix::isNoTransform() const |
143 | 0 | { |
144 | 0 | return (*this == Matrix::identity); |
145 | 0 | } |
146 | | |
147 | | RotationMatrix& RotationMatrix::operator*=(const RotationMatrix& ma) |
148 | 0 | { |
149 | 0 | 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], |
150 | 0 | 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] ); |
151 | |
|
152 | 0 | return *this; |
153 | 0 | } |
154 | | |
155 | | bool RotationMatrix::operator==(const RotationMatrix& ma) const |
156 | 0 | { |
157 | 0 | return m[0][0]==ma.m[0][0] && |
158 | 0 | m[0][1]==ma.m[0][1] && |
159 | 0 | m[1][0]==ma.m[1][0] && |
160 | 0 | m[1][1]==ma.m[1][1]; |
161 | 0 | } |
162 | | |
163 | | bool RotationMatrix::operator!=(const RotationMatrix& ma) const |
164 | 0 | { |
165 | 0 | return !(*this==ma); |
166 | 0 | } |
167 | | |
168 | | RotationMatrix& RotationMatrix::operator*=(TransformationAction action) |
169 | 0 | { |
170 | 0 | return (*this *= Matrix::matrix(action)); |
171 | 0 | } |
172 | | |
173 | | RotationMatrix& RotationMatrix::operator*=(QList<TransformationAction> actions) |
174 | 0 | { |
175 | 0 | for (const TransformationAction& action : std::as_const(actions)) |
176 | 0 | { |
177 | 0 | *this *= Matrix::matrix(action); |
178 | 0 | } |
179 | |
|
180 | 0 | return *this; |
181 | 0 | } |
182 | | |
183 | | RotationMatrix& RotationMatrix::operator*=(KExiv2::ImageOrientation exifOrientation) |
184 | 0 | { |
185 | 0 | return (*this *= Matrix::matrix(exifOrientation)); |
186 | 0 | } |
187 | | |
188 | | /** Converts the mathematically correct description |
189 | | into the primitive operations that can be carried out losslessly. |
190 | | */ |
191 | | QList<RotationMatrix::TransformationAction> RotationMatrix::transformations() const |
192 | 0 | { |
193 | 0 | QList<TransformationAction> transforms; |
194 | |
|
195 | 0 | if (*this == Matrix::rotate90) |
196 | 0 | { |
197 | 0 | transforms << Rotate90; |
198 | 0 | } |
199 | 0 | else if (*this == Matrix::rotate180) |
200 | 0 | { |
201 | 0 | transforms << Rotate180; |
202 | 0 | } |
203 | 0 | else if (*this == Matrix::rotate270) |
204 | 0 | { |
205 | 0 | transforms << Rotate270; |
206 | 0 | } |
207 | 0 | else if (*this == Matrix::flipHorizontal) |
208 | 0 | { |
209 | 0 | transforms << FlipHorizontal; |
210 | 0 | } |
211 | 0 | else if (*this == Matrix::flipVertical) |
212 | 0 | { |
213 | 0 | transforms << FlipVertical; |
214 | 0 | } |
215 | 0 | else if (*this == Matrix::rotate90flipHorizontal) |
216 | 0 | { |
217 | | //first rotate, then flip! |
218 | 0 | transforms << Rotate90; |
219 | 0 | transforms << FlipHorizontal; |
220 | 0 | } |
221 | 0 | else if (*this == Matrix::rotate90flipVertical) |
222 | 0 | { |
223 | | //first rotate, then flip! |
224 | 0 | transforms << Rotate90; |
225 | 0 | transforms << FlipVertical; |
226 | 0 | } |
227 | |
|
228 | 0 | return transforms; |
229 | 0 | } |
230 | | |
231 | | KExiv2::ImageOrientation RotationMatrix::exifOrientation() const |
232 | 0 | { |
233 | 0 | if (*this == Matrix::identity) |
234 | 0 | { |
235 | 0 | return KExiv2::ORIENTATION_NORMAL; |
236 | 0 | } |
237 | | |
238 | 0 | if (*this == Matrix::rotate90) |
239 | 0 | { |
240 | 0 | return KExiv2::ORIENTATION_ROT_90; |
241 | 0 | } |
242 | 0 | else if (*this == Matrix::rotate180) |
243 | 0 | { |
244 | 0 | return KExiv2::ORIENTATION_ROT_180; |
245 | 0 | } |
246 | 0 | else if (*this == Matrix::rotate270) |
247 | 0 | { |
248 | 0 | return KExiv2::ORIENTATION_ROT_270; |
249 | 0 | } |
250 | 0 | else if (*this == Matrix::flipHorizontal) |
251 | 0 | { |
252 | 0 | return KExiv2::ORIENTATION_HFLIP; |
253 | 0 | } |
254 | 0 | else if (*this == Matrix::flipVertical) |
255 | 0 | { |
256 | 0 | return KExiv2::ORIENTATION_VFLIP; |
257 | 0 | } |
258 | 0 | else if (*this == Matrix::rotate90flipHorizontal) |
259 | 0 | { |
260 | 0 | return KExiv2::ORIENTATION_ROT_90_HFLIP; |
261 | 0 | } |
262 | 0 | else if (*this == Matrix::rotate90flipVertical) |
263 | 0 | { |
264 | 0 | return KExiv2::ORIENTATION_ROT_90_VFLIP; |
265 | 0 | } |
266 | | |
267 | 0 | return KExiv2::ORIENTATION_UNSPECIFIED; |
268 | 0 | } |
269 | | |
270 | | QTransform RotationMatrix::toTransform() const |
271 | 0 | { |
272 | 0 | return toTransform(exifOrientation()); |
273 | 0 | } |
274 | | |
275 | | QTransform RotationMatrix::toTransform(KExiv2::ImageOrientation orientation) |
276 | 34 | { |
277 | 34 | QTransform matrix; |
278 | | |
279 | 34 | switch (orientation) |
280 | 34 | { |
281 | 13 | case KExiv2::ORIENTATION_NORMAL: |
282 | 23 | case KExiv2::ORIENTATION_UNSPECIFIED: |
283 | 23 | break; |
284 | | |
285 | 0 | case KExiv2::ORIENTATION_HFLIP: |
286 | 0 | matrix.scale(-1, 1); |
287 | 0 | break; |
288 | | |
289 | 0 | case KExiv2::ORIENTATION_ROT_180: |
290 | 0 | matrix.rotate(180); |
291 | 0 | break; |
292 | | |
293 | 4 | case KExiv2::ORIENTATION_VFLIP: |
294 | 4 | matrix.scale(1, -1); |
295 | 4 | break; |
296 | | |
297 | 0 | case KExiv2::ORIENTATION_ROT_90_HFLIP: |
298 | 0 | matrix.scale(-1, 1); |
299 | 0 | matrix.rotate(90); |
300 | 0 | break; |
301 | | |
302 | 7 | case KExiv2::ORIENTATION_ROT_90: |
303 | 7 | matrix.rotate(90); |
304 | 7 | break; |
305 | | |
306 | 0 | case KExiv2::ORIENTATION_ROT_90_VFLIP: |
307 | 0 | matrix.scale(1, -1); |
308 | 0 | matrix.rotate(90); |
309 | 0 | break; |
310 | | |
311 | 0 | case KExiv2::ORIENTATION_ROT_270: |
312 | 0 | matrix.rotate(270); |
313 | 0 | break; |
314 | 34 | } |
315 | | |
316 | 34 | return matrix; |
317 | 34 | } |
318 | | |
319 | | } // namespace KExiv2Iface |