Move locale information into FontDescription
[WebKit-https.git] / Source / WebCore / platform / graphics / ShadowBlur.cpp
index e59dbf9..8acbda2 100644 (file)
@@ -1,8 +1,9 @@
 /*
- * Copyright (C) 2011 Apple Inc.
- * Copyright (C) 2010 Sencha, Inc.
- * Copyright (C) 2010 Igalia S.L.
- * All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Sencha, Inc. All rights reserved.
+ * Copyright (C) 2010 Igalia S.L. All rights reserved.
+ * Copyright (C) Research In Motion Limited 2011. All rights reserved.
+ * Copyright (C) 2013 Digia Plc. and/or its subsidiary(-ies).
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -16,7 +17,7 @@
  * 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
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE 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
 #include <wtf/MathExtras.h>
 #include <wtf/Noncopyable.h>
 
-using namespace std;
-
 namespace WebCore {
 
+enum {
+    leftLobe = 0,
+    rightLobe = 1
+};
+
 static inline int roundUpToMultipleOf32(int d)
 {
     return (1 + (d >> 5)) << 5;
@@ -50,27 +54,72 @@ static inline int roundUpToMultipleOf32(int d)
 // Instead of creating and destroying the buffer for every operation,
 // we create a buffer which will be automatically purged via a timer.
 class ScratchBuffer {
+    WTF_MAKE_FAST_ALLOCATED;
 public:
     ScratchBuffer()
-        : m_purgeTimer(this, &ScratchBuffer::timerFired)
+        : m_purgeTimer(*this, &ScratchBuffer::clearScratchBuffer)
+        , m_lastWasInset(false)
+#if !ASSERT_DISABLED
+        , m_bufferInUse(false)
+#endif
     {
     }
     
     ImageBuffer* getScratchBuffer(const IntSize& size)
     {
+        ASSERT(!m_bufferInUse);
+#if !ASSERT_DISABLED
+        m_bufferInUse = true;
+#endif
         // 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())
+        if (m_imageBuffer && m_imageBuffer->logicalSize().width() >= size.width() && m_imageBuffer->logicalSize().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);
+        clearScratchBuffer();
+
+        // ShadowBlur is not used with accelerated drawing, so it's OK to make an unconditionally unaccelerated buffer.
+        m_imageBuffer = ImageBuffer::create(roundedSize, Unaccelerated, 1);
         return m_imageBuffer.get();
     }
 
+    bool setCachedShadowValues(const FloatSize& radius, const Color& color, const FloatRect& shadowRect, const FloatRoundedRect::Radii& radii, const FloatSize& layerSize)
+    {
+        if (!m_lastWasInset && m_lastRadius == radius && m_lastColor == color && m_lastShadowRect == shadowRect &&  m_lastRadii == radii && m_lastLayerSize == layerSize)
+            return false;
+
+        m_lastWasInset = false;
+        m_lastRadius = radius;
+        m_lastColor = color;
+        m_lastShadowRect = shadowRect;
+        m_lastRadii = radii;
+        m_lastLayerSize = layerSize;
+
+        return true;
+    }
+
+    bool setCachedInsetShadowValues(const FloatSize& radius, const Color& color, const FloatRect& bounds, const FloatRect& shadowRect, const FloatRoundedRect::Radii& radii)
+    {
+        if (m_lastWasInset && m_lastRadius == radius && m_lastColor == color && m_lastInsetBounds == bounds && shadowRect == m_lastShadowRect && radii == m_lastRadii)
+            return false;
+
+        m_lastWasInset = true;
+        m_lastInsetBounds = bounds;
+        m_lastRadius = radius;
+        m_lastColor = color;
+        m_lastShadowRect = shadowRect;
+        m_lastRadii = radii;
+
+        return true;
+    }
+
     void scheduleScratchBufferPurge()
     {
+#if !ASSERT_DISABLED
+        m_bufferInUse = false;
+#endif
         if (m_purgeTimer.isActive())
             m_purgeTimer.stop();
 
@@ -78,44 +127,100 @@ public:
         m_purgeTimer.startOneShot(scratchBufferPurgeInterval);
     }
     
-    static ScratchBuffer& shared();
+    static ScratchBuffer& singleton();
 
 private:
-    void timerFired(Timer<ScratchBuffer>*)
-    {
-        clearScratchBuffer();
-    }
-    
     void clearScratchBuffer()
     {
-        m_imageBuffer = 0;
+        m_imageBuffer = nullptr;
+        m_lastRadius = FloatSize();
+        m_lastLayerSize = FloatSize();
     }
 
-    OwnPtr<ImageBuffer> m_imageBuffer;
-    Timer<ScratchBuffer> m_purgeTimer;
+    std::unique_ptr<ImageBuffer> m_imageBuffer;
+    Timer m_purgeTimer;
+    
+    FloatRect m_lastInsetBounds;
+    FloatRect m_lastShadowRect;
+    FloatRoundedRect::Radii m_lastRadii;
+    Color m_lastColor;
+    FloatSize m_lastRadius;
+    bool m_lastWasInset;
+    FloatSize m_lastLayerSize;
+    
+#if !ASSERT_DISABLED
+    bool m_bufferInUse;
+#endif
 };
 
-ScratchBuffer& ScratchBuffer::shared()
+ScratchBuffer& ScratchBuffer::singleton()
 {
-    DEFINE_STATIC_LOCAL(ScratchBuffer, scratchBuffer, ());
+    DEPRECATED_DEFINE_STATIC_LOCAL(ScratchBuffer, scratchBuffer, ());
     return scratchBuffer;
 }
 
-ShadowBlur::ShadowBlur(float radius, const FloatSize& offset, const Color& color, ColorSpace colorSpace)
+static const int templateSideLength = 1;
+
+#if USE(CG)
+static float radiusToLegacyRadius(float radius)
+{
+    return radius > 8 ? 8 + 4 * sqrt((radius - 8) / 2) : radius;
+}
+#endif
+
+ShadowBlur::ShadowBlur(const FloatSize& radius, const FloatSize& offset, const Color& color)
     : m_color(color)
-    , m_colorSpace(colorSpace)
     , m_blurRadius(radius)
     , m_offset(offset)
+    , m_layerImage(0)
     , m_shadowsIgnoreTransforms(false)
 {
+    updateShadowBlurValues();
+}
+
+ShadowBlur::ShadowBlur(const GraphicsContextState& state)
+    : m_color(state.shadowColor)
+    , m_blurRadius(state.shadowBlur, state.shadowBlur)
+    , m_offset(state.shadowOffset)
+    , m_layerImage(0)
+    , m_shadowsIgnoreTransforms(state.shadowsIgnoreTransforms)
+{
+#if USE(CG)
+    if (state.shadowsUseLegacyRadius) {
+        float shadowBlur = radiusToLegacyRadius(state.shadowBlur);
+        m_blurRadius = FloatSize(shadowBlur, shadowBlur);
+    }
+#endif
+    updateShadowBlurValues();
+}
+
+ShadowBlur::ShadowBlur()
+    : m_type(NoShadow)
+    , m_blurRadius(0, 0)
+    , m_shadowsIgnoreTransforms(false)
+{
+}
+
+void ShadowBlur::setShadowValues(const FloatSize& radius, const FloatSize& offset, const Color& color, bool ignoreTransforms)
+{
+    m_blurRadius = radius;
+    m_offset = offset;
+    m_color = color;
+    m_shadowsIgnoreTransforms = ignoreTransforms;
+
+    updateShadowBlurValues();
+}
+
+void ShadowBlur::updateShadowBlurValues()
+{
     // Limit blur radius to 128 to avoid lots of very expensive blurring.
-    m_blurRadius = min<float>(m_blurRadius, 128);
+    m_blurRadius = m_blurRadius.shrunkTo(FloatSize(128, 128));
 
     // The type of shadow is decided by the blur radius, shadow offset, and shadow color.
-    if (!m_color.isValid() || !color.alpha()) {
+    if (!m_color.isValid() || !m_color.alpha()) {
         // Can't paint the shadow with invalid or invisible color.
         m_type = NoShadow;
-    } else if (radius > 0) {
+    } else if (m_blurRadius.width() > 0 || m_blurRadius.height() > 0) {
         // Shadow is always blurred, even the offset is zero.
         m_type = BlurShadow;
     } else if (!m_offset.width() && !m_offset.height()) {
@@ -127,33 +232,62 @@ ShadowBlur::ShadowBlur(float radius, const FloatSize& offset, const Color& color
 
 // 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.
+// Takes a two dimensional array with three rows and two columns for the lobes.
+static void calculateLobes(int lobes[][2], float blurRadius, bool shadowsIgnoreTransforms)
+{
+    int diameter;
+    if (shadowsIgnoreTransforms)
+        diameter = std::max(2, static_cast<int>(floorf((2 / 3.f) * blurRadius))); // Canvas shadow. FIXME: we should adjust the blur radius higher up.
+    else {
+        // http://dev.w3.org/csswg/css3-background/#box-shadow
+        // Approximate a Gaussian blur with a standard deviation equal to half the blur radius,
+        // which http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement tell us how to do.
+        // However, shadows rendered according to that spec will extend a little further than m_blurRadius,
+        // so we apply a fudge factor to bring the radius down slightly.
+        float stdDev = blurRadius / 2;
+        const float gaussianKernelFactor = 3 / 4.f * sqrtf(2 * piFloat);
+        const float fudgeFactor = 0.88f;
+        diameter = std::max(2, static_cast<int>(floorf(stdDev * gaussianKernelFactor * fudgeFactor + 0.5f)));
+    }
 
-void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride)
+    if (diameter & 1) {
+        // if d is odd, use three box-blurs of size 'd', centered on the output pixel.
+        int lobeSize = (diameter - 1) / 2;
+        lobes[0][leftLobe] = lobeSize;
+        lobes[0][rightLobe] = lobeSize;
+        lobes[1][leftLobe] = lobeSize;
+        lobes[1][rightLobe] = lobeSize;
+        lobes[2][leftLobe] = lobeSize;
+        lobes[2][rightLobe] = lobeSize;
+    } else {
+        // if d is even, two box-blurs of size 'd' (the first one centered on the pixel boundary
+        // between the output pixel and the one to the left, the second one centered on the pixel
+        // boundary between the output pixel and the one to the right) and one box blur of size 'd+1' centered on the output pixel
+        int lobeSize = diameter / 2;
+        lobes[0][leftLobe] = lobeSize;
+        lobes[0][rightLobe] = lobeSize - 1;
+        lobes[1][leftLobe] = lobeSize - 1;
+        lobes[1][rightLobe] = lobeSize;
+        lobes[2][leftLobe] = lobeSize;
+        lobes[2][rightLobe] = lobeSize;
+    }
+}
+
+void ShadowBlur::clear()
 {
-    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
+    m_type = NoShadow;
+    m_color = Color();
+    m_blurRadius = FloatSize();
+    m_offset = FloatSize();
+}
 
-    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
+void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride)
+{
+    const int channels[4] = { 3, 0, 1, 3 };
 
-    int dMax = diameter >> 1;
-    int dMin = dMax - 1 + (diameter & 1);
-    if (dMin < 0)
-        dMin = 0;
+    int lobes[3][2]; // indexed by pass, and left/right lobe
+    calculateLobes(lobes, m_blurRadius.width(), m_shadowsIgnoreTransforms);
 
     // First pass is horizontal.
     int stride = 4;
@@ -164,6 +298,9 @@ void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, i
     // Two stages: horizontal and vertical
     for (int pass = 0; pass < 2; ++pass) {
         unsigned char* pixels = imageData;
+        
+        if (!pass && !m_blurRadius.width())
+            final = 0; // Do no work if horizonal blur is zero.
 
         for (int j = 0; j < final; ++j, pixels += delta) {
             // For each step, we blur the alpha in a channel and store the result
@@ -172,8 +309,8 @@ void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, i
             // 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 side1 = lobes[step][leftLobe];
+                int side2 = lobes[step][rightLobe];
                 int pixelCount = side1 + 1 + side2;
                 int invCount = ((1 << blurSumShift) + pixelCount - 1) / pixelCount;
                 int ofs = 1 + side2;
@@ -218,230 +355,285 @@ void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, i
         delta = 4;
         final = size.width();
         dim = size.height();
+
+        if (!m_blurRadius.height())
+            break;
+
+        if (m_blurRadius.width() != m_blurRadius.height())
+            calculateLobes(lobes, m_blurRadius.height(), m_shadowsIgnoreTransforms);
     }
 }
 
-void ShadowBlur::adjustBlurDistance(GraphicsContext* context)
+void ShadowBlur::adjustBlurRadius(GraphicsContext& context)
 {
     if (!m_shadowsIgnoreTransforms)
         return;
 
-    const AffineTransform transform = context->getCTM();
+    AffineTransform transform = context.getCTM();
+    m_blurRadius.scale(1 / static_cast<float>(transform.xScale()), 1 / static_cast<float>(transform.yScale()));
+}
 
-    // Adjust blur if we're scaling, since the radius must not be affected by transformations.
-    // FIXME: use AffineTransform::isIdentityOrTranslationOrFlipped()?
-    if (transform.isIdentity())
-        return;
+IntSize ShadowBlur::blurredEdgeSize() const
+{
+    IntSize edgeSize = expandedIntSize(m_blurRadius);
 
-    // 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);
+    // To avoid slowing down blurLayerImage() for radius == 1, we give it two empty pixels on each side.
+    if (edgeSize.width() == 1)
+        edgeSize.setWidth(2);
+
+    if (edgeSize.height() == 1)
+        edgeSize.setHeight(2);
+
+    return edgeSize;
 }
 
-IntRect ShadowBlur::calculateLayerBoundingRect(GraphicsContext* context, const FloatRect& shadowedRect, const IntRect& clipRect)
+IntRect ShadowBlur::calculateLayerBoundingRect(GraphicsContext& context, const FloatRect& shadowedRect, const IntRect& clipRect)
 {
-    const float roundedRadius = ceilf(m_blurRadius);
+    IntSize edgeSize = blurredEdgeSize();
 
     // Calculate the destination of the blurred and/or transformed layer.
-    FloatRect layerFloatRect;
-    float inflation = 0;
+    FloatRect layerRect;
+    IntSize inflation;
 
-    const AffineTransform transform = context->getCTM();
+    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();
+        layerRect = transform.inverse().mapQuad(transformedPolygon).boundingBox();
     } else {
-        layerFloatRect = shadowedRect;
-        layerFloatRect.move(m_offset);
+        layerRect = shadowedRect;
+        layerRect.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(roundedRadius);
-        inflation += roundedRadius;
+        layerRect.inflateX(edgeSize.width());
+        layerRect.inflateY(edgeSize.height());
+        inflation = edgeSize;
     }
 
-    FloatRect unclippedLayerRect = layerFloatRect;
+    FloatRect unclippedLayerRect = layerRect;
 
-    if (!clipRect.contains(enclosingIntRect(layerFloatRect))) {
+    if (!clipRect.contains(enclosingIntRect(layerRect))) {
         // If we are totally outside the clip region, we aren't painting at all.
-        if (intersection(layerFloatRect, clipRect).isEmpty())
+        if (intersection(layerRect, 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(roundedRadius);
+        if (m_type == BlurShadow) {
+            inflatedClip.inflateX(edgeSize.width());
+            inflatedClip.inflateY(edgeSize.height());
+        } else {
+            // Enlarge the clipping area 1 pixel so that the fill does not
+            // bleed (due to antialiasing) even if the unaligned clip rect occurred
+            inflatedClip.inflateX(1);
+            inflatedClip.inflateY(1);
+        }
         
-        layerFloatRect.intersect(inflatedClip);
+        layerRect.intersect(inflatedClip);
     }
 
-    const int frameSize = inflation * 2;
-    m_sourceRect = FloatRect(0, 0, shadowedRect.width() + frameSize, shadowedRect.height() + frameSize);
-    m_layerOrigin = FloatPoint(layerFloatRect.x(), layerFloatRect.y());
-    m_layerSize = layerFloatRect.size();
+    IntSize frameSize = inflation;
+    frameSize.scale(2);
+    m_sourceRect = FloatRect(0, 0, shadowedRect.width() + frameSize.width(), shadowedRect.height() + frameSize.height());
+    m_layerOrigin = FloatPoint(layerRect.x(), layerRect.y());
+    m_layerSize = layerRect.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());
+    float translationX = -shadowedRect.x() + inflation.width() - fabsf(clippedOut.width());
+    float translationY = -shadowedRect.y() + inflation.height() - fabsf(clippedOut.height());
     m_layerContextTranslation = FloatSize(translationX, translationY);
 
-    return enclosingIntRect(layerFloatRect);
+    return enclosingIntRect(layerRect);
 }
 
-GraphicsContext* ShadowBlur::beginShadowLayer(GraphicsContext* graphicsContext, const FloatRect& shadowedRect)
+void ShadowBlur::drawShadowBuffer(GraphicsContext& graphicsContext)
 {
-    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();
+    if (!m_layerImage)
+        return;
 
-    layerContext->save(); // Balanced by restore() in endShadowLayer().
+    GraphicsContextStateSaver stateSaver(graphicsContext);
 
-    // 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);
+    IntSize bufferSize = m_layerImage->internalSize();
+    if (bufferSize != m_layerSize) {
+        // The rect passed to clipToImageBuffer() has to be the size of the entire buffer,
+        // but we may not have cleared it all, so clip to the filled part first.
+        graphicsContext.clip(FloatRect(m_layerOrigin, m_layerSize));
+    }
+    graphicsContext.clipToImageBuffer(*m_layerImage, FloatRect(m_layerOrigin, bufferSize));
+    graphicsContext.setFillColor(m_color);
 
-    return layerContext;
+    graphicsContext.clearShadow();
+    graphicsContext.fillRect(FloatRect(m_layerOrigin, m_sourceRect.size()));
 }
 
-void ShadowBlur::endShadowLayer(GraphicsContext* graphicsContext)
+static void computeSliceSizesFromRadii(const IntSize& twiceRadius, const FloatRoundedRect::Radii& radii, int& leftSlice, int& rightSlice, int& topSlice, int& bottomSlice)
 {
-    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());
-    }
+    leftSlice = twiceRadius.width() + std::max(radii.topLeft().width(), radii.bottomLeft().width());
+    rightSlice = twiceRadius.width() + std::max(radii.topRight().width(), radii.bottomRight().width());
 
-    graphicsContext->save();
+    topSlice = twiceRadius.height() + std::max(radii.topLeft().height(), radii.topRight().height());
+    bottomSlice = twiceRadius.height() + std::max(radii.bottomLeft().height(), radii.bottomRight().height());
+}
 
-    graphicsContext->clipToImageBuffer(m_layerImage, FloatRect(m_layerOrigin, m_layerImage->size()));
-    graphicsContext->setFillColor(m_color, m_colorSpace);
+IntSize ShadowBlur::templateSize(const IntSize& radiusPadding, const FloatRoundedRect::Radii& radii) const
+{
+    const int templateSideLength = 1;
 
-    graphicsContext->clearShadow();
-    graphicsContext->fillRect(FloatRect(m_layerOrigin, m_sourceRect.size()));
+    int leftSlice;
+    int rightSlice;
+    int topSlice;
+    int bottomSlice;
     
-    graphicsContext->restore();
+    IntSize blurExpansion = radiusPadding;
+    blurExpansion.scale(2);
 
-    m_layerImage = 0;
-
-    // Schedule a purge of the scratch buffer. We do not need to destroy the surface.
-    ScratchBuffer::shared().scheduleScratchBufferPurge();
+    computeSliceSizesFromRadii(blurExpansion, radii, leftSlice, rightSlice, topSlice, bottomSlice);
+    
+    return IntSize(templateSideLength + leftSlice + rightSlice,
+                   templateSideLength + topSlice + bottomSlice);
 }
 
-void ShadowBlur::drawRectShadow(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii)
+void ShadowBlur::drawRectShadow(GraphicsContext& graphicsContext, const FloatRoundedRect& shadowedRect)
 {
-    // drawShadowedRect does not work with rotations.
+    IntRect layerRect = calculateLayerBoundingRect(graphicsContext, shadowedRect.rect(), graphicsContext.clipBounds());
+    if (layerRect.isEmpty())
+        return;
+
+    adjustBlurRadius(graphicsContext);
+
+    // drawRectShadowWithTiling 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);
+    if (!graphicsContext.getCTM().preservesAxisAlignment() || m_type != BlurShadow) {
+        drawRectShadowWithoutTiling(graphicsContext, shadowedRect, layerRect);
         return;
     }
 
-    const float roundedRadius = ceilf(m_blurRadius);
-    const float twiceRadius = roundedRadius * 2;
-    
-    const int templateSideLength = 1;
-    
-    // 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);
+    IntSize edgeSize = blurredEdgeSize();
+    IntSize templateSize = this->templateSize(edgeSize, shadowedRect.radii());
+    const FloatRect& rect = shadowedRect.rect();
 
-    if (shadowTemplateSize.width() > shadowedRect.width() || shadowTemplateSize.height() > shadowedRect.height()) {
-        drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, 1);
+    if (templateSize.width() > rect.width() || templateSize.height() > rect.height()
+        || (templateSize.width() * templateSize.height() > m_sourceRect.width() * m_sourceRect.height())) {
+        drawRectShadowWithoutTiling(graphicsContext, shadowedRect, layerRect);
         return;
     }
 
-    // Determine dimensions of shadow rect.
-    FloatRect shadowRect = shadowedRect;
-    shadowRect.inflate(roundedRadius); // FIXME: duplicating code with calculateLayerBoundingRect.
+    drawRectShadowWithTiling(graphicsContext, shadowedRect, templateSize, edgeSize);
+}
+
+void ShadowBlur::drawInsetShadow(GraphicsContext& graphicsContext, const FloatRect& rect, const FloatRoundedRect& holeRect)
+{
+    IntRect layerRect = calculateLayerBoundingRect(graphicsContext, rect, graphicsContext.clipBounds());
+    if (layerRect.isEmpty())
+        return;
+
+    adjustBlurRadius(graphicsContext);
 
-    // Reduce the size of what we have to draw with the clip area.
-    calculateLayerBoundingRect(graphicsContext, shadowedRect, graphicsContext->clipBounds());
+    // drawInsetShadowWithTiling does not work with rotations.
+    // https://bugs.webkit.org/show_bug.cgi?id=45042
+    if (!graphicsContext.getCTM().preservesAxisAlignment() || m_type != BlurShadow) {
+        drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, layerRect);
+        return;
+    }
 
-    // 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);
+    IntSize edgeSize = blurredEdgeSize();
+    IntSize templateSize = this->templateSize(edgeSize, holeRect.radii());
+    const FloatRect& hRect = holeRect.rect();
+
+    if (templateSize.width() > hRect.width() || templateSize.height() > hRect.height()
+        || (templateSize.width() * templateSize.height() > hRect.width() * hRect.height())) {
+        drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, layerRect);
         return;
     }
-    
-    drawRectShadowWithTiling(graphicsContext, shadowedRect, radii, 1, shadowTemplateSize);
+
+    drawInsetShadowWithTiling(graphicsContext, rect, holeRect, templateSize, edgeSize);
 }
 
-void ShadowBlur::drawInsetShadow(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& holeRadii)
+void ShadowBlur::drawRectShadowWithoutTiling(GraphicsContext& graphicsContext, const FloatRoundedRect& shadowedRect, const IntRect& layerRect)
 {
-    // FIXME: add a tiling code path here.
-    GraphicsContext* shadowContext = beginShadowLayer(graphicsContext, rect);
-    if (!shadowContext)
+    m_layerImage = ScratchBuffer::singleton().getScratchBuffer(layerRect.size());
+    if (!m_layerImage)
         return;
 
-    Path path;
-    path.addRect(rect);
-    path.addRoundedRect(holeRect, holeRadii.topLeft(), holeRadii.topRight(), holeRadii.bottomLeft(), holeRadii.bottomRight());
+    FloatRect bufferRelativeShadowedRect = shadowedRect.rect();
+    bufferRelativeShadowedRect.move(m_layerContextTranslation);
+
+    // Only redraw in the scratch buffer if its cached contents don't match our needs
+    bool redrawNeeded = ScratchBuffer::singleton().setCachedShadowValues(m_blurRadius, Color::black, bufferRelativeShadowedRect, shadowedRect.radii(), m_layerSize);
+    if (redrawNeeded) {
+        GraphicsContext& shadowContext = m_layerImage->context();
+        GraphicsContextStateSaver stateSaver(shadowContext);
+
+        // Add a pixel to avoid later edge aliasing when rotated.
+        shadowContext.clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1));
+        shadowContext.translate(m_layerContextTranslation);
+        shadowContext.setFillColor(Color::black);
+        if (shadowedRect.radii().isZero())
+            shadowContext.fillRect(shadowedRect.rect());
+        else {
+            Path path;
+            path.addRoundedRect(shadowedRect);
+            shadowContext.fillPath(path);
+        }
 
-    shadowContext->setFillRule(RULE_EVENODD);
-    shadowContext->setFillColor(Color(.0f, .0f, .0f, 1.f), ColorSpaceDeviceRGB);
-    shadowContext->fillPath(path);
+        blurShadowBuffer(expandedIntSize(m_layerSize));
+    }
     
-    endShadowLayer(graphicsContext);
+    drawShadowBuffer(graphicsContext);
+    m_layerImage = nullptr;
+    ScratchBuffer::singleton().scheduleScratchBufferPurge();
 }
 
-void ShadowBlur::drawRectShadowWithoutTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii, float alpha)
+void ShadowBlur::drawInsetShadowWithoutTiling(GraphicsContext& graphicsContext, const FloatRect& rect, const FloatRoundedRect& holeRect, const IntRect& layerRect)
 {
-    GraphicsContext* shadowContext = beginShadowLayer(graphicsContext, shadowedRect);
-    if (!shadowContext)
+    m_layerImage = ScratchBuffer::singleton().getScratchBuffer(layerRect.size());
+    if (!m_layerImage)
         return;
 
-    Path path;
-    path.addRoundedRect(shadowedRect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
+    FloatRect bufferRelativeRect = rect;
+    bufferRelativeRect.move(m_layerContextTranslation);
+
+    FloatRect bufferRelativeHoleRect = holeRect.rect();
+    bufferRelativeHoleRect.move(m_layerContextTranslation);
+
+    // Only redraw in the scratch buffer if its cached contents don't match our needs
+    bool redrawNeeded = ScratchBuffer::singleton().setCachedInsetShadowValues(m_blurRadius, Color::black, bufferRelativeRect, bufferRelativeHoleRect, holeRect.radii());
+    if (redrawNeeded) {
+        GraphicsContext& shadowContext = m_layerImage->context();
+        GraphicsContextStateSaver stateSaver(shadowContext);
+
+        // Add a pixel to avoid later edge aliasing when rotated.
+        shadowContext.clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1));
+        shadowContext.translate(m_layerContextTranslation);
+
+        Path path;
+        path.addRect(rect);
+        if (holeRect.radii().isZero())
+            path.addRect(holeRect.rect());
+        else
+            path.addRoundedRect(holeRect);
 
-    shadowContext->setFillColor(Color(.0f, .0f, .0f, alpha), ColorSpaceDeviceRGB);
-    shadowContext->fillPath(path);
+        shadowContext.setFillRule(RULE_EVENODD);
+        shadowContext.setFillColor(Color::black);
+        shadowContext.fillPath(path);
 
-    endShadowLayer(graphicsContext);
+        blurShadowBuffer(expandedIntSize(m_layerSize));
+    }
+    
+    drawShadowBuffer(graphicsContext);
+    m_layerImage = nullptr;
+    ScratchBuffer::singleton().scheduleScratchBufferPurge();
 }
 
 /*
-  This function uses tiling to improve the performance of the shadow
+  These functions use tiling to improve the performance of the shadow
   drawing of rounded rectangles. The code basically does the following
   steps:
 
@@ -465,145 +657,262 @@ void ShadowBlur::drawRectShadowWithoutTiling(GraphicsContext* graphicsContext, c
 
      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.
+     effect. We fill the central or outer 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)
+void ShadowBlur::drawInsetShadowWithTiling(GraphicsContext& graphicsContext, const FloatRect& rect, const FloatRoundedRect& holeRect, const IntSize& templateSize, const IntSize& edgeSize)
 {
-    const float roundedRadius = ceilf(m_blurRadius);
-    const float twiceRadius = roundedRadius * 2;
+    m_layerImage = ScratchBuffer::singleton().getScratchBuffer(templateSize);
+    if (!m_layerImage)
+        return;
 
-    // Size of the tiling side.
-    const int templateSideLength = 1;
+    // Draw the rectangle with hole.
+    FloatRect templateBounds(0, 0, templateSize.width(), templateSize.height());
+    FloatRect templateHole = FloatRect(edgeSize.width(), edgeSize.height(), templateSize.width() - 2 * edgeSize.width(), templateSize.height() - 2 * edgeSize.height());
+
+    // Only redraw in the scratch buffer if its cached contents don't match our needs
+    bool redrawNeeded = ScratchBuffer::singleton().setCachedInsetShadowValues(m_blurRadius, m_color, templateBounds, templateHole, holeRect.radii());
+    if (redrawNeeded) {
+        // Draw shadow into a new ImageBuffer.
+        GraphicsContext& shadowContext = m_layerImage->context();
+        GraphicsContextStateSaver shadowStateSaver(shadowContext);
+        shadowContext.clearRect(templateBounds);
+        shadowContext.setFillRule(RULE_EVENODD);
+        shadowContext.setFillColor(Color::black);
+
+        Path path;
+        path.addRect(templateBounds);
+        if (holeRect.radii().isZero())
+            path.addRect(templateHole);
+        else
+            path.addRoundedRect(FloatRoundedRect(templateHole, holeRect.radii()));
+
+        shadowContext.fillPath(path);
+
+        blurAndColorShadowBuffer(templateSize);
+    }
+    FloatSize offset = m_offset;
+    if (shadowsIgnoreTransforms()) {
+        AffineTransform transform = graphicsContext.getCTM();
+        offset.scale(1 / transform.xScale(), 1 / transform.yScale());
+    }
 
-    FloatRect shadowRect = shadowedRect;
-    shadowRect.inflate(roundedRadius); // FIXME: duplicating code with calculateLayerBoundingRect.
+    FloatRect boundingRect = rect;
+    boundingRect.move(offset);
 
-    shadowRect.move(m_offset.width(), m_offset.height());
+    FloatRect destHoleRect = holeRect.rect();
+    destHoleRect.move(offset);
+    FloatRect destHoleBounds = destHoleRect;
+    destHoleBounds.inflateX(edgeSize.width());
+    destHoleBounds.inflateY(edgeSize.height());
 
-    m_layerImage = ScratchBuffer::shared().getScratchBuffer(shadowTemplateSize);
+    // Fill the external part of the shadow (which may be visible because of offset).
+    Path exteriorPath;
+    exteriorPath.addRect(boundingRect);
+    exteriorPath.addRect(destHoleBounds);
 
-    // Draw shadow into a new ImageBuffer.
-    GraphicsContext* shadowContext = m_layerImage->context();
-    shadowContext->save();
+    {
+        GraphicsContextStateSaver fillStateSaver(graphicsContext);
+        graphicsContext.setFillRule(RULE_EVENODD);
+        graphicsContext.setFillColor(m_color);
+        graphicsContext.clearShadow();
+        graphicsContext.fillPath(exteriorPath);
+    }
     
-    shadowContext->clearRect(FloatRect(0, 0, shadowTemplateSize.width(), shadowTemplateSize.height()));
+    drawLayerPieces(graphicsContext, destHoleBounds, holeRect.radii(), edgeSize, templateSize, InnerShadow);
 
-    // Draw the rectangle.
-    FloatRect templateRect = FloatRect(roundedRadius, roundedRadius, shadowTemplateSize.width() - twiceRadius, shadowTemplateSize.height() - twiceRadius);
-    Path path;
-    path.addRoundedRect(templateRect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
+    m_layerImage = nullptr;
+    ScratchBuffer::singleton().scheduleScratchBufferPurge();
+}
 
-    shadowContext->setFillColor(Color(.0f, .0f, .0f, alpha), ColorSpaceDeviceRGB);
-    shadowContext->fillPath(path);
+void ShadowBlur::drawRectShadowWithTiling(GraphicsContext& graphicsContext, const FloatRoundedRect& shadowedRect, const IntSize& templateSize, const IntSize& edgeSize)
+{
+    auto& scratchBuffer = ScratchBuffer::singleton();
+    m_layerImage = scratchBuffer.getScratchBuffer(templateSize);
+    if (!m_layerImage)
+        return;
 
-    // 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());
-    }
+    FloatRect templateShadow = FloatRect(edgeSize.width(), edgeSize.height(), templateSize.width() - 2 * edgeSize.width(), templateSize.height() - 2 * edgeSize.height());
 
-    // 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();
+    // Only redraw in the scratch buffer if its cached contents don't match our needs
+    bool redrawNeeded = scratchBuffer.setCachedShadowValues(m_blurRadius, m_color, templateShadow, shadowedRect.radii(), m_layerSize);
+    if (redrawNeeded) {
+        // Draw shadow into the ImageBuffer.
+        GraphicsContext& shadowContext = m_layerImage->context();
+        GraphicsContextStateSaver shadowStateSaver(shadowContext);
 
-    // Fill the internal part of the shadow.
-    shadowRect.inflate(-twiceRadius);
-    if (!shadowRect.isEmpty()) {
-        graphicsContext->save();
+        shadowContext.clearRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
+        shadowContext.setFillColor(Color::black);
         
-        path.clear();
-        path.addRoundedRect(shadowRect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
+        if (shadowedRect.radii().isZero())
+            shadowContext.fillRect(templateShadow);
+        else {
+            Path path;
+            path.addRoundedRect(FloatRoundedRect(templateShadow, shadowedRect.radii()));
+            shadowContext.fillPath(path);
+        }
 
-        graphicsContext->setFillColor(m_color, m_colorSpace);
-        graphicsContext->fillPath(path);
-        
-        graphicsContext->restore();
+        blurAndColorShadowBuffer(templateSize);
+    }
+    FloatSize offset = m_offset;
+    if (shadowsIgnoreTransforms()) {
+        AffineTransform transform = graphicsContext.getCTM();
+        offset.scale(1 / transform.xScale(), 1 / transform.yScale());
+    }
+
+    FloatRect shadowBounds = shadowedRect.rect();
+    shadowBounds.move(offset);
+    shadowBounds.inflateX(edgeSize.width());
+    shadowBounds.inflateY(edgeSize.height());
+
+    drawLayerPieces(graphicsContext, shadowBounds, shadowedRect.radii(), edgeSize, templateSize, OuterShadow);
+
+    m_layerImage = nullptr;
+    ScratchBuffer::singleton().scheduleScratchBufferPurge();
+}
+
+void ShadowBlur::drawLayerPieces(GraphicsContext& graphicsContext, const FloatRect& shadowBounds, const FloatRoundedRect::Radii& radii, const IntSize& bufferPadding, const IntSize& templateSize, ShadowDirection direction)
+{
+    const IntSize twiceRadius = IntSize(bufferPadding.width() * 2, bufferPadding.height() * 2);
+
+    int leftSlice;
+    int rightSlice;
+    int topSlice;
+    int bottomSlice;
+    computeSliceSizesFromRadii(twiceRadius, radii, leftSlice, rightSlice, topSlice, bottomSlice);
+
+    int centerWidth = shadowBounds.width() - leftSlice - rightSlice;
+    int centerHeight = shadowBounds.height() - topSlice - bottomSlice;
+
+    if (direction == OuterShadow) {
+        FloatRect shadowInterior(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight);
+        if (!shadowInterior.isEmpty()) {
+            GraphicsContextStateSaver stateSaver(graphicsContext);
+            graphicsContext.setFillColor(m_color);
+            graphicsContext.clearShadow();
+            graphicsContext.fillRect(shadowInterior);
+        }
     }
-    shadowRect.inflate(twiceRadius);
+
+    GraphicsContextStateSaver stateSaver(graphicsContext);
+    graphicsContext.setFillColor(m_color);
+    graphicsContext.clearShadow();
 
     // 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() - roundedRadius * 4);
-    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+    FloatRect centerRect(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight);
+    centerRect = graphicsContext.roundToDevicePixels(centerRect);
+    
+    // Top side.
+    FloatRect tileRect = FloatRect(leftSlice, 0, templateSideLength, topSlice);
+    FloatRect destRect = FloatRect(centerRect.x(), centerRect.y() - topSlice, centerRect.width(), topSlice);
+    graphicsContext.drawImageBuffer(*m_layerImage, 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() - roundedRadius * 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() - roundedRadius * 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() - roundedRadius * 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();
+    tileRect.setY(templateSize.height() - bottomSlice);
+    tileRect.setHeight(bottomSlice);
+    destRect.setY(centerRect.maxY());
+    destRect.setHeight(bottomSlice);
+    graphicsContext.drawImageBuffer(*m_layerImage, destRect, tileRect);
+
+    // Left side.
+    tileRect = FloatRect(0, topSlice, leftSlice, templateSideLength);
+    destRect = FloatRect(centerRect.x() - leftSlice, centerRect.y(), leftSlice, centerRect.height());
+    graphicsContext.drawImageBuffer(*m_layerImage, destRect, tileRect);
+
+    // Right side.
+    tileRect.setX(templateSize.width() - rightSlice);
+    tileRect.setWidth(rightSlice);
+    destRect.setX(centerRect.maxX());
+    destRect.setWidth(rightSlice);
+    graphicsContext.drawImageBuffer(*m_layerImage, destRect, tileRect);
+
+    // Top left corner.
+    tileRect = FloatRect(0, 0, leftSlice, topSlice);
+    destRect = FloatRect(centerRect.x() - leftSlice, centerRect.y() - topSlice, leftSlice, topSlice);
+    graphicsContext.drawImageBuffer(*m_layerImage, destRect, tileRect);
+
+    // Top right corner.
+    tileRect = FloatRect(templateSize.width() - rightSlice, 0, rightSlice, topSlice);
+    destRect = FloatRect(centerRect.maxX(), centerRect.y() - topSlice, rightSlice, topSlice);
+    graphicsContext.drawImageBuffer(*m_layerImage, destRect, tileRect);
+
+    // Bottom right corner.
+    tileRect = FloatRect(templateSize.width() - rightSlice, templateSize.height() - bottomSlice, rightSlice, bottomSlice);
+    destRect = FloatRect(centerRect.maxX(), centerRect.maxY(), rightSlice, bottomSlice);
+    graphicsContext.drawImageBuffer(*m_layerImage, destRect, tileRect);
+
+    // Bottom left corner.
+    tileRect = FloatRect(0, templateSize.height() - bottomSlice, leftSlice, bottomSlice);
+    destRect = FloatRect(centerRect.x() - leftSlice, centerRect.maxY(), leftSlice, bottomSlice);
+    graphicsContext.drawImageBuffer(*m_layerImage, destRect, tileRect);
 }
 
-#if !PLATFORM(CG) && !PLATFORM(CHROMIUM)
-IntRect ShadowBlur::clipBounds(GraphicsContext*)
+
+void ShadowBlur::blurShadowBuffer(const IntSize& templateSize)
 {
-    // FIXME: add clipBounds() to GraphicsContext.
-    ASSERT_NOT_REACHED();
-    return IntRect();
+    if (m_type != BlurShadow)
+        return;
+
+    IntRect blurRect(IntPoint(), templateSize);
+    RefPtr<Uint8ClampedArray> layerData = m_layerImage->getUnmultipliedImageData(blurRect);
+    blurLayerImage(layerData->data(), blurRect.size(), blurRect.width() * 4);
+    m_layerImage->putByteArray(Unmultiplied, layerData.get(), blurRect.size(), blurRect, IntPoint());
+}
+
+void ShadowBlur::blurAndColorShadowBuffer(const IntSize& templateSize)
+{
+    blurShadowBuffer(templateSize);
+
+    // Mask the image with the shadow color.
+    GraphicsContext& shadowContext = m_layerImage->context();
+    GraphicsContextStateSaver stateSaver(shadowContext);
+    shadowContext.setCompositeOperation(CompositeSourceIn);
+    shadowContext.setFillColor(m_color);
+    shadowContext.fillRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
+}
+
+GraphicsContext* ShadowBlur::beginShadowLayer(GraphicsContext& context, const FloatRect& layerArea)
+{
+    adjustBlurRadius(context);
+
+    IntRect layerRect = calculateLayerBoundingRect(context, layerArea, context.clipBounds());
+
+    if (layerRect.isEmpty())
+        return nullptr;
+
+    // We reset the scratch buffer values here, because the buffer will no longer contain
+    // data from any previous rectangle or inset shadows drawn via the tiling path.
+    auto& scratchBuffer = ScratchBuffer::singleton();
+    scratchBuffer.setCachedShadowValues(FloatSize(), Color::black, IntRect(), FloatRoundedRect::Radii(), m_layerSize);
+    m_layerImage = scratchBuffer.getScratchBuffer(layerRect.size());
+
+    GraphicsContext& shadowContext = m_layerImage->context();
+    shadowContext.save();
+
+    // Add a pixel to avoid later edge aliasing when rotated.
+    shadowContext.clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1));
+
+    shadowContext.translate(m_layerContextTranslation);
+    return &shadowContext;
+}
+
+void ShadowBlur::endShadowLayer(GraphicsContext& context)
+{
+    m_layerImage->context().restore();
+
+    blurAndColorShadowBuffer(expandedIntSize(m_layerSize));
+    GraphicsContextStateSaver stateSave(context);
+
+    context.clearShadow();
+    context.drawImageBuffer(*m_layerImage, FloatRect(roundedIntPoint(m_layerOrigin), m_layerSize), FloatRect(FloatPoint(), m_layerSize), context.compositeOperation());
+
+    m_layerImage = nullptr;
+    ScratchBuffer::singleton().scheduleScratchBufferPurge();
 }
-#endif
 
 } // namespace WebCore