2011-01-30 Simon Fraser <simon.fraser@apple.com>
authorsimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 31 Jan 2011 00:44:18 +0000 (00:44 +0000)
committersimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 31 Jan 2011 00:44:18 +0000 (00:44 +0000)
        Reviewed by Sam Weinig.

        Make ContextShadow code cross-platform
        https://bugs.webkit.org/show_bug.cgi?id=51312

        Add a new class, ShadowBlur, that contains most of the
        code from ContextShadow, but is fully cross-platform.
        It depends on one new method, GraphicsContext::clipBounds(),
        which platforms will have to implement.

        Add ShadowBlur to the Mac Xcode project, but don't use it
        anywhere yet.

        * WebCore.xcodeproj/project.pbxproj:
        * platform/graphics/GraphicsContext.cpp:
        (WebCore::GraphicsContext::clipBounds):
        * platform/graphics/GraphicsContext.h:
        * platform/graphics/ShadowBlur.cpp: Added.
        (WebCore::roundUpToMultipleOf32):
        (WebCore::ScratchBuffer::ScratchBuffer):
        (WebCore::ScratchBuffer::getScratchBuffer):
        (WebCore::ScratchBuffer::scheduleScratchBufferPurge):
        (WebCore::ScratchBuffer::timerFired):
        (WebCore::ScratchBuffer::clearScratchBuffer):
        (WebCore::ScratchBuffer::shared):
        (WebCore::ShadowBlur::ShadowBlur):
        (WebCore::ShadowBlur::blurLayerImage):
        (WebCore::ShadowBlur::adjustBlurDistance):
        (WebCore::ShadowBlur::calculateLayerBoundingRect):
        (WebCore::ShadowBlur::beginShadowLayer):
        (WebCore::ShadowBlur::endShadowLayer):
        (WebCore::ShadowBlur::drawRectShadow):
        (WebCore::ShadowBlur::drawRectShadowWithoutTiling):
        (WebCore::ShadowBlur::drawRectShadowWithTiling):
        (WebCore::ShadowBlur::clipBounds):
        * platform/graphics/ShadowBlur.h: Added.
        (WebCore::ShadowBlur::setShadowsIgnoreTransforms):
        (WebCore::ShadowBlur::shadowsIgnoreTransforms):
        * platform/graphics/cg/GraphicsContextCG.cpp:
        (WebCore::GraphicsContext::clipBounds):

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

Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/platform/graphics/ContextShadow.h
Source/WebCore/platform/graphics/GraphicsContext.cpp
Source/WebCore/platform/graphics/GraphicsContext.h
Source/WebCore/platform/graphics/ShadowBlur.cpp [new file with mode: 0644]
Source/WebCore/platform/graphics/ShadowBlur.h [new file with mode: 0644]
Source/WebCore/platform/graphics/cg/GraphicsContextCG.cpp

index 77fc2a0..42af1f3 100644 (file)
@@ -1,3 +1,46 @@
+2011-01-30  Simon Fraser  <simon.fraser@apple.com>
+
+        Reviewed by Sam Weinig.
+
+        Make ContextShadow code cross-platform
+        https://bugs.webkit.org/show_bug.cgi?id=51312
+
+        Add a new class, ShadowBlur, that contains most of the
+        code from ContextShadow, but is fully cross-platform.
+        It depends on one new method, GraphicsContext::clipBounds(),
+        which platforms will have to implement.
+        
+        Add ShadowBlur to the Mac Xcode project, but don't use it
+        anywhere yet.
+
+        * WebCore.xcodeproj/project.pbxproj:
+        * platform/graphics/GraphicsContext.cpp:
+        (WebCore::GraphicsContext::clipBounds):
+        * platform/graphics/GraphicsContext.h:
+        * platform/graphics/ShadowBlur.cpp: Added.
+        (WebCore::roundUpToMultipleOf32):
+        (WebCore::ScratchBuffer::ScratchBuffer):
+        (WebCore::ScratchBuffer::getScratchBuffer):
+        (WebCore::ScratchBuffer::scheduleScratchBufferPurge):
+        (WebCore::ScratchBuffer::timerFired):
+        (WebCore::ScratchBuffer::clearScratchBuffer):
+        (WebCore::ScratchBuffer::shared):
+        (WebCore::ShadowBlur::ShadowBlur):
+        (WebCore::ShadowBlur::blurLayerImage):
+        (WebCore::ShadowBlur::adjustBlurDistance):
+        (WebCore::ShadowBlur::calculateLayerBoundingRect):
+        (WebCore::ShadowBlur::beginShadowLayer):
+        (WebCore::ShadowBlur::endShadowLayer):
+        (WebCore::ShadowBlur::drawRectShadow):
+        (WebCore::ShadowBlur::drawRectShadowWithoutTiling):
+        (WebCore::ShadowBlur::drawRectShadowWithTiling):
+        (WebCore::ShadowBlur::clipBounds):
+        * platform/graphics/ShadowBlur.h: Added.
+        (WebCore::ShadowBlur::setShadowsIgnoreTransforms):
+        (WebCore::ShadowBlur::shadowsIgnoreTransforms):
+        * platform/graphics/cg/GraphicsContextCG.cpp:
+        (WebCore::GraphicsContext::clipBounds):
+
 2011-01-29  Simon Fraser  <simon.fraser@apple.com>
 
         Reviewed by Dan Bernstein.
index 2df193a..98aa4c7 100644 (file)
                0C45342810CDBBFA00869157 /* JSWebGLUniformLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C45342610CDBBFA00869157 /* JSWebGLUniformLocation.h */; };
                0F11A54F0F39233100C37884 /* RenderSelectionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F11A54E0F39233100C37884 /* RenderSelectionInfo.h */; };
                0F15DA8A0F3AAEE70000CE47 /* AnimationControllerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F15DA890F3AAEE70000CE47 /* AnimationControllerPrivate.h */; };
+               0F3DD44F12F5EA1B000D9190 /* ShadowBlur.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0F3DD44D12F5EA1B000D9190 /* ShadowBlur.cpp */; };
+               0F3DD45012F5EA1B000D9190 /* ShadowBlur.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F3DD44E12F5EA1B000D9190 /* ShadowBlur.h */; };
                0F500AAF0F54DB1B00EEF928 /* TransformState.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F500AAE0F54DB1B00EEF928 /* TransformState.h */; };
                0F500AB10F54DB3100EEF928 /* TransformState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0F500AB00F54DB3100EEF928 /* TransformState.cpp */; };
                0F56028F0E4B76580065B038 /* RenderMarquee.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F56028D0E4B76580065B038 /* RenderMarquee.h */; };
                0C45342610CDBBFA00869157 /* JSWebGLUniformLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSWebGLUniformLocation.h; sourceTree = "<group>"; };
                0F11A54E0F39233100C37884 /* RenderSelectionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderSelectionInfo.h; sourceTree = "<group>"; };
                0F15DA890F3AAEE70000CE47 /* AnimationControllerPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AnimationControllerPrivate.h; path = animation/AnimationControllerPrivate.h; sourceTree = "<group>"; };
+               0F3DD44D12F5EA1B000D9190 /* ShadowBlur.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ShadowBlur.cpp; sourceTree = "<group>"; };
+               0F3DD44E12F5EA1B000D9190 /* ShadowBlur.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShadowBlur.h; sourceTree = "<group>"; };
                0F500AAE0F54DB1B00EEF928 /* TransformState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TransformState.h; sourceTree = "<group>"; };
                0F500AB00F54DB3100EEF928 /* TransformState.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TransformState.cpp; sourceTree = "<group>"; };
                0F56028D0E4B76580065B038 /* RenderMarquee.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderMarquee.h; sourceTree = "<group>"; };
                                A73F95FD12C97BFE0031AAF9 /* RoundedIntRect.h */,
                                371F4FFB0D25E7F300ECE0D5 /* SegmentedFontData.cpp */,
                                371F4FFA0D25E7F300ECE0D5 /* SegmentedFontData.h */,
+                               0F3DD44D12F5EA1B000D9190 /* ShadowBlur.cpp */,
+                               0F3DD44E12F5EA1B000D9190 /* ShadowBlur.h */,
                                B2C3DA530D006CD600EF6F26 /* SimpleFontData.cpp */,
                                B2C3DA540D006CD600EF6F26 /* SimpleFontData.h */,
                                B23540F00D00782E002382FA /* StringTruncator.cpp */,
                                E134F5AB12EE343F004EC58D /* IntRectHash.h in Headers */,
                                977E2DCE12F0E28300C13379 /* HTMLSourceTracker.h in Headers */,
                                977E2E0F12F0FC9C00C13379 /* XSSFilter.h in Headers */,
+                               0F3DD45012F5EA1B000D9190 /* ShadowBlur.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                4F2D205512EAE7B3005C2874 /* InspectorAgent.cpp in Sources */,
                                977E2DCD12F0E28300C13379 /* HTMLSourceTracker.cpp in Sources */,
                                977E2E0E12F0FC9C00C13379 /* XSSFilter.cpp in Sources */,
+                               0F3DD44F12F5EA1B000D9190 /* ShadowBlur.cpp in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index a1fba5c..c0571f0 100644 (file)
@@ -68,6 +68,8 @@ typedef void* PlatformContext;
 // This class should be copyable since GraphicsContextQt keeps a stack of
 // the shadow state for savePlatformState and restorePlatformState.
 
+// This class is Deprecated. Platforms should migrate to ShadowBlur.
+
 class ContextShadow {
 public:
     enum {
index e05a578..5b00e25 100644 (file)
@@ -565,6 +565,14 @@ void GraphicsContext::clipToImageBuffer(ImageBuffer* buffer, const FloatRect& re
     buffer->clip(this, rect);
 }
 
+#if !PLATFORM(CG)
+IntRect GraphicsContext::clipBounds() const
+{
+    ASSERT_NOT_REACHED();
+    return IntRect();
+}
+#endif
+
 TextDrawingModeFlags GraphicsContext::textDrawingMode() const
 {
     return m_state.textDrawingMode;
index 6c9a2f6..c018d67 100644 (file)
@@ -320,6 +320,8 @@ namespace WebCore {
         void clipPath(const Path&, WindRule);
         void clipConvexPolygon(size_t numPoints, const FloatPoint*, bool antialias = true);
         void clipToImageBuffer(ImageBuffer*, const FloatRect&);
+        
+        IntRect clipBounds() const;
 
         TextDrawingModeFlags textDrawingMode() const;
         void setTextDrawingMode(TextDrawingModeFlags);
@@ -373,7 +375,7 @@ namespace WebCore {
 
         // This clip function is used only by <canvas> code. It allows
         // implementations to handle clipping on the canvas differently since
-        // the disipline is different.
+        // the discipline is different.
         void canvasClip(const Path&);
         void clipOut(const Path&);
 
diff --git a/Source/WebCore/platform/graphics/ShadowBlur.cpp b/Source/WebCore/platform/graphics/ShadowBlur.cpp
new file mode 100644 (file)
index 0000000..238d774
--- /dev/null
@@ -0,0 +1,586 @@
+/*
+ * Copyright (C) 2011 Apple Inc.
+ * Copyright (C) 2010 Sencha, Inc.
+ * Copyright (C) 2010 Igalia S.L.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#include "config.h"
+#include "ShadowBlur.h"
+
+#include "AffineTransform.h"
+#include "FloatQuad.h"
+#include "GraphicsContext.h"
+#include "ImageBuffer.h"
+#include "Timer.h"
+#include <wtf/MathExtras.h>
+#include <wtf/Noncopyable.h>
+
+using namespace std;
+
+namespace WebCore {
+
+static inline int roundUpToMultipleOf32(int d)
+{
+    return (1 + (d >> 5)) << 5;
+}
+
+// ShadowBlur needs a scratch image as the buffer for the blur filter.
+// Instead of creating and destroying the buffer for every operation,
+// we create a buffer which will be automatically purged via a timer.
+class ScratchBuffer {
+public:
+    ScratchBuffer()
+        : m_purgeTimer(this, &ScratchBuffer::timerFired)
+    {
+    }
+    
+    ImageBuffer* getScratchBuffer(const IntSize& size)
+    {
+        // We do not need to recreate the buffer if the current buffer is large enough.
+        if (m_imageBuffer && m_imageBuffer->width() >= size.width() && m_imageBuffer->height() >= size.height())
+            return m_imageBuffer.get();
+
+        // Round to the nearest 32 pixels so we do not grow the buffer for similar sized requests.
+        IntSize roundedSize(roundUpToMultipleOf32(size.width()), roundUpToMultipleOf32(size.height()));
+
+        m_imageBuffer = ImageBuffer::create(roundedSize);
+        return m_imageBuffer.get();
+    }
+
+    void scheduleScratchBufferPurge()
+    {
+        if (m_purgeTimer.isActive())
+            m_purgeTimer.stop();
+
+        const double scratchBufferPurgeInterval = 2;
+        m_purgeTimer.startOneShot(scratchBufferPurgeInterval);
+    }
+    
+    static ScratchBuffer& shared();
+
+private:
+    void timerFired(Timer<ScratchBuffer>*)
+    {
+        clearScratchBuffer();
+    }
+    
+    void clearScratchBuffer()
+    {
+        m_imageBuffer = 0;
+    }
+
+    OwnPtr<ImageBuffer> m_imageBuffer;
+    Timer<ScratchBuffer> m_purgeTimer;
+};
+
+ScratchBuffer& ScratchBuffer::shared()
+{
+    DEFINE_STATIC_LOCAL(ScratchBuffer, scratchBuffer, ());
+    return scratchBuffer;
+}
+
+ShadowBlur::ShadowBlur(float radius, const FloatSize& offset, const Color& color, ColorSpace colorSpace)
+    : m_color(color)
+    , m_colorSpace(colorSpace)
+    , m_blurRadius(radius)
+    , m_offset(offset)
+    , m_shadowsIgnoreTransforms(false)
+{
+    // Limit blur radius to 128 to avoid lots of very expensive blurring.
+    m_blurRadius = min<float>(m_blurRadius, 128);
+
+    // The type of shadow is decided by the blur radius, shadow offset, and shadow color.
+    if (!m_color.isValid() || !color.alpha()) {
+        // Can't paint the shadow with invalid or invisible color.
+        m_type = NoShadow;
+    } else if (radius > 0) {
+        // Shadow is always blurred, even the offset is zero.
+        m_type = BlurShadow;
+    } else if (!m_offset.width() && !m_offset.height()) {
+        // Without blur and zero offset means the shadow is fully hidden.
+        m_type = NoShadow;
+    } else
+        m_type = SolidShadow;
+}
+
+// Instead of integer division, we use 17.15 for fixed-point division.
+static const int blurSumShift = 15;
+static const float gaussianKernelFactor = 3 / 4.f * sqrtf(2 * piFloat);
+
+// Check http://www.w3.org/TR/SVG/filters.html#feGaussianBlur.
+// As noted in the SVG filter specification, running box blur 3x
+// approximates a real gaussian blur nicely.
+
+void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride)
+{
+    const int channels[4] =
+#if CPU(BIG_ENDIAN)
+        { 0, 3, 2, 0 };
+#elif CPU(MIDDLE_ENDIAN)
+        { 1, 2, 3, 1 };
+#else
+        { 3, 0, 1, 3 };
+#endif
+
+    int diameter;
+    if (m_shadowsIgnoreTransforms)
+        diameter = max(2, static_cast<int>(floorf((2 / 3.f) * m_blurRadius))); // Canvas shadow. FIXME: we should adjust the blur radius higher up.
+    else
+        diameter = max(2, static_cast<int>(floorf(m_blurRadius / 2 * gaussianKernelFactor + 0.5f))); // CSS
+
+    int dMax = diameter >> 1;
+    int dMin = dMax - 1 + (diameter & 1);
+    if (dMin < 0)
+        dMin = 0;
+
+    // First pass is horizontal.
+    int stride = 4;
+    int delta = rowStride;
+    int final = size.height();
+    int dim = size.width();
+
+    // Two stages: horizontal and vertical
+    for (int pass = 0; pass < 2; ++pass) {
+        unsigned char* pixels = imageData;
+
+        for (int j = 0; j < final; ++j, pixels += delta) {
+            // For each step, we blur the alpha in a channel and store the result
+            // in another channel for the subsequent step.
+            // We use sliding window algorithm to accumulate the alpha values.
+            // This is much more efficient than computing the sum of each pixels
+            // covered by the box kernel size for each x.
+            for (int step = 0; step < 3; ++step) {
+                int side1 = (!step) ? dMin : dMax;
+                int side2 = (step == 1) ? dMin : dMax;
+                int pixelCount = side1 + 1 + side2;
+                int invCount = ((1 << blurSumShift) + pixelCount - 1) / pixelCount;
+                int ofs = 1 + side2;
+                int alpha1 = pixels[channels[step]];
+                int alpha2 = pixels[(dim - 1) * stride + channels[step]];
+
+                unsigned char* ptr = pixels + channels[step + 1];
+                unsigned char* prev = pixels + stride + channels[step];
+                unsigned char* next = pixels + ofs * stride + channels[step];
+
+                int i;
+                int sum = side1 * alpha1 + alpha1;
+                int limit = (dim < side2 + 1) ? dim : side2 + 1;
+
+                for (i = 1; i < limit; ++i, prev += stride)
+                    sum += *prev;
+
+                if (limit <= side2)
+                    sum += (side2 - limit + 1) * alpha2;
+
+                limit = (side1 < dim) ? side1 : dim;
+                for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) {
+                    *ptr = (sum * invCount) >> blurSumShift;
+                    sum += ((ofs < dim) ? *next : alpha2) - alpha1;
+                }
+                
+                prev = pixels + channels[step];
+                for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) {
+                    *ptr = (sum * invCount) >> blurSumShift;
+                    sum += (*next) - (*prev);
+                }
+                
+                for (; i < dim; ptr += stride, prev += stride, ++i) {
+                    *ptr = (sum * invCount) >> blurSumShift;
+                    sum += alpha2 - (*prev);
+                }
+            }
+        }
+
+        // Last pass is vertical.
+        stride = rowStride;
+        delta = 4;
+        final = size.width();
+        dim = size.height();
+    }
+}
+
+void ShadowBlur::adjustBlurDistance(GraphicsContext* context)
+{
+    if (!m_shadowsIgnoreTransforms)
+        return;
+
+    const AffineTransform transform = context->getCTM();
+
+    // Adjust blur if we're scaling, since the radius must not be affected by transformations.
+    // FIXME: use AffineTransform::isIdentityOrTranslationOrFlipped()?
+    if (transform.isIdentity())
+        return;
+
+    // Calculate transformed unit vectors.
+    const FloatQuad unitQuad(FloatPoint(0, 0), FloatPoint(1, 0),
+                             FloatPoint(0, 1), FloatPoint(1, 1));
+    const FloatQuad transformedUnitQuad = transform.mapQuad(unitQuad);
+
+    // Calculate X axis scale factor.
+    const FloatSize xUnitChange = transformedUnitQuad.p2() - transformedUnitQuad.p1();
+    const float xAxisScale = sqrtf(xUnitChange.width() * xUnitChange.width()
+                                   + xUnitChange.height() * xUnitChange.height());
+
+    // Calculate Y axis scale factor.
+    const FloatSize yUnitChange = transformedUnitQuad.p3() - transformedUnitQuad.p1();
+    const float yAxisScale = sqrtf(yUnitChange.width() * yUnitChange.width()
+                                   + yUnitChange.height() * yUnitChange.height());
+
+    // blurLayerImage() does not support per-axis blurring, so calculate a balanced scaling.
+    // FIXME: does AffineTransform.xScale()/yScale() help?
+    const float scale = sqrtf(xAxisScale * yAxisScale);
+    m_blurRadius = roundf(m_blurRadius / scale);
+}
+
+IntRect ShadowBlur::calculateLayerBoundingRect(GraphicsContext* context, const FloatRect& shadowedRect, const IntRect& clipRect)
+{
+    // Calculate the destination of the blurred and/or transformed layer.
+    FloatRect layerFloatRect;
+    float inflation = 0;
+
+    const AffineTransform transform = context->getCTM();
+    if (m_shadowsIgnoreTransforms && !transform.isIdentity()) {
+        FloatQuad transformedPolygon = transform.mapQuad(FloatQuad(shadowedRect));
+        transformedPolygon.move(m_offset);
+        layerFloatRect = transform.inverse().mapQuad(transformedPolygon).boundingBox();
+    } else {
+        layerFloatRect = shadowedRect;
+        layerFloatRect.move(m_offset);
+    }
+
+    // We expand the area by the blur radius to give extra space for the blur transition.
+    if (m_type == BlurShadow) {
+        layerFloatRect.inflate(m_blurRadius);
+        inflation += m_blurRadius;
+    }
+
+    FloatRect unclippedLayerRect = layerFloatRect;
+
+    if (!clipRect.contains(enclosingIntRect(layerFloatRect))) {
+        // If we are totally outside the clip region, we aren't painting at all.
+        if (intersection(layerFloatRect, clipRect).isEmpty())
+            return IntRect();
+
+        IntRect inflatedClip = clipRect;
+        // Pixels at the edges can be affected by pixels outside the buffer,
+        // so intersect with the clip inflated by the blur.
+        if (m_type == BlurShadow)
+            inflatedClip.inflate(m_blurRadius);
+        
+        layerFloatRect.intersect(inflatedClip);
+    }
+
+    const int frameSize = inflation * 2;
+    m_sourceRect = IntRect(0, 0, shadowedRect.width() + frameSize, shadowedRect.height() + frameSize);
+    m_layerOrigin = FloatPoint(layerFloatRect.x(), layerFloatRect.y());
+    m_layerSize = layerFloatRect.size();
+
+    const FloatPoint unclippedLayerOrigin = FloatPoint(unclippedLayerRect.x(), unclippedLayerRect.y());
+    const FloatSize clippedOut = unclippedLayerOrigin - m_layerOrigin;
+
+    // Set the origin as the top left corner of the scratch image, or, in case there's a clipped
+    // out region, set the origin accordingly to the full bounding rect's top-left corner.
+    float translationX = -shadowedRect.x() + inflation - fabsf(clippedOut.width());
+    float translationY = -shadowedRect.y() + inflation - fabsf(clippedOut.height());
+    m_layerContextTranslation = FloatSize(translationX, translationY);
+
+    return enclosingIntRect(layerFloatRect);
+}
+
+GraphicsContext* ShadowBlur::beginShadowLayer(GraphicsContext* graphicsContext, const FloatRect& shadowedRect)
+{
+    adjustBlurDistance(graphicsContext);
+
+    IntRect layerRect = calculateLayerBoundingRect(graphicsContext, shadowedRect, graphicsContext->clipBounds());
+    // Don't paint if we are totally outside the clip region.
+    if (layerRect.isEmpty())
+        return 0;
+
+    m_layerImage = ScratchBuffer::shared().getScratchBuffer(layerRect.size());
+    GraphicsContext* layerContext = m_layerImage->context();
+
+    layerContext->save(); // Balanced by restore() in endShadowLayer().
+
+    // Always clear the surface first. FIXME: we could avoid the clear on first allocation.
+    // Add a pixel to avoid later edge aliasing when rotated.
+    layerContext->clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1));
+    layerContext->translate(m_layerContextTranslation);
+
+    return layerContext;
+}
+
+void ShadowBlur::endShadowLayer(GraphicsContext* graphicsContext)
+{
+    if (!m_layerImage)
+        return;
+        
+    m_layerImage->context()->restore();
+
+    if (m_type == BlurShadow) {
+        IntRect blurRect = enclosingIntRect(FloatRect(FloatPoint(), m_layerSize));
+        RefPtr<ByteArray> layerData = m_layerImage->getUnmultipliedImageData(blurRect);
+        blurLayerImage(layerData->data(), blurRect.size(), blurRect.width() * 4);
+        m_layerImage->putUnmultipliedImageData(layerData.get(), blurRect.size(), blurRect, IntPoint());
+    }
+
+    graphicsContext->save();
+
+    graphicsContext->clipToImageBuffer(m_layerImage, FloatRect(m_layerOrigin, m_layerImage->size()));
+    graphicsContext->setFillColor(m_color, m_colorSpace);
+
+    graphicsContext->clearShadow();
+    graphicsContext->fillRect(FloatRect(m_layerOrigin, m_sourceRect.size()));
+    
+    graphicsContext->restore();
+
+    m_layerImage = 0;
+
+    // Schedule a purge of the scratch buffer. We do not need to destroy the surface.
+    ScratchBuffer::shared().scheduleScratchBufferPurge();
+}
+
+void ShadowBlur::drawRectShadow(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii)
+{
+    // drawShadowedRect does not work with rotations.
+    // https://bugs.webkit.org/show_bug.cgi?id=45042
+    if (!graphicsContext->getCTM().isIdentityOrTranslationOrFlipped() || m_type != BlurShadow) {
+        drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, 1);
+        return;
+    }
+
+    int templateSideLength = 1;
+    float twiceRadius = m_blurRadius * 2;
+    
+    // Find the extra space needed from the curve of the corners.
+    int extraWidthFromCornerRadii = twiceRadius + max(radii.topLeft().width(), radii.bottomLeft().width()) + twiceRadius + max(radii.topRight().width(), radii.bottomRight().width());
+    int extraHeightFromCornerRadii = twiceRadius + max(radii.topLeft().height(), radii.topRight().height()) + twiceRadius + max(radii.bottomLeft().height(), radii.bottomRight().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(templateSideLength + extraWidthFromCornerRadii, templateSideLength + extraHeightFromCornerRadii);
+
+    if (shadowTemplateSize.width() > shadowedRect.width() || shadowTemplateSize.height() > shadowedRect.height()) {
+        drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, 1);
+        return;
+    }
+
+    // Determine dimensions of shadow rect.
+    FloatRect shadowRect = shadowedRect;
+    shadowRect.inflate(m_blurRadius); // FIXME: duplicating code with calculateLayerBoundingRect.
+
+    // Reduce the size of what we have to draw with the clip area.
+    calculateLayerBoundingRect(graphicsContext, shadowedRect, graphicsContext->clipBounds());
+
+    // If the template area ends up being larger than the area to be blurred, use the simple case.
+    // FIXME: when does this happen?
+    if ((shadowTemplateSize.width() * shadowTemplateSize.height() > m_sourceRect.width() * m_sourceRect.height())) {
+        drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, 1);
+        return;
+    }
+    
+    drawRectShadowWithTiling(graphicsContext, shadowedRect, radii, 1, shadowTemplateSize);
+}
+
+void ShadowBlur::drawRectShadowWithoutTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii, float alpha)
+{
+    GraphicsContext* shadowContext = beginShadowLayer(graphicsContext, shadowedRect);
+    if (!shadowContext)
+        return;
+
+    Path path;
+    path.addRoundedRect(shadowedRect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
+
+    shadowContext->setFillColor(Color(.0f, .0f, .0f, alpha), ColorSpaceDeviceRGB);
+    shadowContext->fillPath(path);
+
+    endShadowLayer(graphicsContext);
+}
+
+/*
+  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 ShadowBlur::drawRectShadowWithTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii, float alpha, const IntSize& shadowTemplateSize)
+{
+    float twiceRadius = m_blurRadius * 2;
+
+    // Size of the tiling side.
+    int templateSideLength = 1;
+
+    FloatRect shadowRect = shadowedRect;
+    shadowRect.inflate(m_blurRadius); // FIXME: duplicating code with calculateLayerBoundingRect.
+
+    shadowRect.move(m_offset.width(), m_offset.height());
+
+    m_layerImage = ScratchBuffer::shared().getScratchBuffer(shadowTemplateSize);
+
+    // Draw shadow into a new ImageBuffer.
+    GraphicsContext* shadowContext = m_layerImage->context();
+    shadowContext->save();
+    
+    shadowContext->clearRect(FloatRect(0, 0, shadowTemplateSize.width(), shadowTemplateSize.height()));
+
+    // Draw the rectangle.
+    IntRect templateRect = IntRect(m_blurRadius, m_blurRadius, shadowTemplateSize.width() - twiceRadius, shadowTemplateSize.height() - twiceRadius);
+    Path path;
+    path.addRoundedRect(templateRect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
+
+    shadowContext->setFillColor(Color(.0f, .0f, .0f, alpha), ColorSpaceDeviceRGB);
+    shadowContext->fillPath(path);
+
+    // Blur the image.
+    {
+        IntRect blurRect(IntPoint(), shadowTemplateSize);
+        RefPtr<ByteArray> layerData = m_layerImage->getUnmultipliedImageData(blurRect);
+        blurLayerImage(layerData->data(), blurRect.size(), blurRect.width() * 4);
+        m_layerImage->putUnmultipliedImageData(layerData.get(), blurRect.size(), blurRect, IntPoint());
+    }
+
+    // Mask the image with the shadow color.
+    shadowContext->setCompositeOperation(CompositeSourceIn);
+    shadowContext->setFillColor(m_color, m_colorSpace);
+    shadowContext->fillRect(FloatRect(0, 0, shadowTemplateSize.width(), shadowTemplateSize.height()));
+    
+    shadowContext->restore();
+
+    // Fill the internal part of the shadow.
+    shadowRect.inflate(-twiceRadius);
+    if (!shadowRect.isEmpty()) {
+        graphicsContext->save();
+        
+        path.clear();
+        path.addRoundedRect(shadowRect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
+
+        graphicsContext->setFillColor(m_color, m_colorSpace);
+        graphicsContext->fillPath(path);
+        
+        graphicsContext->restore();
+    }
+    shadowRect.inflate(twiceRadius);
+
+    // Note that drawing the ImageBuffer is faster than creating a Image and drawing that,
+    // because ImageBuffer::draw() knows that it doesn't have to copy the image bits.
+        
+    // Draw top side.
+    FloatRect tileRect = FloatRect(twiceRadius + radii.topLeft().width(), 0, templateSideLength, twiceRadius);
+    FloatRect destRect = tileRect;
+    destRect.move(shadowRect.x(), shadowRect.y());
+    destRect.setWidth(shadowRect.width() - radii.topLeft().width() - radii.topRight().width() - m_blurRadius * 4);
+    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+
+    // Draw the bottom side.
+    tileRect = FloatRect(twiceRadius + radii.bottomLeft().width(), shadowTemplateSize.height() - twiceRadius, templateSideLength, twiceRadius);
+    destRect = tileRect;
+    destRect.move(shadowRect.x(), shadowRect.y() + twiceRadius + shadowedRect.height() - shadowTemplateSize.height());
+    destRect.setWidth(shadowRect.width() - radii.bottomLeft().width() - radii.bottomRight().width() - m_blurRadius * 4);
+    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+
+    // Draw the right side.
+    tileRect = FloatRect(shadowTemplateSize.width() - twiceRadius, twiceRadius + radii.topRight().height(), twiceRadius, templateSideLength);
+    destRect = tileRect;
+    destRect.move(shadowRect.x() + twiceRadius + shadowedRect.width() - shadowTemplateSize.width(), shadowRect.y());
+    destRect.setHeight(shadowRect.height() - radii.topRight().height() - radii.bottomRight().height() - m_blurRadius * 4);
+    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+
+    // Draw the left side.
+    tileRect = FloatRect(0, twiceRadius + radii.topLeft().height(), twiceRadius, templateSideLength);
+    destRect = tileRect;
+    destRect.move(shadowRect.x(), shadowRect.y());
+    destRect.setHeight(shadowRect.height() - radii.topLeft().height() - radii.bottomLeft().height() - m_blurRadius * 4);
+    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+
+    // Draw the top left corner.
+    tileRect = FloatRect(0, 0, twiceRadius + radii.topLeft().width(), twiceRadius + radii.topLeft().height());
+    destRect = tileRect;
+    destRect.move(shadowRect.x(), shadowRect.y());
+    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+
+    // Draw the top right corner.
+    tileRect = FloatRect(shadowTemplateSize.width() - twiceRadius - radii.topRight().width(), 0, twiceRadius + radii.topRight().width(),
+                         twiceRadius + radii.topRight().height());
+    destRect = tileRect;
+    destRect.move(shadowRect.x() + shadowedRect.width() - shadowTemplateSize.width() + twiceRadius, shadowRect.y());
+    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+
+    // Draw the bottom right corner.
+    tileRect = FloatRect(shadowTemplateSize.width() - twiceRadius - radii.bottomRight().width(),
+                         shadowTemplateSize.height() - twiceRadius - radii.bottomRight().height(),
+                         twiceRadius + radii.bottomRight().width(), twiceRadius + radii.bottomRight().height());
+    destRect = tileRect;
+    destRect.move(shadowRect.x() + shadowedRect.width() - shadowTemplateSize.width() + twiceRadius,
+                  shadowRect.y() + shadowedRect.height() - shadowTemplateSize.height() + twiceRadius);
+    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+
+    // Draw the bottom left corner.
+    tileRect = FloatRect(0, shadowTemplateSize.height() - twiceRadius - radii.bottomLeft().height(),
+                         twiceRadius + radii.bottomLeft().width(), twiceRadius + radii.bottomLeft().height());
+    destRect = tileRect;
+    destRect.move(shadowRect.x(), shadowRect.y() + shadowedRect.height() - shadowTemplateSize.height() + twiceRadius);
+    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+
+    m_layerImage = 0;
+    // Schedule a purge of the scratch buffer.
+    ScratchBuffer::shared().scheduleScratchBufferPurge();
+}
+
+#if !PLATFORM(CG)
+IntRect ShadowBlur::clipBounds(GraphicsContext*)
+{
+    // FIXME: add clipBounds() to GraphicsContext.
+    ASSERT_NOT_REACHED();
+    return IntRect();
+}
+#endif
+
+} // namespace WebCore
diff --git a/Source/WebCore/platform/graphics/ShadowBlur.h b/Source/WebCore/platform/graphics/ShadowBlur.h
new file mode 100644 (file)
index 0000000..a93eb4d
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 Apple Inc.
+ * Copyright (C) 2010 Sencha, Inc.
+ * Copyright (C) 2010 Igalia S.L.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#ifndef ShadowBlur_h
+#define ShadowBlur_h
+
+#include "Color.h"
+#include "FloatRect.h"
+#include "RoundedIntRect.h"
+#include <wtf/Noncopyable.h>
+
+namespace WebCore {
+
+class AffineTransform;
+class GraphicsContext;
+class ImageBuffer;
+
+class ShadowBlur {
+    WTF_MAKE_NONCOPYABLE(ShadowBlur);
+public:
+    ShadowBlur(float radius, const FloatSize& offset, const Color&, ColorSpace);
+
+    void setShadowsIgnoreTransforms(bool ignoreTransforms) { m_shadowsIgnoreTransforms = ignoreTransforms; }
+    bool shadowsIgnoreTransforms() const { return m_shadowsIgnoreTransforms; }
+
+    void drawRectShadow(GraphicsContext*, const FloatRect&, const RoundedIntRect::Radii&);
+
+private:
+    GraphicsContext* beginShadowLayer(GraphicsContext*, const FloatRect& layerArea);
+    void endShadowLayer(GraphicsContext*);
+
+    void adjustBlurDistance(GraphicsContext*);
+    void blurLayerImage(unsigned char*, const IntSize&, int stride);
+    IntRect calculateLayerBoundingRect(GraphicsContext*, const FloatRect& layerArea, const IntRect& clipRect);
+
+    void drawRectShadowWithoutTiling(GraphicsContext*, const FloatRect&, const RoundedIntRect::Radii&, float alpha);
+    void drawRectShadowWithTiling(GraphicsContext*, const FloatRect&, const RoundedIntRect::Radii&, float alpha, const IntSize& shadowTemplateSize);
+    
+    enum ShadowType {
+        NoShadow,
+        SolidShadow,
+        BlurShadow
+    };
+    
+    ShadowType m_type;
+
+    Color m_color;
+    ColorSpace m_colorSpace;
+    float m_blurRadius;
+    FloatSize m_offset;
+
+    ImageBuffer* m_layerImage; // Buffer to where the temporary shadow will be drawn to.
+
+    FloatRect m_sourceRect; // Sub-rect of m_layerImage that contains the shadow pixels.
+    FloatPoint m_layerOrigin; // Top-left corner of the (possibly clipped) bounding rect to draw the shadow to.
+    FloatSize m_layerSize; // Size of m_layerImage pixels that need blurring.
+    FloatSize m_layerContextTranslation; // Translation to apply to m_layerContext for the shadow to be correctly clipped.
+
+    bool m_shadowsIgnoreTransforms;
+};
+
+} // namespace WebCore
+
+#endif // ShadowBlur_h
index 6142569..60356b6 100644 (file)
@@ -727,6 +727,11 @@ void GraphicsContext::clipPath(const Path& path, WindRule clipRule)
         CGContextClip(context);
 }
 
+IntRect GraphicsContext::clipBounds() const
+{
+    return enclosingIntRect(CGContextGetClipBoundingBox(platformContext()));
+}
+
 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
 {
     if (paintingDisabled())