diff options
author | Ariya Hidayat <ariya.hidayat@nokia.com> | 2009-06-02 11:57:45 (GMT) |
---|---|---|
committer | Ariya Hidayat <ariya.hidayat@nokia.com> | 2009-06-02 12:06:04 (GMT) |
commit | 96773d4b21ce288b26857159dfbb553a81ae3a94 (patch) | |
tree | 2e8611e4737233a636d6249ce7d90e98bd657a75 /src/gui/graphicsview/qgraphicseffect.cpp | |
parent | f001cda07f6aa026d59e448b49212c0182ed895c (diff) | |
download | Qt-96773d4b21ce288b26857159dfbb553a81ae3a94.zip Qt-96773d4b21ce288b26857159dfbb553a81ae3a94.tar.gz Qt-96773d4b21ce288b26857159dfbb553a81ae3a94.tar.bz2 |
Another n-th attempt at making an API for the effect framework.
The implementation is not efficient, it serves as the proof-of-concept
only. Check the notes in qgraphicseffect.cpp for details.
Diffstat (limited to 'src/gui/graphicsview/qgraphicseffect.cpp')
-rw-r--r-- | src/gui/graphicsview/qgraphicseffect.cpp | 556 |
1 files changed, 556 insertions, 0 deletions
diff --git a/src/gui/graphicsview/qgraphicseffect.cpp b/src/gui/graphicsview/qgraphicseffect.cpp new file mode 100644 index 0000000..1d0e485 --- /dev/null +++ b/src/gui/graphicsview/qgraphicseffect.cpp @@ -0,0 +1,556 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui 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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgraphicseffect.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include <QtGui/qimage.h> +#include <QtGui/qgraphicsitem.h> +#include <QtGui/qgraphicsscene.h> +#include <QtGui/qpainter.h> + +#include <image/qpixmapfilter_p.h> + +/* + + List of known drawbacks which are being discussed: + + * No d-pointer yet. + + * No auto test yet. + + * No API documentation yet. + + * The API is far from being finalized. + + * Most of the effect implementation is not efficient, + as this is still a proof of concept only. + + * Painting artifacts occasionally occur when e.g. moving + an item over another item that has a large effective + bounding rect. + + * Item transformation is not taken into account. + For example, the effective bounding rect is calculated at + item coordinate (fast), but the painting is mostly + done at device coordinate. + + * Coordinate mode: item vs device. Most effects make sense only + in device coordinate. Should we keep both options open? + See also above transformation issue. + + * Right now the pixmap for effect drawing is stored in each item. + There can be problems with coordinates, see above. + + * There is a lot of duplication in drawItems() for each effect. + + * Port to use the new layer feature in QGraphicsView. + This should solve the above pixmap problem. + + * Frame effect is likely useless. However it is very useful + to check than the effective bounding rect is handled properly. + + * Proper exposed region and rect for style option are missing. + + * Pixelize effect is using raster only, because there is no + pixmap filter for it. We need to implement the missing pixmap filter. + + * Blur effect is using raster only, with exponential blur algorithm. + Perhaps use stack blur (better approximate Gaussian blur) instead? + QPixmapConvolutionFilter is too slow for this simple blur effect. + + * Bloom and shadow effect are also raster only. Same reason as above. + + * Make it work with widgets (QGraphicsWidget). + +*/ + +QGraphicsEffect::QGraphicsEffect() +{ +} + +QGraphicsEffect::~QGraphicsEffect() +{ +} + +QRectF QGraphicsEffect::boundingRectFor(const QGraphicsItem *item) +{ + // default is to give the item's bounding rect + // do NOT call item->effectiveBoundRect() because + // that function will call this one (infinite loop) + return item->boundingRect(); +} + +// this helper function is only for subclasses of QGraphicsEffect +// the implementation is trivial, but this allows us to keep +// QGraphicsScene::drawItem() as a protected function +// (since QGraphicsEffect is a friend of QGraphicsScene) +QPixmap* QGraphicsEffect::drawItemOnPixmap(QPainter *painter, QGraphicsItem *item, + const QStyleOptionGraphicsItem *option, QWidget *widget, int flags) +{ + if (!item->scene()) + return 0; + return item->scene()->drawItemOnPixmap(painter, item, option, widget, flags); +} + +QGraphicsGrayscaleEffect::QGraphicsGrayscaleEffect(): QGraphicsEffect() +{ + filter = new QPixmapColorizeFilter; + filter->setColor(Qt::black); +} + +QGraphicsGrayscaleEffect::~QGraphicsGrayscaleEffect() +{ + delete filter; +} + +void QGraphicsGrayscaleEffect::drawItem(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + // Find the item's bounds in device coordinates. + QRectF deviceBounds = painter->worldTransform().mapRect(item->boundingRect()); + QRect deviceRect = deviceBounds.toRect().adjusted(-1, -1, 1, 1); + if (deviceRect.isEmpty()) + return; + + QPixmap *pixmap = QGraphicsEffect::drawItemOnPixmap(painter, item, option, widget, 0); + if (!pixmap) + return; + + // Draw the pixmap with the filter using an untransformed painter. + QTransform restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + filter->draw(painter, deviceRect.topLeft(), *pixmap, pixmap->rect()); + painter->setWorldTransform(restoreTransform); +} + +QGraphicsColorizeEffect::QGraphicsColorizeEffect(): QGraphicsEffect() +{ + filter = new QPixmapColorizeFilter; +} + +QGraphicsColorizeEffect::~QGraphicsColorizeEffect() +{ + delete filter; +} + +QColor QGraphicsColorizeEffect::color() const +{ + return filter->color(); +} + +void QGraphicsColorizeEffect::setColor(const QColor &c) +{ + filter->setColor(c); +} + +void QGraphicsColorizeEffect::drawItem(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + // Find the item's bounds in device coordinates. + QRectF deviceBounds = painter->worldTransform().mapRect(item->boundingRect()); + QRect deviceRect = deviceBounds.toRect().adjusted(-1, -1, 1, 1); + if (deviceRect.isEmpty()) + return; + + QPixmap *pixmap = QGraphicsEffect::drawItemOnPixmap(painter, item, option, widget, 0); + if (!pixmap) + return; + + // Draw the pixmap with the filter using an untransformed painter. + QTransform restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + filter->draw(painter, deviceRect.topLeft(), *pixmap, pixmap->rect()); + painter->setWorldTransform(restoreTransform); +} + +QGraphicsPixelizeEffect::QGraphicsPixelizeEffect(): QGraphicsEffect(), size(2) +{ +} + +QGraphicsPixelizeEffect::~QGraphicsPixelizeEffect() +{ +} + +void QGraphicsPixelizeEffect::drawItem(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + // Find the item's bounds in device coordinates. + QRectF deviceBounds = painter->worldTransform().mapRect(item->boundingRect()); + QRect deviceRect = deviceBounds.toRect().adjusted(-1, -1, 1, 1); + if (deviceRect.isEmpty()) + return; + + QPixmap *pixmap = QGraphicsEffect::drawItemOnPixmap(painter, item, option, widget, 0); + if (!pixmap) + return; + + // pixelize routine + QImage img = pixmap->toImage().convertToFormat(QImage::Format_ARGB32); + if (size > 0) { + int width = img.width(); + int height = img.height(); + for (int y = 0; y < height; y += size) { + int ys = qMin(height - 1, y + size / 2); + QRgb *sbuf = reinterpret_cast<QRgb*>(img.scanLine(ys)); + for (int x = 0; x < width; x += size) { + int xs = qMin(width - 1, x + size / 2); + QRgb color = sbuf[xs]; + for (int yi = 0; yi < qMin(size, height - y); ++yi) { + QRgb *buf = reinterpret_cast<QRgb*>(img.scanLine(y + yi)); + for (int xi = 0; xi < qMin(size, width - x); ++xi) + buf[x + xi] = color; + } + } + } + } + + // Draw using an untransformed painter. + QTransform restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + painter->drawImage(deviceRect.topLeft(), img); + painter->setWorldTransform(restoreTransform); +} + + +QGraphicsBlurEffect::QGraphicsBlurEffect(): QGraphicsEffect(), radius(6) +{ +} + +QGraphicsBlurEffect::~QGraphicsBlurEffect() +{ +} + +// 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) +{ + 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]; + + QImage result = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + int r1 = rect.top(); + int r2 = rect.bottom(); + int c1 = rect.left(); + int c2 = rect.right(); + + int bpl = result.bytesPerLine(); + int rgba[4]; + unsigned char* p; + + for (int col = c1; col <= c2; col++) { + p = result.scanLine(r1) + col * 4; + for (int i = 0; i < 4; i++) + rgba[i] = p[i] << 4; + + p += bpl; + for (int j = r1; j < r2; j++, p += bpl) + for (int i = 0; i < 4; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + for (int row = r1; row <= r2; row++) { + p = result.scanLine(row) + c1 * 4; + for (int i = 0; i < 4; i++) + rgba[i] = p[i] << 4; + + p += 4; + for (int j = c1; j < c2; j++, p += 4) + for (int i = 0; i < 4; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + for (int col = c1; col <= c2; col++) { + p = result.scanLine(r2) + col * 4; + for (int i = 0; i < 4; i++) + rgba[i] = p[i] << 4; + + p -= bpl; + for (int j = r1; j < r2; j++, p -= bpl) + for (int i = 0; i < 4; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + for (int row = r1; row <= r2; row++) { + p = result.scanLine(row) + c2 * 4; + for (int i = 0; i < 4; i++) + rgba[i] = p[i] << 4; + + p -= 4; + for (int j = c1; j < c2; j++, p -= 4) + for (int i = 0; i < 4; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + return result; +} + +QRectF QGraphicsBlurEffect::boundingRectFor(const QGraphicsItem *item) +{ + qreal delta = radius * 3; + QRectF blurRect = item->boundingRect(); + blurRect.adjust(-delta, -delta, delta, delta); + return blurRect; +} + +void QGraphicsBlurEffect::drawItem(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + // Find the item's bounds in device coordinates. + QRectF deviceBounds = painter->worldTransform().mapRect(item->boundingRect()); + QRect deviceRect = deviceBounds.toRect().adjusted(-1, -1, 1, 1); + if (deviceRect.isEmpty()) + return; + + QPixmap *pixmap = QGraphicsEffect::drawItemOnPixmap(painter, item, option, widget, 0); + if (!pixmap) + return; + + // blur routine + QImage img = pixmap->toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); + img = blurred(img, img.rect(), radius); + + // Draw using an untransformed painter. + QTransform restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + painter->drawImage(deviceRect.topLeft() - QPointF(radius * 3, radius * 3), img); + painter->setWorldTransform(restoreTransform); +} + + +QGraphicsBloomEffect::QGraphicsBloomEffect(): QGraphicsEffect(), radius(6), alpha(0.7) +{ +} + +QGraphicsBloomEffect::~QGraphicsBloomEffect() +{ +} + +QRectF QGraphicsBloomEffect::boundingRectFor(const QGraphicsItem *item) +{ + qreal delta = radius * 3; + QRectF blurRect = item->boundingRect(); + blurRect.adjust(-delta, -delta, delta, delta); + return blurRect; +} + +// Change brightness (positive integer) of each pixel +static QImage brightened(const QImage& image, int brightness) +{ + int tab[ 256 ]; + for (int i = 0; i < 256; ++i) + tab[i] = qMin(i + brightness, 255); + + QImage img = image.convertToFormat(QImage::Format_ARGB32); + for (int y = 0; y < img.height(); y++) { + QRgb* line = (QRgb*)(img.scanLine(y)); + for (int x = 0; x < img.width(); x++) { + QRgb c = line[x]; + line[x] = qRgba(tab[qRed(c)], tab[qGreen(c)], tab[qBlue(c)], qAlpha(c)); + } + } + + return img; +} + +// Composite two QImages using given composition mode and opacity +static QImage composited(const QImage& img1, const QImage& img2, qreal opacity, QPainter::CompositionMode mode) +{ + QImage result = img1.convertToFormat(QImage::Format_ARGB32_Premultiplied); + QPainter painter(&result); + painter.setCompositionMode(mode); + painter.setOpacity(opacity); + painter.drawImage(0, 0, img2); + painter.end(); + return result; +} + +void QGraphicsBloomEffect::drawItem(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + // Find the item's bounds in device coordinates. + QRectF deviceBounds = painter->worldTransform().mapRect(item->boundingRect()); + QRect deviceRect = deviceBounds.toRect().adjusted(-1, -1, 1, 1); + if (deviceRect.isEmpty()) + return; + + QPixmap *pixmap = QGraphicsEffect::drawItemOnPixmap(painter, item, option, widget, 0); + if (!pixmap) + return; + + // bloom routine + QImage img = pixmap->toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); + QImage overlay = blurred(img, img.rect(), radius); + overlay = brightened(overlay, 70); + img = composited(img, overlay, alpha, QPainter::CompositionMode_Overlay); + + // Draw using an untransformed painter. + QTransform restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + painter->drawImage(deviceRect.topLeft() - QPointF(radius * 3, radius * 3), img); + painter->setWorldTransform(restoreTransform); +} + +QGraphicsFrameEffect::QGraphicsFrameEffect() + : QGraphicsEffect() + , color(Qt::blue) + , width(5) + , alpha(0.6) +{ +} + +QGraphicsFrameEffect::~QGraphicsFrameEffect() +{ +} + +QRectF QGraphicsFrameEffect::boundingRectFor(const QGraphicsItem *item) +{ + QRectF frameRect = item->boundingRect(); + frameRect.adjust(-width, -width, width, width); + return frameRect; +} + +void QGraphicsFrameEffect::drawItem(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + // Find the item's bounds in device coordinates. + QRectF deviceBounds = painter->worldTransform().mapRect(item->boundingRect()); + QRect deviceRect = deviceBounds.toRect().adjusted(-1, -1, 1, 1); + if (deviceRect.isEmpty()) + return; + + QPixmap *pixmap = QGraphicsEffect::drawItemOnPixmap(painter, item, option, widget, 0); + if (!pixmap) + return; + + QRectF frameRect = deviceBounds; + frameRect.adjust(-width, -width, width, width); + + painter->save(); + painter->setWorldTransform(QTransform()); + + painter->save(); + painter->setOpacity(painter->opacity() * alpha); + painter->setPen(Qt::NoPen); + painter->setBrush(color); + painter->drawRoundedRect(frameRect, 20, 20, Qt::RelativeSize); + painter->restore(); + + painter->drawPixmap(frameRect.topLeft(), *pixmap); + + painter->restore(); +} + + +QGraphicsShadowEffect::QGraphicsShadowEffect() + : QGraphicsEffect() + , offset(4,4) + , radius(8) + , alpha(0.7) +{ +} + +QGraphicsShadowEffect::~QGraphicsShadowEffect() +{ +} + +QRectF QGraphicsShadowEffect::boundingRectFor(const QGraphicsItem *item) +{ + QRectF shadowRect = item->boundingRect(); + shadowRect.adjust(offset.x(), offset.y(), offset.x(), offset.y()); + QRectF blurRect = shadowRect; + qreal delta = radius * 3; + blurRect.adjust(-delta, -delta, delta, delta); + QRectF totalRect = blurRect.united(item->boundingRect()); + return totalRect; +} + +void QGraphicsShadowEffect::drawItem(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + // Find the item's bounds in device coordinates. + QRectF deviceBounds = painter->worldTransform().mapRect(item->boundingRect()); + QRect deviceRect = deviceBounds.toRect().adjusted(-1, -1, 1, 1); + if (deviceRect.isEmpty()) + return; + + QRectF shadowRect = deviceBounds; + shadowRect.adjust(offset.x(), offset.y(), offset.x(), offset.y()); + QRectF blurRect = shadowRect; + qreal delta = radius * 3; + blurRect.adjust(-delta, -delta, delta, delta); + QRectF totalRect = blurRect.united(deviceRect); + + QPixmap *pixmap = QGraphicsEffect::drawItemOnPixmap(painter, item, option, widget, 0); + if (!pixmap) + return; + + QImage img = pixmap->toImage(); + QImage shadowImage(img.size(), QImage::Format_ARGB32); + shadowImage.fill(qRgba(0, 0, 0, alpha * 255)); + shadowImage.setAlphaChannel(img.alphaChannel()); + shadowImage = blurred(shadowImage, shadowImage.rect(), radius); + + // Draw using an untransformed painter. + QTransform restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + + QRect shadowAlignedRect = shadowRect.toAlignedRect(); + + qreal shadowx = blurRect.x() + delta; + qreal shadowy = blurRect.y() + delta; + if (blurRect.x() < deviceRect.x()) + shadowx = blurRect.x() + offset.x(); + if (blurRect.y() < deviceRect.y()) + shadowy = blurRect.y() + offset.y(); + painter->drawImage(shadowx, shadowy, shadowImage); + + qreal itemx = qMin(blurRect.x(), deviceBounds.x()); + qreal itemy = qMin(blurRect.y(), deviceBounds.y()); + painter->drawPixmap(itemx, itemy, *pixmap); + + painter->setWorldTransform(restoreTransform); +} + + +#endif |