src/hbcore/ovgeffects/hbvgmaskeffect.cpp
branchGCC_SURGE
changeset 15 f378acbc9cfb
parent 7 923ff622b8b9
child 21 4633027730f5
--- a/src/hbcore/ovgeffects/hbvgmaskeffect.cpp	Thu Jul 15 14:03:49 2010 +0100
+++ b/src/hbcore/ovgeffects/hbvgmaskeffect.cpp	Thu Jul 22 16:36:53 2010 +0100
@@ -38,21 +38,32 @@
  * defined by a pixmap's alpha channel transparent. The opacity effect
  * parameter is ignored.
  *
+ * With certain OpenVG implementations masking with vgMask() may be
+ * problematic.  In such cases enable the pure QPainter implementation
+ * with setForceSwMode().
+ *
+ * Currently the sw version only support masks set by setMask() or
+ * provided via the callback.  Mask rectangles are not supported.
+ *
  * \internal
  */
 
 HbVgMaskEffectPrivate::HbVgMaskEffectPrivate()
-    : maskRectIsInDeviceCoords(false), maskCallback(0)
+    : maskRectIsInDeviceCoords(false),
+      maskCallback(0),
+      maskCallbackParam(0),
+      lastMainWindowRotationAngle(-1),
+      includeSourceItemOnly(false)
 {
 }
 
 HbVgMaskEffect::HbVgMaskEffect(QObject *parent)
-    : HbVgEffect(*new HbVgMaskEffectPrivate, parent)
+    : HbVgFrameEffect(*new HbVgMaskEffectPrivate, parent)
 {
 }
 
 HbVgMaskEffect::HbVgMaskEffect(HbVgMaskEffectPrivate &dd, QObject *parent)
-    : HbVgEffect(dd, parent)
+    : HbVgFrameEffect(dd, parent)
 {
 }
 
@@ -86,7 +97,8 @@
 /*!
  * Returns the scaled version of the mask that was used during the previous
  * paint. Will return a null pixmap if no painting took place since the last
- * setMask() call.
+ * setMask() call. Any other automatic transformation (e.g. rotation) of the
+ * mask is not included in the returned pixmap.
  */
 QPixmap HbVgMaskEffect::scaledMask() const
 {
@@ -99,7 +111,8 @@
  * is 0 will be set transparent. The pixmap is subject to scaling and therefore
  * distortion may occur. If this is not acceptable then use the callback
  * version. Any previously set mask pixmap or rectangle will not be effective
- * anymore.
+ * anymore. If the graphics view is rotated (due to a transformed screen
+ * orientation) then the mask will be rotated automatically too.
  */
 void HbVgMaskEffect::setMask(const QPixmap &mask)
 {
@@ -141,8 +154,9 @@
 void HbVgMaskEffect::setMaskCallback(MaskCallback callback, void *param)
 {
     Q_D(HbVgMaskEffect);
-    if (d->maskCallback == callback)
+    if (d->maskCallback == callback) {
         return;
+    }
     clear();
     d->maskCallback = callback;
     d->maskCallbackParam = param;
@@ -176,8 +190,9 @@
 void HbVgMaskEffect::setMaskRect(const QRectF &rect)
 {
     Q_D(HbVgMaskEffect);
-    if (rect == d->maskRect && !d->maskRectIsInDeviceCoords)
+    if (rect == d->maskRect && !d->maskRectIsInDeviceCoords) {
         return;
+    }
     clear();
     d->maskRect = rect;
     d->maskRectIsInDeviceCoords = false;
@@ -189,12 +204,17 @@
  * Similar to setMask() but the rectangle is assumed to be in device coordinates
  * (i.e. relative to the entire screen instead of the source item), meaning that
  * the source item will be clipped where it intersects with \a rect.
+ *
+ * Even when the graphics view is transformed (e.g. when being in "landscape
+ * orientation") the rectangle passed here is treated as being in device
+ * coordinates, ignoring the rotation of the graphics view.
  */
 void HbVgMaskEffect::setMaskDeviceRect(const QRectF &rect)
 {
     Q_D(HbVgMaskEffect);
-    if (rect == d->maskRect && d->maskRectIsInDeviceCoords)
+    if (rect == d->maskRect && d->maskRectIsInDeviceCoords) {
         return;
+    }
     clear();
     d->maskRect = rect;
     d->maskRectIsInDeviceCoords = true;
@@ -203,6 +223,33 @@
 }
 
 /*!
+ * Returns the current setting for masking only the source graphics item. By
+ * default this is disabled.
+ */
+bool HbVgMaskEffect::includeSourceItemOnly() const
+{
+    Q_D(const HbVgMaskEffect);
+    return d->includeSourceItemOnly;
+}
+
+/*!
+ * When enabled, only the source item (excluding its children) is masked.  This
+ * is needed when shaping e.g. a pop-up or other widget that has a scroll area
+ * in it. The real size, together with all the children, is usually much bigger
+ * then the size of the widget itself (and it depends on the position in the
+ * scroll area etc.).  To solve this issue, enable this setting when shaping
+ * such widgets.
+ */
+void HbVgMaskEffect::setIncludeSourceItemOnly(bool b)
+{
+    Q_D(HbVgMaskEffect);
+    clear();
+    d->includeSourceItemOnly = b;
+    updateEffect();
+    emit includeSourceItemOnlyChanged(b);
+}
+
+/*!
  * \reimp
  */
 QRectF HbVgMaskEffect::boundingRectFor(const QRectF &rect) const
@@ -222,7 +269,35 @@
 }
 
 /*!
+ * \internal
+ */
+QRectF HbVgMaskEffectPrivate::mapRect(const QRectF &rect, const QSize &srcSize) const
+{
+    qreal rotationAngle = mainWindowRotation();
+    qreal x1 = 0;
+    qreal y1 = 0;
+
+    QPointF mp = mapOffset(QPointF(rect.x(), rect.y()));
+    QSizeF sz = mapSize(rect.size());
+
+    if (rotationAngle == -90 || rotationAngle == 270) {
+        x1 = mp.x();
+        y1 = mp.y() + srcSize.height() - sz.height();
+    } else if (rotationAngle == 90 || rotationAngle == -270) {
+        x1 = mp.x() + srcSize.width() - sz.width();
+        y1 = mp.y();
+    } else {
+        x1 = mp.x();
+        y1 = mp.y();
+    }
+
+    return QRectF(x1, y1, sz.width(), sz.height());
+}
+
+/*!
  * \reimp
+ *
+ * OpenVG-based implementation. Supports all masking options.
  */
 void HbVgMaskEffect::performEffect(QPainter *painter,
                                    const QPointF &offset,
@@ -233,19 +308,36 @@
     Q_UNUSED(vgImage);
     Q_D(HbVgMaskEffect);
 
+    if (!painter->paintEngine()) {
+        return;
+    }
+
+    // Find out the target size and position.
+    QPaintDevice *pdev = painter->paintEngine()->paintDevice();
+    QRectF srcDevRect(d->deviceRectForSource(HbVgFrameEffectPrivate::ExcludeChildren, pdev));
+    QSize targetSize = d->includeSourceItemOnly ? srcDevRect.size().toSize() : vgImageSize;
+    int posX = (int) offset.x();
+    int posY = (int) offset.y();
+    if (d->includeSourceItemOnly) {
+        posX = (int) srcDevRect.x();
+        posY = (int) srcDevRect.y();
+    }
+
     // Initialize scaledMask if the mask has changed or the size of the source
     // is different than before.
     if (!d->mask.isNull()) {
-        if (d->scaledMask.isNull())
+        if (d->scaledMask.isNull()) {
             d->scaledMask = d->mask;
+        }
         // Scale only when really needed, i.e. when the size is different than
         // before (or there is a new mask).
-        if (d->scaledMask.size() != vgImageSize)
-            d->scaledMask = d->mask.scaled(vgImageSize);
+        if (d->scaledMask.size() != targetSize) {
+            d->scaledMask = d->mask.scaled(targetSize);
+            d->rotatedPixmap = QPixmap();
+        }
     }
 
     vgSeti(VG_MASKING, VG_TRUE);
-    QPaintDevice *pdev = painter->paintEngine()->paintDevice();
     // Set the mask for the entire surface to 1 (i.e. nothing is transparent).
     vgMask(VG_INVALID_HANDLE, VG_FILL_MASK,
            0, 0, pdev->width(), pdev->height());
@@ -254,17 +346,16 @@
     // of these is set then try the callback, if that is not set either then
     // just draw the source normally.
     QPixmap *maskPtr = 0;
-    int ox = (int) offset.x();
-    int oy = (int) offset.y();
     if (d->scaledMask.isNull() && !d->maskRect.isNull()) {
-        int x1 = (int) d->maskRect.x();
-        int y1 = (int) d->maskRect.y();
-        int w = (int) d->maskRect.width();
-        int h = (int) d->maskRect.height();
+        QRectF mappedRect = d->maskRect;
         if (!d->maskRectIsInDeviceCoords) {
-            x1 += ox;
-            y1 += oy;
+            mappedRect = d->mapRect(d->maskRect, targetSize);
+            mappedRect.adjust(posX, posY, posX, posY);
         }
+        int x1 = (int) mappedRect.x();
+        int y1 = (int) mappedRect.y();
+        int w = (int) mappedRect.width();
+        int h = (int) mappedRect.height();
         // Make the area defined by the rectangle transparent. Passing
         // VG_CLEAR_MASK results in writing 0 to the mask which results in
         // transparent pixels at that position.
@@ -276,18 +367,30 @@
     } else if (d->maskCallback) {
         // Invoke the callback but only if it has just been set or the size of
         // the source is different than before.
-        if (d->callbackResult.isNull() || d->callbackResult.size() != vgImageSize)
-            d->callbackResult = d->maskCallback(vgImageSize, d->maskCallbackParam);
+        if (d->callbackResult.isNull() || d->callbackResult.size() != targetSize) {
+            d->callbackResult = d->maskCallback(targetSize, d->maskCallbackParam);
+            d->rotatedPixmap = QPixmap();
+        }
         maskPtr = &d->callbackResult;
     }
 
     if (maskPtr) {
-        int w = vgImageSize.width();
-        int h = vgImageSize.height();
+        int w = targetSize.width();
+        int h = targetSize.height();
+        QPixmap pm;
+        qreal rotationAngle = d->mainWindowRotation();
+        if (rotationAngle != 0) {
+            if (d->rotatedPixmap.isNull() || rotationAngle != d->lastMainWindowRotationAngle) {
+                d->rotatedPixmap = maskPtr->transformed(d->rotationTransform()).scaled(targetSize);
+                d->lastMainWindowRotationAngle = rotationAngle;
+            }
+            pm = d->rotatedPixmap;
+        }
+        QPixmap *finalMaskPtr = pm.isNull() ? maskPtr : ±
         // Will use the alpha channel from the image, alpha=0 => 0 in the mask
         // => transparent pixel, alpha=255 => 1 in the mask => opaque pixel.
-        vgMask(qPixmapToVGImage(*maskPtr), VG_SET_MASK,
-               ox, toVgYH(oy, h, pdev),
+        vgMask(qPixmapToVGImage(*finalMaskPtr), VG_SET_MASK,
+               posX, toVgYH(posY, h, pdev),
                w, h);
     }
 
@@ -302,3 +405,87 @@
     Q_UNUSED(vgImageSize);
 #endif
 }
+
+/*!
+ * \reimp
+ *
+ * QPainter-based implementation. Currently only supports masking via pixmaps.
+ *
+ * The behavior for partially visible items (clipped by the device rect) is
+ * somewhat wrong, the mask is never clipped, it is just scaled down to match
+ * the visible part of the item.
+ */
+void HbVgMaskEffect::performEffectSw(QPainter *painter)
+{
+    Q_D(HbVgMaskEffect);
+
+    QPoint offset;
+    QPixmap srcPixmap = sourcePixmap(Qt::DeviceCoordinates, &offset); // needs the original world transform
+    if (srcPixmap.isNull()) {
+        return;
+    }
+
+    QPaintDevice *pdev = painter->paintEngine()->paintDevice();
+    d->worldTransform = painter->worldTransform(); // deviceRectForSource needs this
+    // The full source rect (without child items) would be
+    // d->worldTransform.mapRect(sourceItemForRoot()->boundingRect()).toRect()
+    // but we only care about the visible part here so clipping must be applied.
+    QRect srcDevRect(d->deviceRectForSource(HbVgFrameEffectPrivate::ExcludeChildren, pdev).toRect());
+    QPoint pos = d->includeSourceItemOnly ? srcDevRect.topLeft() : offset;
+    QSize size = d->includeSourceItemOnly ? srcDevRect.size() : srcPixmap.size();
+    if (size.width() <= 0 || size.height() <= 0) {
+        return;
+    }
+
+    QPixmap maskPixmap;
+    if (d->maskCallback) {
+        if (d->callbackResult.isNull() || d->callbackResult.size() != size) {
+            d->callbackResult = d->maskCallback(size, d->maskCallbackParam);
+            d->rotatedPixmap = QPixmap();
+        }
+        maskPixmap = d->callbackResult;
+    } else if (!d->mask.isNull()) {
+        if (d->scaledMask.isNull()) {
+            d->scaledMask = d->mask;
+        }
+        if (d->scaledMask.size() != size) {
+            d->scaledMask = d->mask.scaled(size);
+            d->rotatedPixmap = QPixmap();
+        }
+        maskPixmap = d->scaledMask;
+    } else {
+        // Masking via rectangles is not supported here.
+        drawSource(painter);
+        return;
+    }
+
+    qreal rotationAngle = d->mainWindowRotation();
+    if (rotationAngle != 0) {
+        if (d->rotatedPixmap.isNull() || rotationAngle != d->lastMainWindowRotationAngle) {
+            d->rotatedPixmap = maskPixmap.transformed(d->rotationTransform()).scaled(size);
+            d->lastMainWindowRotationAngle = rotationAngle;
+        }
+        maskPixmap = d->rotatedPixmap;
+    }
+
+    if (d->includeSourceItemOnly) {
+        // Take only the source item itself, excluding its children.
+        srcPixmap = srcPixmap.copy(srcDevRect.adjusted(-offset.x(), -offset.y(), -offset.x(), -offset.y()));
+    }
+
+    painter->setWorldTransform(QTransform());
+
+    QImage image(size, QImage::Format_ARGB32_Premultiplied);
+    QPainter p(&image);
+    p.setCompositionMode(QPainter::CompositionMode_Source);
+    p.fillRect(image.rect(), Qt::transparent);
+    p.setCompositionMode(QPainter::CompositionMode_SourceOver);
+    p.drawPixmap(0, 0, srcPixmap);
+    p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
+    p.drawPixmap(0, 0, maskPixmap);
+    p.end();
+
+    painter->drawImage(pos, image);
+
+    painter->setWorldTransform(d->worldTransform);
+}