/**************************************************************************** ** ** 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$ ** GNU Lesser General Public License Usage ** 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. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $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 "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::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 { 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 { 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 { public: QGLPixmapBlurFilter(); protected: bool processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const; }; class QGLPixmapDropShadowFilter : public QGLCustomShaderStage, public QGLPixmapFilter { 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(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 matrix(kernelSize); QVarLengthArray 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(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 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 cache; static QList blurTextureCaches; int timerId; }; QList QGLBlurTextureCache::blurTextureCaches; Q_GLOBAL_STATIC(QGLContextGroupResource, 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::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(prepadded.bits()), prepadded.width(), prepadded.height(), prepadded.bytesPerLine(), reinterpret_cast(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(subImage).bits()); QGL2PaintEngineEx *engine = static_cast(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(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::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(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