File indexing completed on 2024-04-28 04:20:23

0001 
0002 /*
0003    Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
0004    All rights reserved.
0005 
0006    Redistribution and use in source and binary forms, with or without
0007    modification, are permitted provided that the following conditions
0008    are met:
0009 
0010    1. Redistributions of source code must retain the above copyright
0011       notice, this list of conditions and the following disclaimer.
0012    2. Redistributions in binary form must reproduce the above copyright
0013       notice, this list of conditions and the following disclaimer in the
0014       documentation and/or other materials provided with the distribution.
0015 
0016    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
0017    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0018    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
0019    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
0020    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
0021    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
0022    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
0023    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0024    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
0025    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0026 */
0027 
0028 
0029 #define DEBUG_KP_PIXMAP_FX 0
0030 
0031 
0032 #include "kpPixmapFX.h"
0033 
0034 #include <QtMath>
0035 
0036 #include <QPainter>
0037 #include <QImage>
0038 #include <QPoint>
0039 #include <QRect>
0040 
0041 #include "kpLogCategories.h"
0042 
0043 #include "layers/selections/kpAbstractSelection.h"
0044 #include "imagelib/kpColor.h"
0045 #include "kpDefs.h"
0046 
0047 //---------------------------------------------------------------------
0048 
0049 // public static
0050 void kpPixmapFX::resize (QImage *destPtr, int w, int h,
0051                          const kpColor &backgroundColor)
0052 {
0053 #if DEBUG_KP_PIXMAP_FX && 1
0054     qCDebug(kpLogPixmapfx) << "kpPixmapFX::resize()";
0055 #endif
0056 
0057     if (!destPtr) {
0058         return;
0059     }
0060 
0061     const int oldWidth = destPtr->width ();
0062     const int oldHeight = destPtr->height ();
0063 
0064     if (w == oldWidth && h == oldHeight) {
0065         return;
0066     }
0067 
0068     QImage newImage (w, h, QImage::Format_ARGB32_Premultiplied);
0069 
0070     // Would have new undefined areas?
0071     if (w > oldWidth || h > oldHeight) {
0072         newImage.fill (backgroundColor.toQRgb ());
0073     }
0074 
0075     // Copy over old pixmap.
0076     QPainter painter(&newImage);
0077     painter.setCompositionMode(QPainter::CompositionMode_Source);
0078     painter.drawImage(0, 0, *destPtr);
0079     painter.end();
0080 
0081     // Replace pixmap with new one.
0082     *destPtr = std::move(newImage);
0083 }
0084 
0085 //---------------------------------------------------------------------
0086 
0087 // public static
0088 QImage kpPixmapFX::resize (const QImage &image, int w, int h,
0089                            const kpColor &backgroundColor)
0090 {
0091     QImage ret = image;
0092     kpPixmapFX::resize (&ret, w, h, backgroundColor);
0093     return ret;
0094 }
0095 
0096 //---------------------------------------------------------------------
0097 
0098 // public static
0099 void kpPixmapFX::scale (QImage *destPtr, int w, int h, bool pretty)
0100 {
0101     if (!destPtr) {
0102         return;
0103     }
0104 
0105     *destPtr = kpPixmapFX::scale (*destPtr, w, h, pretty);
0106 }
0107 
0108 //---------------------------------------------------------------------
0109 
0110 // public static
0111 QImage kpPixmapFX::scale (const QImage &image, int w, int h, bool pretty)
0112 {
0113 #if DEBUG_KP_PIXMAP_FX && 0
0114     qCDebug(kpLogPixmapfx) << "kpPixmapFX::scale(oldRect=" << image.rect ()
0115                << ",w=" << w
0116                << ",h=" << h
0117                << ",pretty=" << pretty
0118                << ")";
0119 #endif
0120 
0121     if (w == image.width () && h == image.height ()) {
0122         return image;
0123     }
0124 
0125     return image.scaled(w, h, Qt::IgnoreAspectRatio,
0126                         pretty ? Qt::SmoothTransformation : Qt::FastTransformation);
0127 }
0128 
0129 //---------------------------------------------------------------------
0130 
0131 // public static
0132 const double kpPixmapFX::AngleInDegreesEpsilon =
0133     qRadiansToDegrees (std::tan (1.0 / 10000.0))
0134         / (2.0/*max error allowed*/ * 2.0/*for good measure*/);
0135 
0136 
0137 static void MatrixDebug (const QString& matrixName, const QTransform &matrix,
0138         int srcPixmapWidth = -1, int srcPixmapHeight = -1)
0139 {
0140 #if DEBUG_KP_PIXMAP_FX
0141     const int w = srcPixmapWidth, h = srcPixmapHeight;
0142 
0143     qCDebug(kpLogPixmapfx) << matrixName << "=" << matrix;
0144     // Sometimes this precision lets us see unexpected rounding errors.
0145     fprintf (stderr, "m11=%.24f m12=%.24f m21=%.24f m22=%.24f dx=%.24f dy=%.24f\n",
0146              matrix.m11 (), matrix.m12 (),
0147              matrix.m21 (), matrix.m22 (),
0148              matrix.dx (), matrix.dy ());
0149     if (w > 0 && h > 0)
0150     {
0151         qCDebug(kpLogPixmapfx) << "(0,0) ->" << matrix.map (QPoint (0, 0));
0152         qCDebug(kpLogPixmapfx) << "(w-1,0) ->" << matrix.map (QPoint (w - 1, 0));
0153         qCDebug(kpLogPixmapfx) << "(0,h-1) ->" << matrix.map (QPoint (0, h - 1));
0154         qCDebug(kpLogPixmapfx) << "(w-1,h-1) ->" << matrix.map (QPoint (w - 1, h - 1));
0155     }
0156 
0157 #else
0158 
0159     Q_UNUSED (matrixName);
0160     Q_UNUSED (matrix);
0161     Q_UNUSED (srcPixmapWidth);
0162     Q_UNUSED (srcPixmapHeight);
0163 
0164 #endif  // DEBUG_KP_PIXMAP_FX
0165 }
0166 
0167 //---------------------------------------------------------------------
0168 
0169 // Theoretically, this should act the same as QPixmap::trueMatrix() but
0170 // it doesn't.  As an example, if you rotate tests/transforms.png by 90
0171 // degrees clockwise, this returns the correct <dx> of 26 but
0172 // QPixmap::trueMatrix() returns 27.
0173 //
0174 // You should use the returned matrix to map points accurately (e.g. selection
0175 // borders).  For QPainter::drawPixmap()/drawImage() + setWorldMatrix()
0176 // rendering accuracy, pass the returned matrix through QPixmap::trueMatrix()
0177 // and use that.
0178 //
0179 // TODO: If you put the flipMatrix() of tests/transforms.png through this,
0180 //       the output is the same as QPixmap::trueMatrix(): <dy> is one off
0181 //       (dy=27 instead of 26).
0182 //       SYNC: I bet this is a Qt4 bug.
0183 static QTransform MatrixWithZeroOrigin (const QTransform &matrix, int width, int height)
0184 {
0185 #if DEBUG_KP_PIXMAP_FX
0186     qCDebug(kpLogPixmapfx) << "matrixWithZeroOrigin(w=" << width << ",h=" << height << ")";
0187     qCDebug(kpLogPixmapfx) << "\tmatrix: m11=" << matrix.m11 ()
0188                << "m12=" << matrix.m12 ()
0189                << "m21=" << matrix.m21 ()
0190                << "m22=" << matrix.m22 ()
0191                << "dx=" << matrix.dx ()
0192                << "dy=" << matrix.dy ();
0193 #endif
0194 
0195     QRect mappedRect = matrix.mapRect (QRect (0, 0, width, height));
0196 #if DEBUG_KP_PIXMAP_FX
0197     qCDebug(kpLogPixmapfx) << "\tmappedRect=" << mappedRect;
0198 #endif
0199 
0200     QTransform translatedMatrix (
0201         matrix.m11 (), matrix.m12 (),
0202         matrix.m21 (), matrix.m22 (),
0203         matrix.dx () - mappedRect.left (), matrix.dy () - mappedRect.top ());
0204 
0205 #if DEBUG_KP_PIXMAP_FX
0206     qCDebug(kpLogPixmapfx) << "\treturning" << translatedMatrix;
0207     qCDebug(kpLogPixmapfx) << "(0,0) ->" << translatedMatrix.map (QPoint (0, 0));
0208     qCDebug(kpLogPixmapfx) << "(w-1,0) ->" << translatedMatrix.map (QPoint (width - 1, 0));
0209     qCDebug(kpLogPixmapfx) << "(0,h-1) ->" << translatedMatrix.map (QPoint (0, height - 1));
0210     qCDebug(kpLogPixmapfx) << "(w-1,h-1) ->" << translatedMatrix.map (QPoint (width - 1, height - 1));
0211 #endif
0212 
0213     return translatedMatrix;
0214 }
0215 
0216 //---------------------------------------------------------------------
0217 
0218 static double TrueMatrixEpsilon = 0.000001;
0219 
0220 // An attempt to reverse tiny rounding errors introduced by QPixmap::trueMatrix()
0221 // when skewing tests/transforms.png by 45% horizontally (with TransformPixmap()
0222 // using a QPixmap painter, prior to the 2007-10-09 change -- did not test after
0223 // the change).
0224 // Unfortunately, this does not work enough to stop the rendering errors
0225 // that follow.  But it was worth a try and might still help us given the
0226 // sometimes excessive aliasing QPainter::draw{Pixmap,Image}() gives us, when
0227 // QPainter::SmoothPixmapTransform is disabled.
0228 static double TrueMatrixFixInts (double x)
0229 {
0230     if (std::fabs (x - qRound (x)) < TrueMatrixEpsilon) {
0231         return qRound (x);
0232     }
0233 
0234     return x;
0235 }
0236 
0237 //---------------------------------------------------------------------
0238 
0239 static QTransform TrueMatrix (const QTransform &matrix, int srcPixmapWidth, int srcPixmapHeight)
0240 {
0241     ::MatrixDebug (QStringLiteral("TrueMatrix(): org"), matrix);
0242 
0243     const QTransform truMat = QPixmap::trueMatrix (matrix, srcPixmapWidth, srcPixmapHeight);
0244     ::MatrixDebug (QStringLiteral("TrueMatrix(): passed through QPixmap::trueMatrix()"), truMat);
0245 
0246     const QTransform retMat (
0247         ::TrueMatrixFixInts (truMat.m11 ()),
0248         ::TrueMatrixFixInts (truMat.m12 ()),
0249         ::TrueMatrixFixInts (truMat.m21 ()),
0250         ::TrueMatrixFixInts (truMat.m22 ()),
0251         ::TrueMatrixFixInts (truMat.dx ()),
0252         ::TrueMatrixFixInts (truMat.dy ()));
0253     ::MatrixDebug (QStringLiteral("TrueMatrix(): fixed ints"), retMat);
0254 
0255     return retMat;
0256 }
0257 
0258 //---------------------------------------------------------------------
0259 
0260 // Like QPixmap::transformed() but fills new areas with <backgroundColor>
0261 // (unless <backgroundColor> is invalid) and works around internal QTransform
0262 // floating point -> integer oddities, that would otherwise give fatally
0263 // incorrect results.  If you don't believe me on this latter point, compare
0264 // QPixmap::transformed() to us using a flip matrix or a rotate-by-multiple-of-90
0265 // matrix on tests/transforms.png -- QPixmap::transformed()'s output is 1
0266 // pixel too high or low depending on whether the matrix is passed through
0267 // QPixmap::trueMatrix().
0268 //
0269 // Use <targetWidth> and <targetHeight> to specify the intended output size
0270 // of the pixmap.  -1 if don't care.
0271 static QImage TransformPixmap (const QImage &pm, const QTransform &transformMatrix_,
0272         const kpColor &backgroundColor,
0273         int targetWidth, int targetHeight)
0274 {
0275     QTransform transformMatrix = transformMatrix_;
0276 
0277 #if DEBUG_KP_PIXMAP_FX && 1
0278     qCDebug(kpLogPixmapfx) << "kppixmapfx.cpp: TransformPixmap(pm.size=" << pm.size ()
0279                << ",targetWidth=" << targetWidth
0280                << ",targetHeight=" << targetHeight
0281                << ")";
0282 #endif
0283 
0284     QRect newRect = transformMatrix.mapRect (pm.rect ());
0285 #if DEBUG_KP_PIXMAP_FX && 1
0286     qCDebug(kpLogPixmapfx) << "\tmappedRect=" << newRect;
0287 
0288 #endif
0289 
0290     QTransform scaleMatrix;
0291     if (targetWidth > 0 && targetWidth != newRect.width ())
0292     {
0293     #if DEBUG_KP_PIXMAP_FX && 1
0294         qCDebug(kpLogPixmapfx) << "\tadjusting for targetWidth";
0295     #endif
0296         scaleMatrix.scale (double (targetWidth) / double (newRect.width ()), 1);
0297     }
0298 
0299     if (targetHeight > 0 && targetHeight != newRect.height ())
0300     {
0301     #if DEBUG_KP_PIXMAP_FX && 1
0302         qCDebug(kpLogPixmapfx) << "\tadjusting for targetHeight";
0303     #endif
0304         scaleMatrix.scale (1, double (targetHeight) / double (newRect.height ()));
0305     }
0306 
0307     if (!scaleMatrix.isIdentity ())
0308     {
0309     #if DEBUG_KP_PIXMAP_FX && 1
0310         // TODO: What is going on here???  Why isn't matrix * working properly?
0311         QTransform wrongMatrix = transformMatrix * scaleMatrix;
0312         QTransform oldHat = transformMatrix;
0313 
0314         if (targetWidth > 0 && targetWidth != newRect.width ()) {
0315             oldHat.scale (double (targetWidth) / double (newRect.width ()), 1);
0316         }
0317         if (targetHeight > 0 && targetHeight != newRect.height ()) {
0318             oldHat.scale (1, double (targetHeight) / double (newRect.height ()));
0319         }
0320 
0321         QTransform altHat = transformMatrix;
0322 
0323         altHat.scale ((targetWidth > 0 && targetWidth != newRect.width ()) ? double (targetWidth) / double (newRect.width ()) : 1,
0324                       (targetHeight > 0 && targetHeight != newRect.height ()) ? double (targetHeight) / double (newRect.height ()) : 1);
0325         QTransform correctMatrix = scaleMatrix * transformMatrix;
0326 
0327         qCDebug(kpLogPixmapfx) << "\tsupposedlyWrongMatrix: m11=" << wrongMatrix.m11 ()  // <<<---- this is the correct matrix???
0328                    << " m12=" << wrongMatrix.m12 ()
0329                    << " m21=" << wrongMatrix.m21 ()
0330                    << " m22=" << wrongMatrix.m22 ()
0331                    << " dx=" << wrongMatrix.dx ()
0332                    << " dy=" << wrongMatrix.dy ()
0333                    << " rect=" << wrongMatrix.mapRect (pm.rect ())
0334                    << "\n"
0335                    << "\ti_used_to_use_thisMatrix: m11=" << oldHat.m11 ()
0336                    << " m12=" << oldHat.m12 ()
0337                    << " m21=" << oldHat.m21 ()
0338                    << " m22=" << oldHat.m22 ()
0339                    << " dx=" << oldHat.dx ()
0340                    << " dy=" << oldHat.dy ()
0341                    << " rect=" << oldHat.mapRect (pm.rect ())
0342                    << "\n"
0343                    << "\tabove but scaled at the same time: m11=" << altHat.m11 ()
0344                    << " m12=" << altHat.m12 ()
0345                    << " m21=" << altHat.m21 ()
0346                    << " m22=" << altHat.m22 ()
0347                    << " dx=" << altHat.dx ()
0348                    << " dy=" << altHat.dy ()
0349                    << " rect=" << altHat.mapRect (pm.rect ())
0350                    << "\n"
0351                    << "\tsupposedlyCorrectMatrix: m11=" << correctMatrix.m11 ()
0352                    << " m12=" << correctMatrix.m12 ()
0353                    << " m21=" << correctMatrix.m21 ()
0354                    << " m22=" << correctMatrix.m22 ()
0355                    << " dx=" << correctMatrix.dx ()
0356                    << " dy=" << correctMatrix.dy ()
0357                    << " rect=" << correctMatrix.mapRect (pm.rect ());
0358     #endif
0359 
0360         transformMatrix = transformMatrix * scaleMatrix;
0361 
0362         newRect = transformMatrix.mapRect (pm.rect ());
0363     #if DEBUG_KP_PIXMAP_FX && 1
0364         qCDebug(kpLogPixmapfx) << "\tnewRect after targetWidth,targetHeight adjust=" << newRect;
0365     #endif
0366     }
0367 
0368 
0369     ::MatrixDebug (QStringLiteral("TransformPixmap(): before trueMatrix"), transformMatrix,
0370                    pm.width (), pm.height ());
0371 #if DEBUG_KP_PIXMAP_FX && 1
0372     QMatrix oldMatrix = transformMatrix;
0373 #endif
0374 
0375     // Translate the matrix to account for Qt rounding errors,
0376     // so that flipping (if it used this method) and rotating by a multiple
0377     // of 90 degrees actually work as expected (try tests/transforms.png).
0378     //
0379     // SYNC: This was not required with Qt3 so we are actually working
0380     //       around a Qt4 bug/feature.
0381     //
0382     // COMPAT: Qt4's rendering with a matrix enabled is low quality anyway
0383     //         but does this reduce quality even further?
0384     //
0385     //         With or without it, skews by 45 degrees with the QImage
0386     //         painter below look bad (with it, you get an extra transparent
0387     //         line on the right; without, you get only about 1/4 of a source
0388     //         line on the left).  In Qt3, with TrueMatrix(), the source
0389     //         image is translated 1 pixel off the destination image.
0390     //
0391     //         Also, if you skew a rectangular selection, the skewed selection
0392     //         border does not line up with the skewed image data.
0393     // TODO: do we need to pass <newRect> through this new matrix?
0394     transformMatrix = ::TrueMatrix (transformMatrix,
0395         pm.width (), pm.height ());
0396 
0397 #if DEBUG_KP_PIXMAP_FX && 1
0398     qCDebug(kpLogPixmapfx) << "trueMatrix changed matrix?" << (oldMatrix == transformMatrix);
0399 #endif
0400     ::MatrixDebug (QStringLiteral("TransformPixmap(): after trueMatrix"), transformMatrix,
0401                    pm.width (), pm.height ());
0402 
0403 
0404     QImage newQImage (targetWidth > 0 ? targetWidth : newRect.width (),
0405                       targetHeight > 0 ? targetHeight : newRect.height (),
0406                       QImage::Format_ARGB32_Premultiplied);
0407 
0408     if ((targetWidth > 0 && targetWidth != newRect.width ()) ||
0409         (targetHeight > 0 && targetHeight != newRect.height ()))
0410     {
0411     #if DEBUG_KP_PIXMAP_FX && 1
0412         qCDebug(kpLogPixmapfx) << "kppixmapfx.cpp: TransformPixmap(pm.size=" << pm.size ()
0413                    << ",targetWidth=" << targetWidth
0414                    << ",targetHeight=" << targetHeight
0415                    << ") newRect=" << newRect
0416                    << " (you are a victim of rounding error)";
0417     #endif
0418     }
0419 
0420 
0421 #if DEBUG_KP_PIXMAP_FX && 0
0422     qCDebug(kpLogPixmapfx) << "\ttranslate top=" << painter.xForm (QPoint (0, 0));
0423     qCDebug(kpLogPixmapfx) << "\tmatrix: m11=" << painter.worldMatrix ().m11 ()
0424                << " m12=" << painter.worldMatrix ().m12 ()
0425                << " m21=" << painter.worldMatrix ().m21 ()
0426                << " m22=" << painter.worldMatrix ().m22 ()
0427                << " dx=" << painter.worldMatrix ().dx ()
0428                << " dy=" << painter.worldMatrix ().dy ()
0429                << endl;
0430 #endif
0431 
0432 
0433     // Note: Do _not_ use "p.setRenderHints (QPainter::SmoothPixmapTransform);"
0434     //       as the user does not want their image to get blurier every
0435     //       time they e.g. rotate it (especially important for multiples
0436     //       of 90 degrees but also true for every other angle).  Being a
0437     //       pixel-based program, we generally like to preserve RGB values
0438     //       and avoid unnecessary blurs -- in the worst case, we'd rather
0439     //       drop pixels, than blur.
0440     QPainter p (&newQImage);
0441     {
0442         // Make sure transparent pixels are drawn into the destination image.
0443         p.setCompositionMode (QPainter::CompositionMode_Source);
0444 
0445         // Fill the entire new image with the background color.
0446         if (backgroundColor.isValid ())
0447         {
0448             p.fillRect (newQImage.rect (), backgroundColor.toQColor ());
0449         }
0450 
0451         p.setWorldTransform (transformMatrix);
0452         p.drawImage (QPoint (0, 0), pm);
0453     }
0454     p.end ();
0455 
0456 #if DEBUG_KP_PIXMAP_FX && 1
0457     qCDebug(kpLogPixmapfx) << "Done";
0458 #endif
0459 
0460     return newQImage;
0461 }
0462 
0463 //---------------------------------------------------------------------
0464 
0465 // public static
0466 QTransform kpPixmapFX::skewMatrix (int width, int height, double hangle, double vangle)
0467 {
0468     if (std::fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon &&
0469         std::fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon)
0470     {
0471         return  {};
0472     }
0473 
0474 
0475     /* Diagram for completeness :)
0476      *
0477      *       |---------- w ----------|
0478      *     (0,0)
0479      *  _     _______________________ (w,0)
0480      *  |    |\~_ va                 |
0481      *  |    | \ ~_                  |
0482      *  |    |ha\  ~__               |
0483      *       |   \    ~__            | dy
0484      *  h    |    \      ~___        |
0485      *       |     \         ~___    |
0486      *  |    |      \            ~___| (w,w*tan(va)=dy)
0487      *  |    |       \         *     \
0488      *  _    |________\________|_____|\                                     vertical shear factor
0489      *     (0,h) dx   ^~_      |       \                                             |
0490      *                |  ~_    \________\________ General Point (x,y)                V
0491      *                |    ~__           \        Skewed Point (x + y*tan(ha),y + x*tan(va))
0492      *      (h*tan(ha)=dx,h)  ~__         \                             ^
0493      *                           ~___      \                            |
0494      *                               ~___   \                   horizontal shear factor
0495      *   Key:                            ~___\
0496      *    ha = hangle                         (w + h*tan(ha)=w+dx,h + w*tan(va)=w+dy)
0497      *    va = vangle
0498      *
0499      * Skewing really just twists a rectangle into a parallelogram.
0500      *
0501      */
0502 
0503     //QTransform matrix (1, tan (KP_DEGREES_TO_RADIANS (vangle)), tan (KP_DEGREES_TO_RADIANS (hangle)), 1, 0, 0);
0504     // I think this is clearer than above :)
0505     QTransform matrix;
0506     matrix.shear (std::tan (qDegreesToRadians (hangle)),
0507                   std::tan (qDegreesToRadians (vangle)));
0508 
0509     return ::MatrixWithZeroOrigin (matrix, width, height);
0510 }
0511 
0512 //---------------------------------------------------------------------
0513 
0514 // public static
0515 QTransform kpPixmapFX::skewMatrix (const QImage &pixmap, double hangle, double vangle)
0516 {
0517     return kpPixmapFX::skewMatrix (pixmap.width (), pixmap.height (), hangle, vangle);
0518 }
0519 
0520 //---------------------------------------------------------------------
0521 
0522 
0523 // public static
0524 void kpPixmapFX::skew (QImage *destPtr, double hangle, double vangle,
0525                        const kpColor &backgroundColor,
0526                        int targetWidth, int targetHeight)
0527 {
0528     if (!destPtr) {
0529         return;
0530     }
0531 
0532     *destPtr = kpPixmapFX::skew (*destPtr, hangle, vangle,
0533                                        backgroundColor,
0534                                        targetWidth, targetHeight);
0535 }
0536 
0537 //---------------------------------------------------------------------
0538 
0539 // public static
0540 QImage kpPixmapFX::skew (const QImage &pm, double hangle, double vangle,
0541                           const kpColor &backgroundColor,
0542                           int targetWidth, int targetHeight)
0543 {
0544 #if DEBUG_KP_PIXMAP_FX
0545     qCDebug(kpLogPixmapfx) << "kpPixmapFX::skew() pm.width=" << pm.width ()
0546                << " pm.height=" << pm.height ()
0547                << " hangle=" << hangle
0548                << " vangle=" << vangle
0549                << " targetWidth=" << targetWidth
0550                << " targetHeight=" << targetHeight;
0551 #endif
0552 
0553     if (std::fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon &&
0554         std::fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon &&
0555         (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/)
0556     {
0557         return pm;
0558     }
0559 
0560     if (std::fabs (hangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon ||
0561         std::fabs (vangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon)
0562     {
0563         qCCritical(kpLogPixmapfx) << "kpPixmapFX::skew() passed hangle and/or vangle out of range (-90 < x < 90)";
0564         return pm;
0565     }
0566 
0567 
0568     QTransform matrix = skewMatrix (pm, hangle, vangle);
0569 
0570     return ::TransformPixmap (pm, matrix, backgroundColor, targetWidth, targetHeight);
0571 }
0572 
0573 //---------------------------------------------------------------------
0574 
0575 
0576 // public static
0577 QTransform kpPixmapFX::rotateMatrix (int width, int height, double angle)
0578 {
0579     if (std::fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon)
0580     {
0581         return  {};
0582     }
0583 
0584     QTransform matrix;
0585     matrix.translate (width / 2, height / 2);
0586     matrix.rotate (angle);
0587 
0588     return ::MatrixWithZeroOrigin (matrix, width, height);
0589 }
0590 
0591 //---------------------------------------------------------------------
0592 
0593 // public static
0594 QTransform kpPixmapFX::rotateMatrix (const QImage &pixmap, double angle)
0595 {
0596     return kpPixmapFX::rotateMatrix (pixmap.width (), pixmap.height (), angle);
0597 }
0598 
0599 //---------------------------------------------------------------------
0600 
0601 
0602 // public static
0603 bool kpPixmapFX::isLosslessRotation (double angle)
0604 {
0605     const double angleIn = angle;
0606 
0607     // Reflect angle into positive if negative
0608     if (angle < 0) {
0609         angle = -angle;
0610     }
0611 
0612     // Remove multiples of 90 to make sure 0 <= angle <= 90
0613     angle -= (static_cast<int> (angle)) / 90 * 90;
0614 
0615     // "Impossible" situation?
0616     if (angle < 0 || angle > 90)
0617     {
0618         qCCritical(kpLogPixmapfx) << "kpPixmapFX::isLosslessRotation(" << angleIn
0619                    << ") result=" << angle;
0620         return false;  // better safe than sorry
0621     }
0622 
0623     const bool ret = (angle < kpPixmapFX::AngleInDegreesEpsilon ||
0624                       90 - angle < kpPixmapFX::AngleInDegreesEpsilon);
0625 #if DEBUG_KP_PIXMAP_FX
0626     qCDebug(kpLogPixmapfx) << "kpPixmapFX::isLosslessRotation(" << angleIn << ")"
0627                << "  residual angle=" << angle
0628                << "  returning " << ret;
0629 #endif
0630     return ret;
0631 }
0632 
0633 //---------------------------------------------------------------------
0634 
0635 
0636 // public static
0637 void kpPixmapFX::rotate (QImage *destPtr, double angle,
0638                          const kpColor &backgroundColor,
0639                          int targetWidth, int targetHeight)
0640 {
0641     if (!destPtr) {
0642         return;
0643     }
0644 
0645     *destPtr = kpPixmapFX::rotate (*destPtr, angle,
0646                                          backgroundColor,
0647                                          targetWidth, targetHeight);
0648 }
0649 
0650 //---------------------------------------------------------------------
0651 
0652 // public static
0653 QImage kpPixmapFX::rotate (const QImage &pm, double angle,
0654                             const kpColor &backgroundColor,
0655                             int targetWidth, int targetHeight)
0656 {
0657     if (std::fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon &&
0658         (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/)
0659     {
0660         return pm;
0661     }
0662 
0663 
0664     QTransform matrix = rotateMatrix (pm, angle);
0665 
0666     return ::TransformPixmap (pm, matrix, backgroundColor, targetWidth, targetHeight);
0667 }
0668 
0669 //---------------------------------------------------------------------