summaryrefslogtreecommitdiffstats
path: root/kolourpaint/pixmapfx/kppixmapfx.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kolourpaint/pixmapfx/kppixmapfx.cpp')
-rw-r--r--kolourpaint/pixmapfx/kppixmapfx.cpp1677
1 files changed, 1677 insertions, 0 deletions
diff --git a/kolourpaint/pixmapfx/kppixmapfx.cpp b/kolourpaint/pixmapfx/kppixmapfx.cpp
new file mode 100644
index 00000000..1bd0b173
--- /dev/null
+++ b/kolourpaint/pixmapfx/kppixmapfx.cpp
@@ -0,0 +1,1677 @@
+
+/*
+ Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org>
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#define DEBUG_KP_PIXMAP_FX 0
+
+
+#include <kppixmapfx.h>
+
+#include <math.h>
+
+#include <qapplication.h>
+#include <qbitmap.h>
+#include <qdatetime.h>
+#include <qimage.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qpointarray.h>
+#include <qrect.h>
+
+#include <kconfig.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include <kpcolor.h>
+#include <kpdefs.h>
+#include <kpselection.h>
+#include <kptool.h>
+
+
+//
+// Overflow Resistant Arithmetic:
+//
+
+// public static
+int kpPixmapFX::addDimensions (int lhs, int rhs)
+{
+ if (lhs < 0 || rhs < 0 ||
+ lhs > INT_MAX - rhs)
+ {
+ return INT_MAX;
+ }
+
+ return lhs + rhs;
+}
+
+// public static
+int kpPixmapFX::multiplyDimensions (int lhs, int rhs)
+{
+ if (rhs == 0)
+ return 0;
+
+ if (lhs < 0 || rhs < 0 ||
+ lhs > INT_MAX / rhs)
+ {
+ return INT_MAX;
+ }
+
+ return lhs * rhs;
+}
+
+
+//
+// QPixmap Statistics
+//
+
+// public static
+int kpPixmapFX::pixmapArea (const QPixmap &pixmap)
+{
+ return kpPixmapFX::pixmapArea (pixmap.width (), pixmap.height ());
+}
+
+// public static
+int kpPixmapFX::pixmapArea (const QPixmap *pixmap)
+{
+ return (pixmap ? kpPixmapFX::pixmapArea (*pixmap) : 0);
+}
+
+// public static
+int kpPixmapFX::pixmapArea (int width, int height)
+{
+ return multiplyDimensions (width, height);
+}
+
+
+// public static
+int kpPixmapFX::pixmapSize (const QPixmap &pixmap)
+{
+ return kpPixmapFX::pixmapSize (pixmap.width (), pixmap.height (),
+ pixmap.depth ());
+}
+
+// public static
+int kpPixmapFX::pixmapSize (const QPixmap *pixmap)
+{
+ return (pixmap ? kpPixmapFX::pixmapSize (*pixmap) : 0);
+}
+
+// public static
+int kpPixmapFX::pixmapSize (int width, int height, int depth)
+{
+ // handle 15bpp
+ int roundedDepth = (depth > 8 ? (depth + 7) / 8 * 8 : depth);
+
+#if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "kpPixmapFX::pixmapSize() w=" << width
+ << " h=" << height
+ << " d=" << depth
+ << " roundedDepth=" << roundedDepth
+ << " ret="
+ << multiplyDimensions (kpPixmapFX::pixmapArea (width, height), roundedDepth) / 8
+ << endl;
+#endif
+ return multiplyDimensions (kpPixmapFX::pixmapArea (width, height), roundedDepth) / 8;
+}
+
+
+// public static
+int kpPixmapFX::imageSize (const QImage &image)
+{
+ return kpPixmapFX::imageSize (image.width (), image.height (), image.depth ());
+}
+
+// public static
+int kpPixmapFX::imageSize (const QImage *image)
+{
+ return (image ? kpPixmapFX::imageSize (*image) : 0);
+}
+
+// public static
+int kpPixmapFX::imageSize (int width, int height, int depth)
+{
+ // handle 15bpp
+ int roundedDepth = (depth > 8 ? (depth + 7) / 8 * 8 : depth);
+
+#if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "kpPixmapFX::imageSize() w=" << width
+ << " h=" << height
+ << " d=" << depth
+ << " roundedDepth=" << roundedDepth
+ << " ret="
+ << multiplyDimensions (multiplyDimensions (width, height), roundedDepth) / 8
+ << endl;
+#endif
+
+ return multiplyDimensions (multiplyDimensions (width, height), roundedDepth) / 8;
+}
+
+
+// public static
+int kpPixmapFX::selectionSize (const kpSelection &sel)
+{
+ return sel.size ();
+}
+
+// public static
+int kpPixmapFX::selectionSize (const kpSelection *sel)
+{
+ return (sel ? sel->size () : 0);
+}
+
+
+// public static
+int kpPixmapFX::stringSize (const QString &string)
+{
+#if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "kpPixmapFX::stringSize(" << string << ")"
+ << " len=" << string.length ()
+ << " sizeof(QChar)=" << sizeof (QChar)
+ << endl;
+#endif
+ return string.length () * sizeof (QChar);
+}
+
+
+// public static
+int kpPixmapFX::pointArraySize (const QPointArray &points)
+{
+#if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "kpPixmapFX::pointArraySize() points.size="
+ << points.size ()
+ << " sizeof(QPoint)=" << sizeof (QPoint)
+ << endl;
+#endif
+
+ return (points.size () * sizeof (QPoint));
+}
+
+
+//
+// QPixmap/QImage Conversion Functions
+//
+
+// public static
+QImage kpPixmapFX::convertToImage (const QPixmap &pixmap)
+{
+ if (pixmap.isNull ())
+ return QImage ();
+
+ return pixmap.convertToImage ();
+}
+
+
+// Returns true if <image> contains translucency (rather than just transparency)
+// QPixmap::hasAlphaChannel() appears to give incorrect results
+static bool imageHasAlphaChannel (const QImage &image)
+{
+ if (image.depth () < 32)
+ return false;
+
+ for (int y = 0; y < image.height (); y++)
+ {
+ for (int x = 0; x < image.width (); x++)
+ {
+ const QRgb rgb = image.pixel (x, y);
+
+ if (qAlpha (rgb) > 0 && qAlpha (rgb) < 255)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int imageNumColorsUpTo (const QImage &image, int max)
+{
+ QMap <QRgb, bool> rgbMap;
+
+ if (image.depth () <= 8)
+ {
+ for (int i = 0; i < image.numColors () && (int) rgbMap.size () < max; i++)
+ {
+ rgbMap.insert (image.color (i), true);
+ }
+ }
+ else
+ {
+ for (int y = 0; y < image.height () && (int) rgbMap.size () < max; y++)
+ {
+ for (int x = 0; x < image.width () && (int) rgbMap.size () < max; x++)
+ {
+ rgbMap.insert (image.pixel (x, y), true);
+ }
+ }
+ }
+
+ return rgbMap.size ();
+}
+
+static void convertToPixmapWarnAboutLoss (const QImage &image,
+ const kpPixmapFX::WarnAboutLossInfo &wali)
+{
+ if (!wali.isValid ())
+ return;
+
+
+ const QString colorDepthTranslucencyDontAskAgain =
+ wali.m_dontAskAgainPrefix + "_ColorDepthTranslucency";
+ const QString colorDepthDontAskAgain =
+ wali.m_dontAskAgainPrefix + "_ColorDepth";
+ const QString translucencyDontAskAgain =
+ wali.m_dontAskAgainPrefix + "_Translucency";
+
+#if DEBUG_KP_PIXMAP_FX && 1
+ QTime timer;
+ timer.start ();
+#endif
+
+ bool hasAlphaChannel =
+ (KMessageBox::shouldBeShownContinue (translucencyDontAskAgain) &&
+ imageHasAlphaChannel (image));
+
+#if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\twarnAboutLoss - check hasAlphaChannel took "
+ << timer.restart () << "msec" << endl;
+#endif
+
+ bool moreColorsThanDisplay =
+ (KMessageBox::shouldBeShownContinue (colorDepthDontAskAgain) &&
+ image.depth () > QColor::numBitPlanes () &&
+ QColor::numBitPlanes () < 24); // 32 indicates alpha channel
+
+ int screenDepthNeeded = 0;
+
+ if (moreColorsThanDisplay)
+ screenDepthNeeded = QMIN (24, image.depth ());
+
+#if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\ttranslucencyShouldBeShown="
+ << KMessageBox::shouldBeShownContinue (translucencyDontAskAgain)
+ << endl
+ << "\thasAlphaChannel=" << hasAlphaChannel
+ << endl
+ << "\tcolorDepthShownBeShown="
+ << KMessageBox::shouldBeShownContinue (colorDepthDontAskAgain)
+ << endl
+ << "\timage.depth()=" << image.depth ()
+ << endl
+ << "\tscreenDepth=" << QColor::numBitPlanes ()
+ << endl
+ << "\tmoreColorsThanDisplay=" << moreColorsThanDisplay
+ << endl
+ << "\tneedDepth=" << screenDepthNeeded
+ << endl;
+#endif
+
+
+ QApplication::setOverrideCursor (Qt::arrowCursor);
+
+ if (moreColorsThanDisplay && hasAlphaChannel)
+ {
+ KMessageBox::information (wali.m_parent,
+ wali.m_moreColorsThanDisplayAndHasAlphaChannelMessage
+ .arg (screenDepthNeeded),
+ QString::null, // or would you prefer "Low Screen Depth and Image Contains Transparency"? :)
+ colorDepthTranslucencyDontAskAgain);
+
+ if (!KMessageBox::shouldBeShownContinue (colorDepthTranslucencyDontAskAgain))
+ {
+ KMessageBox::saveDontShowAgainContinue (colorDepthDontAskAgain);
+ KMessageBox::saveDontShowAgainContinue (translucencyDontAskAgain);
+ }
+ }
+ else if (moreColorsThanDisplay)
+ {
+ KMessageBox::information (wali.m_parent,
+ wali.m_moreColorsThanDisplayMessage
+ .arg (screenDepthNeeded),
+ i18n ("Low Screen Depth"),
+ colorDepthDontAskAgain);
+ }
+ else if (hasAlphaChannel)
+ {
+ KMessageBox::information (wali.m_parent,
+ wali.m_hasAlphaChannelMessage,
+ i18n ("Image Contains Translucency"),
+ translucencyDontAskAgain);
+ }
+
+ QApplication::restoreOverrideCursor ();
+}
+
+// public static
+QPixmap kpPixmapFX::convertToPixmap (const QImage &image, bool pretty,
+ const WarnAboutLossInfo &wali)
+{
+#if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "kpPixmapFX::convertToPixmap(image,pretty=" << pretty
+ << ",warnAboutLossInfo.isValid=" << wali.isValid ()
+ << ")" << endl;
+ QTime timer;
+ timer.start ();
+#endif
+
+ if (image.isNull ())
+ return QPixmap ();
+
+
+ QPixmap destPixmap;
+
+ if (!pretty)
+ {
+ destPixmap.convertFromImage (image,
+ Qt::ColorOnly/*always display depth*/ |
+ Qt::ThresholdDither/*no dither*/ |
+ Qt::ThresholdAlphaDither/*no dither alpha*/|
+ Qt::AvoidDither);
+ }
+ else
+ {
+ destPixmap.convertFromImage (image,
+ Qt::ColorOnly/*always display depth*/ |
+ Qt::DiffuseDither/*hi quality dither*/ |
+ Qt::ThresholdAlphaDither/*no dither alpha*/ |
+ Qt::PreferDither/*(dither even if <256 colours)*/);
+ }
+
+#if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\tconversion took " << timer.elapsed () << "msec" << endl;
+#endif
+
+ kpPixmapFX::ensureNoAlphaChannel (&destPixmap);
+
+
+ if (wali.isValid ())
+ convertToPixmapWarnAboutLoss (image, wali);
+
+
+ return destPixmap;
+}
+
+// TODO: don't dup convertToPixmap() code
+// public static
+QPixmap kpPixmapFX::convertToPixmapAsLosslessAsPossible (const QImage &image,
+ const WarnAboutLossInfo &wali)
+{
+#if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "kpPixmapFX::convertToPixmapAsLosslessAsPossible(image depth="
+ << image.depth ()
+ << ",warnAboutLossInfo.isValid=" << wali.isValid ()
+ << ") screenDepth=" << QPixmap::defaultDepth ()
+ << " imageNumColorsUpTo257=" << imageNumColorsUpTo (image, 257)
+ << endl;
+ QTime timer;
+ timer.start ();
+#endif
+
+ if (image.isNull ())
+ return QPixmap ();
+
+
+ const int screenDepth = (QPixmap::defaultDepth () >= 24 ?
+ 32 :
+ QPixmap::defaultDepth ());
+
+ QPixmap destPixmap;
+ int ditherFlags = 0;
+
+ if (image.depth () <= screenDepth)
+ {
+ #if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\timage depth <= screen depth - don't dither"
+ << " (AvoidDither | ThresholdDither)" << endl;
+ #endif
+
+ ditherFlags = (Qt::AvoidDither | Qt::ThresholdDither);
+ }
+ // PRE: image.depth() > screenDepth
+ // ASSERT: screenDepth < 32
+ else if (screenDepth <= 8)
+ {
+ const int screenNumColors = (1 << screenDepth);
+
+ #if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\tscreen depth <= 8; imageNumColorsUpTo"
+ << (screenNumColors + 1)
+ << "=" << imageNumColorsUpTo (image, screenNumColors + 1)
+ << endl;
+ #endif
+
+ if (imageNumColorsUpTo (image, screenNumColors + 1) <= screenNumColors)
+ {
+ #if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\t\tcolors fit on screen - don't dither"
+ << " (AvoidDither | ThresholdDither)" << endl;
+ #endif
+ ditherFlags = (Qt::AvoidDither | Qt::ThresholdDither);
+ }
+ else
+ {
+ #if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\t\tcolors don't fit on screen - dither"
+ << " (PreferDither | DiffuseDither)" << endl;
+ #endif
+ ditherFlags = (Qt::PreferDither | Qt::DiffuseDither);
+ }
+ }
+ // PRE: image.depth() > screenDepth &&
+ // screenDepth > 8
+ // ASSERT: screenDepth < 32
+ else
+ {
+ #if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\tscreen depth > 8 - read config" << endl;
+ #endif
+
+ int configDitherIfNumColorsGreaterThan = 323;
+
+ KConfigGroupSaver cfgGroupSaver (KGlobal::config (),
+ kpSettingsGroupGeneral);
+ KConfigBase *cfg = cfgGroupSaver.config ();
+
+ if (cfg->hasKey (kpSettingDitherOnOpen))
+ {
+ configDitherIfNumColorsGreaterThan = cfg->readNumEntry (kpSettingDitherOnOpen);
+ }
+ else
+ {
+ cfg->writeEntry (kpSettingDitherOnOpen, configDitherIfNumColorsGreaterThan);
+ cfg->sync ();
+ }
+
+ #if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\t\tcfg=" << configDitherIfNumColorsGreaterThan
+ << " image=" << imageNumColorsUpTo (image, configDitherIfNumColorsGreaterThan + 1)
+ << endl;
+ #endif
+
+ if (imageNumColorsUpTo (image, configDitherIfNumColorsGreaterThan + 1) >
+ configDitherIfNumColorsGreaterThan)
+ {
+ #if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\t\t\talways dither (PreferDither | DiffuseDither)"
+ << endl;
+ #endif
+ ditherFlags = (Qt::PreferDither | Qt::DiffuseDither);
+ }
+ else
+ {
+ #if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\t\t\tdon't dither (AvoidDither | ThresholdDither)"
+ << endl;
+ #endif
+ ditherFlags = (Qt::AvoidDither | Qt::ThresholdDither);
+ }
+ }
+
+
+ destPixmap.convertFromImage (image,
+ Qt::ColorOnly/*always display depth*/ |
+ Qt::ThresholdAlphaDither/*no dither alpha*/ |
+ ditherFlags);
+
+#if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\tconversion took " << timer.elapsed () << "msec" << endl;
+#endif
+
+ kpPixmapFX::ensureNoAlphaChannel (&destPixmap);
+
+
+ if (wali.isValid ())
+ convertToPixmapWarnAboutLoss (image, wali);
+
+
+ return destPixmap;
+}
+
+
+// public static
+QPixmap kpPixmapFX::pixmapWithDefinedTransparentPixels (const QPixmap &pixmap,
+ const QColor &transparentColor)
+{
+ if (!pixmap.mask ())
+ return pixmap;
+
+ QPixmap retPixmap (pixmap.width (), pixmap.height ());
+ retPixmap.fill (transparentColor);
+
+ QPainter p (&retPixmap);
+ p.drawPixmap (QPoint (0, 0), pixmap);
+ p.end ();
+
+ retPixmap.setMask (*pixmap.mask ());
+ return retPixmap;
+}
+
+
+//
+// Get/Set Parts of Pixmap
+//
+
+
+// public static
+QPixmap kpPixmapFX::getPixmapAt (const QPixmap &pm, const QRect &rect)
+{
+ QPixmap retPixmap (rect.width (), rect.height ());
+
+#if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "kpPixmapFX::getPixmapAt(pm.hasMask="
+ << (pm.mask () ? 1 : 0)
+ << ",rect="
+ << rect
+ << ")"
+ << endl;
+#endif
+
+ const QRect validSrcRect = pm.rect ().intersect (rect);
+ const bool wouldHaveUndefinedPixels = (validSrcRect != rect);
+
+ if (wouldHaveUndefinedPixels)
+ {
+ #if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "\tret would contain undefined pixels - setting them to transparent" << endl;
+ #endif
+ QBitmap transparentMask (rect.width (), rect.height ());
+ transparentMask.fill (Qt::color0/*transparent*/);
+ retPixmap.setMask (transparentMask);
+ }
+
+ if (validSrcRect.isEmpty ())
+ {
+ #if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "\tsilly case - completely invalid rect - ret transparent pixmap" << endl;
+ #endif
+ return retPixmap;
+ }
+
+
+ const QPoint destTopLeft = validSrcRect.topLeft () - rect.topLeft ();
+
+ // copy data _and_ mask (if avail)
+ copyBlt (&retPixmap, /* dest */
+ destTopLeft.x (), destTopLeft.y (), /* dest pt */
+ &pm, /* src */
+ validSrcRect.x (), validSrcRect.y (), /* src pt */
+ validSrcRect.width (), validSrcRect.height ());
+
+ if (wouldHaveUndefinedPixels && retPixmap.mask () && !pm.mask ())
+ {
+ #if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "\tensure opaque in valid region" << endl;
+ #endif
+ kpPixmapFX::ensureOpaqueAt (&retPixmap,
+ QRect (destTopLeft.x (), destTopLeft.y (),
+ validSrcRect.width (), validSrcRect.height ()));
+ }
+
+#if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "\tretPixmap.hasMask="
+ << (retPixmap.mask () ? 1 : 0)
+ << endl;
+#endif
+
+ return retPixmap;
+}
+
+
+// public static
+void kpPixmapFX::setPixmapAt (QPixmap *destPixmapPtr, const QRect &destRect,
+ const QPixmap &srcPixmap)
+{
+ if (!destPixmapPtr)
+ return;
+
+#if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "kpPixmapFX::setPixmapAt(destPixmap->rect="
+ << destPixmapPtr->rect ()
+ << ",destPixmap->hasMask="
+ << (destPixmapPtr->mask () ? 1 : 0)
+ << ",destRect="
+ << destRect
+ << ",srcPixmap.rect="
+ << srcPixmap.rect ()
+ << ",srcPixmap.hasMask="
+ << (srcPixmap.mask () ? 1 : 0)
+ << ")"
+ << endl;
+#endif
+
+#if DEBUG_KP_PIXMAP_FX && 0
+ if (destPixmapPtr->mask ())
+ {
+ QImage image = kpPixmapFX::convertToImage (*destPixmapPtr);
+ int numTrans = 0;
+
+ for (int y = 0; y < image.height (); y++)
+ {
+ for (int x = 0; x < image.width (); x++)
+ {
+ if (qAlpha (image.pixel (x, y)) == 0)
+ numTrans++;
+ }
+ }
+
+ kdDebug () << "\tdestPixmapPtr numTrans=" << numTrans << endl;
+ }
+#endif
+
+#if 0
+ // TODO: why does undo'ing a single pen dot on a transparent pixel,
+ // result in a opaque image, except for that single transparent pixel???
+ // Qt bug on boundary case?
+
+ // copy data _and_ mask
+ copyBlt (destPixmapPtr,
+ destAt.x (), destAt.y (),
+ &srcPixmap,
+ 0, 0,
+ destRect.width (), destRect.height ());
+#else
+ bitBlt (destPixmapPtr,
+ destRect.x (), destRect.y (),
+ &srcPixmap,
+ 0, 0,
+ destRect.width (), destRect.height (),
+ Qt::CopyROP,
+ true/*ignore mask*/);
+
+ if (srcPixmap.mask ())
+ {
+ QBitmap mask = getNonNullMask (*destPixmapPtr);
+ bitBlt (&mask,
+ destRect.x (), destRect.y (),
+ srcPixmap.mask (),
+ 0, 0,
+ destRect.width (), destRect.height (),
+ Qt::CopyROP,
+ true/*ignore mask*/);
+ destPixmapPtr->setMask (mask);
+ }
+#endif
+
+ if (destPixmapPtr->mask () && !srcPixmap.mask ())
+ {
+ #if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "\t\topaque'ing dest rect" << endl;
+ #endif
+ kpPixmapFX::ensureOpaqueAt (destPixmapPtr, destRect);
+ }
+
+#if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "\tdestPixmap->hasMask="
+ << (destPixmapPtr->mask () ? 1 : 0)
+ << endl;
+ if (destPixmapPtr->mask ())
+ {
+ QImage image = kpPixmapFX::convertToImage (*destPixmapPtr);
+ int numTrans = 0;
+
+ for (int y = 0; y < image.height (); y++)
+ {
+ for (int x = 0; x < image.width (); x++)
+ {
+ if (qAlpha (image.pixel (x, y)) == 0)
+ numTrans++;
+ }
+ }
+
+ kdDebug () << "\tdestPixmapPtr numTrans=" << numTrans << endl;
+ }
+#endif
+}
+
+// public static
+void kpPixmapFX::setPixmapAt (QPixmap *destPixmapPtr, const QPoint &destAt,
+ const QPixmap &srcPixmap)
+{
+ kpPixmapFX::setPixmapAt (destPixmapPtr,
+ QRect (destAt.x (), destAt.y (),
+ srcPixmap.width (), srcPixmap.height ()),
+ srcPixmap);
+}
+
+// public static
+void kpPixmapFX::setPixmapAt (QPixmap *destPixmapPtr, int destX, int destY,
+ const QPixmap &srcPixmap)
+{
+ kpPixmapFX::setPixmapAt (destPixmapPtr, QPoint (destX, destY), srcPixmap);
+}
+
+
+// public static
+void kpPixmapFX::paintPixmapAt (QPixmap *destPixmapPtr, const QPoint &destAt,
+ const QPixmap &srcPixmap)
+{
+ if (!destPixmapPtr)
+ return;
+
+ // Copy src (masked by src's mask) on top of dest.
+ bitBlt (destPixmapPtr, /* dest */
+ destAt.x (), destAt.y (), /* dest pt */
+ &srcPixmap, /* src */
+ 0, 0 /* src pt */);
+
+ kpPixmapFX::ensureOpaqueAt (destPixmapPtr, destAt, srcPixmap);
+}
+
+// public static
+void kpPixmapFX::paintPixmapAt (QPixmap *destPixmapPtr, int destX, int destY,
+ const QPixmap &srcPixmap)
+{
+ kpPixmapFX::paintPixmapAt (destPixmapPtr, QPoint (destX, destY), srcPixmap);
+}
+
+
+// public static
+kpColor kpPixmapFX::getColorAtPixel (const QPixmap &pm, const QPoint &at)
+{
+#if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "kpToolColorPicker::colorAtPixel" << p << endl;
+#endif
+
+ if (at.x () < 0 || at.x () >= pm.width () ||
+ at.y () < 0 || at.y () >= pm.height ())
+ {
+ return kpColor::invalid;
+ }
+
+ QPixmap pixmap = getPixmapAt (pm, QRect (at, at));
+ QImage image = kpPixmapFX::convertToImage (pixmap);
+ if (image.isNull ())
+ {
+ kdError () << "kpPixmapFX::getColorAtPixel(QPixmap) could not convert to QImage" << endl;
+ return kpColor::invalid;
+ }
+
+ return getColorAtPixel (image, QPoint (0, 0));
+}
+
+// public static
+kpColor kpPixmapFX::getColorAtPixel (const QPixmap &pm, int x, int y)
+{
+ return kpPixmapFX::getColorAtPixel (pm, QPoint (x, y));
+}
+
+// public static
+kpColor kpPixmapFX::getColorAtPixel (const QImage &img, const QPoint &at)
+{
+ if (!img.valid (at.x (), at.y ()))
+ return kpColor::invalid;
+
+ QRgb rgba = img.pixel (at.x (), at.y ());
+ return kpColor (rgba);
+}
+
+// public static
+kpColor kpPixmapFX::getColorAtPixel (const QImage &img, int x, int y)
+{
+ return kpPixmapFX::getColorAtPixel (img, QPoint (x, y));
+}
+
+
+//
+// Mask Operations
+//
+
+
+// public static
+void kpPixmapFX::ensureNoAlphaChannel (QPixmap *destPixmapPtr)
+{
+ if (destPixmapPtr->hasAlphaChannel ())
+ destPixmapPtr->setMask (kpPixmapFX::getNonNullMask/*just in case*/ (*destPixmapPtr));
+}
+
+
+// public static
+QBitmap kpPixmapFX::getNonNullMask (const QPixmap &pm)
+{
+ if (pm.mask ())
+ return *pm.mask ();
+ else
+ {
+ QBitmap maskBitmap (pm.width (), pm.height ());
+ maskBitmap.fill (Qt::color1/*opaque*/);
+
+ return maskBitmap;
+ }
+}
+
+
+// public static
+void kpPixmapFX::ensureTransparentAt (QPixmap *destPixmapPtr, const QRect &destRect)
+{
+ if (!destPixmapPtr)
+ return;
+
+ QBitmap maskBitmap = getNonNullMask (*destPixmapPtr);
+
+ QPainter p (&maskBitmap);
+
+ p.setPen (Qt::color0/*transparent*/);
+ p.setBrush (Qt::color0/*transparent*/);
+
+ p.drawRect (destRect);
+
+ p.end ();
+
+ destPixmapPtr->setMask (maskBitmap);
+}
+
+
+// public static
+void kpPixmapFX::paintMaskTransparentWithBrush (QPixmap *destPixmapPtr, const QPoint &destAt,
+ const QPixmap &brushBitmap)
+{
+ if (!destPixmapPtr)
+ return;
+
+ if (brushBitmap.depth () > 1)
+ {
+ kdError () << "kpPixmapFX::paintMaskTransparentWidthBrush() passed brushPixmap with depth > 1" << endl;
+ return;
+ }
+
+ QBitmap destMaskBitmap = kpPixmapFX::getNonNullMask (*destPixmapPtr);
+
+ // Src
+ // Dest Mask Brush Bitmap = Result
+ // -------------------------------------
+ // 0 0 0
+ // 0 1 0
+ // 1 0 1
+ // 1 1 0
+ //
+ // Brush Bitmap value of 1 means "make transparent"
+ // 0 means "leave it as it is"
+
+ bitBlt (&destMaskBitmap,
+ destAt.x (), destAt.y (),
+ &brushBitmap,
+ 0, 0,
+ brushBitmap.width (), brushBitmap.height (),
+ Qt::NotAndROP);
+
+ destPixmapPtr->setMask (destMaskBitmap);
+}
+
+// public static
+void kpPixmapFX::paintMaskTransparentWithBrush (QPixmap *destPixmapPtr, int destX, int destY,
+ const QPixmap &brushBitmap)
+{
+ kpPixmapFX::paintMaskTransparentWithBrush (destPixmapPtr,
+ QPoint (destX, destY),
+ brushBitmap);
+}
+
+
+// public static
+void kpPixmapFX::ensureOpaqueAt (QPixmap *destPixmapPtr, const QRect &destRect)
+{
+ if (!destPixmapPtr || !destPixmapPtr->mask ()/*already opaque*/)
+ return;
+
+ QBitmap maskBitmap = *destPixmapPtr->mask ();
+
+ QPainter p (&maskBitmap);
+
+ p.setPen (Qt::color1/*opaque*/);
+ p.setBrush (Qt::color1/*opaque*/);
+
+ p.drawRect (destRect);
+
+ p.end ();
+
+ destPixmapPtr->setMask (maskBitmap);
+}
+
+// public static
+void kpPixmapFX::ensureOpaqueAt (QPixmap *destPixmapPtr, const QPoint &destAt,
+ const QPixmap &srcPixmap)
+{
+ if (!destPixmapPtr || !destPixmapPtr->mask ()/*already opaque*/)
+ return;
+
+ QBitmap destMask = *destPixmapPtr->mask ();
+
+ if (srcPixmap.mask ())
+ {
+ bitBlt (&destMask, /* dest */
+ destAt, /* dest pt */
+ srcPixmap.mask (), /* src */
+ QRect (0, 0, srcPixmap.width (), srcPixmap.height ()), /* src rect */
+ Qt::OrROP/*if either is opaque, it's opaque*/);
+ }
+ else
+ {
+ QPainter p (&destMask);
+
+ p.setPen (Qt::color1/*opaque*/);
+ p.setBrush (Qt::color1/*opaque*/);
+
+ p.drawRect (destAt.x (), destAt.y (),
+ srcPixmap.width (), srcPixmap.height ());
+
+ p.end ();
+ }
+
+ destPixmapPtr->setMask (destMask);
+}
+
+// public static
+void kpPixmapFX::ensureOpaqueAt (QPixmap *destPixmapPtr, int destX, int destY,
+ const QPixmap &srcPixmap)
+{
+ kpPixmapFX::ensureOpaqueAt (destPixmapPtr, QPoint (destX, destY), srcPixmap);
+}
+
+
+//
+// Effects
+//
+
+// public static
+void kpPixmapFX::convertToGrayscale (QPixmap *destPixmapPtr)
+{
+ QImage image = kpPixmapFX::convertToImage (*destPixmapPtr);
+ kpPixmapFX::convertToGrayscale (&image);
+ *destPixmapPtr = kpPixmapFX::convertToPixmap (image);
+}
+
+// public static
+QPixmap kpPixmapFX::convertToGrayscale (const QPixmap &pm)
+{
+ QImage image = kpPixmapFX::convertToImage (pm);
+ kpPixmapFX::convertToGrayscale (&image);
+ return kpPixmapFX::convertToPixmap (image);
+}
+
+static QRgb toGray (QRgb rgb)
+{
+ // naive way that doesn't preserve brightness
+ // int gray = (qRed (rgb) + qGreen (rgb) + qBlue (rgb)) / 3;
+
+ // over-exaggerates red & blue
+ // int gray = qGray (rgb);
+
+ int gray = (212671 * qRed (rgb) + 715160 * qGreen (rgb) + 72169 * qBlue (rgb)) / 1000000;
+ return qRgba (gray, gray, gray, qAlpha (rgb));
+}
+
+// public static
+void kpPixmapFX::convertToGrayscale (QImage *destImagePtr)
+{
+ if (destImagePtr->depth () > 8)
+ {
+ // hmm, why not just write to the pixmap directly???
+
+ for (int y = 0; y < destImagePtr->height (); y++)
+ {
+ for (int x = 0; x < destImagePtr->width (); x++)
+ {
+ destImagePtr->setPixel (x, y, toGray (destImagePtr->pixel (x, y)));
+ }
+ }
+ }
+ else
+ {
+ // 1- & 8- bit images use a color table
+ for (int i = 0; i < destImagePtr->numColors (); i++)
+ destImagePtr->setColor (i, toGray (destImagePtr->color (i)));
+ }
+}
+
+// public static
+QImage kpPixmapFX::convertToGrayscale (const QImage &img)
+{
+ QImage retImage = img;
+ kpPixmapFX::convertToGrayscale (&retImage);
+ return retImage;
+}
+
+
+// public static
+void kpPixmapFX::fill (QPixmap *destPixmapPtr, const kpColor &color)
+{
+ if (!destPixmapPtr)
+ return;
+
+ if (color.isOpaque ())
+ {
+ destPixmapPtr->setMask (QBitmap ()); // no mask = opaque
+ destPixmapPtr->fill (color.toQColor ());
+ }
+ else
+ {
+ kpPixmapFX::ensureTransparentAt (destPixmapPtr, destPixmapPtr->rect ());
+ }
+}
+
+// public static
+QPixmap kpPixmapFX::fill (const QPixmap &pm, const kpColor &color)
+{
+ QPixmap ret = pm;
+ kpPixmapFX::fill (&ret, color);
+ return ret;
+}
+
+
+// public static
+void kpPixmapFX::resize (QPixmap *destPixmapPtr, int w, int h,
+ const kpColor &backgroundColor, bool fillNewAreas)
+{
+#if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "kpPixmapFX::resize()" << endl;
+#endif
+
+ if (!destPixmapPtr)
+ return;
+
+ int oldWidth = destPixmapPtr->width ();
+ int oldHeight = destPixmapPtr->height ();
+
+ if (w == oldWidth && h == oldHeight)
+ return;
+
+
+ destPixmapPtr->resize (w, h);
+
+ if (fillNewAreas && (w > oldWidth || h > oldHeight))
+ {
+ #if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\tfilling in new areas" << endl;
+ #endif
+ QBitmap maskBitmap;
+ QPainter painter, maskPainter;
+
+ if (backgroundColor.isOpaque ())
+ {
+ painter.begin (destPixmapPtr);
+ painter.setPen (backgroundColor.toQColor ());
+ painter.setBrush (backgroundColor.toQColor ());
+ }
+
+ if (backgroundColor.isTransparent () || destPixmapPtr->mask ())
+ {
+ maskBitmap = kpPixmapFX::getNonNullMask (*destPixmapPtr);
+ maskPainter.begin (&maskBitmap);
+ maskPainter.setPen (backgroundColor.maskColor ());
+ maskPainter.setBrush (backgroundColor.maskColor ());
+ }
+
+ #define PAINTER_CALL(cmd) \
+ { \
+ if (painter.isActive ()) \
+ painter . cmd ; \
+ \
+ if (maskPainter.isActive ()) \
+ maskPainter . cmd ; \
+ }
+ if (w > oldWidth)
+ PAINTER_CALL (drawRect (oldWidth, 0, w - oldWidth, oldHeight));
+
+ if (h > oldHeight)
+ PAINTER_CALL (drawRect (0, oldHeight, w, h - oldHeight));
+ #undef PAINTER_CALL
+
+ if (maskPainter.isActive ())
+ maskPainter.end ();
+
+ if (painter.isActive ())
+ painter.end ();
+
+ if (!maskBitmap.isNull ())
+ destPixmapPtr->setMask (maskBitmap);
+ }
+}
+
+// public static
+QPixmap kpPixmapFX::resize (const QPixmap &pm, int w, int h,
+ const kpColor &backgroundColor, bool fillNewAreas)
+{
+ QPixmap ret = pm;
+ kpPixmapFX::resize (&ret, w, h, backgroundColor, fillNewAreas);
+ return ret;
+}
+
+
+// public static
+void kpPixmapFX::scale (QPixmap *destPixmapPtr, int w, int h, bool pretty)
+{
+ if (!destPixmapPtr)
+ return;
+
+ *destPixmapPtr = kpPixmapFX::scale (*destPixmapPtr, w, h, pretty);
+}
+
+// public static
+QPixmap kpPixmapFX::scale (const QPixmap &pm, int w, int h, bool pretty)
+{
+#if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "kpPixmapFX::scale(oldRect=" << pm.rect ()
+ << ",w=" << w
+ << ",h=" << h
+ << ",pretty=" << pretty
+ << ")"
+ << endl;
+#endif
+
+ if (w == pm.width () && h == pm.height ())
+ return pm;
+
+ if (pretty)
+ {
+ QImage image = kpPixmapFX::convertToImage (pm);
+
+ #if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "\tBefore smooth scale:" << endl;
+ for (int y = 0; y < image.height (); y++)
+ {
+ for (int x = 0; x < image.width (); x++)
+ {
+ fprintf (stderr, " %08X", image.pixel (x, y));
+ }
+ fprintf (stderr, "\n");
+ }
+ #endif
+
+ image = image.smoothScale (w, h);
+
+ #if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "\tAfter smooth scale:" << endl;
+ for (int y = 0; y < image.height (); y++)
+ {
+ for (int x = 0; x < image.width (); x++)
+ {
+ fprintf (stderr, " %08X", image.pixel (x, y));
+ }
+ fprintf (stderr, "\n");
+ }
+ #endif
+
+ return kpPixmapFX::convertToPixmap (image, false/*let's not smooth it again*/);
+ }
+ else
+ {
+ QWMatrix matrix;
+
+ matrix.scale (double (w) / double (pm.width ()),
+ double (h) / double (pm.height ()));
+
+ return pm.xForm (matrix);
+ }
+}
+
+
+// public static
+double kpPixmapFX::AngleInDegreesEpsilon =
+ KP_RADIANS_TO_DEGREES (atan (1.0 / 10000.0))
+ / (2.0/*max error allowed*/ * 2.0/*for good measure*/);
+
+
+static QWMatrix matrixWithZeroOrigin (const QWMatrix &matrix, int width, int height)
+{
+#if DEBUG_KP_PIXMAP_FX
+ kdDebug () << "matrixWithZeroOrigin(w=" << width << ",h=" << height << ")" << endl;
+ kdDebug () << "\tmatrix: m11=" << matrix.m11 ()
+ << " m12=" << matrix.m12 ()
+ << " m21=" << matrix.m21 ()
+ << " m22=" << matrix.m22 ()
+ << " dx=" << matrix.dx ()
+ << " dy=" << matrix.dy ()
+ << endl;
+#endif
+ // TODO: Should we be using QWMatrix::Areas?
+ QRect newRect = matrix.mapRect (QRect (0, 0, width, height));
+#if DEBUG_KP_PIXMAP_FX
+ kdDebug () << "\tnewRect=" << newRect << endl;
+#endif
+
+ QWMatrix translatedMatrix (matrix.m11 (), matrix.m12 (), matrix.m21 (), matrix.m22 (),
+ matrix.dx () - newRect.left (), matrix.dy () - newRect.top ());
+
+ return translatedMatrix;
+}
+
+static QPixmap xForm (const QPixmap &pm, const QWMatrix &transformMatrix_,
+ const kpColor &backgroundColor,
+ int targetWidth, int targetHeight)
+{
+ QWMatrix transformMatrix = transformMatrix_;
+
+#if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "kppixmapfx.cpp: xForm(pm.size=" << pm.size ()
+ << ",targetWidth=" << targetWidth
+ << ",targetHeight=" << targetHeight
+ << ")"
+ << endl;
+#endif
+ // TODO: Should we be using QWMatrix::Areas?
+ QRect newRect = transformMatrix.map (pm.rect ());
+#if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\tmappedRect=" << newRect << endl;
+
+#endif
+
+ QWMatrix scaleMatrix;
+ if (targetWidth > 0 && targetWidth != newRect.width ())
+ {
+ #if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\tadjusting for targetWidth" << endl;
+ #endif
+ scaleMatrix.scale (double (targetWidth) / double (newRect.width ()), 1);
+ }
+
+ if (targetHeight > 0 && targetHeight != newRect.height ())
+ {
+ #if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\tadjusting for targetHeight" << endl;
+ #endif
+ scaleMatrix.scale (1, double (targetHeight) / double (newRect.height ()));
+ }
+
+ if (!scaleMatrix.isIdentity ())
+ {
+ #if DEBUG_KP_PIXMAP_FX && 1
+ // TODO: What is going on here??? Why isn't matrix * working properly?
+ QWMatrix wrongMatrix = transformMatrix * scaleMatrix;
+ QWMatrix oldHat = transformMatrix;
+ if (targetWidth > 0 && targetWidth != newRect.width ())
+ oldHat.scale (double (targetWidth) / double (newRect.width ()), 1);
+ if (targetHeight > 0 && targetHeight != newRect.height ())
+ oldHat.scale (1, double (targetHeight) / double (newRect.height ()));
+ QWMatrix altHat = transformMatrix;
+ altHat.scale ((targetWidth > 0 && targetWidth != newRect.width ()) ? double (targetWidth) / double (newRect.width ()) : 1,
+ (targetHeight > 0 && targetHeight != newRect.height ()) ? double (targetHeight) / double (newRect.height ()) : 1);
+ QWMatrix correctMatrix = scaleMatrix * transformMatrix;
+
+ kdDebug () << "\tsupposedlyWrongMatrix: m11=" << wrongMatrix.m11 () // <<<---- this is the correct matrix???
+ << " m12=" << wrongMatrix.m12 ()
+ << " m21=" << wrongMatrix.m21 ()
+ << " m22=" << wrongMatrix.m22 ()
+ << " dx=" << wrongMatrix.dx ()
+ << " dy=" << wrongMatrix.dy ()
+ << " rect=" << wrongMatrix.map (pm.rect ())
+ << endl
+ << "\ti_used_to_use_thisMatrix: m11=" << oldHat.m11 ()
+ << " m12=" << oldHat.m12 ()
+ << " m21=" << oldHat.m21 ()
+ << " m22=" << oldHat.m22 ()
+ << " dx=" << oldHat.dx ()
+ << " dy=" << oldHat.dy ()
+ << " rect=" << oldHat.map (pm.rect ())
+ << endl
+ << "\tabove but scaled at the same time: m11=" << altHat.m11 ()
+ << " m12=" << altHat.m12 ()
+ << " m21=" << altHat.m21 ()
+ << " m22=" << altHat.m22 ()
+ << " dx=" << altHat.dx ()
+ << " dy=" << altHat.dy ()
+ << " rect=" << altHat.map (pm.rect ())
+ << endl
+ << "\tsupposedlyCorrectMatrix: m11=" << correctMatrix.m11 ()
+ << " m12=" << correctMatrix.m12 ()
+ << " m21=" << correctMatrix.m21 ()
+ << " m22=" << correctMatrix.m22 ()
+ << " dx=" << correctMatrix.dx ()
+ << " dy=" << correctMatrix.dy ()
+ << " rect=" << correctMatrix.map (pm.rect ())
+ << endl;
+ #endif
+
+ transformMatrix = transformMatrix * scaleMatrix;
+
+ // TODO: Should we be using QWMatrix::Areas?
+ newRect = transformMatrix.map (pm.rect ());
+ #if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\tnewRect after targetWidth,targetHeight adjust=" << newRect << endl;
+ #endif
+ }
+
+
+ QPixmap newPixmap (targetWidth > 0 ? targetWidth : newRect.width (),
+ targetHeight > 0 ? targetHeight : newRect.height ());
+ if ((targetWidth > 0 && targetWidth != newRect.width ()) ||
+ (targetHeight > 0 && targetHeight != newRect.height ()))
+ {
+ #if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "kppixmapfx.cpp: xForm(pm.size=" << pm.size ()
+ << ",targetWidth=" << targetWidth
+ << ",targetHeight=" << targetHeight
+ << ") newRect=" << newRect
+ << " (you are a victim of rounding error)"
+ << endl;
+ #endif
+ }
+
+ QBitmap newBitmapMask;
+
+ if (backgroundColor.isOpaque ())
+ newPixmap.fill (backgroundColor.toQColor ());
+
+ if (backgroundColor.isTransparent () || pm.mask ())
+ {
+ newBitmapMask.resize (newPixmap.width (), newPixmap.height ());
+ newBitmapMask.fill (backgroundColor.maskColor ());
+ }
+
+ QPainter painter (&newPixmap);
+#if DEBUG_KP_PIXMAP_FX && 1
+ kdDebug () << "\tmatrix: m11=" << transformMatrix.m11 ()
+ << " m12=" << transformMatrix.m12 ()
+ << " m21=" << transformMatrix.m21 ()
+ << " m22=" << transformMatrix.m22 ()
+ << " dx=" << transformMatrix.dx ()
+ << " dy=" << transformMatrix.dy ()
+ << endl;
+ const QWMatrix trueMatrix = QPixmap::trueMatrix (transformMatrix,
+ pm.width (), pm.height ());
+ kdDebug () << "\ttrue matrix: m11=" << trueMatrix.m11 ()
+ << " m12=" << trueMatrix.m12 ()
+ << " m21=" << trueMatrix.m21 ()
+ << " m22=" << trueMatrix.m22 ()
+ << " dx=" << trueMatrix.dx ()
+ << " dy=" << trueMatrix.dy ()
+ << endl;
+#endif
+ painter.setWorldMatrix (transformMatrix);
+#if DEBUG_KP_PIXMAP_FX && 0
+ kdDebug () << "\ttranslate top=" << painter.xForm (QPoint (0, 0)) << endl;
+ kdDebug () << "\tmatrix: m11=" << painter.worldMatrix ().m11 ()
+ << " m12=" << painter.worldMatrix ().m12 ()
+ << " m21=" << painter.worldMatrix ().m21 ()
+ << " m22=" << painter.worldMatrix ().m22 ()
+ << " dx=" << painter.worldMatrix ().dx ()
+ << " dy=" << painter.worldMatrix ().dy ()
+ << endl;
+#endif
+ painter.drawPixmap (QPoint (0, 0), pm);
+ painter.end ();
+
+ if (!newBitmapMask.isNull ())
+ {
+ QPainter maskPainter (&newBitmapMask);
+ maskPainter.setWorldMatrix (transformMatrix);
+ maskPainter.drawPixmap (QPoint (0, 0), kpPixmapFX::getNonNullMask (pm));
+ maskPainter.end ();
+ newPixmap.setMask (newBitmapMask);
+ }
+
+ return newPixmap;
+}
+
+// public static
+QWMatrix kpPixmapFX::skewMatrix (int width, int height, double hangle, double vangle)
+{
+ if (fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon &&
+ fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon)
+ {
+ return QWMatrix ();
+ }
+
+
+ /* Diagram for completeness :)
+ *
+ * |---------- w ----------|
+ * (0,0)
+ * _ _______________________ (w,0)
+ * | |\~_ va |
+ * | | \ ~_ |
+ * | |ha\ ~__ |
+ * | \ ~__ | dy
+ * h | \ ~___ |
+ * | \ ~___ |
+ * | | \ ~___| (w,w*tan(va)=dy)
+ * | | \ * \
+ * _ |________\________|_____|\ vertical shear factor
+ * (0,h) dx ^~_ | \ |
+ * | ~_ \________\________ General Point (x,y) V
+ * | ~__ \ Skewed Point (x + y*tan(ha),y + x*tan(va))
+ * (h*tan(ha)=dx,h) ~__ \ ^
+ * ~___ \ |
+ * ~___ \ horizontal shear factor
+ * Key: ~___\
+ * ha = hangle (w + h*tan(ha)=w+dx,h + w*tan(va)=w+dy)
+ * va = vangle
+ *
+ * Skewing really just twists a rectangle into a parallelogram.
+ *
+ */
+
+ //QWMatrix matrix (1, tan (KP_DEGREES_TO_RADIANS (vangle)), tan (KP_DEGREES_TO_RADIANS (hangle)), 1, 0, 0);
+ // I think this is clearer than above :)
+ QWMatrix matrix;
+ matrix.shear (tan (KP_DEGREES_TO_RADIANS (hangle)),
+ tan (KP_DEGREES_TO_RADIANS (vangle)));
+
+ return matrixWithZeroOrigin (matrix, width, height);
+}
+
+// public static
+QWMatrix kpPixmapFX::skewMatrix (const QPixmap &pixmap, double hangle, double vangle)
+{
+ return kpPixmapFX::skewMatrix (pixmap.width (), pixmap.height (), hangle, vangle);
+}
+
+
+// public static
+void kpPixmapFX::skew (QPixmap *destPixmapPtr, double hangle, double vangle,
+ const kpColor &backgroundColor,
+ int targetWidth, int targetHeight)
+{
+ if (!destPixmapPtr)
+ return;
+
+ *destPixmapPtr = kpPixmapFX::skew (*destPixmapPtr, hangle, vangle,
+ backgroundColor,
+ targetWidth, targetHeight);
+}
+
+// public static
+QPixmap kpPixmapFX::skew (const QPixmap &pm, double hangle, double vangle,
+ const kpColor &backgroundColor,
+ int targetWidth, int targetHeight)
+{
+#if DEBUG_KP_PIXMAP_FX
+ kdDebug () << "kpPixmapFX::skew() pm.width=" << pm.width ()
+ << " pm.height=" << pm.height ()
+ << " hangle=" << hangle
+ << " vangle=" << vangle
+ << " targetWidth=" << targetWidth
+ << " targetHeight=" << targetHeight
+ << endl;
+#endif
+
+ if (fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon &&
+ fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon &&
+ (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/)
+ {
+ return pm;
+ }
+
+ if (fabs (hangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon ||
+ fabs (vangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon)
+ {
+ kdError () << "kpPixmapFX::skew() passed hangle and/or vangle out of range (-90 < x < 90)" << endl;
+ return pm;
+ }
+
+
+ QWMatrix matrix = skewMatrix (pm, hangle, vangle);
+
+ return ::xForm (pm, matrix, backgroundColor, targetWidth, targetHeight);
+}
+
+
+// public static
+QWMatrix kpPixmapFX::rotateMatrix (int width, int height, double angle)
+{
+ if (fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon)
+ {
+ return QWMatrix ();
+ }
+
+ QWMatrix matrix;
+ matrix.translate (width / 2, height / 2);
+ matrix.rotate (angle);
+
+ return matrixWithZeroOrigin (matrix, width, height);
+}
+
+// public static
+QWMatrix kpPixmapFX::rotateMatrix (const QPixmap &pixmap, double angle)
+{
+ return kpPixmapFX::rotateMatrix (pixmap.width (), pixmap.height (), angle);
+}
+
+
+// public static
+bool kpPixmapFX::isLosslessRotation (double angle)
+{
+ const double angleIn = angle;
+
+ // Reflect angle into positive if negative
+ if (angle < 0)
+ angle = -angle;
+
+ // Remove multiples of 90 to make sure 0 <= angle <= 90
+ angle -= ((int) angle) / 90 * 90;
+
+ // "Impossible" situation?
+ if (angle < 0 || angle > 90)
+ {
+ kdError () << "kpPixmapFX::isLosslessRotation(" << angleIn
+ << ") result=" << angle
+ << endl;
+ return false; // better safe than sorry
+ }
+
+ const bool ret = (angle < kpPixmapFX::AngleInDegreesEpsilon ||
+ 90 - angle < kpPixmapFX::AngleInDegreesEpsilon);
+#if DEBUG_KP_PIXMAP_FX
+ kdDebug () << "kpPixmapFX::isLosslessRotation(" << angleIn << ")"
+ << " residual angle=" << angle
+ << " returning " << ret
+ << endl;
+#endif
+ return ret;
+}
+
+
+// public static
+void kpPixmapFX::rotate (QPixmap *destPixmapPtr, double angle,
+ const kpColor &backgroundColor,
+ int targetWidth, int targetHeight)
+{
+ if (!destPixmapPtr)
+ return;
+
+ *destPixmapPtr = kpPixmapFX::rotate (*destPixmapPtr, angle,
+ backgroundColor,
+ targetWidth, targetHeight);
+}
+
+// public static
+QPixmap kpPixmapFX::rotate (const QPixmap &pm, double angle,
+ const kpColor &backgroundColor,
+ int targetWidth, int targetHeight)
+{
+ if (fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon &&
+ (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/)
+ {
+ return pm;
+ }
+
+
+ QWMatrix matrix = rotateMatrix (pm, angle);
+
+ return ::xForm (pm, matrix, backgroundColor, targetWidth, targetHeight);
+}
+
+
+// public static
+QWMatrix kpPixmapFX::flipMatrix (int width, int height, bool horz, bool vert)
+{
+ if (width <= 0 || height <= 0)
+ {
+ kdError () << "kpPixmapFX::flipMatrix() passed invalid dimensions" << endl;
+ return QWMatrix ();
+ }
+
+ return QWMatrix (horz ? -1 : +1, // m11
+ 0, // m12
+ 0, // m21
+ vert ? -1 : +1, // m22
+ horz ? (width - 1) : 0, // dx
+ vert ? (height - 1) : 0); // dy
+}
+
+// public static
+QWMatrix kpPixmapFX::flipMatrix (const QPixmap &pixmap, bool horz, bool vert)
+{
+ return kpPixmapFX::flipMatrix (pixmap.width (), pixmap.height (),
+ horz, vert);
+}
+
+
+// public static
+void kpPixmapFX::flip (QPixmap *destPixmapPtr, bool horz, bool vert)
+{
+ if (!horz && !vert)
+ return;
+
+ *destPixmapPtr = kpPixmapFX::flip (*destPixmapPtr, horz, vert);
+}
+
+// public static
+QPixmap kpPixmapFX::flip (const QPixmap &pm, bool horz, bool vert)
+{
+ if (!horz && !vert)
+ return pm;
+
+ return pm.xForm (flipMatrix (pm, horz, vert));
+}
+
+// public static
+void kpPixmapFX::flip (QImage *destImagePtr, bool horz, bool vert)
+{
+ if (!horz && !vert)
+ return;
+
+ *destImagePtr = kpPixmapFX::flip (*destImagePtr, horz, vert);
+}
+
+// public static
+QImage kpPixmapFX::flip (const QImage &img, bool horz, bool vert)
+{
+ if (!horz && !vert)
+ return img;
+
+ return img.mirror (horz, vert);
+}