--- 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