src/gui/image/qpixmapfilter.cpp
changeset 3 41300fa6a67c
parent 0 1918ee327afb
child 4 3b1da2848fc7
child 7 f7bc934e204c
child 18 2f34d5167611
--- a/src/gui/image/qpixmapfilter.cpp	Tue Jan 26 12:42:25 2010 +0200
+++ b/src/gui/image/qpixmapfilter.cpp	Tue Feb 02 00:43:10 2010 +0200
@@ -52,7 +52,12 @@
 #include "private/qgraphicssystem_p.h"
 #include "private/qpaintengineex_p.h"
 #include "private/qpaintengine_raster_p.h"
+#include "qmath.h"
+#include "private/qmath_p.h"
+#include "private/qmemrotate_p.h"
+#include "private/qdrawhelper_p.h"
 
+#ifndef QT_NO_GRAPHICSEFFECT
 QT_BEGIN_NAMESPACE
 
 class QPixmapFilterPrivate : public QObjectPrivate
@@ -489,7 +494,7 @@
     which is applied when \l{QPixmapFilter::}{draw()} is called.
 
     The filter lets you specialize the radius of the blur as well
-    as hint as to whether to prefer performance or quality.
+    as hints as to whether to prefer performance or quality.
 
     By default, the blur effect is produced by applying an exponential
     filter generated from the specified blurRadius().  Paint engines
@@ -504,10 +509,10 @@
 class QPixmapBlurFilterPrivate : public QPixmapFilterPrivate
 {
 public:
-    QPixmapBlurFilterPrivate() : radius(5), hint(Qt::PerformanceHint) {}
+    QPixmapBlurFilterPrivate() : radius(5), hints(QGraphicsBlurEffect::PerformanceHint) {}
 
-    int radius;
-    Qt::RenderHint hint;
+    qreal radius;
+    QGraphicsBlurEffect::BlurHints hints;
 };
 
 
@@ -535,7 +540,7 @@
 
     \internal
 */
-void QPixmapBlurFilter::setRadius(int radius)
+void QPixmapBlurFilter::setRadius(qreal radius)
 {
     Q_D(QPixmapBlurFilter);
     d->radius = radius;
@@ -546,139 +551,377 @@
 
     \internal
 */
-int QPixmapBlurFilter::radius() const
+qreal QPixmapBlurFilter::radius() const
 {
     Q_D(const QPixmapBlurFilter);
     return d->radius;
 }
 
 /*!
-    Setting the blur hint to PerformanceHint causes the implementation
+    Setting the blur hints to PerformanceHint causes the implementation
     to trade off visual quality to blur the image faster.  Setting the
-    blur hint to QualityHint causes the implementation to improve
-    visual quality at the expense of speed.  The implementation is free
-    to ignore this value if it only has a single blur algorithm.
+    blur hints to QualityHint causes the implementation to improve
+    visual quality at the expense of speed.
+
+    AnimationHint causes the implementation to optimize for animating
+    the blur radius, possibly by caching blurred versions of the source
+    pixmap.
+
+    The implementation is free to ignore this value if it only has a single
+    blur algorithm.
 
     \internal
 */
-void QPixmapBlurFilter::setBlurHint(Qt::RenderHint hint)
+void QPixmapBlurFilter::setBlurHints(QGraphicsBlurEffect::BlurHints hints)
 {
     Q_D(QPixmapBlurFilter);
-    d->hint = hint;
+    d->hints = hints;
 }
 
 /*!
-    Gets the blur hint of the blur filter.
+    Gets the blur hints of the blur filter.
 
     \internal
 */
-Qt::RenderHint QPixmapBlurFilter::blurHint() const
+QGraphicsBlurEffect::BlurHints QPixmapBlurFilter::blurHints() const
 {
     Q_D(const QPixmapBlurFilter);
-    return d->hint;
+    return d->hints;
 }
 
+const qreal radiusScale = qreal(2.5);
+
 /*!
     \internal
 */
 QRectF QPixmapBlurFilter::boundingRectFor(const QRectF &rect) const
 {
     Q_D(const QPixmapBlurFilter);
-    const qreal delta = d->radius * 2;
+    const qreal delta = radiusScale * d->radius + 1;
     return rect.adjusted(-delta, -delta, delta, delta);
 }
 
-// Blur the image according to the blur radius
-// Based on exponential blur algorithm by Jani Huhtanen
-// (maximum radius is set to 16)
-static QImage blurred(const QImage& image, const QRect& rect, int radius, bool alphaOnly = false)
+template <int shift>
+inline int static_shift(int value)
+{
+    if (shift == 0)
+        return value;
+    else if (shift > 0)
+        return value << (uint(shift) & 0x1f);
+    else
+        return value >> (uint(-shift) & 0x1f);
+}
+
+template<int aprec, int zprec>
+inline void blurinner(uchar *bptr, int &zR, int &zG, int &zB, int &zA, int alpha)
 {
-    int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
-    int alpha = (radius < 1)  ? 16 : (radius > 17) ? 1 : tab[radius-1];
+    QRgb *pixel = (QRgb *)bptr;
+
+#define Z_MASK (0xff << zprec)
+    const int A_zprec = static_shift<zprec - 24>(*pixel) & Z_MASK;
+    const int R_zprec = static_shift<zprec - 16>(*pixel) & Z_MASK;
+    const int G_zprec = static_shift<zprec - 8>(*pixel)  & Z_MASK;
+    const int B_zprec = static_shift<zprec>(*pixel)      & Z_MASK;
+#undef Z_MASK
+
+    const int zR_zprec = zR >> aprec;
+    const int zG_zprec = zG >> aprec;
+    const int zB_zprec = zB >> aprec;
+    const int zA_zprec = zA >> aprec;
+
+    zR += alpha * (R_zprec - zR_zprec);
+    zG += alpha * (G_zprec - zG_zprec);
+    zB += alpha * (B_zprec - zB_zprec);
+    zA += alpha * (A_zprec - zA_zprec);
 
-    QImage result = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
-    int r1 = rect.top();
-    int r2 = rect.bottom();
-    int c1 = rect.left();
-    int c2 = rect.right();
+#define ZA_MASK (0xff << (zprec + aprec))
+    *pixel =
+        static_shift<24 - zprec - aprec>(zA & ZA_MASK)
+        | static_shift<16 - zprec - aprec>(zR & ZA_MASK)
+        | static_shift<8 - zprec - aprec>(zG & ZA_MASK)
+        | static_shift<-zprec - aprec>(zB & ZA_MASK);
+#undef ZA_MASK
+}
+
+const int alphaIndex = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3);
+
+template<int aprec, int zprec>
+inline void blurinner_alphaOnly(uchar *bptr, int &z, int alpha)
+{
+    const int A_zprec = int(*(bptr)) << zprec;
+    const int z_zprec = z >> aprec;
+    z += alpha * (A_zprec - z_zprec);
+    *(bptr) = z >> (zprec + aprec);
+}
+
+template<int aprec, int zprec, bool alphaOnly>
+inline void blurrow(QImage & im, int line, int alpha)
+{
+    uchar *bptr = im.scanLine(line);
+
+    int zR = 0, zG = 0, zB = 0, zA = 0;
 
-    int bpl = result.bytesPerLine();
-    int rgba[4];
-    unsigned char* p;
+    if (alphaOnly && im.format() != QImage::Format_Indexed8)
+        bptr += alphaIndex;
 
-    int i1 = 0;
-    int i2 = 3;
+    const int stride = im.depth() >> 3;
+    const int im_width = im.width();
+    for (int index = 0; index < im_width; ++index) {
+        if (alphaOnly)
+            blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha);
+        else
+            blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha);
+        bptr += stride;
+    }
 
-    if (alphaOnly)
-        i1 = i2 = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3);
+    bptr -= stride;
+
+    for (int index = im_width - 2; index >= 0; --index) {
+        bptr -= stride;
+        if (alphaOnly)
+            blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha);
+        else
+            blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha);
+    }
+}
 
-    for (int col = c1; col <= c2; col++) {
-        p = result.scanLine(r1) + col * 4;
-        for (int i = i1; i <= i2; i++)
-            rgba[i] = p[i] << 4;
+/*
+*  expblur(QImage &img, int radius)
+*
+*  Based on exponential blur algorithm by Jani Huhtanen
+*
+*  In-place blur of image 'img' with kernel
+*  of approximate radius 'radius'.
+*
+*  Blurs with two sided exponential impulse
+*  response.
+*
+*  aprec = precision of alpha parameter
+*  in fixed-point format 0.aprec
+*
+*  zprec = precision of state parameters
+*  zR,zG,zB and zA in fp format 8.zprec
+*/
+template <int aprec, int zprec, bool alphaOnly>
+void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0)
+{
+    // halve the radius if we're using two passes
+    if (improvedQuality)
+        radius *= qreal(0.5);
 
-        p += bpl;
-        for (int j = r1; j < r2; j++, p += bpl)
-            for (int i = i1; i <= i2; i++)
-                p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
+    Q_ASSERT(img.format() == QImage::Format_ARGB32_Premultiplied
+             || img.format() == QImage::Format_RGB32);
+
+    // choose the alpha such that pixels at radius distance from a fully
+    // saturated pixel will have an alpha component of no greater than
+    // the cutOffIntensity
+    const qreal cutOffIntensity = 2;
+    int alpha = radius <= qreal(1e-5)
+        ? ((1 << aprec)-1)
+        : qRound((1<<aprec)*(1 - qPow(cutOffIntensity * (1 / qreal(255)), 1 / radius)));
+
+    int img_height = img.height();
+    for (int row = 0; row < img_height; ++row) {
+        for (int i = 0; i <= improvedQuality; ++i)
+            blurrow<aprec, zprec, alphaOnly>(img, row, alpha);
     }
 
-    for (int row = r1; row <= r2; row++) {
-        p = result.scanLine(row) + c1 * 4;
-        for (int i = i1; i <= i2; i++)
-            rgba[i] = p[i] << 4;
+    QImage temp(img.height(), img.width(), img.format());
+    if (transposed >= 0) {
+        if (img.depth() == 8) {
+            qt_memrotate270(reinterpret_cast<const quint8*>(img.bits()),
+                            img.width(), img.height(), img.bytesPerLine(),
+                            reinterpret_cast<quint8*>(temp.bits()),
+                            temp.bytesPerLine());
+        } else {
+            qt_memrotate270(reinterpret_cast<const quint32*>(img.bits()),
+                            img.width(), img.height(), img.bytesPerLine(),
+                            reinterpret_cast<quint32*>(temp.bits()),
+                            temp.bytesPerLine());
+        }
+    } else {
+        if (img.depth() == 8) {
+            qt_memrotate90(reinterpret_cast<const quint8*>(img.bits()),
+                           img.width(), img.height(), img.bytesPerLine(),
+                           reinterpret_cast<quint8*>(temp.bits()),
+                           temp.bytesPerLine());
+        } else {
+            qt_memrotate90(reinterpret_cast<const quint32*>(img.bits()),
+                           img.width(), img.height(), img.bytesPerLine(),
+                           reinterpret_cast<quint32*>(temp.bits()),
+                           temp.bytesPerLine());
+        }
+    }
 
-        p += 4;
-        for (int j = c1; j < c2; j++, p += 4)
-            for (int i = i1; i <= i2; i++)
-                p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
+    img_height = temp.height();
+    for (int row = 0; row < img_height; ++row) {
+        for (int i = 0; i <= improvedQuality; ++i)
+            blurrow<aprec, zprec, alphaOnly>(temp, row, alpha);
     }
 
-    for (int col = c1; col <= c2; col++) {
-        p = result.scanLine(r2) + col * 4;
-        for (int i = i1; i <= i2; i++)
-            rgba[i] = p[i] << 4;
+    if (transposed == 0) {
+        qt_memrotate90(reinterpret_cast<const quint32*>(temp.bits()),
+                       temp.width(), temp.height(), temp.bytesPerLine(),
+                       reinterpret_cast<quint32*>(img.bits()),
+                       img.bytesPerLine());
+    } else {
+        img = temp;
+    }
+}
+#define AVG(a,b)  ( ((((a)^(b)) & 0xfefefefeUL) >> 1) + ((a)&(b)) )
+#define AVG16(a,b)  ( ((((a)^(b)) & 0xf7deUL) >> 1) + ((a)&(b)) )
+
+Q_GUI_EXPORT QImage qt_halfScaled(const QImage &source)
+{
+    QImage srcImage = source;
+
+    if (source.format() == QImage::Format_Indexed8) {
+        // assumes grayscale
+        QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
+
+        const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits());
+        int sx = srcImage.bytesPerLine();
+        int sx2 = sx << 1;
+
+        uchar *dst = reinterpret_cast<uchar*>(dest.bits());
+        int dx = dest.bytesPerLine();
+        int ww = dest.width();
+        int hh = dest.height();
+
+        for (int y = hh; y; --y, dst += dx, src += sx2) {
+            const uchar *p1 = src;
+            const uchar *p2 = src + sx;
+            uchar *q = dst;
+            for (int x = ww; x; --x, ++q, p1 += 2, p2 += 2)
+                *q = ((int(p1[0]) + int(p1[1]) + int(p2[0]) + int(p2[1])) + 2) >> 2;
+        }
 
-        p -= bpl;
-        for (int j = r1; j < r2; j++, p -= bpl)
-            for (int i = i1; i <= i2; i++)
-                p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
+        return dest;
+    } else if (source.format() == QImage::Format_ARGB8565_Premultiplied) {
+        QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
+
+        const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits());
+        int sx = srcImage.bytesPerLine();
+        int sx2 = sx << 1;
+
+        uchar *dst = reinterpret_cast<uchar*>(dest.bits());
+        int dx = dest.bytesPerLine();
+        int ww = dest.width();
+        int hh = dest.height();
+
+        for (int y = hh; y; --y, dst += dx, src += sx2) {
+            const uchar *p1 = src;
+            const uchar *p2 = src + sx;
+            uchar *q = dst;
+            for (int x = ww; x; --x, q += 3, p1 += 6, p2 += 6) {
+                // alpha
+                q[0] = AVG(AVG(p1[0], p1[3]), AVG(p2[0], p2[3]));
+                // rgb
+                const quint16 p16_1 = (p1[2] << 8) | p1[1];
+                const quint16 p16_2 = (p1[5] << 8) | p1[4];
+                const quint16 p16_3 = (p2[2] << 8) | p2[1];
+                const quint16 p16_4 = (p2[5] << 8) | p2[4];
+                const quint16 result = AVG16(AVG16(p16_1, p16_2), AVG16(p16_3, p16_4));
+                q[1] = result & 0xff;
+                q[2] = result >> 8;
+            }
+        }
+
+        return dest;
+    } else if (source.format() != QImage::Format_ARGB32_Premultiplied
+               && source.format() != QImage::Format_RGB32)
+    {
+        srcImage = source.convertToFormat(QImage::Format_ARGB32_Premultiplied);
     }
 
-    for (int row = r1; row <= r2; row++) {
-        p = result.scanLine(row) + c2 * 4;
-        for (int i = i1; i <= i2; i++)
-            rgba[i] = p[i] << 4;
+    QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
+
+    const quint32 *src = reinterpret_cast<const quint32*>(const_cast<const QImage &>(srcImage).bits());
+    int sx = srcImage.bytesPerLine() >> 2;
+    int sx2 = sx << 1;
 
-        p -= 4;
-        for (int j = c1; j < c2; j++, p -= 4)
-            for (int i = i1; i <= i2; i++)
-                p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
+    quint32 *dst = reinterpret_cast<quint32*>(dest.bits());
+    int dx = dest.bytesPerLine() >> 2;
+    int ww = dest.width();
+    int hh = dest.height();
+
+    for (int y = hh; y; --y, dst += dx, src += sx2) {
+        const quint32 *p1 = src;
+        const quint32 *p2 = src + sx;
+        quint32 *q = dst;
+        for (int x = ww; x; --x, q++, p1 += 2, p2 += 2)
+            *q = AVG(AVG(p1[0], p1[1]), AVG(p2[0], p2[1]));
     }
 
-    return result;
+    return dest;
 }
 
+Q_GUI_EXPORT void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0)
+{
+    if (blurImage.format() != QImage::Format_ARGB32_Premultiplied
+        && blurImage.format() != QImage::Format_RGB32)
+    {
+        blurImage = blurImage.convertToFormat(QImage::Format_ARGB32_Premultiplied);
+    }
+
+    qreal scale = 1;
+    if (radius >= 4) {
+        blurImage = qt_halfScaled(blurImage);
+        scale = 2;
+        radius *= qreal(0.5);
+    }
+
+    if (alphaOnly)
+        expblur<12, 10, true>(blurImage, radius, quality, transposed);
+    else
+        expblur<12, 10, false>(blurImage, radius, quality, transposed);
+
+    if (p) {
+        p->scale(scale, scale);
+        p->setRenderHint(QPainter::SmoothPixmapTransform);
+        p->drawImage(QRect(0, 0, blurImage.width(), blurImage.height()), blurImage);
+    }
+}
+
+Q_GUI_EXPORT void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0)
+{
+    if (blurImage.format() == QImage::Format_Indexed8)
+        expblur<12, 10, true>(blurImage, radius, quality, transposed);
+    else
+        expblur<12, 10, false>(blurImage, radius, quality, transposed);
+}
+
+bool qt_scaleForTransform(const QTransform &transform, qreal *scale);
+
 /*!
     \internal
 */
-void QPixmapBlurFilter::draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF &srcRect) const
+void QPixmapBlurFilter::draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF &rect) const
 {
     Q_D(const QPixmapBlurFilter);
     if (!painter->isActive())
         return;
 
-    if (d->radius == 0) {
+    QRectF srcRect = rect;
+    if (srcRect.isNull())
+        srcRect = src.rect();
+
+    if (d->radius <= 1) {
         painter->drawPixmap(srcRect.translated(p), src, srcRect);
         return;
     }
 
+    qreal scaledRadius = radiusScale * d->radius;
+    qreal scale;
+    if (qt_scaleForTransform(painter->transform(), &scale))
+        scaledRadius /= scale;
+
     QPixmapFilter *filter = painter->paintEngine() && painter->paintEngine()->isExtended() ?
         static_cast<QPaintEngineEx *>(painter->paintEngine())->pixmapFilter(type(), this) : 0;
     QPixmapBlurFilter *blurFilter = static_cast<QPixmapBlurFilter*>(filter);
     if (blurFilter) {
-        blurFilter->setRadius(d->radius);
-        blurFilter->setBlurHint(d->hint);
+        blurFilter->setRadius(scaledRadius);
+        blurFilter->setBlurHints(d->hints);
         blurFilter->draw(painter, p, src, srcRect);
         return;
     }
@@ -686,17 +929,17 @@
     QImage srcImage;
     QImage destImage;
 
-    if (srcRect.isNull()) {
+    if (srcRect == src.rect()) {
         srcImage = src.toImage();
-        destImage = blurred(srcImage, srcImage.rect(), d->radius);
     } else {
         QRect rect = srcRect.toAlignedRect().intersected(src.rect());
-
         srcImage = src.copy(rect).toImage();
-        destImage = blurred(srcImage, srcImage.rect(), d->radius);
     }
 
-    painter->drawImage(p, destImage);
+    QTransform transform = painter->worldTransform();
+    painter->translate(p);
+    qt_blurImage(painter, srcImage, scaledRadius, (d->hints & QGraphicsBlurEffect::QualityHint), false);
+    painter->setWorldTransform(transform);
 }
 
 // grayscales the image to dest (could be same). If rect isn't defined
@@ -902,7 +1145,7 @@
 
     QPointF offset;
     QColor color;
-    int radius;
+    qreal radius;
 };
 
 /*!
@@ -966,7 +1209,7 @@
 
     \internal
 */
-int QPixmapDropShadowFilter::blurRadius() const
+qreal QPixmapDropShadowFilter::blurRadius() const
 {
     Q_D(const QPixmapDropShadowFilter);
     return d->radius;
@@ -981,7 +1224,7 @@
 
     \internal
 */
-void QPixmapDropShadowFilter::setBlurRadius(int radius)
+void QPixmapDropShadowFilter::setBlurRadius(qreal radius)
 {
     Q_D(QPixmapDropShadowFilter);
     d->radius = radius;
@@ -1057,14 +1300,7 @@
 QRectF QPixmapDropShadowFilter::boundingRectFor(const QRectF &rect) const
 {
     Q_D(const QPixmapDropShadowFilter);
-
-    const qreal delta = qreal(d->radius * 2);
-    qreal x1 = qMin(rect.left(), rect.left() + d->offset.x() - delta);
-    qreal y1 = qMin(rect.top(), rect.top() + d->offset.y() - delta);
-    qreal x2 = qMax(rect.right(), rect.right() + d->offset.x() + delta);
-    qreal y2 = qMax(rect.bottom(), rect.bottom() + d->offset.y() + delta);
-
-    return QRectF(x1, y1, x2 - x1, y2 - y1);
+    return rect.united(rect.translated(d->offset).adjusted(-d->radius, -d->radius, d->radius, d->radius));
 }
 
 /*!
@@ -1087,22 +1323,35 @@
         return;
     }
 
-    QImage tmp = src.isNull() ? px.toImage() : px.copy(src.toAlignedRect()).toImage();
+    QImage tmp(px.size(), QImage::Format_ARGB32_Premultiplied);
+    tmp.fill(0);
+    QPainter tmpPainter(&tmp);
+    tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
+    tmpPainter.drawPixmap(d->offset, px);
+    tmpPainter.end();
 
     // blur the alpha channel
-    tmp = blurred(tmp, tmp.rect(), d->radius, true);
+    QImage blurred(tmp.size(), QImage::Format_ARGB32_Premultiplied);
+    blurred.fill(0);
+    QPainter blurPainter(&blurred);
+    qt_blurImage(&blurPainter, tmp, d->radius, false, true);
+    blurPainter.end();
+
+    tmp = blurred;
 
     // blacken the image...
-    QPainter tmpPainter(&tmp);
+    tmpPainter.begin(&tmp);
     tmpPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
-    tmpPainter.fillRect(0, 0, tmp.width(), tmp.height(), d->color);
+    tmpPainter.fillRect(tmp.rect(), d->color);
     tmpPainter.end();
 
     // draw the blurred drop shadow...
-    p->drawImage(pos + d->offset, tmp);
+    p->drawImage(pos, tmp);
 
     // Draw the actual pixmap...
     p->drawPixmap(pos, px, src);
 }
 
 QT_END_NAMESPACE
+
+#endif //QT_NO_GRAPHICSEFFECT