3 Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
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>
36 #include <qapplication.h>
38 #include <qdatetime.h>
41 #include <qpainterpath.h>
48 #include <kconfiggroup.h>
52 #include <kmessagebox.h>
54 #include <kpAbstractSelection.h>
57 #include <kpEffectReduceColors.h>
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()";
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
)
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
;
90 if (backgroundColor
.isOpaque ())
91 newPixmap
.fill (backgroundColor
.toQColor ());
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
);
110 QPixmap
kpPixmapFX::resize (const QPixmap
&pm
, int w
, int h
,
111 const kpColor
&backgroundColor
)
114 kpPixmapFX::resize (&ret
, w
, h
, backgroundColor
);
120 void kpPixmapFX::scale (QPixmap
*destPixmapPtr
, int w
, int h
, bool pretty
)
125 *destPixmapPtr
= kpPixmapFX::scale (*destPixmapPtr
, w
, h
, pretty
);
129 QPixmap
kpPixmapFX::scale (const QPixmap
&pm
, int w
, int h
, bool pretty
)
133 #if DEBUG_KP_PIXMAP_FX && 0
134 kDebug () << "kpPixmapFX::scale(oldRect=" << pm
.rect ()
137 << ",pretty=" << pretty
142 KP_PFX_CHECK_NO_ALPHA_CHANNEL (pm
);
144 if (w
== pm
.width () && h
== pm
.height ())
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");
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");
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
191 retPixmap
= kpPixmapFX::convertToPixmap (image
, false/*let's not smooth it again*/);
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
);
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 ());
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));
236 QMatrix trueMatrix
= QPixmap::trueMatrix (matrix
, w
, h
);
237 kDebug () << matrixName
<< "trueMatrix=" << trueMatrix
;
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));
249 Q_UNUSED (matrixName
);
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()
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 ();
284 QRect mappedRect
= matrix
.mapRect (QRect (0, 0, width
, height
));
285 #if DEBUG_KP_PIXMAP_FX
286 kDebug () << "\tmappedRect=" << mappedRect
;
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));
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
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
)
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
);
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
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
;
385 if (targetWidth
> 0 && targetWidth
!= newRect
.width ())
387 #if DEBUG_KP_PIXMAP_FX && 1
388 kDebug () << "\tadjusting for targetWidth";
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";
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 ())
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 ())
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 ())
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 ())
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
;
459 ::MatrixDebug ("TransformPixmap(): before trueMatrix", transformMatrix
,
460 pm
.width (), pm
.height ());
461 #if DEBUG_KP_PIXMAP_FX && 1
462 QMatrix oldMatrix
= transformMatrix
;
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
);
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)"
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 ()
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 ());
549 p
.fillRect (newQImage
.rect (), QColor (0, 0, 0, 0)/*transparent*/);
552 p
.setMatrix (transformMatrix
);
553 p
.drawImage (QPoint (0, 0), srcQImage
);
557 const QPixmap newPixmap
= kpPixmapFX::convertToPixmap (newQImage
);
560 #if DEBUG_KP_PIXMAP_FX && 1
561 kDebug () << "Done" << endl
<< endl
;
564 KP_PFX_CHECK_NO_ALPHA_CHANNEL (newPixmap
);
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
)
579 /* Diagram for completeness :)
581 * |---------- w ----------|
583 * _ _______________________ (w,0)
590 * | | \ ~___| (w,w*tan(va)=dy)
592 * _ |________\________|_____|\ vertical shear factor
594 * | ~_ \________\________ General Point (x,y) V
595 * | ~__ \ Skewed Point (x + y*tan(ha),y + x*tan(va))
596 * (h*tan(ha)=dx,h) ~__ \ ^
598 * ~___ \ horizontal shear factor
600 * ha = hangle (w + h*tan(ha)=w+dx,h + w*tan(va)=w+dy)
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 :)
610 matrix
.shear (tan (KP_DEGREES_TO_RADIANS (hangle
)),
611 tan (KP_DEGREES_TO_RADIANS (vangle
)));
613 return ::MatrixWithZeroOrigin (matrix
, width
, height
);
617 QMatrix
kpPixmapFX::skewMatrix (const QPixmap
&pixmap
, double hangle
, double vangle
)
619 return kpPixmapFX::skewMatrix (pixmap
.width (), pixmap
.height (), hangle
, vangle
);
624 void kpPixmapFX::skew (QPixmap
*destPixmapPtr
, double hangle
, double vangle
,
625 const kpColor
&backgroundColor
,
626 int targetWidth
, int targetHeight
)
631 *destPixmapPtr
= kpPixmapFX::skew (*destPixmapPtr
, hangle
, vangle
,
633 targetWidth
, targetHeight
);
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
651 if (fabs (hangle
- 0) < kpPixmapFX::AngleInDegreesEpsilon
&&
652 fabs (vangle
- 0) < kpPixmapFX::AngleInDegreesEpsilon
&&
653 (targetWidth
<= 0 && targetHeight
<= 0)/*don't want to scale?*/)
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
;
666 QMatrix matrix
= skewMatrix (pm
, hangle
, vangle
);
668 return ::TransformPixmap (pm
, matrix
, backgroundColor
, targetWidth
, targetHeight
);
673 QMatrix
kpPixmapFX::rotateMatrix (int width
, int height
, double angle
)
675 if (fabs (angle
- 0) < kpPixmapFX::AngleInDegreesEpsilon
)
681 matrix
.translate (width
/ 2, height
/ 2);
682 matrix
.rotate (angle
);
684 return ::MatrixWithZeroOrigin (matrix
, width
, height
);
688 QMatrix
kpPixmapFX::rotateMatrix (const QPixmap
&pixmap
, double angle
)
690 return kpPixmapFX::rotateMatrix (pixmap
.width (), pixmap
.height (), angle
);
695 bool kpPixmapFX::isLosslessRotation (double angle
)
697 const double angleIn
= angle
;
699 // Reflect angle into positive if negative
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
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
728 void kpPixmapFX::rotate (QPixmap
*destPixmapPtr
, double angle
,
729 const kpColor
&backgroundColor
,
730 int targetWidth
, int targetHeight
)
735 *destPixmapPtr
= kpPixmapFX::rotate (*destPixmapPtr
, angle
,
737 targetWidth
, targetHeight
);
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?*/)
752 QMatrix matrix
= rotateMatrix (pm
, angle
);
754 return ::TransformPixmap (pm
, matrix
, backgroundColor
, targetWidth
, targetHeight
);
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
;
767 QMatrix
matrix (horz
? -1 : +1, // m11
770 vert
? -1 : +1, // m22
771 horz
? (width
- 1) : 0, // dx
772 vert
? (height
- 1) : 0); // dy
777 QMatrix
kpPixmapFX::flipMatrix (const QPixmap
&pixmap
, bool horz
, bool vert
)
779 return kpPixmapFX::flipMatrix (pixmap
.width (), pixmap
.height (),
785 void kpPixmapFX::flip (QPixmap
*destPixmapPtr
, bool horz
, bool vert
)
790 *destPixmapPtr
= kpPixmapFX::flip (*destPixmapPtr
, horz
, vert
);
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
<< ")";
804 QImage newQImage
= kpPixmapFX::convertToQImage (pm
);
805 #if DEBUG_KP_PIXMAP_FX && 1
806 kDebug () << "\tqimage.depth=" << newQImage
.depth ()
807 << "format=" << newQImage
.format ();
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
);
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");
853 newPixmap
= QBitmap::fromImage (newQImage
,
854 Qt::MonoOnly
/*to QBitmap*/ |
855 Qt::ThresholdDither
/*no dither*/ |
856 Qt::ThresholdAlphaDither
/*no dither alpha*/|
861 Q_ASSERT (!"unexpected pixmap depth");
864 #if DEBUG_KP_PIXMAP_FX && 1
865 kDebug () << "\tnewPixmap.depth=" << newPixmap
.depth ();
867 Q_ASSERT (newPixmap
.depth () == pm
.depth ());
873 void kpPixmapFX::flip (QImage
*destImagePtr
, bool horz
, bool vert
)
878 *destImagePtr
= kpPixmapFX::flip (*destImagePtr
, horz
, vert
);
882 QImage
kpPixmapFX::flip (const QImage
&img
, bool horz
, bool vert
)
887 return img
.mirrored (horz
, vert
);