2010-10-04 Alejandro G. Castro <alex@igalia.com>
authoralex@webkit.org <alex@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 5 Oct 2010 08:08:13 +0000 (08:08 +0000)
committeralex@webkit.org <alex@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 5 Oct 2010 08:08:13 +0000 (08:08 +0000)
        Reviewed by Martin Robinson.

        [Cairo] Port drawTiledShadow to the new ContextShadow
        https://bugs.webkit.org/show_bug.cgi?id=45902

        Ported the drawTiledShadow function to the ContextShadow, it
        renders shadows for rects faster than the simple blurring using
        tiling of a smaller rect. We will remove the old function in a
        next patch when starting to use ContextShadows for cairo
        rendering.

        * platform/graphics/ContextShadow.h:
        * platform/graphics/cairo/ContextShadowCairo.cpp:
        (WebCore::ContextShadow::drawRectShadowWithoutTiling):
        (WebCore::ContextShadow::drawRectShadow):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@69092 268f45cc-cd09-0410-ab3c-d52691b4dbfc

WebCore/ChangeLog
WebCore/platform/graphics/ContextShadow.h
WebCore/platform/graphics/cairo/ContextShadowCairo.cpp

index 0e27d49..2cf3a58 100644 (file)
@@ -1,3 +1,21 @@
+2010-10-04  Alejandro G. Castro  <alex@igalia.com>
+
+        Reviewed by Martin Robinson.
+
+        [Cairo] Port drawTiledShadow to the new ContextShadow
+        https://bugs.webkit.org/show_bug.cgi?id=45902
+
+        Ported the drawTiledShadow function to the ContextShadow, it
+        renders shadows for rects faster than the simple blurring using
+        tiling of a smaller rect. We will remove the old function in a
+        next patch when starting to use ContextShadows for cairo
+        rendering.
+
+        * platform/graphics/ContextShadow.h:
+        * platform/graphics/cairo/ContextShadowCairo.cpp:
+        (WebCore::ContextShadow::drawRectShadowWithoutTiling):
+        (WebCore::ContextShadow::drawRectShadow):
+
 2010-10-05  Chris Rogers  <crogers@google.com>
 
         Reviewed by Kenneth Russell.
index ede9336..335af8d 100644 (file)
@@ -31,6 +31,7 @@
 
 #include "Color.h"
 #include "FloatRect.h"
+#include "GraphicsContext.h"
 #include "IntRect.h"
 #include "RefCounted.h"
 
@@ -98,6 +99,9 @@ public:
     PlatformContext beginShadowLayer(PlatformContext, const FloatRect& layerArea);
     void endShadowLayer(PlatformContext);
     static void purgeScratchBuffer();
+#if PLATFORM(CAIRO)
+    void drawRectShadow(GraphicsContext* context, const IntRect& rect, const IntSize& topLeftRadius, const IntSize& topRightRadius, const IntSize& bottomLeftRadius, const IntSize& bottomRightRadius);
+#endif
 
 #if PLATFORM(QT)
     QPointF offset() { return QPointF(m_offset.width(), m_offset.height()); }
@@ -111,6 +115,9 @@ private:
 
     void blurLayerImage(unsigned char*, const IntSize& imageSize, int stride);
     void calculateLayerBoundingRect(const FloatRect& layerArea, const IntRect& clipRect);
+#if PLATFORM(CAIRO)
+    void drawRectShadowWithoutTiling(PlatformContext context, const IntRect& shadowRect, const IntSize& topLeftRadius, const IntSize& topRightRadius, const IntSize& bottomLeftRadius, const IntSize& bottomRightRadius, float alpha);
+#endif
 };
 
 } // namespace WebCore
index 81615bd..3133041 100644 (file)
 #include "config.h"
 #include "ContextShadow.h"
 
+#include "AffineTransform.h"
 #include "CairoUtilities.h"
+#include "OwnPtrCairo.h"
+#include "Path.h"
 #include "Timer.h"
 #include <cairo.h>
 
+using WTF::max;
+
 namespace WebCore {
 
 static cairo_surface_t* scratchBuffer = 0;
@@ -122,4 +127,219 @@ void ContextShadow::endShadowLayer(cairo_t* cr)
     scheduleScratchBufferPurge();
 }
 
+void ContextShadow::drawRectShadowWithoutTiling(PlatformContext context, const IntRect& shadowRect, const IntSize& topLeftRadius, const IntSize& topRightRadius, const IntSize& bottomLeftRadius, const IntSize& bottomRightRadius, float alpha)
+{
+    beginShadowLayer(context, shadowRect);
+
+    if (!m_layerContext)
+        return;
+
+    appendWebCorePathToCairoContext(m_layerContext, Path::createRoundedRectangle(shadowRect, topLeftRadius, topRightRadius,
+                                                                                 bottomLeftRadius, bottomRightRadius));
+    cairo_set_source_rgba(m_layerContext, 0, 0, 0, alpha);
+    cairo_fill(m_layerContext);
+
+    endShadowLayer(context);
+}
+
+static inline FloatPoint getPhase(const FloatRect& dest, const FloatRect& tile)
+{
+    FloatPoint phase = dest.location();
+    phase.move(-tile.x(), -tile.y());
+
+    return phase;
+}
+
+/*
+  This function uses tiling to improve the performance of the shadow
+  drawing of rounded rectangles. The code basically does the following
+  steps:
+
+     1. Calculate the size of the shadow template, a rectangle that
+     contains all the necessary tiles to draw the complete shadow.
+
+     2. If that size is smaller than the real rectangle render the new
+     template rectangle and its shadow in a new surface, in other case
+     render the shadow of the real rectangle in the destination
+     surface.
+
+     3. Calculate the sizes and positions of the tiles and their
+     destinations and use drawPattern to render the final shadow. The
+     code divides the rendering in 8 tiles:
+
+        1 | 2 | 3
+       -----------
+        4 |   | 5
+       -----------
+        6 | 7 | 8
+
+     The corners are directly copied from the template rectangle to the
+     real one and the side tiles are 1 pixel width, we use them as
+
+     tiles to cover the destination side. The corner tiles are bigger
+     than just the side of the rounded corner, we need to increase it
+     because the modifications caused by the corner over the blur
+     effect. We fill the central part with solid color to complete the
+     shadow.
+ */
+void ContextShadow::drawRectShadow(GraphicsContext* context, const IntRect& rect, const IntSize& topLeftRadius, const IntSize& topRightRadius, const IntSize& bottomLeftRadius, const IntSize& bottomRightRadius)
+{
+
+    // drawShadowedRect still does not work with rotations.
+    // https://bugs.webkit.org/show_bug.cgi?id=45042
+    float radiusTwice = m_blurRadius * 2;
+    cairo_t* cr = context->platformContext();
+    if ((!context->getCTM().isIdentityOrTranslationOrFlipped()) || (radiusTwice > rect.width())
+        || (radiusTwice > rect.height()) || (m_type != BlurShadow)) {
+        drawRectShadowWithoutTiling(cr, rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius, context->getAlpha());
+        return;
+    }
+
+    // Calculate size of the template shadow buffer.
+    IntSize shadowBufferSize = IntSize(rect.width() + radiusTwice, rect.height() + radiusTwice);
+
+    // Determine dimensions of shadow rect.
+    FloatRect shadowRect = FloatRect(rect.location(), shadowBufferSize);
+    shadowRect.move(- m_blurRadius, - m_blurRadius);
+
+    // Size of the tiling side.
+    int sideTileWidth = 1;
+
+    // Find the extra space needed from the curve of the corners.
+    int extraWidthFromCornerRadii = radiusTwice + max(topLeftRadius.width(), bottomLeftRadius.width()) +
+        radiusTwice + max(topRightRadius.width(), bottomRightRadius.width());
+    int extraHeightFromCornerRadii = radiusTwice + max(topLeftRadius.height(), topRightRadius.height()) +
+        radiusTwice + max(bottomLeftRadius.height(), bottomRightRadius.height());
+
+    // The length of a side of the buffer is the enough space for four blur radii,
+    // the radii of the corners, and then 1 pixel to draw the side tiles.
+    IntSize shadowTemplateSize = IntSize(sideTileWidth + extraWidthFromCornerRadii,
+                                         sideTileWidth + extraHeightFromCornerRadii);
+
+    // Reduce the size of what we have to draw with the clip area.
+    double x1, x2, y1, y2;
+    cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
+    calculateLayerBoundingRect(shadowRect, IntRect(x1, y1, x2 - x1, y2 - y1));
+
+    if ((shadowTemplateSize.width() * shadowTemplateSize.height() > m_layerRect.width() * m_layerRect.height())) {
+        drawRectShadowWithoutTiling(cr, rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius, context->getAlpha());
+        return;
+    }
+
+    shadowRect.move(m_offset.width(), m_offset.height());
+
+    m_layerImage = getScratchBuffer(shadowTemplateSize);
+
+    // Draw shadow into a new ImageBuffer.
+    m_layerContext = cairo_create(m_layerImage);
+
+    // Clear the surface first.
+    cairo_set_operator(m_layerContext, CAIRO_OPERATOR_CLEAR);
+    cairo_paint(m_layerContext);
+    cairo_set_operator(m_layerContext, CAIRO_OPERATOR_OVER);
+
+    // Draw the rectangle.
+    IntRect templateRect = IntRect(m_blurRadius, m_blurRadius, shadowTemplateSize.width() - radiusTwice, shadowTemplateSize.height() - radiusTwice);
+    appendWebCorePathToCairoContext(m_layerContext, Path::createRoundedRectangle(templateRect, topLeftRadius, topRightRadius,
+                                                                                 bottomLeftRadius, bottomRightRadius));
+
+    cairo_set_source_rgba(m_layerContext, 0, 0, 0, context->getAlpha());
+    cairo_fill(m_layerContext);
+
+    // Blur the image.
+    cairo_surface_flush(m_layerImage);
+    blurLayerImage(cairo_image_surface_get_data(m_layerImage), shadowTemplateSize, cairo_image_surface_get_stride(m_layerImage));
+    cairo_surface_mark_dirty(m_layerImage);
+
+    // Mask the image with the shadow color.
+    cairo_set_operator(m_layerContext, CAIRO_OPERATOR_IN);
+    setSourceRGBAFromColor(m_layerContext, m_color);
+    cairo_paint(m_layerContext);
+
+    cairo_destroy(m_layerContext);
+    m_layerContext = 0;
+
+    // Fill the internal part of the shadow.
+    shadowRect.inflate(-radiusTwice);
+    if (!shadowRect.isEmpty()) {
+        cairo_save(cr);
+        appendWebCorePathToCairoContext(cr, Path::createRoundedRectangle(shadowRect, topLeftRadius,
+                                                                              topRightRadius, bottomLeftRadius, bottomRightRadius));
+        setSourceRGBAFromColor(cr, m_color);
+        cairo_fill(cr);
+        cairo_restore(cr);
+    }
+    shadowRect.inflate(radiusTwice);
+
+    // Draw top side.
+    FloatRect tileRect = FloatRect(radiusTwice + topLeftRadius.width(), 0, sideTileWidth, radiusTwice);
+    FloatRect destRect = tileRect;
+    destRect.move(shadowRect.x(), shadowRect.y());
+    destRect.setWidth(shadowRect.width() - topLeftRadius.width() - topRightRadius.width() - m_blurRadius * 4);
+    FloatPoint phase = getPhase(destRect, tileRect);
+    AffineTransform patternTransform;
+    patternTransform.makeIdentity();
+    drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
+
+    // Draw the bottom side.
+    tileRect = FloatRect(radiusTwice + bottomLeftRadius.width(), shadowTemplateSize.height() - radiusTwice, sideTileWidth, radiusTwice);
+    destRect = tileRect;
+    destRect.move(shadowRect.x(), shadowRect.y() + radiusTwice + rect.height() - shadowTemplateSize.height());
+    destRect.setWidth(shadowRect.width() - bottomLeftRadius.width() - bottomRightRadius.width() - m_blurRadius * 4);
+    phase = getPhase(destRect, tileRect);
+    drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
+
+    // Draw the right side.
+    tileRect = FloatRect(shadowTemplateSize.width() - radiusTwice, radiusTwice + topRightRadius.height(), radiusTwice, sideTileWidth);
+    destRect = tileRect;
+    destRect.move(shadowRect.x() + radiusTwice + rect.width() - shadowTemplateSize.width(), shadowRect.y());
+    destRect.setHeight(shadowRect.height() - topRightRadius.height() - bottomRightRadius.height() - m_blurRadius * 4);
+    phase = getPhase(destRect, tileRect);
+    drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
+
+    // Draw the left side.
+    tileRect = FloatRect(0, radiusTwice + topLeftRadius.height(), radiusTwice, sideTileWidth);
+    destRect = tileRect;
+    destRect.move(shadowRect.x(), shadowRect.y());
+    destRect.setHeight(shadowRect.height() - topLeftRadius.height() - bottomLeftRadius.height() - m_blurRadius * 4);
+    phase = FloatPoint(destRect.x() - tileRect.x(), destRect.y() - tileRect.y());
+    drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
+
+    // Draw the top left corner.
+    tileRect = FloatRect(0, 0, radiusTwice + topLeftRadius.width(), radiusTwice + topLeftRadius.height());
+    destRect = tileRect;
+    destRect.move(shadowRect.x(), shadowRect.y());
+    phase = getPhase(destRect, tileRect);
+    drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
+
+    // Draw the top right corner.
+    tileRect = FloatRect(shadowTemplateSize.width() - radiusTwice - topRightRadius.width(), 0, radiusTwice + topRightRadius.width(),
+                         radiusTwice + topRightRadius.height());
+    destRect = tileRect;
+    destRect.move(shadowRect.x() + rect.width() - shadowTemplateSize.width() + radiusTwice, shadowRect.y());
+    phase = getPhase(destRect, tileRect);
+    drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
+
+    // Draw the bottom right corner.
+    tileRect = FloatRect(shadowTemplateSize.width() - radiusTwice - bottomRightRadius.width(),
+                         shadowTemplateSize.height() - radiusTwice - bottomRightRadius.height(),
+                         radiusTwice + bottomRightRadius.width(), radiusTwice + bottomRightRadius.height());
+    destRect = tileRect;
+    destRect.move(shadowRect.x() + rect.width() - shadowTemplateSize.width() + radiusTwice,
+                  shadowRect.y() + rect.height() - shadowTemplateSize.height() + radiusTwice);
+    phase = getPhase(destRect, tileRect);
+    drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
+
+    // Draw the bottom left corner.
+    tileRect = FloatRect(0, shadowTemplateSize.height() - radiusTwice - bottomLeftRadius.height(),
+                         radiusTwice + bottomLeftRadius.width(), radiusTwice + bottomLeftRadius.height());
+    destRect = tileRect;
+    destRect.move(shadowRect.x(), shadowRect.y() + rect.height() - shadowTemplateSize.height() + radiusTwice);
+    phase = getPhase(destRect, tileRect);
+    drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
+
+    // Schedule a purge of the scratch buffer.
+    scheduleScratchBufferPurge();
+}
+
 }