diff options
-rw-r--r-- | src/gui/image/qpixmapfilter.cpp | 183 | ||||
-rw-r--r-- | src/gui/image/qpixmapfilter_p.h | 25 | ||||
-rw-r--r-- | src/opengl/qglpixmapfilter.cpp | 256 |
3 files changed, 463 insertions, 1 deletions
diff --git a/src/gui/image/qpixmapfilter.cpp b/src/gui/image/qpixmapfilter.cpp index c5f3663..ee4f7cf 100644 --- a/src/gui/image/qpixmapfilter.cpp +++ b/src/gui/image/qpixmapfilter.cpp @@ -87,6 +87,8 @@ public: \value ConvolutionFilter A filter that is used to calculate the convolution of the image with a kernel. See QPixmapConvolutionFilter for more information. + \value BlurFilter A filter that is used to blur an image. See + QPixmapConvolutionFilter for more information. \value ColorizeFilter A filter that is used to change the overall color of an image. See QPixmapColorizeFilter for more information. @@ -479,6 +481,187 @@ void QPixmapConvolutionFilter::draw(QPainter *painter, const QPointF &p, const Q } } +/*! + \class QPixmapBlurFilter + \since 4.6 + \ingroup multimedia + + \brief The QPixmapBlurFilter class provides blur filtering + for pixmaps. + + QPixmapBlurFilter implements a blur pixmap filter, + which is applied when \l{QPixmapFilter::}{draw()} is called. + + The filter lets you specialize the radius of the blur as well + as the quality. + + \sa {Pixmap Filters Example}, QPixmapConvolutionFilter, QPixmapDropShadowFilter + + \internal +*/ + +class QPixmapBlurFilterPrivate : public QPixmapFilterPrivate +{ +public: + QPixmapBlurFilterPrivate() : radius(5), quality(Qt::FastTransformation) {} + + int radius; + Qt::TransformationMode quality; +}; + + +/*! + Constructs a pixmap blur filter. + + \internal +*/ +QPixmapBlurFilter::QPixmapBlurFilter(QObject *parent) + : QPixmapFilter(*new QPixmapBlurFilterPrivate, BlurFilter, parent) +{ +} + +/*! + Destructor of pixmap blur filter. + + \internal +*/ +QPixmapBlurFilter::~QPixmapBlurFilter() +{ +} + +/*! + Sets the radius of the blur filter. Higher radius produces increased blurriness. + + \internal +*/ +void QPixmapBlurFilter::setRadius(int radius) +{ + Q_D(QPixmapBlurFilter); + d->radius = radius; +} + +/*! + Gets the radius of the blur filter. + + \internal +*/ +int QPixmapBlurFilter::radius() const +{ + Q_D(const QPixmapBlurFilter); + return d->radius; +} + +/*! + Sets the quality of the blur filter. Lower quality yields better performance. + + \internal +*/ +void QPixmapBlurFilter::setQuality(Qt::TransformationMode quality) +{ + Q_D(QPixmapBlurFilter); + d->quality = quality; +} + +/*! + Gets the quality of the blur filter. + + \internal +*/ +Qt::TransformationMode QPixmapBlurFilter::quality() const +{ + Q_D(const QPixmapBlurFilter); + return d->quality; +} + +/*! + \reimp + + \internal +*/ +QRectF QPixmapBlurFilter::boundingRectFor(const QRectF &rect) const +{ + return rect; +} + +/*! + \reimp + + \internal +*/ +void QPixmapBlurFilter::draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF &srcRect) const +{ + Q_D(const QPixmapBlurFilter); + if (!painter->isActive()) + return; + + if (d->radius == 0) { + painter->drawPixmap(srcRect.translated(p), src, srcRect); + return; + } + + QPixmapFilter *filter = painter->paintEngine() && painter->paintEngine()->isExtended() ? + static_cast<QPaintEngineEx *>(painter->paintEngine())->createPixmapFilter(type()) : 0; + QPixmapBlurFilter *blurFilter = static_cast<QPixmapBlurFilter*>(filter); + if (blurFilter) { + blurFilter->setRadius(d->radius); + blurFilter->setQuality(d->quality); + blurFilter->draw(painter, p, src, srcRect); + delete blurFilter; + return; + } + +#if 0 + // falling back to raster implementation + + QImage *target = 0; + if (painter->paintEngine()->paintDevice()->devType() == QInternal::Image) { + target = static_cast<QImage *>(painter->paintEngine()->paintDevice()); + + QTransform mat = painter->combinedTransform(); + + if (mat.type() > QTransform::TxTranslate) { + // Disabled because of transformation... + target = 0; + } else { + QRasterPaintEngine *pe = static_cast<QRasterPaintEngine *>(painter->paintEngine()); + if (pe->clipType() == QRasterPaintEngine::ComplexClip) + // disabled because of complex clipping... + target = 0; + else { + QRectF clip = pe->clipBoundingRect(); + QRectF rect = boundingRectFor(srcRect.isEmpty() ? src.rect() : srcRect); + QTransform x = painter->deviceTransform(); + if (!clip.contains(rect.translated(x.dx() + p.x(), x.dy() + p.y()))) { + target = 0; + } + + } + } + } + + if (target) { + QTransform x = painter->deviceTransform(); + QPointF offset(x.dx(), x.dy()); + + convolute(target, p+offset, src.toImage(), srcRect, QPainter::CompositionMode_SourceOver, d->convolutionKernel, d->kernelWidth, d->kernelHeight); + } else { + QRect srect = srcRect.isNull() ? src.rect() : srcRect.toRect(); + QRect rect = boundingRectFor(srect).toRect(); + QImage result = QImage(rect.size(), QImage::Format_ARGB32_Premultiplied); + QPoint offset = srect.topLeft() - rect.topLeft(); + convolute(&result, + offset, + src.toImage(), + srect, + QPainter::CompositionMode_Source, + d->convolutionKernel, + d->kernelWidth, + d->kernelHeight); + painter->drawImage(p - offset, result); + } +#endif +} + // grayscales the image to dest (could be same). If rect isn't defined // destination image size is used to determine the dimension of grayscaling // process. diff --git a/src/gui/image/qpixmapfilter_p.h b/src/gui/image/qpixmapfilter_p.h index 51292b3..6978f03 100644 --- a/src/gui/image/qpixmapfilter_p.h +++ b/src/gui/image/qpixmapfilter_p.h @@ -78,6 +78,7 @@ public: ConvolutionFilter, ColorizeFilter, DropShadowFilter, + BlurFilter, UserFilter = 1024 }; @@ -117,6 +118,30 @@ private: int columns() const; }; +class QPixmapBlurFilterPrivate; + +class Q_GUI_EXPORT QPixmapBlurFilter : public QPixmapFilter +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPixmapBlurFilter) + +public: + QPixmapBlurFilter(QObject *parent = 0); + ~QPixmapBlurFilter(); + + void setRadius(int radius); + void setQuality(Qt::TransformationMode mode); + + QRectF boundingRectFor(const QRectF &rect) const; + void draw(QPainter *painter, const QPointF &dest, const QPixmap &src, const QRectF &srcRect = QRectF()) const; + +private: + friend class QGLPixmapBlurFilter; + + int radius() const; + Qt::TransformationMode quality() const; +}; + class QPixmapColorizeFilterPrivate; class Q_GUI_EXPORT QPixmapColorizeFilter : public QPixmapFilter diff --git a/src/opengl/qglpixmapfilter.cpp b/src/opengl/qglpixmapfilter.cpp index 7514743..6812c43 100644 --- a/src/opengl/qglpixmapfilter.cpp +++ b/src/opengl/qglpixmapfilter.cpp @@ -40,15 +40,19 @@ ****************************************************************************/ #include "private/qpixmapfilter_p.h" +#include "private/qpaintengineex_opengl2_p.h" +#include "private/qglengineshadermanager_p.h" #include "qglpixmapfilter_p.h" #include "qgraphicssystem_gl_p.h" #include "qpaintengine_opengl_p.h" +#include "qcache.h" -#include "qglpixelbuffer.h" +#include "qglframebufferobject.h" #include "qglshaderprogram.h" #include "qgl_p.h" #include "private/qapplication_p.h" +#include "private/qmath_p.h" QT_BEGIN_NAMESPACE @@ -97,6 +101,28 @@ private: mutable int m_kernelHeight; }; +class QGLPixmapBlurFilter : public QGLCustomShader, public QGLPixmapFilter<QPixmapBlurFilter> +{ +public: + QGLPixmapBlurFilter(); + ~QGLPixmapBlurFilter(); + + void updateUniforms(QGLShaderProgram *program); + +protected: + bool processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const; + +private: + static QByteArray generateBlurShader(int radius, bool gaussianBlur); + + mutable QGLShader *m_shader; + mutable QGLFramebufferObject *m_fbo; + + mutable QSize m_textureSize; + + QGLShaderProgram *m_program; +}; + extern QGLWidget *qt_gl_share_widget(); QPixmapFilter *QGLContextPrivate::createPixmapFilter(int type) const @@ -105,6 +131,8 @@ QPixmapFilter *QGLContextPrivate::createPixmapFilter(int type) const case QPixmapFilter::ColorizeFilter: return new QGLPixmapColorizeFilter; + case QPixmapFilter::BlurFilter: + return new QGLPixmapBlurFilter; case QPixmapFilter::ConvolutionFilter: return new QGLPixmapConvolutionFilter; @@ -281,4 +309,230 @@ bool QGLPixmapConvolutionFilter::processGL(QPainter *, const QPointF &pos, const return true; } +QGLPixmapBlurFilter::QGLPixmapBlurFilter() + : m_fbo(0) +{ +} + +QGLPixmapBlurFilter::~QGLPixmapBlurFilter() +{ + delete m_fbo; +} + +bool QGLPixmapBlurFilter::processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &) const +{ + QGLCustomShader *customShader = const_cast<QGLPixmapBlurFilter *>(this); + + if (!shader()) { + QGLShader *blurShader = new QGLShader(QGLShader::FragmentShader); + blurShader->compile(generateBlurShader(radius(), quality() == Qt::SmoothTransformation)); + + customShader->setShader(blurShader); + + m_fbo = new QGLFramebufferObject(src.size()); + + glBindTexture(GL_TEXTURE_2D, m_fbo->texture()); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + } + + QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx *>(painter->paintEngine()); + QGLEngineShaderManager *manager = engine->shaderManager(); + + engine->syncState(); + painter->save(); + + // ensure GL_LINEAR filtering is used + painter->setRenderHint(QPainter::SmoothPixmapTransform); + + // prepare for updateUniforms + m_textureSize = src.size(); + + // first pass, to fbo + m_fbo->bind(); + manager->setCustomShader(customShader); + engine->drawPixmap(src.rect(), src, src.rect()); + m_fbo->release(); + + // second pass, to widget + m_program->setUniformValue("delta", 0.0, 1.0); + engine->drawTexture(src.rect().translated(pos.x(), pos.y()), m_fbo->texture(), src.size(), src.rect()); + manager->setCustomShader(0); + + painter->restore(); + + return true; +} + +void QGLPixmapBlurFilter::updateUniforms(QGLShaderProgram *program) +{ + program->setUniformValue("invTextureSize", 1.0 / m_textureSize.width(), 1.0 / m_textureSize.height()); + program->setUniformValue("delta", 1.0, 0.0); + + m_program = program; +} + +static inline qreal gaussian(qreal dx, qreal sigma) +{ + return exp(-dx * dx / (2 * sigma * sigma)) / (Q_2PI * sigma * sigma); +} + +QByteArray QGLPixmapBlurFilter::generateBlurShader(int radius, bool gaussianBlur) +{ + Q_ASSERT(radius >= 1); + + QByteArray source; + + source.append("uniform highp vec2 invTextureSize;\n"); + + bool separateXY = true; + bool clip = false; + + if (separateXY) { + source.append("uniform highp vec2 delta;\n"); + + if (clip) + source.append("uniform highp vec2 clip;\n"); + } else if (clip) { + source.append("uniform highp vec4 clip;\n"); + } + + source.append("mediump vec4 customShader(sampler2D src, vec2 srcCoords) {\n"); + + QVector<qreal> sampleOffsets; + QVector<qreal> weights; + + if (gaussianBlur) { + QVector<qreal> gaussianComponents; + + qreal sigma = radius / 1.65; + + qreal sum = 0; + for (int i = -radius; i <= radius; ++i) { + float value = gaussian(i, sigma); + gaussianComponents << value; + sum += value; + } + + // normalize + for (int i = 0; i < gaussianComponents.size(); ++i) + gaussianComponents[i] /= sum; + + for (int i = 0; i < gaussianComponents.size() - 1; i += 2) { + qreal weight = gaussianComponents.at(i) + gaussianComponents.at(i + 1); + qreal offset = i - radius + gaussianComponents.at(i + 1) / weight; + + sampleOffsets << offset; + weights << weight; + } + + // odd size ? + if (gaussianComponents.size() & 1) { + sampleOffsets << radius; + weights << gaussianComponents.last(); + } + } else { + for (int i = 0; i < radius; ++i) { + sampleOffsets << 2 * i - radius + 0.5; + weights << qreal(1); + } + sampleOffsets << radius; + weights << qreal(0.5); + } + + int currentVariable = 1; + source.append(" mediump vec4 sample = vec4(0.0);\n"); + source.append(" mediump vec2 coord;\n"); + + qreal weightSum = 0; + if (separateXY) { + source.append(" mediump float c;\n"); + for (int i = 0; i < sampleOffsets.size(); ++i) { + qreal delta = sampleOffsets.at(i); + + ++currentVariable; + + QByteArray coordinate = "srcCoords"; + if (delta != qreal(0)) { + coordinate.append(" + invTextureSize * delta * float("); + coordinate.append(QByteArray::number(delta)); + coordinate.append(")"); + } + + source.append(" coord = "); + source.append(coordinate); + source.append(";\n"); + + if (clip) { + source.append(" c = dot(coord, delta);\n"); + source.append(" if (c > clip.x && c < clip.y)\n "); + } + + source.append(" sample += texture2D(src, coord)"); + + weightSum += weights.at(i); + if (weights.at(i) != qreal(1)) { + source.append(" * float("); + source.append(QByteArray::number(weights.at(i))); + source.append(");\n"); + } else { + source.append(";\n"); + } + } + } else { + for (int y = 0; y < sampleOffsets.size(); ++y) { + for (int x = 0; x < sampleOffsets.size(); ++x) { + QByteArray coordinate = "srcCoords"; + + qreal dx = sampleOffsets.at(x); + qreal dy = sampleOffsets.at(y); + + if (dx != qreal(0) || dy != qreal(0)) { + coordinate.append(" + invTextureSize * vec2(float("); + coordinate.append(QByteArray::number(dx)); + coordinate.append("), float("); + coordinate.append(QByteArray::number(dy)); + coordinate.append("))"); + } + + source.append(" coord = "); + source.append(coordinate); + source.append(";\n"); + + if (clip) + source.append(" if (coord.x > clip.x && coord.x < clip.y && coord.y > clip.z && coord.y < clip.w)\n "); + + source.append(" sample += texture2D(src, coord)"); + + ++currentVariable; + + weightSum += weights.at(x) * weights.at(y); + if ((weights.at(x) != qreal(1) || weights.at(y) != qreal(1))) { + source.append(" * float("); + source.append(QByteArray::number(weights.at(x) * weights.at(y))); + source.append(");\n"); + } else { + source.append(";\n"); + } + } + } + } + + source.append(" return "); + if (!gaussianBlur) { + source.append("float("); + if (separateXY) + source.append(QByteArray::number(1 / weightSum)); + else + source.append(QByteArray::number(1 / weightSum)); + source.append(") * "); + } + source.append("sample;\n"); + source.append("}\n"); + + return source; +} + QT_END_NAMESPACE |