diff -r 000000000000 -r 16d8024aca5e src/hbcore/ovgeffects/hbvgeffect.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hbcore/ovgeffects/hbvgeffect.cpp Mon Apr 19 14:02:13 2010 +0300 @@ -0,0 +1,586 @@ +/**************************************************************************** +** +** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (developer.feedback@nokia.com) +** +** This file is part of the HbCore module of the UI Extensions for Mobile. +** +** 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at developer.feedback@nokia.com. +** +****************************************************************************/ + +#include "hbvgeffect_p.h" +#include "hbvgeffect_p_p.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/*! + * \class HbVgEffect + * + * \brief Abstract base class for OpenVG effects. + * + * \internal + * + * Brief guide for creating new effects: + *
    + * + *
  • Derive from HbVgEffect. + * + *
  • Add getter/setter/property/signal for new effect parameters. + * + *
  • In the setter make sure to call either updateEffectBoundingRect() or + * updateEffect(), depending on the parameter (some parameters affect the + * bounding rectangle, some not). + * + *
  • Reimplement boundingRectFor(). + * + *
  • Implement performEffect(). This function will typically generate + * VGImages containing the filtered source image (or just the extra visuals + * that appear due to the effect) and draw them via the given painter's + * drawPixmap() (or vgDrawImage). Do not forget to draw the source image too, + * if needed. The VGImage argument is passed in a variant to keep things + * building even without OpenVG headers/libraries. Always use an appropriate + * ifdef for code that uses OpenVG types or functions. + * + *
+ * + * Note that these effects must not be installed using QGraphicsItem::setGraphicsEffect(), + * use install() instead. + * + * \sa install() + */ + +/*! + * \fn void HbVgEffect::performEffect(QPainter *painter, const QPointF &offset, + * const QVariant &vgImage, const QSize &vgImageSize) + * + * This function must be implemented in derived classes, providing the actual + * implementation for the effect generation. + * + * \a painter is the painter to be used (it is guaranteed to be an OpenVG-based + * one), \a offset is the target position, the variant \a vgImage contains a + * VGImage handle for the source image, and \a vgImageSize is the size of that + * image. The handle in \a vgImage is actually the underlying VGImage of the + * pixmap stored in d->srcPixmap. + * + * Note that the size of this pixmap (and thus vgImageSize) already includes the + * extra margins added by the effect's boundingRectFor, so drawing the entire + * pixmap at any other position than \a offset will lead to painting outside the + * item's effective bounding rectangle which may cause artifacts on the screen. + * + * \sa QGraphicsEffect::boundingRectFor() + */ + +Q_GLOBAL_STATIC(QSet, cacheKeys) + +HbVgEffectPrivate::HbVgEffectPrivate() + : q_ptr(0), + paramsChanged(true), + cacheInvalidated(true), + opacity(1), + caching(false), + rootEffect(0), + sourceGraphicsItem(0), + mainWindow(0), + lastUsedRotation(0), + lastRotationTransformAngle(0) +{ +} + +/*! + * \internal + */ +HbVgEffectPrivate::~HbVgEffectPrivate() +{ +} + +#ifdef HB_EFFECTS_OPENVG +/*! + * Makes sure that the given pixmap has the given \a size. Returns + * the underlying VGImage handle. Typically used with dstPixmap and + * tmpPixmap. + * + * \internal + */ +VGImage HbVgEffectPrivate::ensurePixmap(QPixmap *pixmap, const QSize& size) +{ + if (pixmap->size() != size) + *pixmap = QPixmap(size); + return qPixmapToVGImage(*pixmap); +} +#endif + +/*! + * Invalidates the cache but only if it has not been done already. + * + * \internal + */ +void HbVgEffectPrivate::ensureCacheInvalidated() +{ + if (!cacheInvalidated) { +#ifdef HBVG_TRACES + Q_Q(HbVgEffect); + qDebug("HbVgEffect [%x]: invalidating cache", (int) q); +#endif + cacheInvalidated = true; + notifyCacheInvalidated(); + } +} + +/*! + * Returns the rotation of the graphics view in degrees. + * + * For some effects it is important to know if the graphics view has been + * transformed or not (e.g. a reflection effect may need to draw at a different + * position when the graphics view is rotated). + * + * \internal + */ +qreal HbVgEffectPrivate::mainWindowRotation() const +{ + if (!mainWindow) { + Q_Q(const HbVgEffect); + const QGraphicsItem *srcItem = q->sourceItemForRoot(); + if (srcItem) { + QGraphicsScene *scene = srcItem->scene(); + if (scene) { + QList views = scene->views(); + foreach (QGraphicsView *view, views) { + mainWindow = qobject_cast(view); + if (mainWindow) + break; + } + } + } + } + return mainWindow ? HbMainWindowPrivate::d_ptr(mainWindow)->rotation() : 0; +} + +/*! + * Returns a transformation that defines the graphics view's current rotation. + * + * \internal + */ +QTransform HbVgEffectPrivate::rotationTransform() const +{ + int rotation = (int) mainWindowRotation(); + if (rotation != lastRotationTransformAngle) { + lastRotationTransformAngle = rotation; + lastRotationTransform = QTransform().rotate(rotation); + } + return lastRotationTransform; +} + +/*! + * Maps the given translation offset to an offset that is based on the current + * rotation of the graphics view. Effects are drawing directly, in device + * coordinates, however they must take into account the rotation of the graphics + * view too. Therefore any translation they use must be mapped using this + * function. (NB. this does not apply to the offset argument of + * performEffect()). + * + * For example if a dropshadow effect gets (x, y) as the offset of the shadow, + * it must call this function instead of using the offset as it is, because the + * correct device coordinates for the offset may be something different, + * e.g. (y, -x) if the graphics view is rotated -90 degrees. + * + * \internal + */ +QPointF HbVgEffectPrivate::mapOffset(const QPointF &offset) const +{ + return rotationTransform().map(offset); +} + +/*! + * Returns a size that has the width and height swapped if the current rotation + * of the graphics view implies that. Typically used with size-like values, + * e.g. the radius for a blur effect. + * + * \internal + */ +QSizeF HbVgEffectPrivate::mapSize(const QSizeF &size) const +{ + QPointF p = mapOffset(QPointF(size.width(), size.height())); + return QSizeF(qAbs(p.x()), qAbs(p.y())); +} + +/*! + * \internal + * + * Returns a cache key that is unique to this effect instance (but only for the + * lifetime of the instance). + */ +inline QString cacheKey(const HbVgEffect *e) +{ + return QString::number((qptrdiff) e); +} + +HbVgEffect::HbVgEffect(QObject *parent) + : QGraphicsEffect(parent), d_ptr(new HbVgEffectPrivate) +{ + Q_D(HbVgEffect); + d->q_ptr = this; +} + +HbVgEffect::HbVgEffect(HbVgEffectPrivate &dd, QObject *parent) + : QGraphicsEffect(parent), d_ptr(&dd) +{ + Q_D(HbVgEffect); + d->q_ptr = this; +} + +HbVgEffect::~HbVgEffect() +{ + // Get rid of the cached pixmap if it is still present. + QString key = cacheKey(this); + QPixmapCache::remove(key); + cacheKeys()->remove(key); + delete d_ptr; +} + +/*! + * Updates the source item (and thus the effect). Also sets the paramsChanged + * and cacheInvalidated flags. This function should be called from the effect + * parameter setters only. + * + * Note that this is not sufficient if the results of boundingRectFor() are also + * changing due to an updated parameter value. In that case call + * updateEffectBoundingRect() instead. + * + * \sa updateEffectBoundingRect() + */ +void HbVgEffect::updateEffect() +{ + Q_D(HbVgEffect); + d->paramsChanged = true; + d->ensureCacheInvalidated(); + if (d->rootEffect) { + d->rootEffect->updateEffect(); + } else { + update(); + } +} + +/*! + * Notifies that the bounding rectangle for the effect (i.e. the return value of + * boundingRectFor()) is possibly different than before. Also updates the source + * item (and thus the effect) and sets the paramsChanged and cacheInvalidated + * flags. This function should be called from the effect parameter setters only. + * + * When changing effect parameters that have no effect on the bounding + * rectangle, use updateEffect() instead because that might be more efficient. + * + * \sa updateEffect() + */ +void HbVgEffect::updateEffectBoundingRect() +{ + Q_D(HbVgEffect); + d->paramsChanged = true; + d->ensureCacheInvalidated(); + if (d->rootEffect) { + d->rootEffect->updateBoundingRect(); + } else { + updateBoundingRect(); + } +} + +/*! + * Enables delegation of update requests to the given \a effect. + * + * This is needed when there are several effects in a chain and thus all update + * requests should be routed from the individual elements to the container + * effect. + */ +void HbVgEffect::setChainRoot(HbVgEffect *effect) +{ + Q_D(HbVgEffect); + d->rootEffect = effect; +} + +/*! + * Returns the current root of the chain or null if this effect is not part of + * any delegation chain. Do not assume that the returned effect is a + * HbVgChainedEffect, it may also be something else (in theory), so use + * qobject_cast if the return value needs to be casted to a more specific type. + */ +HbVgEffect *HbVgEffect::chainRoot() const +{ + Q_D(const HbVgEffect); + return d->rootEffect; +} + +/*! + * Returns the current opacity parameter of the effect. + * + * \sa setOpacity() + */ +qreal HbVgEffect::opacity() const +{ + Q_D(const HbVgEffect); + return d->opacity; +} + +/*! + * Sets the \a opacity parameter of the effect. + * The value must fall between 0.0 and 1.0. + * + * \sa opacity() + */ +void HbVgEffect::setOpacity(qreal opacity) +{ + Q_D(HbVgEffect); + if (d->opacity == opacity) + return; + d->opacity = opacity; + updateEffect(); + emit opacityChanged(opacity); +} + +/*! + * \reimp + * + * Implementation of QGraphicsEffect's draw(). After setting up the environment + * for direct OpenVG painting, the work is delegated to the performEffect() + * implementation of the derived classes. + */ +void HbVgEffect::draw(QPainter *painter) +{ + // Just draw the source without effects if the painter's paint engine + // is not using OpenVG. + if (painter->paintEngine()->type() != QPaintEngine::OpenVG) { + drawSource(painter); + return; + } + +#ifdef HB_EFFECTS_OPENVG + Q_D(HbVgEffect); + // Render the source into a pixmap. Note that the effect's modified bounding + // rectangle is already taken into account in the size of the pixmap created + // here, so there is no need to waste time with boundingRectFor() calls + // later in performEffect() when creating temporary buffers. + QPoint offset; + // Don't bother with optimizations based on sourceIsPixmap() because we will + // rarely be used on QGraphicsPixmapItems anyway. + d->srcPixmap = sourcePixmap(Qt::DeviceCoordinates, &offset); + + // Pull out the underlying VGImage from the pixmap. + VGImage vgImage = qPixmapToVGImage(d->srcPixmap); + // If something went wrong then just draw the source without effects. + if (vgImage == VG_INVALID_HANDLE) { + drawSource(painter); + return; + } + + // Save the world transformation and then reset it. + d->worldTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + + // Invalidate the cache if the rotation of the graphics view has changed. + qreal rotation = d->mainWindowRotation(); + if (rotation != d->lastUsedRotation) { + d->lastUsedRotation = rotation; + d->ensureCacheInvalidated(); + } + + // Enter raw VG mode. + painter->beginNativePainting(); + // Draw something. + performEffect(painter, offset, QVariant::fromValue(vgImage), d->srcPixmap.size()); + // Clear the param/cache flags as all data should be up-to-date now. + d->paramsChanged = d->cacheInvalidated = false; + // Leave raw VG mode. + painter->endNativePainting(); + + // Restore the painter's previously set transformations. + painter->setWorldTransform(d->worldTransform); + +#else + // OpenVG code disabled => effect is not shown (but have the source drawn still). + drawSource(painter); +#endif +} + +/*! + * \reimp + */ +void HbVgEffect::sourceChanged(ChangeFlags flags) +{ + // If the bounding rectangle or appearance of the source has changed then + // set our flag to indicate that any cached visual data will have to be + // dropped. + if (flags & SourceBoundingRectChanged || flags & SourceInvalidated) { + Q_D(HbVgEffect); + d->ensureCacheInvalidated(); + } +} + +/*! + * Returns the current status of caching. + * + * \sa setCaching() + */ +bool HbVgEffect::caching() const +{ + Q_D(const HbVgEffect); + return d->caching; +} + +/*! + * Enables or disables caching of the output of the effects. + * + * By default caching is disabled. + * + * Note that caching may not cause any increase in performance if the size or + * apperance of the source item is changing rapidly, it is only useful for items + * that are static enough. + * + * Enabling this setting may have no effect because some subclasses may not be + * able to support it, it is treated as a hint only. + * + * \sa caching() + */ +void HbVgEffect::setCaching(bool caching) +{ + Q_D(HbVgEffect); + if (d->caching == caching) + return; + d->caching = caching; + emit cachingChanged(caching); +} + +/*! + * Returns the cached pixmap that is associated with this effect instance. Will + * return a null pixmap if caching is disabled or there was no tryCache() call + * for this instance before or the cache has been invalidated (e.g. because the + * source item's appearance has changed). + * + * \sa tryCache() + */ +QPixmap HbVgEffect::cached(const QSize &size) const +{ + Q_D(const HbVgEffect); + if (d->caching) { + QString key = cacheKey(this); + if (d->cacheInvalidated) { + QPixmapCache::remove(key); + cacheKeys()->remove(key); + } else { + QPixmap cachedPm; + // Check if we have a pixmap in the cache, then check the size + // too. The size check is important because the effects will + // typically render their source using device coordinates which + // causes a clipping to the device viewport, therefore the cached + // pixmap for an item that was/is clipped should not be used. + if (QPixmapCache::find(key, &cachedPm) + && (size.isNull() || cachedPm.size() == size)) + { +#ifdef HBVG_TRACES + qDebug("HbVgEffect [%x]: cache hit", (int) this); +#endif + return cachedPm; + } + } + } + return QPixmap(); +} + +/*! + * Inserts/replaces the given pixmap into/in the cache if caching is enabled. + * The pixmap will be associated with this effect instance and can be retrieved + * later by calling cached(). + * + * Note that the OpenVG effects rely on "out-of-sync" pixmaps, i.e. pixmaps + * where the underlying QImage and VGImage are not synchronized. Therefore the + * QPixmapCache must be cleared whenever there is a risk that some other entity + * (Qt) would destroy the underlying VGImages. This is currently handled in + * HbForegroundWatcher and HbOogmWatcher by calling releaseCachedResources(). + * + * \sa cached() + */ +void HbVgEffect::tryCache(const QPixmap &pm) +{ + Q_D(HbVgEffect); + if (d->caching) { + QString key = cacheKey(this); + QPixmapCache::insert(key, pm); + cacheKeys()->insert(key); + } +} + +/*! + * Drops all cached data used by any effect in the application. Other pixmaps + * (not related to effects) are left intact in the global pixmap cache. + */ +void HbVgEffect::releaseCachedResources() +{ + QSet *keys = cacheKeys(); + foreach (const QString &key, *keys) { + QPixmapCache::remove(key); + } + keys->clear(); +} + +/*! + * Returns the source graphics item for this effect or null if it is not + * available for some reason. Also works for chained effects (i.e. it uses the + * root effect if needed). + * + * NOTE: If the effect has not been attached to a graphics item using install() + * then this function returns null (even if setGraphicsEffect() was used). So + * use install(). + */ +const QGraphicsItem *HbVgEffect::sourceItemForRoot() const +{ + Q_D(const HbVgEffect); + const HbVgEffectPrivate *p = d->rootEffect ? HbVgEffectPrivate::d_ptr(d->rootEffect) : d; + return p->sourceGraphicsItem; +} + +/*! + * Returns the bounding rectangle of the source graphics item. Also works for + * chained effects (it will use the root effect if needed). + */ +QRectF HbVgEffect::sourceBoundingRectForRoot() const +{ + Q_D(const HbVgEffect); + return d->rootEffect ? d->rootEffect->sourceBoundingRect() : sourceBoundingRect(); +} + +/*! + * Installs the effect on a given graphics item. This is merely a wrapper to + * QGraphicsItem::setGraphicsEffect(). To uninstall the effect use + * setGraphicsEffect in the usual way (i.e. pass null). This function will do + * nothing if \a item is null. + * + * Note that installing effects derived from HbVgEffect using + * QGraphicsItem::setGraphicsEffect() will not work and will lead to undefined + * behaviour. Use this function instead. You have been warned. + */ +void HbVgEffect::install(QGraphicsItem *item) +{ + if (item) { + Q_D(HbVgEffect); + d->sourceGraphicsItem = item; + item->setGraphicsEffect(this); + } +}