[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    KDE/kdegraphics/kolourpaint/pixmapfx
From:       Clarence Dang <dang () kde ! org>
Date:       2007-10-09 12:41:06
Message-ID: 1191933666.419786.12028.nullmailer () svn ! kde ! org
[Download RAW message or body]

SVN commit 723359 by dang:

Fix transforms for non-XRENDER by converting to a QImage, doing the transform
using the raster paint engine and converting back to a QPixmap, rather than
using the normal kpPixmapFX::draw() mechanism.

This is needed because drawing a transformed QBitmap on top of a QBitmap causes
X errors and does not work, if XRENDER is disabled (Qt 4.3.1 bug).

The code is a lot simpler now and was probably the way it should have been
done in the first place.


 M  +114 -159  kpPixmapFX_Transforms.cpp  


--- trunk/KDE/kdegraphics/kolourpaint/pixmapfx/kpPixmapFX_Transforms.cpp #723358:723359
@@ -54,6 +54,7 @@
 #include <kpAbstractSelection.h>
 #include <kpColor.h>
 #include <kpDefs.h>
+#include <kpEffectReduceColors.h>
 #include <kpTool.h>
 
 
@@ -304,10 +305,12 @@
 static double TrueMatrixEpsilon = 0.000001;
 
 // An attempt to reverse tiny rounding errors introduced by QPixmap::trueMatrix()
-// when skewing tests/transforms.png by 45% horizontally.
+// when skewing tests/transforms.png by 45% horizontally (with TransformPixmap()
+// using a QPixmap painter, prior to the 2007-10-09 change -- did not test after
+// the change).
 // Unfortunately, this does not work enough to stop the rendering errors
 // that follow.  But it was worth a try and might still help us given the
-// sometimes excessive aliasing QPainter::drawPixmap() gives us, when
+// sometimes excessive aliasing QPainter::draw{Pixmap,Image}() gives us, when
 // QPainter::SmoothPixmapTransform is disabled.
 static double TrueMatrixFixInts (double x)
 {
@@ -337,59 +340,6 @@
 }
 
 
-struct TransformPixmapPack
-{
-    QSize destPixmapSize;
-    const QPixmap *srcPixmap;
-    QMatrix transformMatrix;
-};
-
-// This "sets" pixels instead of "painting" them.
-static void TransformPixmapHelper (QPainter *p, bool drawingOnRGBLayer, void *data)
-{ 
-#if DEBUG_KP_PIXMAP_FX
-    kDebug () << "CALL(drawOnRGBLayer=" << drawingOnRGBLayer << ")";
-#endif
-    TransformPixmapPack *pack = static_cast <TransformPixmapPack *> (data);
-
-    p->save ();
-
-    // Note: Do _not_ use "p->setRenderHints (QPainter::SmoothPixmapTransform);"
-    //       as the user does not want their image to get blurier every
-    //       time they e.g. rotate it (especially important for multiples
-    //       of 90 degrees but also true for every other angle).
-
-    if (drawingOnRGBLayer)
-    {
-        p->setMatrix (pack->transformMatrix);
-        p->drawPixmap (QPoint (0, 0), *pack->srcPixmap);
-    }
-    else
-    {
-        // SYNC: Work around a Qt "feature": QBitmap's (e.g. "srcMask") are considered to
-        //       mask themselves (i.e. "srcMask.mask()" returns "srcMask" instead of
-        //       "QBitmap()").  Therefore, "drawPixmap(srcMask)" can never create
-        //       transparent pixels.
-        //
-        // Notice the way we do this -- by not involving a matrix in the
-        // fill, we guarantee we won't miss any pixels due to rounding error
-        // etc.
-        const QRect destRect (0, 0,
-            pack->destPixmapSize.width (), pack->destPixmapSize.height ());
-        p->fillRect (destRect, Qt::color0/*transparent*/);
-
-        p->setMatrix (pack->transformMatrix);
-
-        const QBitmap srcMask = kpPixmapFX::getNonNullMask (*pack->srcPixmap);
-        p->drawPixmap (QPoint (0, 0), srcMask);
-    }
-
-    p->restore ();
-#if DEBUG_KP_PIXMAP_FX
-    kDebug () << "DONE";
-#endif
-}
-
 // Like QPixmap::transformed() but fills new areas with <backgroundColor>
 // (unless <backgroundColor> is invalid) and works around internal QMatrix
 // floating point -> integer oddities, that would otherwise give fatally
@@ -421,7 +371,7 @@
     // Futhermore, since Qt doesn't support masks on such bitmaps and their
     // color channel only has up to 2 colors, it makes little sense to
     // support arbitrary transformations of them (other than flipping,
-    // which is already covered by FlipPixmapDepth1() below).
+    // which is already covered by kpPixmapFX::flip() below).
     Q_ASSERT (pm.depth () > 1);
 
     QRect newRect = transformMatrix.mapRect (pm.rect ());
@@ -512,18 +462,23 @@
 #endif
 
     // Translate the matrix to account for Qt rounding errors,
-    // so that flipping and rotating by a multiple of 90 degrees actually
-    // work as expected (try tests/transforms.png).
+    // so that flipping (if it used this method) and rotating by a multiple
+    // of 90 degrees actually work as expected (try tests/transforms.png).
     //
     // SYNC: This was not required with Qt3 so we are actually working
     //       around a Qt4 bug/feature.
     //
-    // COMPAT: Regrettably, this decreases the quality of rotating and skewing
-    //         by 45 degrees (in Qt4, some outermost edges disappear and
-    //         this also means that if you skew a rectangular selection,
-    //         the skewed selection border does not line up with the skewed
-    //         image data; in Qt3, the source image is translated 1 pixel
-    //         off the destination image).
+    // COMPAT: Qt4's rendering with a matrix enabled is low quality anyway
+    //         but does this reduce quality even further?
+    //
+    //         With or without it, skews by 45 degrees with the QImage
+    //         painter below look bad (with it, you get an extra transparent
+    //         line on the right; without, you get only about 1/4 of a source
+    //         line on the left).  In Qt3, with TrueMatrix(), the source
+    //         image is translated 1 pixel off the destination image.
+    //
+    //         Also, if you skew a rectangular selection, the skewed selection
+    //         border does not line up with the skewed image data. 
     // TODO: do we need to pass <newRect> through this new matrix?
     transformMatrix = ::TrueMatrix (transformMatrix,
         pm.width (), pm.height ());
@@ -535,10 +490,10 @@
                    pm.width (), pm.height ());
 
 
-    QPixmap newPixmap (targetWidth > 0 ? targetWidth : newRect.width (),
-                       targetHeight > 0 ? targetHeight : newRect.height ());
+    QImage newQImage (targetWidth > 0 ? targetWidth : newRect.width (),
+                      targetHeight > 0 ? targetHeight : newRect.height (),
+                      QImage::Format_ARGB32_Premultiplied);
 
-
     if ((targetWidth > 0 && targetWidth != newRect.width ()) ||
         (targetHeight > 0 && targetHeight != newRect.height ()))
     {
@@ -565,72 +520,42 @@
 #endif
 
 
-    // Some insurance in case the background drawing code below misses a few
-    // pixels.
-    if (backgroundColor.isValid ())
-        kpPixmapFX::fill (&newPixmap, backgroundColor);
-
-
-    TransformPixmapPack pack;
-    pack.destPixmapSize = newPixmap.size ();
-    pack.srcPixmap = &pm;
-    pack.transformMatrix = transformMatrix;
-
-#if DEBUG_KP_PIXMAP_FX && 1
-    kDebug () << "drawing";
-#endif
-    kpPixmapFX::draw (&newPixmap, &::TransformPixmapHelper,
-        true/*always "draw"/copy RGB layer*/,
-        kpPixmapFX::hasMask (pm)/*draw on mask*/,
-        &pack);
-
-
-    // For rotate and skew matrices, there will be initialized pixels in
-    // the corners.  Fill these corners with the background color.
-    if (backgroundColor.isValid ())
+    // Drawing a transformed QBitmap on top of a QBitmap causes
+    // X errors and does not work, if XRENDER is disabled (Qt 4.3.1 bug).
+    // So we can't use the normal kpPixmapFX::draw() mechanism.
+    //
+    // Use QImage and QPainter instead.
+    QImage srcQImage = kpPixmapFX::convertToQImage (pm);
+    
+    // Note: Do _not_ use "p.setRenderHints (QPainter::SmoothPixmapTransform);"
+    //       as the user does not want their image to get blurier every
+    //       time they e.g. rotate it (especially important for multiples
+    //       of 90 degrees but also true for every other angle).  Being a
+    //       pixel-based program, we generally like to preserve RGB values
+    //       and avoid unnecessary blurs -- in the worst case, we'd rather
+    //       drop pixels, than blur.
+    QPainter p (&newQImage);
     {
-    #if DEBUG_KP_PIXMAP_FX && 1
-        kDebug () << "filling with background color";
-    #endif
-        // OPT: The below is hideously slow.
-
-        QBitmap opaqueMask (pm.width (), pm.height ());
-        opaqueMask.fill (Qt::color1/*opaque*/);
-
-        QBitmap mask (newPixmap.width (), newPixmap.height ());
-        mask.fill (Qt::color0/*transparent*/);
+        // Make sure transparent pixels are drawn into the destination image.
+        p.setCompositionMode (QPainter::CompositionMode_Source);
     
-        QPainter maskPainter (&mask);
-        maskPainter.setMatrix (transformMatrix);
-        // We could have used QPainter::fillRect() but we fear that that
-        // could be setting a slightly different pixel area compared to
-        // TransformPixmapHelper().
-        maskPainter.drawPixmap (QPoint (0, 0), opaqueMask);
-        maskPainter.end ();
-    
- 
-        // Invert the pixels of "mask".
-        QImage maskQImage = kpPixmapFX::convertToQImage (mask);
-        maskQImage.invertPixels ();
-        mask = kpPixmapFX::convertToPixmap (maskQImage);
-    
-
-        if (backgroundColor.isOpaque ())
+        // Fill the entire new image with the background color.
+        if (backgroundColor.isValid ())
         {
-            QPixmap fillPixmap (newPixmap.width (), newPixmap.height ());
-            fillPixmap.fill (backgroundColor.toQColor ());
-            fillPixmap.setMask (mask);
-    
-            kpPixmapFX::paintPixmapAt (&newPixmap, QPoint (0, 0), fillPixmap);
+            if (backgroundColor.isOpaque ())
+                p.fillRect (newQImage.rect (), backgroundColor.toQColor ());
+            else
+                p.fillRect (newQImage.rect (), QColor (0, 0, 0, 0)/*transparent*/);
         }
-        else
-        {
-            kpPixmapFX::paintMaskTransparentWithBrush (&newPixmap,
-                QPoint (0, 0), mask);
-        }
+
+        p.setMatrix (transformMatrix);
+        p.drawImage (QPoint (0, 0), srcQImage);
     }
+    p.end ();
 
+    const QPixmap newPixmap = kpPixmapFX::convertToPixmap (newQImage);
 
+
 #if DEBUG_KP_PIXMAP_FX && 1
     kDebug () << "Done" << endl << endl << endl;
 #endif
@@ -639,33 +564,7 @@
     return newPixmap;
 }
 
-// A lightweight version of TransformPixmap(), only supporting flipping 1-bit bitmaps.
-static QPixmap FlipPixmapDepth1 (const QPixmap &pm, const QMatrix &flipMatrix)
-{
-    KP_PFX_CHECK_NO_ALPHA_CHANNEL (pm);
-    Q_ASSERT (pm.depth () == 1);
 
-    // A flip does not change the size of a bitmap.
-    QBitmap retPixmap (pm.width (), pm.height ());
-
-    // SYNC: Work around a Qt "feature": QBitmap's (e.g. "srcMask") are considered to
-    //       mask themselves (i.e. "srcMask.mask()" returns "srcMask" instead of
-    //       "QBitmap()").  Therefore, "drawPixmap(srcMask)" can never create
-    //       transparent pixels.
-    retPixmap.fill (Qt::color0/*transparent*/);
-
-    QPainter p (&retPixmap);
-    QMatrix transformMatrix = ::TrueMatrix (flipMatrix,
-        pm.width (), pm.height ());
-    p.setMatrix (transformMatrix);
-    p.drawPixmap (QPoint (0, 0), pm);
-    p.end ();
-
-    KP_PFX_CHECK_NO_ALPHA_CHANNEL (retPixmap);
-    return retPixmap;
-}
-
-
 // public static
 QMatrix kpPixmapFX::skewMatrix (int width, int height, double hangle, double vangle)
 {
@@ -893,24 +792,80 @@
 // public static
 QPixmap kpPixmapFX::flip (const QPixmap &pm, bool horz, bool vert)
 {
+#if DEBUG_KP_PIXMAP_FX && 1
+    kDebug () << "CALL(pm.depth=" << pm.depth ()
+              << ",horz=" << horz << ",vert=" << vert << ")";
+#endif
+
     if (!horz && !vert)
         return pm;
 
-    const int w = pm.width (), h = pm.height ();
+    QImage newQImage = kpPixmapFX::convertToQImage (pm);
+#if DEBUG_KP_PIXMAP_FX && 1
+    kDebug () << "\tqimage.depth=" << newQImage.depth ()
+              << "format=" << newQImage.format ();
+#endif
 
-    const QMatrix matrix = flipMatrix (pm, horz, vert);
+    QPixmap newPixmap;
 
     if (pm.depth () > 1)
     {
-        return ::TransformPixmap (pm, matrix,
-            kpColor::Invalid/*don't need a background*/, w, h);
+        Q_ASSERT (newQImage.depth () > 1);
+
+        // Work around converToPixmap() nuking mask if the <newQImage>
+        // (and therefore, <pm>) is only 8-bit.  The name "ReduceColors"
+        // is a misnomer in this case.
+        newQImage = kpEffectReduceColors::convertImageDepth (
+            newQImage, 24/*new depth*/, false/*no dither*/);
+
+        // For depth>1, we could do the entire transform using ::TransformPixmap()
+        // but given the rounding error hacks in that method, we'd rather use
+        // this guaranteed way of doing it (QImage::mirrored()).
+        kpPixmapFX::flip (&newQImage, horz, vert);
+
+        newPixmap = kpPixmapFX::convertToPixmap (newQImage);
     }
+    // pm.depth() == 1 is used by kpAbstractImageSelection::flip()
+    // flipping the selection transparency mask.
+    else if (pm.depth () == 1)
+    {
+        Q_ASSERT (newQImage.depth () == 1);
+
+        // For depth==1, drawing a transformed QBitmap on top of a QBitmap causes
+        // X errors and does not work, if XRENDER is disabled (Qt 4.3.1 bug).
+        // So we use QImage (and QImage::mirrored()) instead.
+        kpPixmapFX::flip (&newQImage, horz, vert);
+
+    #if 0
+        // Dump pixels from the QImage.
+        for (int y = 0; y < newQImage.height (); y++)
+        {
+            fprintf (stderr, "%d:", y);
+            for (int x = 0; x < newQImage.width (); x++)
+            {
+                fprintf (stderr, " %x", newQImage.pixel (x, y));
+            }
+            fprintf (stderr, "\n");
+        }
+    #endif
+
+        newPixmap = QBitmap::fromImage (newQImage,
+            Qt::MonoOnly/*to QBitmap*/ |
+            Qt::ThresholdDither/*no dither*/ |
+            Qt::ThresholdAlphaDither/*no dither alpha*/|
+            Qt::AvoidDither);
+    }
     else
     {
-        // pm.depth() == 1 is used by kpAbstractImageSelection::flip()
-        // flipping the selection transparency mask.
-        return ::FlipPixmapDepth1 (pm, matrix);
+        Q_ASSERT (!"unexpected pixmap depth");
     }
+
+#if DEBUG_KP_PIXMAP_FX && 1
+    kDebug () << "\tnewPixmap.depth=" << newPixmap.depth ();
+#endif
+    Q_ASSERT (newPixmap.depth () == pm.depth ());
+
+    return newPixmap;
 }
 
 // public static
[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic