there is no moc file generated for this class
[kdegraphics.git] / kolourpaint / pixmapfx / kpPixmapFX_Transforms.cpp
blob01ab2720466f61ec18b70da2cf196bae9926e2ac
2 /*
3 Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
4 All rights reserved.
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
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.
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.
29 #define DEBUG_KP_PIXMAP_FX 0
32 #include <kpPixmapFX.h>
34 #include <math.h>
36 #include <qapplication.h>
37 #include <qbitmap.h>
38 #include <qdatetime.h>
39 #include <qimage.h>
40 #include <qpainter.h>
41 #include <qpainterpath.h>
42 #include <qpixmap.h>
43 #include <qpoint.h>
44 #include <qpolygon.h>
45 #include <qrect.h>
47 #include <kconfig.h>
48 #include <kconfiggroup.h>
49 #include <kdebug.h>
50 #include <kglobal.h>
51 #include <klocale.h>
52 #include <kmessagebox.h>
54 #include <kpAbstractSelection.h>
55 #include <kpColor.h>
56 #include <kpDefs.h>
57 #include <kpEffectReduceColors.h>
58 #include <kpTool.h>
61 // public static
62 void kpPixmapFX::resize (QPixmap *destPixmapPtr, int w, int h,
63 const kpColor &backgroundColor)
65 #if DEBUG_KP_PIXMAP_FX && 1
66 kDebug () << "kpPixmapFX::resize()";
67 #endif
69 if (!destPixmapPtr)
70 return;
72 KP_PFX_CHECK_NO_ALPHA_CHANNEL (*destPixmapPtr);
74 const int oldWidth = destPixmapPtr->width ();
75 const int oldHeight = destPixmapPtr->height ();
77 if (w == oldWidth && h == oldHeight)
78 return;
81 QPixmap newPixmap (w, h);
83 // Would have new undefined areas?
84 if (w > oldWidth || h > oldHeight)
86 #if DEBUG_KP_PIXMAP_FX && 1
87 kDebug () << "\tbacking with fill opqaque="
88 << backgroundColor.isOpaque () << endl;
89 #endif
90 if (backgroundColor.isOpaque ())
91 newPixmap.fill (backgroundColor.toQColor ());
92 else
94 QBitmap newPixmapMask (w, h);
95 newPixmapMask.fill (Qt::color0/*transparent*/);
96 newPixmap.setMask (newPixmapMask);
100 // Copy over old pixmap.
101 setPixmapAt (&newPixmap, 0, 0, *destPixmapPtr);
103 // Replace pixmap with new one.
104 *destPixmapPtr = newPixmap;
106 KP_PFX_CHECK_NO_ALPHA_CHANNEL (*destPixmapPtr);
109 // public static
110 QPixmap kpPixmapFX::resize (const QPixmap &pm, int w, int h,
111 const kpColor &backgroundColor)
113 QPixmap ret = pm;
114 kpPixmapFX::resize (&ret, w, h, backgroundColor);
115 return ret;
119 // public static
120 void kpPixmapFX::scale (QPixmap *destPixmapPtr, int w, int h, bool pretty)
122 if (!destPixmapPtr)
123 return;
125 *destPixmapPtr = kpPixmapFX::scale (*destPixmapPtr, w, h, pretty);
128 // public static
129 QPixmap kpPixmapFX::scale (const QPixmap &pm, int w, int h, bool pretty)
131 QPixmap retPixmap;
133 #if DEBUG_KP_PIXMAP_FX && 0
134 kDebug () << "kpPixmapFX::scale(oldRect=" << pm.rect ()
135 << ",w=" << w
136 << ",h=" << h
137 << ",pretty=" << pretty
138 << ")"
139 << endl;
140 #endif
142 KP_PFX_CHECK_NO_ALPHA_CHANNEL (pm);
144 if (w == pm.width () && h == pm.height ())
145 return pm;
148 if (pretty)
150 // We don't use QPixmap::scaled() with Qt::SmoothTransformation since
151 // that double-smoothes, making the image blurier than intended
152 // -- internally QPixmap::scaled() does the following:
154 // 1. Calls QImage::scaled() with Qt::SmoothTransformation, like we do.
156 // 2. But it then calls the Qt equivalent of kpPixmapFX::convertToPixmap(),
157 // which will do an unwanted second smooth on screens of depth
158 // < 32 (since it dithers down a 32-bit QImage).
159 QImage image = kpPixmapFX::convertToQImage (pm);
161 #if DEBUG_KP_PIXMAP_FX && 0
162 kDebug () << "\tBefore smooth scale:";
163 for (int y = 0; y < image.height (); y++)
165 for (int x = 0; x < image.width (); x++)
167 fprintf (stderr, " %08X", image.pixel (x, y));
169 fprintf (stderr, "\n");
171 #endif
173 image = image.scaled (w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
175 #if DEBUG_KP_PIXMAP_FX && 0
176 kDebug () << "\tAfter smooth scale:";
177 for (int y = 0; y < image.height (); y++)
179 for (int x = 0; x < image.width (); x++)
181 fprintf (stderr, " %08X", image.pixel (x, y));
183 fprintf (stderr, "\n");
185 #endif
187 // COMPAT: Regression compared to Qt3.
188 // This causes some smudging between transparent and non-transparent
189 // pixels after scaling. Some transparent pixels on the boundary
190 // become black.
191 retPixmap = kpPixmapFX::convertToPixmap (image, false/*let's not smooth it again*/);
193 else
195 QMatrix matrix;
197 matrix.scale (double (w) / double (pm.width ()),
198 double (h) / double (pm.height ()));
200 retPixmap = pm.transformed (matrix);
204 KP_PFX_CHECK_NO_ALPHA_CHANNEL (retPixmap);
205 return retPixmap;
209 // public static
210 const double kpPixmapFX::AngleInDegreesEpsilon =
211 KP_RADIANS_TO_DEGREES (atan (1.0 / 10000.0))
212 / (2.0/*max error allowed*/ * 2.0/*for good measure*/);
215 static void MatrixDebug (const QString matrixName, const QMatrix &matrix,
216 int srcPixmapWidth = -1, int srcPixmapHeight = -1)
218 #if DEBUG_KP_PIXMAP_FX
219 const int w = srcPixmapWidth, h = srcPixmapHeight;
221 kDebug () << matrixName << "=" << matrix;
222 // Sometimes this precision lets us see unexpected rounding errors.
223 fprintf (stderr, "m11=%.24f m12=%.24f m21=%.24f m22=%.24f dx=%.24f dy=%.24f\n",
224 matrix.m11 (), matrix.m12 (),
225 matrix.m21 (), matrix.m22 (),
226 matrix.dx (), matrix.dy ());
227 if (w > 0 && h > 0)
229 kDebug () << "(0,0) ->" << matrix.map (QPoint (0, 0));
230 kDebug () << "(w-1,0) ->" << matrix.map (QPoint (w - 1, 0));
231 kDebug () << "(0,h-1) ->" << matrix.map (QPoint (0, h - 1));
232 kDebug () << "(w-1,h-1) ->" << matrix.map (QPoint (w - 1, h - 1));
235 #if 0
236 QMatrix trueMatrix = QPixmap::trueMatrix (matrix, w, h);
237 kDebug () << matrixName << "trueMatrix=" << trueMatrix;
238 if (w > 0 && h > 0)
240 kDebug () << "(0,0) ->" << trueMatrix.map (QPoint (0, 0));
241 kDebug () << "(w-1,0) ->" << trueMatrix.map (QPoint (w - 1, 0));
242 kDebug () << "(0,h-1) ->" << trueMatrix.map (QPoint (0, h - 1));
243 kDebug () << "(w-1,h-1) ->" << trueMatrix.map (QPoint (w - 1, h - 1));
245 #endif
247 #else
249 Q_UNUSED (matrixName);
250 Q_UNUSED (matrix);
251 Q_UNUSED (srcPixmapWidth);
252 Q_UNUSED (srcPixmapHeight);
254 #endif // DEBUG_KP_PIXMAP_FX
258 // Theoretically, this should act the same as QPixmap::trueMatrix() but
259 // it doesn't. As an example, if you rotate tests/transforms.png by 90
260 // degrees clockwise, this returns the correct <dx> of 26 but
261 // QPixmap::trueMatrix() returns 27.
263 // You should use the returned matrix to map points accurately (e.g. selection
264 // borders). For QPainter::drawPixmap()/drawImage() + setWorldMatrix()
265 // rendering accuracy, pass the returned matrix through QPixmap::trueMatrix()
266 // and use that.
268 // TODO: If you put the flipMatrix() of tests/transforms.png through this,
269 // the output is the same as QPixmap::trueMatrix(): <dy> is one off
270 // (dy=27 instead of 26).
271 // SYNC: I bet this is a Qt4 bug.
272 static QMatrix MatrixWithZeroOrigin (const QMatrix &matrix, int width, int height)
274 #if DEBUG_KP_PIXMAP_FX
275 kDebug () << "matrixWithZeroOrigin(w=" << width << ",h=" << height << ")";
276 kDebug () << "\tmatrix: m11=" << matrix.m11 ()
277 << "m12=" << matrix.m12 ()
278 << "m21=" << matrix.m21 ()
279 << "m22=" << matrix.m22 ()
280 << "dx=" << matrix.dx ()
281 << "dy=" << matrix.dy ();
282 #endif
284 QRect mappedRect = matrix.mapRect (QRect (0, 0, width, height));
285 #if DEBUG_KP_PIXMAP_FX
286 kDebug () << "\tmappedRect=" << mappedRect;
287 #endif
289 QMatrix translatedMatrix (
290 matrix.m11 (), matrix.m12 (),
291 matrix.m21 (), matrix.m22 (),
292 matrix.dx () - mappedRect.left (), matrix.dy () - mappedRect.top ());
294 #if DEBUG_KP_PIXMAP_FX
295 kDebug () << "\treturning" << translatedMatrix;
296 kDebug () << "(0,0) ->" << translatedMatrix.map (QPoint (0, 0));
297 kDebug () << "(w-1,0) ->" << translatedMatrix.map (QPoint (width - 1, 0));
298 kDebug () << "(0,h-1) ->" << translatedMatrix.map (QPoint (0, height - 1));
299 kDebug () << "(w-1,h-1) ->" << translatedMatrix.map (QPoint (width - 1, height - 1));
300 #endif
302 return translatedMatrix;
306 static double TrueMatrixEpsilon = 0.000001;
308 // An attempt to reverse tiny rounding errors introduced by QPixmap::trueMatrix()
309 // when skewing tests/transforms.png by 45% horizontally (with TransformPixmap()
310 // using a QPixmap painter, prior to the 2007-10-09 change -- did not test after
311 // the change).
312 // Unfortunately, this does not work enough to stop the rendering errors
313 // that follow. But it was worth a try and might still help us given the
314 // sometimes excessive aliasing QPainter::draw{Pixmap,Image}() gives us, when
315 // QPainter::SmoothPixmapTransform is disabled.
316 static double TrueMatrixFixInts (double x)
318 if (fabs (x - qRound (x)) < TrueMatrixEpsilon)
319 return qRound (x);
320 else
321 return x;
324 static QMatrix TrueMatrix (const QMatrix &matrix, int srcPixmapWidth, int srcPixmapHeight)
326 ::MatrixDebug ("TrueMatrix(): org", matrix);
328 const QMatrix truMat = QPixmap::trueMatrix (matrix, srcPixmapWidth, srcPixmapHeight);
329 ::MatrixDebug ("TrueMatrix(): passed through QPixmap::trueMatrix()", truMat);
331 const QMatrix retMat (
332 ::TrueMatrixFixInts (truMat.m11 ()),
333 ::TrueMatrixFixInts (truMat.m12 ()),
334 ::TrueMatrixFixInts (truMat.m21 ()),
335 ::TrueMatrixFixInts (truMat.m22 ()),
336 ::TrueMatrixFixInts (truMat.dx ()),
337 ::TrueMatrixFixInts (truMat.dy ()));
338 ::MatrixDebug ("TrueMatrix(): fixed ints", retMat);
340 return retMat;
344 // Like QPixmap::transformed() but fills new areas with <backgroundColor>
345 // (unless <backgroundColor> is invalid) and works around internal QMatrix
346 // floating point -> integer oddities, that would otherwise give fatally
347 // incorrect results. If you don't believe me on this latter point, compare
348 // QPixmap::transformed() to us using a flip matrix or a rotate-by-multiple-of-90
349 // matrix on tests/transforms.png -- QPixmap::transformed()'s output is 1
350 // pixel too high or low depending on whether the matrix is passed through
351 // QPixmap::trueMatrix().
353 // Use <targetWidth> and <targetHeight> to specify the intended output size
354 // of the pixmap. -1 if don't care.
355 static QPixmap TransformPixmap (const QPixmap &pm, const QMatrix &transformMatrix_,
356 const kpColor &backgroundColor,
357 int targetWidth, int targetHeight)
359 QMatrix transformMatrix = transformMatrix_;
361 #if DEBUG_KP_PIXMAP_FX && 1
362 kDebug () << "kppixmapfx.cpp: TransformPixmap(pm.size=" << pm.size ()
363 << ",targetWidth=" << targetWidth
364 << ",targetHeight=" << targetHeight
365 << ")"
366 << endl;
367 #endif
368 KP_PFX_CHECK_NO_ALPHA_CHANNEL (pm);
370 // Code path has not been tested on monochrone bitmaps.
372 // Futhermore, since Qt doesn't support masks on such bitmaps and their
373 // color channel only has up to 2 colors, it makes little sense to
374 // support arbitrary transformations of them (other than flipping,
375 // which is already covered by kpPixmapFX::flip() below).
376 Q_ASSERT (pm.depth () > 1);
378 QRect newRect = transformMatrix.mapRect (pm.rect ());
379 #if DEBUG_KP_PIXMAP_FX && 1
380 kDebug () << "\tmappedRect=" << newRect;
382 #endif
384 QMatrix scaleMatrix;
385 if (targetWidth > 0 && targetWidth != newRect.width ())
387 #if DEBUG_KP_PIXMAP_FX && 1
388 kDebug () << "\tadjusting for targetWidth";
389 #endif
390 scaleMatrix.scale (double (targetWidth) / double (newRect.width ()), 1);
393 if (targetHeight > 0 && targetHeight != newRect.height ())
395 #if DEBUG_KP_PIXMAP_FX && 1
396 kDebug () << "\tadjusting for targetHeight";
397 #endif
398 scaleMatrix.scale (1, double (targetHeight) / double (newRect.height ()));
401 if (!scaleMatrix.isIdentity ())
403 #if DEBUG_KP_PIXMAP_FX && 1
404 // TODO: What is going on here??? Why isn't matrix * working properly?
405 QMatrix wrongMatrix = transformMatrix * scaleMatrix;
406 QMatrix oldHat = transformMatrix;
407 if (targetWidth > 0 && targetWidth != newRect.width ())
408 oldHat.scale (double (targetWidth) / double (newRect.width ()), 1);
409 if (targetHeight > 0 && targetHeight != newRect.height ())
410 oldHat.scale (1, double (targetHeight) / double (newRect.height ()));
411 QMatrix altHat = transformMatrix;
412 altHat.scale ((targetWidth > 0 && targetWidth != newRect.width ()) ? double (targetWidth) / double (newRect.width ()) : 1,
413 (targetHeight > 0 && targetHeight != newRect.height ()) ? double (targetHeight) / double (newRect.height ()) : 1);
414 QMatrix correctMatrix = scaleMatrix * transformMatrix;
416 kDebug () << "\tsupposedlyWrongMatrix: m11=" << wrongMatrix.m11 () // <<<---- this is the correct matrix???
417 << " m12=" << wrongMatrix.m12 ()
418 << " m21=" << wrongMatrix.m21 ()
419 << " m22=" << wrongMatrix.m22 ()
420 << " dx=" << wrongMatrix.dx ()
421 << " dy=" << wrongMatrix.dy ()
422 << " rect=" << wrongMatrix.mapRect (pm.rect ())
423 << endl
424 << "\ti_used_to_use_thisMatrix: m11=" << oldHat.m11 ()
425 << " m12=" << oldHat.m12 ()
426 << " m21=" << oldHat.m21 ()
427 << " m22=" << oldHat.m22 ()
428 << " dx=" << oldHat.dx ()
429 << " dy=" << oldHat.dy ()
430 << " rect=" << oldHat.mapRect (pm.rect ())
431 << endl
432 << "\tabove but scaled at the same time: m11=" << altHat.m11 ()
433 << " m12=" << altHat.m12 ()
434 << " m21=" << altHat.m21 ()
435 << " m22=" << altHat.m22 ()
436 << " dx=" << altHat.dx ()
437 << " dy=" << altHat.dy ()
438 << " rect=" << altHat.mapRect (pm.rect ())
439 << endl
440 << "\tsupposedlyCorrectMatrix: m11=" << correctMatrix.m11 ()
441 << " m12=" << correctMatrix.m12 ()
442 << " m21=" << correctMatrix.m21 ()
443 << " m22=" << correctMatrix.m22 ()
444 << " dx=" << correctMatrix.dx ()
445 << " dy=" << correctMatrix.dy ()
446 << " rect=" << correctMatrix.mapRect (pm.rect ())
447 << endl;
448 #endif
450 transformMatrix = transformMatrix * scaleMatrix;
452 newRect = transformMatrix.mapRect (pm.rect ());
453 #if DEBUG_KP_PIXMAP_FX && 1
454 kDebug () << "\tnewRect after targetWidth,targetHeight adjust=" << newRect;
455 #endif
459 ::MatrixDebug ("TransformPixmap(): before trueMatrix", transformMatrix,
460 pm.width (), pm.height ());
461 #if DEBUG_KP_PIXMAP_FX && 1
462 QMatrix oldMatrix = transformMatrix;
463 #endif
465 // Translate the matrix to account for Qt rounding errors,
466 // so that flipping (if it used this method) and rotating by a multiple
467 // of 90 degrees actually work as expected (try tests/transforms.png).
469 // SYNC: This was not required with Qt3 so we are actually working
470 // around a Qt4 bug/feature.
472 // COMPAT: Qt4's rendering with a matrix enabled is low quality anyway
473 // but does this reduce quality even further?
475 // With or without it, skews by 45 degrees with the QImage
476 // painter below look bad (with it, you get an extra transparent
477 // line on the right; without, you get only about 1/4 of a source
478 // line on the left). In Qt3, with TrueMatrix(), the source
479 // image is translated 1 pixel off the destination image.
481 // Also, if you skew a rectangular selection, the skewed selection
482 // border does not line up with the skewed image data.
483 // TODO: do we need to pass <newRect> through this new matrix?
484 transformMatrix = ::TrueMatrix (transformMatrix,
485 pm.width (), pm.height ());
487 #if DEBUG_KP_PIXMAP_FX && 1
488 kDebug () << "trueMatrix changed matrix?" << (oldMatrix == transformMatrix);
489 #endif
490 ::MatrixDebug ("TransformPixmap(): after trueMatrix", transformMatrix,
491 pm.width (), pm.height ());
494 QImage newQImage (targetWidth > 0 ? targetWidth : newRect.width (),
495 targetHeight > 0 ? targetHeight : newRect.height (),
496 QImage::Format_ARGB32_Premultiplied);
498 if ((targetWidth > 0 && targetWidth != newRect.width ()) ||
499 (targetHeight > 0 && targetHeight != newRect.height ()))
501 #if DEBUG_KP_PIXMAP_FX && 1
502 kDebug () << "kppixmapfx.cpp: TransformPixmap(pm.size=" << pm.size ()
503 << ",targetWidth=" << targetWidth
504 << ",targetHeight=" << targetHeight
505 << ") newRect=" << newRect
506 << " (you are a victim of rounding error)"
507 << endl;
508 #endif
512 #if DEBUG_KP_PIXMAP_FX && 0
513 kDebug () << "\ttranslate top=" << painter.xForm (QPoint (0, 0));
514 kDebug () << "\tmatrix: m11=" << painter.worldMatrix ().m11 ()
515 << " m12=" << painter.worldMatrix ().m12 ()
516 << " m21=" << painter.worldMatrix ().m21 ()
517 << " m22=" << painter.worldMatrix ().m22 ()
518 << " dx=" << painter.worldMatrix ().dx ()
519 << " dy=" << painter.worldMatrix ().dy ()
520 << endl;
521 #endif
524 // Drawing a transformed QBitmap on top of a QBitmap causes
525 // X errors and does not work, if XRENDER is disabled (Qt 4.3.1 bug).
526 // So we can't use the normal kpPixmapFX::draw() mechanism.
528 // Use QImage and QPainter instead.
529 QImage srcQImage = kpPixmapFX::convertToQImage (pm);
531 // Note: Do _not_ use "p.setRenderHints (QPainter::SmoothPixmapTransform);"
532 // as the user does not want their image to get blurier every
533 // time they e.g. rotate it (especially important for multiples
534 // of 90 degrees but also true for every other angle). Being a
535 // pixel-based program, we generally like to preserve RGB values
536 // and avoid unnecessary blurs -- in the worst case, we'd rather
537 // drop pixels, than blur.
538 QPainter p (&newQImage);
540 // Make sure transparent pixels are drawn into the destination image.
541 p.setCompositionMode (QPainter::CompositionMode_Source);
543 // Fill the entire new image with the background color.
544 if (backgroundColor.isValid ())
546 if (backgroundColor.isOpaque ())
547 p.fillRect (newQImage.rect (), backgroundColor.toQColor ());
548 else
549 p.fillRect (newQImage.rect (), QColor (0, 0, 0, 0)/*transparent*/);
552 p.setMatrix (transformMatrix);
553 p.drawImage (QPoint (0, 0), srcQImage);
555 p.end ();
557 const QPixmap newPixmap = kpPixmapFX::convertToPixmap (newQImage);
560 #if DEBUG_KP_PIXMAP_FX && 1
561 kDebug () << "Done" << endl << endl;
562 #endif
564 KP_PFX_CHECK_NO_ALPHA_CHANNEL (newPixmap);
565 return newPixmap;
569 // public static
570 QMatrix kpPixmapFX::skewMatrix (int width, int height, double hangle, double vangle)
572 if (fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon &&
573 fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon)
575 return QMatrix ();
579 /* Diagram for completeness :)
581 * |---------- w ----------|
582 * (0,0)
583 * _ _______________________ (w,0)
584 * | |\~_ va |
585 * | | \ ~_ |
586 * | |ha\ ~__ |
587 * | \ ~__ | dy
588 * h | \ ~___ |
589 * | \ ~___ |
590 * | | \ ~___| (w,w*tan(va)=dy)
591 * | | \ * \
592 * _ |________\________|_____|\ vertical shear factor
593 * (0,h) dx ^~_ | \ |
594 * | ~_ \________\________ General Point (x,y) V
595 * | ~__ \ Skewed Point (x + y*tan(ha),y + x*tan(va))
596 * (h*tan(ha)=dx,h) ~__ \ ^
597 * ~___ \ |
598 * ~___ \ horizontal shear factor
599 * Key: ~___\
600 * ha = hangle (w + h*tan(ha)=w+dx,h + w*tan(va)=w+dy)
601 * va = vangle
603 * Skewing really just twists a rectangle into a parallelogram.
607 //QMatrix matrix (1, tan (KP_DEGREES_TO_RADIANS (vangle)), tan (KP_DEGREES_TO_RADIANS (hangle)), 1, 0, 0);
608 // I think this is clearer than above :)
609 QMatrix matrix;
610 matrix.shear (tan (KP_DEGREES_TO_RADIANS (hangle)),
611 tan (KP_DEGREES_TO_RADIANS (vangle)));
613 return ::MatrixWithZeroOrigin (matrix, width, height);
616 // public static
617 QMatrix kpPixmapFX::skewMatrix (const QPixmap &pixmap, double hangle, double vangle)
619 return kpPixmapFX::skewMatrix (pixmap.width (), pixmap.height (), hangle, vangle);
623 // public static
624 void kpPixmapFX::skew (QPixmap *destPixmapPtr, double hangle, double vangle,
625 const kpColor &backgroundColor,
626 int targetWidth, int targetHeight)
628 if (!destPixmapPtr)
629 return;
631 *destPixmapPtr = kpPixmapFX::skew (*destPixmapPtr, hangle, vangle,
632 backgroundColor,
633 targetWidth, targetHeight);
636 // public static
637 QPixmap kpPixmapFX::skew (const QPixmap &pm, double hangle, double vangle,
638 const kpColor &backgroundColor,
639 int targetWidth, int targetHeight)
641 #if DEBUG_KP_PIXMAP_FX
642 kDebug () << "kpPixmapFX::skew() pm.width=" << pm.width ()
643 << " pm.height=" << pm.height ()
644 << " hangle=" << hangle
645 << " vangle=" << vangle
646 << " targetWidth=" << targetWidth
647 << " targetHeight=" << targetHeight
648 << endl;
649 #endif
651 if (fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon &&
652 fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon &&
653 (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/)
655 return pm;
658 if (fabs (hangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon ||
659 fabs (vangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon)
661 kError () << "kpPixmapFX::skew() passed hangle and/or vangle out of range (-90 < x < 90)" << endl;
662 return pm;
666 QMatrix matrix = skewMatrix (pm, hangle, vangle);
668 return ::TransformPixmap (pm, matrix, backgroundColor, targetWidth, targetHeight);
672 // public static
673 QMatrix kpPixmapFX::rotateMatrix (int width, int height, double angle)
675 if (fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon)
677 return QMatrix ();
680 QMatrix matrix;
681 matrix.translate (width / 2, height / 2);
682 matrix.rotate (angle);
684 return ::MatrixWithZeroOrigin (matrix, width, height);
687 // public static
688 QMatrix kpPixmapFX::rotateMatrix (const QPixmap &pixmap, double angle)
690 return kpPixmapFX::rotateMatrix (pixmap.width (), pixmap.height (), angle);
694 // public static
695 bool kpPixmapFX::isLosslessRotation (double angle)
697 const double angleIn = angle;
699 // Reflect angle into positive if negative
700 if (angle < 0)
701 angle = -angle;
703 // Remove multiples of 90 to make sure 0 <= angle <= 90
704 angle -= ((int) angle) / 90 * 90;
706 // "Impossible" situation?
707 if (angle < 0 || angle > 90)
709 kError () << "kpPixmapFX::isLosslessRotation(" << angleIn
710 << ") result=" << angle
711 << endl;
712 return false; // better safe than sorry
715 const bool ret = (angle < kpPixmapFX::AngleInDegreesEpsilon ||
716 90 - angle < kpPixmapFX::AngleInDegreesEpsilon);
717 #if DEBUG_KP_PIXMAP_FX
718 kDebug () << "kpPixmapFX::isLosslessRotation(" << angleIn << ")"
719 << " residual angle=" << angle
720 << " returning " << ret
721 << endl;
722 #endif
723 return ret;
727 // public static
728 void kpPixmapFX::rotate (QPixmap *destPixmapPtr, double angle,
729 const kpColor &backgroundColor,
730 int targetWidth, int targetHeight)
732 if (!destPixmapPtr)
733 return;
735 *destPixmapPtr = kpPixmapFX::rotate (*destPixmapPtr, angle,
736 backgroundColor,
737 targetWidth, targetHeight);
740 // public static
741 QPixmap kpPixmapFX::rotate (const QPixmap &pm, double angle,
742 const kpColor &backgroundColor,
743 int targetWidth, int targetHeight)
745 if (fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon &&
746 (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/)
748 return pm;
752 QMatrix matrix = rotateMatrix (pm, angle);
754 return ::TransformPixmap (pm, matrix, backgroundColor, targetWidth, targetHeight);
758 // public static
759 QMatrix kpPixmapFX::flipMatrix (int width, int height, bool horz, bool vert)
761 if (width <= 0 || height <= 0)
763 kError () << "kpPixmapFX::flipMatrix() passed invalid dimensions" << endl;
764 return QMatrix ();
767 QMatrix matrix (horz ? -1 : +1, // m11
768 0, // m12
769 0, // m21
770 vert ? -1 : +1, // m22
771 horz ? (width - 1) : 0, // dx
772 vert ? (height - 1) : 0); // dy
773 return matrix;
776 // public static
777 QMatrix kpPixmapFX::flipMatrix (const QPixmap &pixmap, bool horz, bool vert)
779 return kpPixmapFX::flipMatrix (pixmap.width (), pixmap.height (),
780 horz, vert);
784 // public static
785 void kpPixmapFX::flip (QPixmap *destPixmapPtr, bool horz, bool vert)
787 if (!horz && !vert)
788 return;
790 *destPixmapPtr = kpPixmapFX::flip (*destPixmapPtr, horz, vert);
793 // public static
794 QPixmap kpPixmapFX::flip (const QPixmap &pm, bool horz, bool vert)
796 #if DEBUG_KP_PIXMAP_FX && 1
797 kDebug () << "CALL(pm.depth=" << pm.depth ()
798 << ",horz=" << horz << ",vert=" << vert << ")";
799 #endif
801 if (!horz && !vert)
802 return pm;
804 QImage newQImage = kpPixmapFX::convertToQImage (pm);
805 #if DEBUG_KP_PIXMAP_FX && 1
806 kDebug () << "\tqimage.depth=" << newQImage.depth ()
807 << "format=" << newQImage.format ();
808 #endif
810 QPixmap newPixmap;
812 if (pm.depth () > 1)
814 Q_ASSERT (newQImage.depth () > 1);
816 // Work around converToPixmap() nuking mask if the <newQImage>
817 // (and therefore, <pm>) is only 8-bit. The name "ReduceColors"
818 // is a misnomer in this case.
819 newQImage = kpEffectReduceColors::convertImageDepth (
820 newQImage, 32/*new depth*/, false/*no dither*/);
822 // For depth>1, we could do the entire transform using ::TransformPixmap()
823 // but given the rounding error hacks in that method, we'd rather use
824 // this guaranteed way of doing it (QImage::mirrored()).
825 kpPixmapFX::flip (&newQImage, horz, vert);
827 newPixmap = kpPixmapFX::convertToPixmap (newQImage);
829 // pm.depth() == 1 is used by kpAbstractImageSelection::flip()
830 // flipping the selection transparency mask.
831 else if (pm.depth () == 1)
833 Q_ASSERT (newQImage.depth () == 1);
835 // For depth==1, drawing a transformed QBitmap on top of a QBitmap causes
836 // X errors and does not work, if XRENDER is disabled (Qt 4.3.1 bug).
837 // So we use QImage (and QImage::mirrored()) instead.
838 kpPixmapFX::flip (&newQImage, horz, vert);
840 #if 0
841 // Dump pixels from the QImage.
842 for (int y = 0; y < newQImage.height (); y++)
844 fprintf (stderr, "%d:", y);
845 for (int x = 0; x < newQImage.width (); x++)
847 fprintf (stderr, " %x", newQImage.pixel (x, y));
849 fprintf (stderr, "\n");
851 #endif
853 newPixmap = QBitmap::fromImage (newQImage,
854 Qt::MonoOnly/*to QBitmap*/ |
855 Qt::ThresholdDither/*no dither*/ |
856 Qt::ThresholdAlphaDither/*no dither alpha*/|
857 Qt::AvoidDither);
859 else
861 Q_ASSERT (!"unexpected pixmap depth");
864 #if DEBUG_KP_PIXMAP_FX && 1
865 kDebug () << "\tnewPixmap.depth=" << newPixmap.depth ();
866 #endif
867 Q_ASSERT (newPixmap.depth () == pm.depth ());
869 return newPixmap;
872 // public static
873 void kpPixmapFX::flip (QImage *destImagePtr, bool horz, bool vert)
875 if (!horz && !vert)
876 return;
878 *destImagePtr = kpPixmapFX::flip (*destImagePtr, horz, vert);
881 // public static
882 QImage kpPixmapFX::flip (const QImage &img, bool horz, bool vert)
884 if (!horz && !vert)
885 return img;
887 return img.mirrored (horz, vert);