File indexing completed on 2025-01-05 03:56:29

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2006-09-15
0007  * Description : Exiv2 library interface.
0008  *               Tools for combining rotation operations.
0009  *
0010  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 // Local includes
0018 
0019 #include "metaengine_rotation.h"
0020 
0021 namespace Digikam
0022 {
0023 
0024 /**
0025  * If the picture is displayed according to the exif orientation tag,
0026  * the user will request rotating operations relative to what he sees,
0027  * and that is the picture rotated according to the EXIF tag.
0028  * So the operation requested and the given EXIF angle must be combined.
0029  * E.g. if orientation is "6" (rotate 90 clockwiseto show correctly)
0030  * and the user selects 180 clockwise, the operation is 270.
0031  * If the user selected 270, the operation would be None (and clearing the exif tag).
0032  *
0033  * This requires to describe the transformations in a model which
0034  * cares for both composing (180+90=270) and eliminating (180+180=no action),
0035  * as well as the non-commutative nature of the operations (vflip+90 is not 90+vflip)
0036  *
0037  * All 2D transformations can be described by a 2x3 matrix, see QWMetaEngineRotation.
0038  * All transformations needed here - rotate 90, 180, 270, flipV, flipH -
0039  * can be described in a 2x2 matrix with the values 0,1,-1
0040  * (because flipping is expressed by changing the sign only,
0041  *  and sine and cosine of 90, 180 and 270 are either 0,1 or -1).
0042  *
0043  *  x' = m11 x + m12 y
0044  *  y' = m21 x + m22 y
0045  *
0046  * Moreover, all combinations of these rotate/flip operations result in one of the eight
0047  * matrices defined below.
0048  * (I did not proof that mathematically, but empirically)
0049  *
0050  * static const MetaEngineRotation identity;               //( 1,  0,  0,  1)
0051  * static const MetaEngineRotation rotate90;               //( 0,  1, -1,  0)
0052  * static const MetaEngineRotation rotate180;              //(-1,  0,  0, -1)
0053  * static const MetaEngineRotation rotate270;              //( 0, -1,  1,  0)
0054  * static const MetaEngineRotation flipHorizontal;         //(-1,  0,  0,  1)
0055  * static const MetaEngineRotation flipVertical;           //( 1,  0,  0, -1)
0056  * static const MetaEngineRotation rotate90flipHorizontal; //( 0,  1,  1,  0), first rotate, then flip
0057  * static const MetaEngineRotation rotate90flipVertical;   //( 0, -1, -1,  0), first rotate, then flip
0058  */
0059 
0060 namespace Matrix
0061 {
0062 
0063 static const MetaEngineRotation identity               ( 1,  0,  0,  1);
0064 static const MetaEngineRotation rotate90               ( 0,  1, -1,  0);
0065 static const MetaEngineRotation rotate180              (-1,  0,  0, -1);
0066 static const MetaEngineRotation rotate270              ( 0, -1,  1,  0);
0067 static const MetaEngineRotation flipHorizontal         (-1,  0,  0,  1);
0068 static const MetaEngineRotation flipVertical           ( 1,  0,  0, -1);
0069 static const MetaEngineRotation rotate90flipHorizontal ( 0,  1,  1,  0);
0070 static const MetaEngineRotation rotate90flipVertical   ( 0, -1, -1,  0);
0071 
0072 MetaEngineRotation matrix(MetaEngineRotation::TransformationAction action)
0073 {
0074     switch (action)
0075     {
0076         case MetaEngineRotation::NoTransformation:
0077         {
0078             return identity;
0079         }
0080 
0081         case MetaEngineRotation::FlipHorizontal:
0082         {
0083             return flipHorizontal;
0084         }
0085 
0086         case MetaEngineRotation::FlipVertical:
0087         {
0088             return flipVertical;
0089         }
0090 
0091         case MetaEngineRotation::Rotate90:
0092         {
0093             return rotate90;
0094         }
0095 
0096         case MetaEngineRotation::Rotate180:
0097         {
0098             return rotate180;
0099         }
0100 
0101         case MetaEngineRotation::Rotate270:
0102         {
0103             return rotate270;
0104         }
0105     }
0106 
0107     return identity;
0108 }
0109 
0110 MetaEngineRotation matrix(MetaEngine::ImageOrientation exifOrientation)
0111 {
0112     switch (exifOrientation)
0113     {
0114         case MetaEngine::ORIENTATION_NORMAL:
0115         {
0116             return identity;
0117         }
0118 
0119         case MetaEngine::ORIENTATION_HFLIP:
0120         {
0121             return flipHorizontal;
0122         }
0123 
0124         case MetaEngine::ORIENTATION_ROT_180:
0125         {
0126             return rotate180;
0127         }
0128 
0129         case MetaEngine::ORIENTATION_VFLIP:
0130         {
0131             return flipVertical;
0132         }
0133 
0134         case MetaEngine::ORIENTATION_ROT_90_HFLIP:
0135         {
0136             return rotate90flipHorizontal;
0137         }
0138 
0139         case MetaEngine::ORIENTATION_ROT_90:
0140         {
0141             return rotate90;
0142         }
0143 
0144         case MetaEngine::ORIENTATION_ROT_90_VFLIP:
0145         {
0146             return rotate90flipVertical;
0147         }
0148 
0149         case MetaEngine::ORIENTATION_ROT_270:
0150         {
0151             return rotate270;
0152         }
0153 
0154         case MetaEngine::ORIENTATION_UNSPECIFIED:
0155         {
0156             return identity;
0157         }
0158     }
0159 
0160     return identity;
0161 }
0162 
0163 } // namespace Matrix
0164 
0165 MetaEngineRotation::MetaEngineRotation()
0166 {
0167     set(1, 0, 0, 1);
0168 }
0169 
0170 MetaEngineRotation::MetaEngineRotation(TransformationAction action)
0171 {
0172     *this = Matrix::matrix(action);
0173 }
0174 
0175 MetaEngineRotation::MetaEngineRotation(MetaEngine::ImageOrientation exifOrientation)
0176 {
0177     *this = Matrix::matrix(exifOrientation);
0178 }
0179 
0180 MetaEngineRotation::MetaEngineRotation(int m11, int m12, int m21, int m22)
0181 {
0182     set(m11, m12, m21, m22);
0183 }
0184 
0185 void MetaEngineRotation::set(int m11, int m12, int m21, int m22)
0186 {
0187     m[0][0] = m11;
0188     m[0][1] = m12;
0189     m[1][0] = m21;
0190     m[1][1] = m22;
0191 }
0192 
0193 bool MetaEngineRotation::isNoTransform() const
0194 {
0195     return (*this == Matrix::identity);
0196 }
0197 
0198 MetaEngineRotation& MetaEngineRotation::operator*=(const MetaEngineRotation& ma)
0199 {
0200     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],
0201         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]);
0202 
0203     return *this;
0204 }
0205 
0206 bool MetaEngineRotation::operator==(const MetaEngineRotation& ma) const
0207 {
0208     return (
0209             (m[0][0] == ma.m[0][0]) &&
0210             (m[0][1] == ma.m[0][1]) &&
0211             (m[1][0] == ma.m[1][0]) &&
0212             (m[1][1] == ma.m[1][1])
0213            );
0214 }
0215 
0216 bool MetaEngineRotation::operator!=(const MetaEngineRotation& ma) const
0217 {
0218     return !(*this==ma);
0219 }
0220 
0221 MetaEngineRotation& MetaEngineRotation::operator*=(TransformationAction action)
0222 {
0223     return (*this *= Matrix::matrix(action));
0224 }
0225 
0226 MetaEngineRotation& MetaEngineRotation::operator*=(QList<TransformationAction> actions)
0227 {
0228     Q_FOREACH (const TransformationAction& action, actions)
0229     {
0230         *this *= Matrix::matrix(action);
0231     }
0232 
0233     return *this;
0234 }
0235 
0236 MetaEngineRotation& MetaEngineRotation::operator*=(MetaEngine::ImageOrientation exifOrientation)
0237 {
0238     return (*this *= Matrix::matrix(exifOrientation));
0239 }
0240 
0241 /**
0242  * Converts the mathematically correct description
0243  * into the primitive operations that can be carried out losslessly.
0244  */
0245 QList<MetaEngineRotation::TransformationAction> MetaEngineRotation::transformations() const
0246 {
0247     QList<TransformationAction> transforms;
0248 
0249     if      (*this == Matrix::rotate90)
0250     {
0251         transforms << Rotate90;
0252     }
0253     else if (*this == Matrix::rotate180)
0254     {
0255         transforms << Rotate180;
0256     }
0257     else if (*this == Matrix::rotate270)
0258     {
0259         transforms << Rotate270;
0260     }
0261     else if (*this == Matrix::flipHorizontal)
0262     {
0263         transforms << FlipHorizontal;
0264     }
0265     else if (*this == Matrix::flipVertical)
0266     {
0267         transforms << FlipVertical;
0268     }
0269     else if (*this == Matrix::rotate90flipHorizontal)
0270     {
0271         // first rotate, then flip!
0272 
0273         transforms << Rotate90;
0274         transforms << FlipHorizontal;
0275     }
0276     else if (*this == Matrix::rotate90flipVertical)
0277     {
0278         // first rotate, then flip!
0279 
0280         transforms << Rotate90;
0281         transforms << FlipVertical;
0282     }
0283 
0284     return transforms;
0285 }
0286 
0287 MetaEngine::ImageOrientation MetaEngineRotation::exifOrientation() const
0288 {
0289     if (*this == Matrix::identity)
0290     {
0291         return MetaEngine::ORIENTATION_NORMAL;
0292     }
0293 
0294     if      (*this == Matrix::rotate90)
0295     {
0296         return MetaEngine::ORIENTATION_ROT_90;
0297     }
0298     else if (*this == Matrix::rotate180)
0299     {
0300         return MetaEngine::ORIENTATION_ROT_180;
0301     }
0302     else if (*this == Matrix::rotate270)
0303     {
0304         return MetaEngine::ORIENTATION_ROT_270;
0305     }
0306     else if (*this == Matrix::flipHorizontal)
0307     {
0308         return MetaEngine::ORIENTATION_HFLIP;
0309     }
0310     else if (*this == Matrix::flipVertical)
0311     {
0312         return MetaEngine::ORIENTATION_VFLIP;
0313     }
0314     else if (*this == Matrix::rotate90flipHorizontal)
0315     {
0316         return MetaEngine::ORIENTATION_ROT_90_HFLIP;
0317     }
0318     else if (*this == Matrix::rotate90flipVertical)
0319     {
0320         return MetaEngine::ORIENTATION_ROT_90_VFLIP;
0321     }
0322 
0323     return MetaEngine::ORIENTATION_UNSPECIFIED;
0324 }
0325 
0326 QTransform MetaEngineRotation::toTransform() const
0327 {
0328     return toTransform(exifOrientation());
0329 }
0330 
0331 QTransform MetaEngineRotation::toTransform(MetaEngine::ImageOrientation orientation)
0332 {
0333     QTransform matrix;
0334 
0335     switch (orientation)
0336     {
0337         case MetaEngine::ORIENTATION_NORMAL:
0338         case MetaEngine::ORIENTATION_UNSPECIFIED:
0339         {
0340             break;
0341         }
0342 
0343         case MetaEngine::ORIENTATION_HFLIP:
0344         {
0345             matrix.scale(-1, 1);
0346             break;
0347         }
0348 
0349         case MetaEngine::ORIENTATION_ROT_180:
0350         {
0351             matrix.rotate(180);
0352             break;
0353         }
0354 
0355         case MetaEngine::ORIENTATION_VFLIP:
0356         {
0357             matrix.scale(1, -1);
0358             break;
0359         }
0360 
0361         case MetaEngine::ORIENTATION_ROT_90_HFLIP:
0362         {
0363             matrix.scale(-1, 1);
0364             matrix.rotate(90);
0365             break;
0366         }
0367 
0368         case MetaEngine::ORIENTATION_ROT_90:
0369         {
0370             matrix.rotate(90);
0371             break;
0372         }
0373 
0374         case MetaEngine::ORIENTATION_ROT_90_VFLIP:
0375         {
0376             matrix.scale(1, -1);
0377             matrix.rotate(90);
0378             break;
0379         }
0380 
0381         case MetaEngine::ORIENTATION_ROT_270:
0382         {
0383             matrix.rotate(270);
0384             break;
0385         }
0386     }
0387 
0388     return matrix;
0389 }
0390 
0391 } // namespace Digikam