diff options
Diffstat (limited to 'kolourpaint/pixmapfx/kppixmapfx.cpp')
-rw-r--r-- | kolourpaint/pixmapfx/kppixmapfx.cpp | 1677 |
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 <[email protected]> + 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); +} |