You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegraphics/kolourpaint/pixmapfx/kppixmapfx.cpp

1678 lines
49 KiB

/*
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 <tqapplication.h>
#include <tqbitmap.h>
#include <tqdatetime.h>
#include <tqimage.h>
#include <tqpainter.h>
#include <tqpixmap.h>
#include <tqpoint.h>
#include <tqpointarray.h>
#include <tqrect.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;
}
//
// TQPixmap Statistics
//
// public static
int kpPixmapFX::pixmapArea (const TQPixmap &pixmap)
{
return kpPixmapFX::pixmapArea (pixmap.width (), pixmap.height ());
}
// public static
int kpPixmapFX::pixmapArea (const TQPixmap *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 TQPixmap &pixmap)
{
return kpPixmapFX::pixmapSize (pixmap.width (), pixmap.height (),
pixmap.depth ());
}
// public static
int kpPixmapFX::pixmapSize (const TQPixmap *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 TQImage &image)
{
return kpPixmapFX::imageSize (image.width (), image.height (), image.depth ());
}
// public static
int kpPixmapFX::imageSize (const TQImage *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 TQString &string)
{
#if DEBUG_KP_PIXMAP_FX && 1
kdDebug () << "kpPixmapFX::stringSize(" << string << ")"
<< " len=" << string.length ()
<< " sizeof(TQChar)=" << sizeof (TQChar)
<< endl;
#endif
return string.length () * sizeof (TQChar);
}
// public static
int kpPixmapFX::pointArraySize (const TQPointArray &points)
{
#if DEBUG_KP_PIXMAP_FX && 1
kdDebug () << "kpPixmapFX::pointArraySize() points.size="
<< points.size ()
<< " sizeof(TQPoint)=" << sizeof (TQPoint)
<< endl;
#endif
return (points.size () * sizeof (TQPoint));
}
//
// TQPixmap/TQImage Conversion Functions
//
// public static
TQImage kpPixmapFX::convertToImage (const TQPixmap &pixmap)
{
if (pixmap.isNull ())
return TQImage ();
return pixmap.convertToImage ();
}
// Returns true if <image> contains translucency (rather than just transparency)
// TQPixmap::hasAlphaChannel() appears to give incorrect results
static bool imageHasAlphaChannel (const TQImage &image)
{
if (image.depth () < 32)
return false;
for (int y = 0; y < image.height (); y++)
{
for (int x = 0; x < image.width (); x++)
{
const TQRgb rgb = image.pixel (x, y);
if (tqAlpha (rgb) > 0 && tqAlpha (rgb) < 255)
return true;
}
}
return false;
}
static int imageNumColorsUpTo (const TQImage &image, int max)
{
TQMap <TQRgb, 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 TQImage &image,
const kpPixmapFX::WarnAboutLossInfo &wali)
{
if (!wali.isValid ())
return;
const TQString colorDepthTranslucencyDontAskAgain =
wali.m_dontAskAgainPrefix + "_ColorDepthTranslucency";
const TQString colorDepthDontAskAgain =
wali.m_dontAskAgainPrefix + "_ColorDepth";
const TQString translucencyDontAskAgain =
wali.m_dontAskAgainPrefix + "_Translucency";
#if DEBUG_KP_PIXMAP_FX && 1
TQTime 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 () > TQColor::numBitPlanes () &&
TQColor::numBitPlanes () < 24); // 32 indicates alpha channel
int screenDepthNeeded = 0;
if (moreColorsThanDisplay)
screenDepthNeeded = TQMIN (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=" << TQColor::numBitPlanes ()
<< endl
<< "\tmoreColorsThanDisplay=" << moreColorsThanDisplay
<< endl
<< "\tneedDepth=" << screenDepthNeeded
<< endl;
#endif
TQApplication::setOverrideCursor (TQt::arrowCursor);
if (moreColorsThanDisplay && hasAlphaChannel)
{
KMessageBox::information (wali.m_parent,
wali.m_moreColorsThanDisplayAndHasAlphaChannelMessage
.arg (screenDepthNeeded),
TQString(), // 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);
}
TQApplication::restoreOverrideCursor ();
}
// public static
TQPixmap kpPixmapFX::convertToPixmap (const TQImage &image, bool pretty,
const WarnAboutLossInfo &wali)
{
#if DEBUG_KP_PIXMAP_FX && 1
kdDebug () << "kpPixmapFX::convertToPixmap(image,pretty=" << pretty
<< ",warnAboutLossInfo.isValid=" << wali.isValid ()
<< ")" << endl;
TQTime timer;
timer.start ();
#endif
if (image.isNull ())
return TQPixmap ();
TQPixmap destPixmap;
if (!pretty)
{
destPixmap.convertFromImage (image,
TQt::ColorOnly/*always display depth*/ |
TQt::ThresholdDither/*no dither*/ |
TQt::ThresholdAlphaDither/*no dither alpha*/|
TQt::AvoidDither);
}
else
{
destPixmap.convertFromImage (image,
TQt::ColorOnly/*always display depth*/ |
TQt::DiffuseDither/*hi quality dither*/ |
TQt::ThresholdAlphaDither/*no dither alpha*/ |
TQt::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
TQPixmap kpPixmapFX::convertToPixmapAsLosslessAsPossible (const TQImage &image,
const WarnAboutLossInfo &wali)
{
#if DEBUG_KP_PIXMAP_FX && 1
kdDebug () << "kpPixmapFX::convertToPixmapAsLosslessAsPossible(image depth="
<< image.depth ()
<< ",warnAboutLossInfo.isValid=" << wali.isValid ()
<< ") screenDepth=" << TQPixmap::defaultDepth ()
<< " imageNumColorsUpTo257=" << imageNumColorsUpTo (image, 257)
<< endl;
TQTime timer;
timer.start ();
#endif
if (image.isNull ())
return TQPixmap ();
const int screenDepth = (TQPixmap::defaultDepth () >= 24 ?
32 :
TQPixmap::defaultDepth ());
TQPixmap 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 = (TQt::AvoidDither | TQt::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 = (TQt::AvoidDither | TQt::ThresholdDither);
}
else
{
#if DEBUG_KP_PIXMAP_FX && 1
kdDebug () << "\t\tcolors don't fit on screen - dither"
<< " (PreferDither | DiffuseDither)" << endl;
#endif
ditherFlags = (TQt::PreferDither | TQt::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 = (TQt::PreferDither | TQt::DiffuseDither);
}
else
{
#if DEBUG_KP_PIXMAP_FX && 1
kdDebug () << "\t\t\tdon't dither (AvoidDither | ThresholdDither)"
<< endl;
#endif
ditherFlags = (TQt::AvoidDither | TQt::ThresholdDither);
}
}
destPixmap.convertFromImage (image,
TQt::ColorOnly/*always display depth*/ |
TQt::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
TQPixmap kpPixmapFX::pixmapWithDefinedTransparentPixels (const TQPixmap &pixmap,
const TQColor &transparentColor)
{
if (!pixmap.tqmask ())
return pixmap;
TQPixmap retPixmap (pixmap.width (), pixmap.height ());
retPixmap.fill (transparentColor);
TQPainter p (&retPixmap);
p.drawPixmap (TQPoint (0, 0), pixmap);
p.end ();
retPixmap.setMask (*pixmap.tqmask ());
return retPixmap;
}
//
// Get/Set Parts of Pixmap
//
// public static
TQPixmap kpPixmapFX::getPixmapAt (const TQPixmap &pm, const TQRect &rect)
{
TQPixmap retPixmap (rect.width (), rect.height ());
#if DEBUG_KP_PIXMAP_FX && 0
kdDebug () << "kpPixmapFX::getPixmapAt(pm.hasMask="
<< (pm.tqmask () ? 1 : 0)
<< ",rect="
<< rect
<< ")"
<< endl;
#endif
const TQRect 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
TQBitmap transparentMask (rect.width (), rect.height ());
transparentMask.fill (TQt::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 TQPoint destTopLeft = validSrcRect.topLeft () - rect.topLeft ();
// copy data _and_ tqmask (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.tqmask () && !pm.tqmask ())
{
#if DEBUG_KP_PIXMAP_FX && 0
kdDebug () << "\tensure opaque in valid region" << endl;
#endif
kpPixmapFX::ensureOpaqueAt (&retPixmap,
TQRect (destTopLeft.x (), destTopLeft.y (),
validSrcRect.width (), validSrcRect.height ()));
}
#if DEBUG_KP_PIXMAP_FX && 0
kdDebug () << "\tretPixmap.hasMask="
<< (retPixmap.tqmask () ? 1 : 0)
<< endl;
#endif
return retPixmap;
}
// public static
void kpPixmapFX::setPixmapAt (TQPixmap *destPixmapPtr, const TQRect &destRect,
const TQPixmap &srcPixmap)
{
if (!destPixmapPtr)
return;
#if DEBUG_KP_PIXMAP_FX && 0
kdDebug () << "kpPixmapFX::setPixmapAt(destPixmap->rect="
<< destPixmapPtr->rect ()
<< ",destPixmap->hasMask="
<< (destPixmapPtr->tqmask () ? 1 : 0)
<< ",destRect="
<< destRect
<< ",srcPixmap.rect="
<< srcPixmap.rect ()
<< ",srcPixmap.hasMask="
<< (srcPixmap.tqmask () ? 1 : 0)
<< ")"
<< endl;
#endif
#if DEBUG_KP_PIXMAP_FX && 0
if (destPixmapPtr->tqmask ())
{
TQImage image = kpPixmapFX::convertToImage (*destPixmapPtr);
int numTrans = 0;
for (int y = 0; y < image.height (); y++)
{
for (int x = 0; x < image.width (); x++)
{
if (tqAlpha (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???
// TQt bug on boundary case?
// copy data _and_ tqmask
copyBlt (destPixmapPtr,
destAt.x (), destAt.y (),
&srcPixmap,
0, 0,
destRect.width (), destRect.height ());
#else
bitBlt (TQT_TQPAINTDEVICE(destPixmapPtr),
destRect.x (), destRect.y (),
TQT_TQPAINTDEVICE(const_cast<TQPixmap*>(&srcPixmap)),
0, 0,
destRect.width (), destRect.height (),
TQt::CopyROP,
true/*ignore tqmask*/);
if (srcPixmap.tqmask ())
{
TQBitmap tqmask = getNonNullMask (*destPixmapPtr);
bitBlt (TQT_TQPAINTDEVICE(&tqmask),
destRect.x (), destRect.y (),
TQT_TQPAINTDEVICE(const_cast<TQBitmap*>(srcPixmap.tqmask ())),
0, 0,
destRect.width (), destRect.height (),
TQt::CopyROP,
true/*ignore tqmask*/);
destPixmapPtr->setMask (tqmask);
}
#endif
if (destPixmapPtr->tqmask () && !srcPixmap.tqmask ())
{
#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->tqmask () ? 1 : 0)
<< endl;
if (destPixmapPtr->tqmask ())
{
TQImage image = kpPixmapFX::convertToImage (*destPixmapPtr);
int numTrans = 0;
for (int y = 0; y < image.height (); y++)
{
for (int x = 0; x < image.width (); x++)
{
if (tqAlpha (image.pixel (x, y)) == 0)
numTrans++;
}
}
kdDebug () << "\tdestPixmapPtr numTrans=" << numTrans << endl;
}
#endif
}
// public static
void kpPixmapFX::setPixmapAt (TQPixmap *destPixmapPtr, const TQPoint &destAt,
const TQPixmap &srcPixmap)
{
kpPixmapFX::setPixmapAt (destPixmapPtr,
TQRect (destAt.x (), destAt.y (),
srcPixmap.width (), srcPixmap.height ()),
srcPixmap);
}
// public static
void kpPixmapFX::setPixmapAt (TQPixmap *destPixmapPtr, int destX, int destY,
const TQPixmap &srcPixmap)
{
kpPixmapFX::setPixmapAt (destPixmapPtr, TQPoint (destX, destY), srcPixmap);
}
// public static
void kpPixmapFX::paintPixmapAt (TQPixmap *destPixmapPtr, const TQPoint &destAt,
const TQPixmap &srcPixmap)
{
if (!destPixmapPtr)
return;
// Copy src (masked by src's tqmask) 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 (TQPixmap *destPixmapPtr, int destX, int destY,
const TQPixmap &srcPixmap)
{
kpPixmapFX::paintPixmapAt (destPixmapPtr, TQPoint (destX, destY), srcPixmap);
}
// public static
kpColor kpPixmapFX::getColorAtPixel (const TQPixmap &pm, const TQPoint &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;
}
TQPixmap pixmap = getPixmapAt (pm, TQRect (at, at));
TQImage image = kpPixmapFX::convertToImage (pixmap);
if (image.isNull ())
{
kdError () << "kpPixmapFX::getColorAtPixel(TQPixmap) could not convert to TQImage" << endl;
return kpColor::invalid;
}
return getColorAtPixel (image, TQPoint (0, 0));
}
// public static
kpColor kpPixmapFX::getColorAtPixel (const TQPixmap &pm, int x, int y)
{
return kpPixmapFX::getColorAtPixel (pm, TQPoint (x, y));
}
// public static
kpColor kpPixmapFX::getColorAtPixel (const TQImage &img, const TQPoint &at)
{
if (!img.valid (at.x (), at.y ()))
return kpColor::invalid;
TQRgb rgba = img.pixel (at.x (), at.y ());
return kpColor (rgba);
}
// public static
kpColor kpPixmapFX::getColorAtPixel (const TQImage &img, int x, int y)
{
return kpPixmapFX::getColorAtPixel (img, TQPoint (x, y));
}
//
// Mask Operations
//
// public static
void kpPixmapFX::ensureNoAlphaChannel (TQPixmap *destPixmapPtr)
{
if (destPixmapPtr->hasAlphaChannel ())
destPixmapPtr->setMask (kpPixmapFX::getNonNullMask/*just in case*/ (*destPixmapPtr));
}
// public static
TQBitmap kpPixmapFX::getNonNullMask (const TQPixmap &pm)
{
if (pm.tqmask ())
return *pm.tqmask ();
else
{
TQBitmap maskBitmap (pm.width (), pm.height ());
maskBitmap.fill (TQt::color1/*opaque*/);
return maskBitmap;
}
}
// public static
void kpPixmapFX::ensureTransparentAt (TQPixmap *destPixmapPtr, const TQRect &destRect)
{
if (!destPixmapPtr)
return;
TQBitmap maskBitmap = getNonNullMask (*destPixmapPtr);
TQPainter p (&maskBitmap);
p.setPen (TQt::color0/*transparent*/);
p.setBrush (TQt::color0/*transparent*/);
p.drawRect (destRect);
p.end ();
destPixmapPtr->setMask (maskBitmap);
}
// public static
void kpPixmapFX::paintMaskTransparentWithBrush (TQPixmap *destPixmapPtr, const TQPoint &destAt,
const TQPixmap &brushBitmap)
{
if (!destPixmapPtr)
return;
if (brushBitmap.depth () > 1)
{
kdError () << "kpPixmapFX::paintMaskTransparentWidthBrush() passed brushPixmap with depth > 1" << endl;
return;
}
TQBitmap 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 (),
TQt::NotAndROP);
destPixmapPtr->setMask (destMaskBitmap);
}
// public static
void kpPixmapFX::paintMaskTransparentWithBrush (TQPixmap *destPixmapPtr, int destX, int destY,
const TQPixmap &brushBitmap)
{
kpPixmapFX::paintMaskTransparentWithBrush (destPixmapPtr,
TQPoint (destX, destY),
brushBitmap);
}
// public static
void kpPixmapFX::ensureOpaqueAt (TQPixmap *destPixmapPtr, const TQRect &destRect)
{
if (!destPixmapPtr || !destPixmapPtr->tqmask ()/*already opaque*/)
return;
TQBitmap maskBitmap = *destPixmapPtr->tqmask ();
TQPainter p (&maskBitmap);
p.setPen (TQt::color1/*opaque*/);
p.setBrush (TQt::color1/*opaque*/);
p.drawRect (destRect);
p.end ();
destPixmapPtr->setMask (maskBitmap);
}
// public static
void kpPixmapFX::ensureOpaqueAt (TQPixmap *destPixmapPtr, const TQPoint &destAt,
const TQPixmap &srcPixmap)
{
if (!destPixmapPtr || !destPixmapPtr->tqmask ()/*already opaque*/)
return;
TQBitmap destMask = *destPixmapPtr->tqmask ();
if (srcPixmap.tqmask ())
{
bitBlt (&destMask, /* dest */
destAt, /* dest pt */
srcPixmap.tqmask (), /* src */
TQRect (0, 0, srcPixmap.width (), srcPixmap.height ()), /* src rect */
TQt::OrROP/*if either is opaque, it's opaque*/);
}
else
{
TQPainter p (&destMask);
p.setPen (TQt::color1/*opaque*/);
p.setBrush (TQt::color1/*opaque*/);
p.drawRect (destAt.x (), destAt.y (),
srcPixmap.width (), srcPixmap.height ());
p.end ();
}
destPixmapPtr->setMask (destMask);
}
// public static
void kpPixmapFX::ensureOpaqueAt (TQPixmap *destPixmapPtr, int destX, int destY,
const TQPixmap &srcPixmap)
{
kpPixmapFX::ensureOpaqueAt (destPixmapPtr, TQPoint (destX, destY), srcPixmap);
}
//
// Effects
//
// public static
void kpPixmapFX::convertToGrayscale (TQPixmap *destPixmapPtr)
{
TQImage image = kpPixmapFX::convertToImage (*destPixmapPtr);
kpPixmapFX::convertToGrayscale (&image);
*destPixmapPtr = kpPixmapFX::convertToPixmap (image);
}
// public static
TQPixmap kpPixmapFX::convertToGrayscale (const TQPixmap &pm)
{
TQImage image = kpPixmapFX::convertToImage (pm);
kpPixmapFX::convertToGrayscale (&image);
return kpPixmapFX::convertToPixmap (image);
}
static TQRgb toGray (TQRgb rgb)
{
// naive way that doesn't preserve brightness
// int gray = (tqRed (rgb) + tqGreen (rgb) + tqBlue (rgb)) / 3;
// over-exaggerates red & blue
// int gray = tqGray (rgb);
int gray = (212671 * tqRed (rgb) + 715160 * tqGreen (rgb) + 72169 * tqBlue (rgb)) / 1000000;
return tqRgba (gray, gray, gray, tqAlpha (rgb));
}
// public static
void kpPixmapFX::convertToGrayscale (TQImage *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
TQImage kpPixmapFX::convertToGrayscale (const TQImage &img)
{
TQImage retImage = img;
kpPixmapFX::convertToGrayscale (&retImage);
return retImage;
}
// public static
void kpPixmapFX::fill (TQPixmap *destPixmapPtr, const kpColor &color)
{
if (!destPixmapPtr)
return;
if (color.isOpaque ())
{
destPixmapPtr->setMask (TQBitmap ()); // no tqmask = opaque
destPixmapPtr->fill (color.toTQColor ());
}
else
{
kpPixmapFX::ensureTransparentAt (destPixmapPtr, destPixmapPtr->rect ());
}
}
// public static
TQPixmap kpPixmapFX::fill (const TQPixmap &pm, const kpColor &color)
{
TQPixmap ret = pm;
kpPixmapFX::fill (&ret, color);
return ret;
}
// public static
void kpPixmapFX::resize (TQPixmap *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
TQBitmap maskBitmap;
TQPainter painter, maskPainter;
if (backgroundColor.isOpaque ())
{
painter.begin (destPixmapPtr);
painter.setPen (backgroundColor.toTQColor ());
painter.setBrush (backgroundColor.toTQColor ());
}
if (backgroundColor.isTransparent () || destPixmapPtr->tqmask ())
{
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
TQPixmap kpPixmapFX::resize (const TQPixmap &pm, int w, int h,
const kpColor &backgroundColor, bool fillNewAreas)
{
TQPixmap ret = pm;
kpPixmapFX::resize (&ret, w, h, backgroundColor, fillNewAreas);
return ret;
}
// public static
void kpPixmapFX::scale (TQPixmap *destPixmapPtr, int w, int h, bool pretty)
{
if (!destPixmapPtr)
return;
*destPixmapPtr = kpPixmapFX::scale (*destPixmapPtr, w, h, pretty);
}
// public static
TQPixmap kpPixmapFX::scale (const TQPixmap &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)
{
TQImage 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
{
TQWMatrix 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 TQWMatrix matrixWithZeroOrigin (const TQWMatrix &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 TQWMatrix::Areas?
TQRect newRect = matrix.mapRect (TQRect (0, 0, width, height));
#if DEBUG_KP_PIXMAP_FX
kdDebug () << "\tnewRect=" << newRect << endl;
#endif
TQWMatrix translatedMatrix (matrix.m11 (), matrix.m12 (), matrix.m21 (), matrix.m22 (),
matrix.dx () - newRect.left (), matrix.dy () - newRect.top ());
return translatedMatrix;
}
static TQPixmap xForm (const TQPixmap &pm, const TQWMatrix &transformMatrix_,
const kpColor &backgroundColor,
int targetWidth, int targetHeight)
{
TQWMatrix 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 TQWMatrix::Areas?
TQRect newRect = transformMatrix.map (pm.rect ());
#if DEBUG_KP_PIXMAP_FX && 1
kdDebug () << "\tmappedRect=" << newRect << endl;
#endif
TQWMatrix 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?
TQWMatrix wrongMatrix = transformMatrix * scaleMatrix;
TQWMatrix 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 ()));
TQWMatrix 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);
TQWMatrix 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 TQWMatrix::Areas?
newRect = transformMatrix.map (pm.rect ());
#if DEBUG_KP_PIXMAP_FX && 1
kdDebug () << "\tnewRect after targetWidth,targetHeight adjust=" << newRect << endl;
#endif
}
TQPixmap 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
}
TQBitmap newBitmapMask;
if (backgroundColor.isOpaque ())
newPixmap.fill (backgroundColor.toTQColor ());
if (backgroundColor.isTransparent () || pm.tqmask ())
{
newBitmapMask.resize (newPixmap.width (), newPixmap.height ());
newBitmapMask.fill (backgroundColor.maskColor ());
}
TQPainter 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 TQWMatrix trueMatrix = TQPixmap::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 (TQPoint (0, 0)) << endl;
kdDebug () << "\tmatrix: m11=" << painter.tqworldMatrix ().m11 ()
<< " m12=" << painter.tqworldMatrix ().m12 ()
<< " m21=" << painter.tqworldMatrix ().m21 ()
<< " m22=" << painter.tqworldMatrix ().m22 ()
<< " dx=" << painter.tqworldMatrix ().dx ()
<< " dy=" << painter.tqworldMatrix ().dy ()
<< endl;
#endif
painter.drawPixmap (TQPoint (0, 0), pm);
painter.end ();
if (!newBitmapMask.isNull ())
{
TQPainter maskPainter (&newBitmapMask);
maskPainter.setWorldMatrix (transformMatrix);
maskPainter.drawPixmap (TQPoint (0, 0), kpPixmapFX::getNonNullMask (pm));
maskPainter.end ();
newPixmap.setMask (newBitmapMask);
}
return newPixmap;
}
// public static
TQWMatrix kpPixmapFX::skewMatrix (int width, int height, double hangle, double vangle)
{
if (fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon &&
fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon)
{
return TQWMatrix ();
}
/* 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.
*
*/
//TQWMatrix matrix (1, tan (KP_DEGREES_TO_RADIANS (vangle)), tan (KP_DEGREES_TO_RADIANS (hangle)), 1, 0, 0);
// I think this is clearer than above :)
TQWMatrix matrix;
matrix.shear (tan (KP_DEGREES_TO_RADIANS (hangle)),
tan (KP_DEGREES_TO_RADIANS (vangle)));
return matrixWithZeroOrigin (matrix, width, height);
}
// public static
TQWMatrix kpPixmapFX::skewMatrix (const TQPixmap &pixmap, double hangle, double vangle)
{
return kpPixmapFX::skewMatrix (pixmap.width (), pixmap.height (), hangle, vangle);
}
// public static
void kpPixmapFX::skew (TQPixmap *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
TQPixmap kpPixmapFX::skew (const TQPixmap &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;
}
TQWMatrix matrix = skewMatrix (pm, hangle, vangle);
return ::xForm (pm, matrix, backgroundColor, targetWidth, targetHeight);
}
// public static
TQWMatrix kpPixmapFX::rotateMatrix (int width, int height, double angle)
{
if (fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon)
{
return TQWMatrix ();
}
TQWMatrix matrix;
matrix.translate (width / 2, height / 2);
matrix.rotate (angle);
return matrixWithZeroOrigin (matrix, width, height);
}
// public static
TQWMatrix kpPixmapFX::rotateMatrix (const TQPixmap &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 (TQPixmap *destPixmapPtr, double angle,
const kpColor &backgroundColor,
int targetWidth, int targetHeight)
{
if (!destPixmapPtr)
return;
*destPixmapPtr = kpPixmapFX::rotate (*destPixmapPtr, angle,
backgroundColor,
targetWidth, targetHeight);
}
// public static
TQPixmap kpPixmapFX::rotate (const TQPixmap &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;
}
TQWMatrix matrix = rotateMatrix (pm, angle);
return ::xForm (pm, matrix, backgroundColor, targetWidth, targetHeight);
}
// public static
TQWMatrix kpPixmapFX::flipMatrix (int width, int height, bool horz, bool vert)
{
if (width <= 0 || height <= 0)
{
kdError () << "kpPixmapFX::flipMatrix() passed invalid dimensions" << endl;
return TQWMatrix ();
}
return TQWMatrix (horz ? -1 : +1, // m11
0, // m12
0, // m21
vert ? -1 : +1, // m22
horz ? (width - 1) : 0, // dx
vert ? (height - 1) : 0); // dy
}
// public static
TQWMatrix kpPixmapFX::flipMatrix (const TQPixmap &pixmap, bool horz, bool vert)
{
return kpPixmapFX::flipMatrix (pixmap.width (), pixmap.height (),
horz, vert);
}
// public static
void kpPixmapFX::flip (TQPixmap *destPixmapPtr, bool horz, bool vert)
{
if (!horz && !vert)
return;
*destPixmapPtr = kpPixmapFX::flip (*destPixmapPtr, horz, vert);
}
// public static
TQPixmap kpPixmapFX::flip (const TQPixmap &pm, bool horz, bool vert)
{
if (!horz && !vert)
return pm;
return pm.xForm (flipMatrix (pm, horz, vert));
}
// public static
void kpPixmapFX::flip (TQImage *destImagePtr, bool horz, bool vert)
{
if (!horz && !vert)
return;
*destImagePtr = kpPixmapFX::flip (*destImagePtr, horz, vert);
}
// public static
TQImage kpPixmapFX::flip (const TQImage &img, bool horz, bool vert)
{
if (!horz && !vert)
return img;
return img.mirror (horz, vert);
}