/* Copyright (c) 2003,2004,2005 Clarence Dang 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // // 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 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 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(&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(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); }