diff options
Diffstat (limited to 'src/opengl/qglpixmapfilter.cpp')
-rw-r--r-- | src/opengl/qglpixmapfilter.cpp | 621 |
1 files changed, 621 insertions, 0 deletions
diff --git a/src/opengl/qglpixmapfilter.cpp b/src/opengl/qglpixmapfilter.cpp new file mode 100644 index 0000000000..aa075617bb --- /dev/null +++ b/src/opengl/qglpixmapfilter.cpp @@ -0,0 +1,621 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtOpenGL module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qpixmapfilter_p.h" +#include "private/qpixmapdata_gl_p.h" +#include "private/qpaintengineex_opengl2_p.h" +#include "private/qglengineshadermanager_p.h" +#include "private/qpixmapdata_p.h" +#include "private/qimagepixmapcleanuphooks_p.h" +#include "qglpixmapfilter_p.h" +#include "qgraphicssystem_gl_p.h" +#include "qpaintengine_opengl_p.h" +#include "qcache.h" + +#include "qglframebufferobject.h" +#include "qglshaderprogram.h" +#include "qgl_p.h" + +#include "private/qapplication_p.h" +#include "private/qdrawhelper_p.h" +#include "private/qmemrotate_p.h" +#include "private/qmath_p.h" +#include "qmath.h" + +QT_BEGIN_NAMESPACE + +// qpixmapfilter.cpp +Q_GUI_EXPORT void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0); +Q_GUI_EXPORT QImage qt_halfScaled(const QImage &source); + +void QGLPixmapFilterBase::bindTexture(const QPixmap &src) const +{ + const_cast<QGLContext *>(QGLContext::currentContext())->d_func()->bindTexture(src, GL_TEXTURE_2D, GL_RGBA, QGLContext::BindOptions(QGLContext::DefaultBindOption | QGLContext::MemoryManagedBindOption)); +} + +void QGLPixmapFilterBase::drawImpl(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF& source) const +{ + processGL(painter, pos, src, source); +} + +class QGLPixmapColorizeFilter: public QGLCustomShaderStage, public QGLPixmapFilter<QPixmapColorizeFilter> +{ +public: + QGLPixmapColorizeFilter(); + + void setUniforms(QGLShaderProgram *program); + +protected: + bool processGL(QPainter *painter, const QPointF &pos, const QPixmap &pixmap, const QRectF &srcRect) const; +}; + +class QGLPixmapConvolutionFilter: public QGLCustomShaderStage, public QGLPixmapFilter<QPixmapConvolutionFilter> +{ +public: + QGLPixmapConvolutionFilter(); + ~QGLPixmapConvolutionFilter(); + + void setUniforms(QGLShaderProgram *program); + +protected: + bool processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const; + +private: + QByteArray generateConvolutionShader() const; + + mutable QSize m_srcSize; + mutable int m_prevKernelSize; +}; + +class QGLPixmapBlurFilter : public QGLCustomShaderStage, public QGLPixmapFilter<QPixmapBlurFilter> +{ +public: + QGLPixmapBlurFilter(); + +protected: + bool processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const; +}; + +class QGLPixmapDropShadowFilter : public QGLCustomShaderStage, public QGLPixmapFilter<QPixmapDropShadowFilter> +{ +public: + QGLPixmapDropShadowFilter(); + + void setUniforms(QGLShaderProgram *program); + +protected: + bool processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const; +}; + +extern const QGLContext *qt_gl_share_context(); + +QPixmapFilter *QGL2PaintEngineEx::pixmapFilter(int type, const QPixmapFilter *prototype) +{ + Q_D(QGL2PaintEngineEx); + switch (type) { + case QPixmapFilter::ColorizeFilter: + if (!d->colorizeFilter) + d->colorizeFilter.reset(new QGLPixmapColorizeFilter); + return d->colorizeFilter.data(); + + case QPixmapFilter::BlurFilter: { + if (!d->blurFilter) + d->blurFilter.reset(new QGLPixmapBlurFilter()); + return d->blurFilter.data(); + } + + case QPixmapFilter::DropShadowFilter: { + if (!d->dropShadowFilter) + d->dropShadowFilter.reset(new QGLPixmapDropShadowFilter()); + return d->dropShadowFilter.data(); + } + + case QPixmapFilter::ConvolutionFilter: + if (!d->convolutionFilter) + d->convolutionFilter.reset(new QGLPixmapConvolutionFilter); + return d->convolutionFilter.data(); + + default: break; + } + return QPaintEngineEx::pixmapFilter(type, prototype); +} + +static const char *qt_gl_colorize_filter = + "uniform lowp vec4 colorizeColor;" + "uniform lowp float colorizeStrength;" + "lowp vec4 customShader(lowp sampler2D src, highp vec2 srcCoords)" + "{" + " lowp vec4 srcPixel = texture2D(src, srcCoords);" + " lowp float gray = dot(srcPixel.rgb, vec3(0.212671, 0.715160, 0.072169));" + " lowp vec3 colorized = 1.0-((1.0-gray)*(1.0-colorizeColor.rgb));" + " return vec4(mix(srcPixel.rgb, colorized * srcPixel.a, colorizeStrength), srcPixel.a);" + "}"; + +QGLPixmapColorizeFilter::QGLPixmapColorizeFilter() +{ + setSource(qt_gl_colorize_filter); +} + +bool QGLPixmapColorizeFilter::processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &) const +{ + QGLPixmapColorizeFilter *filter = const_cast<QGLPixmapColorizeFilter *>(this); + + filter->setOnPainter(painter); + painter->drawPixmap(pos, src); + filter->removeFromPainter(painter); + + return true; +} + +void QGLPixmapColorizeFilter::setUniforms(QGLShaderProgram *program) +{ + program->setUniformValue("colorizeColor", color()); + program->setUniformValue("colorizeStrength", float(strength())); +} + +void QGLPixmapConvolutionFilter::setUniforms(QGLShaderProgram *program) +{ + const qreal *kernel = convolutionKernel(); + int kernelWidth = columns(); + int kernelHeight = rows(); + int kernelSize = kernelWidth * kernelHeight; + + QVarLengthArray<GLfloat> matrix(kernelSize); + QVarLengthArray<GLfloat> offset(kernelSize * 2); + + for(int i = 0; i < kernelSize; ++i) + matrix[i] = kernel[i]; + + for(int y = 0; y < kernelHeight; ++y) { + for(int x = 0; x < kernelWidth; ++x) { + offset[(y * kernelWidth + x) * 2] = x - (kernelWidth / 2); + offset[(y * kernelWidth + x) * 2 + 1] = (kernelHeight / 2) - y; + } + } + + const qreal iw = 1.0 / m_srcSize.width(); + const qreal ih = 1.0 / m_srcSize.height(); + program->setUniformValue("inv_texture_size", iw, ih); + program->setUniformValueArray("matrix", matrix.constData(), kernelSize, 1); + program->setUniformValueArray("offset", offset.constData(), kernelSize, 2); +} + +// generates convolution filter code for arbitrary sized kernel +QByteArray QGLPixmapConvolutionFilter::generateConvolutionShader() const { + QByteArray code; + int kernelWidth = columns(); + int kernelHeight = rows(); + int kernelSize = kernelWidth * kernelHeight; + code.append("uniform highp vec2 inv_texture_size;\n" + "uniform mediump float matrix["); + code.append(QByteArray::number(kernelSize)); + code.append("];\n" + "uniform highp vec2 offset["); + code.append(QByteArray::number(kernelSize)); + code.append("];\n"); + code.append("lowp vec4 customShader(lowp sampler2D src, highp vec2 srcCoords) {\n"); + + code.append(" int i = 0;\n" + " lowp vec4 sum = vec4(0.0);\n" + " for (i = 0; i < "); + code.append(QByteArray::number(kernelSize)); + code.append("; i++) {\n" + " sum += matrix[i] * texture2D(src,srcCoords+inv_texture_size*offset[i]);\n" + " }\n" + " return sum;\n" + "}"); + return code; +} + +QGLPixmapConvolutionFilter::QGLPixmapConvolutionFilter() + : m_prevKernelSize(-1) +{ +} + +QGLPixmapConvolutionFilter::~QGLPixmapConvolutionFilter() +{ +} + +bool QGLPixmapConvolutionFilter::processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const +{ + QGLPixmapConvolutionFilter *filter = const_cast<QGLPixmapConvolutionFilter *>(this); + + m_srcSize = src.size(); + + int kernelSize = rows() * columns(); + if (m_prevKernelSize == -1 || m_prevKernelSize != kernelSize) { + filter->setSource(generateConvolutionShader()); + m_prevKernelSize = kernelSize; + } + + filter->setOnPainter(painter); + painter->drawPixmap(pos, src, srcRect); + filter->removeFromPainter(painter); + + return true; +} + +QGLPixmapBlurFilter::QGLPixmapBlurFilter() +{ +} + +class QGLBlurTextureInfo +{ +public: + QGLBlurTextureInfo(const QImage &image, GLuint tex, qreal r) + : m_texture(tex) + , m_radius(r) + { + m_paddedImage << image; + } + + ~QGLBlurTextureInfo() + { + glDeleteTextures(1, &m_texture); + } + + QImage paddedImage(int scaleLevel = 0) const; + GLuint texture() const { return m_texture; } + qreal radius() const { return m_radius; } + +private: + mutable QList<QImage> m_paddedImage; + GLuint m_texture; + qreal m_radius; +}; + +QImage QGLBlurTextureInfo::paddedImage(int scaleLevel) const +{ + for (int i = m_paddedImage.size() - 1; i <= scaleLevel; ++i) + m_paddedImage << qt_halfScaled(m_paddedImage.at(i)); + + return m_paddedImage.at(scaleLevel); +} + +class QGLBlurTextureCache : public QObject +{ +public: + static QGLBlurTextureCache *cacheForContext(const QGLContext *context); + + QGLBlurTextureCache(const QGLContext *); + ~QGLBlurTextureCache(); + + QGLBlurTextureInfo *takeBlurTextureInfo(const QPixmap &pixmap); + bool hasBlurTextureInfo(quint64 cacheKey) const; + void insertBlurTextureInfo(const QPixmap &pixmap, QGLBlurTextureInfo *info); + void clearBlurTextureInfo(quint64 cacheKey); + + void timerEvent(QTimerEvent *event); + +private: + static void pixmapDestroyed(QPixmapData *pixmap); + + QCache<quint64, QGLBlurTextureInfo > cache; + + static QList<QGLBlurTextureCache *> blurTextureCaches; + + int timerId; +}; + +QList<QGLBlurTextureCache *> QGLBlurTextureCache::blurTextureCaches; +Q_GLOBAL_STATIC(QGLContextGroupResource<QGLBlurTextureCache>, qt_blur_texture_caches) + +QGLBlurTextureCache::QGLBlurTextureCache(const QGLContext *) + : timerId(0) +{ + cache.setMaxCost(4 * 1024 * 1024); + blurTextureCaches.append(this); +} + +QGLBlurTextureCache::~QGLBlurTextureCache() +{ + blurTextureCaches.removeAt(blurTextureCaches.indexOf(this)); +} + +void QGLBlurTextureCache::timerEvent(QTimerEvent *) +{ + killTimer(timerId); + timerId = 0; + + cache.clear(); +} + +QGLBlurTextureCache *QGLBlurTextureCache::cacheForContext(const QGLContext *context) +{ + return qt_blur_texture_caches()->value(context); +} + +QGLBlurTextureInfo *QGLBlurTextureCache::takeBlurTextureInfo(const QPixmap &pixmap) +{ + return cache.take(pixmap.cacheKey()); +} + +void QGLBlurTextureCache::clearBlurTextureInfo(quint64 cacheKey) +{ + cache.remove(cacheKey); +} + +bool QGLBlurTextureCache::hasBlurTextureInfo(quint64 cacheKey) const +{ + return cache.contains(cacheKey); +} + +void QGLBlurTextureCache::insertBlurTextureInfo(const QPixmap &pixmap, QGLBlurTextureInfo *info) +{ + static bool hookAdded = false; + if (!hookAdded) { + QImagePixmapCleanupHooks::instance()->addPixmapDataDestructionHook(pixmapDestroyed); + QImagePixmapCleanupHooks::instance()->addPixmapDataModificationHook(pixmapDestroyed); + hookAdded = true; + } + + QImagePixmapCleanupHooks::enableCleanupHooks(pixmap); + cache.insert(pixmap.cacheKey(), info, pixmap.width() * pixmap.height()); + + if (timerId) + killTimer(timerId); + + timerId = startTimer(8000); +} + +void QGLBlurTextureCache::pixmapDestroyed(QPixmapData *pmd) +{ + foreach (QGLBlurTextureCache *cache, blurTextureCaches) { + if (cache->hasBlurTextureInfo(pmd->cacheKey())) + cache->clearBlurTextureInfo(pmd->cacheKey()); + } +} + +static const int qAnimatedBlurLevelIncrement = 16; +static const int qMaxBlurHalfScaleLevel = 1; + +static GLuint generateBlurTexture(const QSize &size, GLenum format = GL_RGBA) +{ + GLuint texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, format, size.width(), size.height(), 0, format, + GL_UNSIGNED_BYTE, 0); + return texture; +} + +static inline uint nextMultiple(uint x, uint multiplier) +{ + uint mod = x % multiplier; + if (mod == 0) + return x; + return x + multiplier - mod; +} + +Q_GUI_EXPORT void qt_memrotate90_gl(const quint32 *src, int srcWidth, int srcHeight, int srcStride, + quint32 *dest, int dstStride); + +bool QGLPixmapBlurFilter::processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &) const +{ + if (radius() < 1) { + painter->drawPixmap(pos, src); + return true; + } + + qreal actualRadius = radius(); + + QGLContext *ctx = const_cast<QGLContext *>(QGLContext::currentContext()); + + QGLBlurTextureCache *blurTextureCache = QGLBlurTextureCache::cacheForContext(ctx); + QGLBlurTextureInfo *info = 0; + int padding = nextMultiple(qCeil(actualRadius), qAnimatedBlurLevelIncrement); + QRect targetRect = src.rect().adjusted(-padding, -padding, padding, padding); + + // pad so that we'll be able to half-scale qMaxBlurHalfScaleLevel times + targetRect.setWidth((targetRect.width() + (qMaxBlurHalfScaleLevel-1)) & ~(qMaxBlurHalfScaleLevel-1)); + targetRect.setHeight((targetRect.height() + (qMaxBlurHalfScaleLevel-1)) & ~(qMaxBlurHalfScaleLevel-1)); + + QSize textureSize; + + info = blurTextureCache->takeBlurTextureInfo(src); + if (!info || info->radius() < actualRadius) { + QSize paddedSize = targetRect.size() / 2; + + QImage padded(paddedSize.height(), paddedSize.width(), QImage::Format_ARGB32_Premultiplied); + padded.fill(0); + + if (info) { + int oldPadding = qRound(info->radius()); + + QPainter p(&padded); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.drawImage((padding - oldPadding) / 2, (padding - oldPadding) / 2, info->paddedImage()); + p.end(); + } else { + // TODO: combine byteswapping and memrotating into one by declaring + // custom GL_RGBA pixel type and qt_colorConvert template for it + QImage prepadded = qt_halfScaled(src.toImage()).convertToFormat(QImage::Format_ARGB32_Premultiplied); + + // byte-swap and memrotates in one go + qt_memrotate90_gl(reinterpret_cast<const quint32*>(prepadded.bits()), + prepadded.width(), prepadded.height(), prepadded.bytesPerLine(), + reinterpret_cast<quint32*>(padded.scanLine(padding / 2)) + padding / 2, + padded.bytesPerLine()); + } + + delete info; + info = new QGLBlurTextureInfo(padded, generateBlurTexture(paddedSize), padding); + + textureSize = paddedSize; + } else { + textureSize = QSize(info->paddedImage().height(), info->paddedImage().width()); + } + + actualRadius *= qreal(0.5); + int level = 1; + for (; level < qMaxBlurHalfScaleLevel; ++level) { + if (actualRadius <= 16) + break; + actualRadius *= qreal(0.5); + } + + const int s = (1 << level); + + int prepadding = qRound(info->radius()); + padding = qMin(prepadding, qCeil(actualRadius) << level); + targetRect = src.rect().adjusted(-padding, -padding, padding, padding); + + targetRect.setWidth(targetRect.width() & ~(s-1)); + targetRect.setHeight(targetRect.height() & ~(s-1)); + + int paddingDelta = (prepadding - padding) >> level; + + QRect subRect(paddingDelta, paddingDelta, targetRect.width() >> level, targetRect.height() >> level); + QImage sourceImage = info->paddedImage(level-1); + + QImage subImage(subRect.height(), subRect.width(), QImage::Format_ARGB32_Premultiplied); + qt_rectcopy((QRgb *)subImage.bits(), ((QRgb *)sourceImage.scanLine(paddingDelta)) + paddingDelta, + 0, 0, subRect.height(), subRect.width(), subImage.bytesPerLine(), sourceImage.bytesPerLine()); + + GLuint texture = info->texture(); + + qt_blurImage(subImage, actualRadius, blurHints() & QGraphicsBlurEffect::QualityHint, 1); + + // subtract one pixel off the end to prevent the bilinear sampling from sampling uninitialized data + QRect textureSubRect = subImage.rect().adjusted(0, 0, -1, -1); + QRectF targetRectF = QRectF(targetRect).adjusted(0, 0, -targetRect.width() / qreal(textureSize.width()), -targetRect.height() / qreal(textureSize.height())); + + glBindTexture(GL_TEXTURE_2D, texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, subImage.width(), subImage.height(), GL_RGBA, + GL_UNSIGNED_BYTE, const_cast<const QImage &>(subImage).bits()); + + QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx *>(painter->paintEngine()); + painter->setRenderHint(QPainter::SmoothPixmapTransform); + + // texture is flipped on the y-axis + targetRectF = QRectF(targetRectF.x(), targetRectF.bottom(), targetRectF.width(), -targetRectF.height()); + engine->drawTexture(targetRectF.translated(pos), texture, textureSize, textureSubRect); + + blurTextureCache->insertBlurTextureInfo(src, info); + + return true; +} + +static const char *qt_gl_drop_shadow_filter = + "uniform lowp vec4 shadowColor;" + "lowp vec4 customShader(lowp sampler2D src, highp vec2 srcCoords)" + "{" + " return shadowColor * texture2D(src, srcCoords.yx).a;" + "}"; + + +QGLPixmapDropShadowFilter::QGLPixmapDropShadowFilter() +{ + setSource(qt_gl_drop_shadow_filter); +} + +bool QGLPixmapDropShadowFilter::processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const +{ + QGLPixmapDropShadowFilter *filter = const_cast<QGLPixmapDropShadowFilter *>(this); + + qreal r = blurRadius(); + QRectF targetRectUnaligned = QRectF(src.rect()).translated(pos + offset()).adjusted(-r, -r, r, r); + QRect targetRect = targetRectUnaligned.toAlignedRect(); + + // ensure even dimensions (going to divide by two) + targetRect.setWidth((targetRect.width() + 1) & ~1); + targetRect.setHeight((targetRect.height() + 1) & ~1); + + QGLContext *ctx = const_cast<QGLContext *>(QGLContext::currentContext()); + QGLBlurTextureCache *blurTextureCache = QGLBlurTextureCache::cacheForContext(ctx); + + QGLBlurTextureInfo *info = blurTextureCache->takeBlurTextureInfo(src); + if (!info || info->radius() != r) { + QImage half = qt_halfScaled(src.toImage().alphaChannel()); + + qreal rx = r + targetRect.left() - targetRectUnaligned.left(); + qreal ry = r + targetRect.top() - targetRectUnaligned.top(); + + QImage image = QImage(targetRect.size() / 2, QImage::Format_Indexed8); + image.setColorTable(half.colorTable()); + image.fill(0); + int dx = qRound(rx * qreal(0.5)); + int dy = qRound(ry * qreal(0.5)); + qt_rectcopy(image.bits(), half.bits(), dx, dy, + half.width(), half.height(), + image.bytesPerLine(), half.bytesPerLine()); + + qt_blurImage(image, r * qreal(0.5), false, 1); + + GLuint texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, image.width(), image.height(), + 0, GL_ALPHA, GL_UNSIGNED_BYTE, image.bits()); + + info = new QGLBlurTextureInfo(image, texture, r); + } + + GLuint texture = info->texture(); + + filter->setOnPainter(painter); + + QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx *>(painter->paintEngine()); + painter->setRenderHint(QPainter::SmoothPixmapTransform); + + engine->drawTexture(targetRect, texture, info->paddedImage().size(), info->paddedImage().rect()); + + filter->removeFromPainter(painter); + + // Now draw the actual pixmap over the top. + painter->drawPixmap(pos, src, srcRect); + + blurTextureCache->insertBlurTextureInfo(src, info); + + return true; +} + +void QGLPixmapDropShadowFilter::setUniforms(QGLShaderProgram *program) +{ + QColor col = color(); + qreal alpha = col.alphaF(); + program->setUniformValue("shadowColor", col.redF() * alpha, + col.greenF() * alpha, + col.blueF() * alpha, + alpha); +} + +QT_END_NAMESPACE |