Move locale information into FontDescription
[WebKit-https.git] / Source / WebCore / platform / graphics / ShadowBlur.cpp
index 73ee219..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 "Timer.h"
 #include <wtf/MathExtras.h>
 #include <wtf/Noncopyable.h>
-#include <wtf/UnusedParam.h>
-
-using namespace std;
 
 namespace WebCore {
 
+enum {
+    leftLobe = 0,
+    rightLobe = 1
+};
+
 static inline int roundUpToMultipleOf32(int d)
 {
     return (1 + (d >> 5)) << 5;
@@ -51,9 +54,11 @@ 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
@@ -67,16 +72,49 @@ public:
         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
@@ -89,50 +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;
 }
 
 static const int templateSideLength = 1;
 
-ShadowBlur::ShadowBlur(float radius, const FloatSize& offset, const Color& color, ColorSpace colorSpace)
+#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 (m_blurRadius > 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()) {
@@ -144,40 +232,25 @@ 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);
 
-void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride)
+// Takes a two dimensional array with three rows and two columns for the lobes.
+static void calculateLobes(int lobes[][2], float blurRadius, bool shadowsIgnoreTransforms)
 {
-    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.
+    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 = m_blurRadius / 2;
+        float stdDev = blurRadius / 2;
+        const float gaussianKernelFactor = 3 / 4.f * sqrtf(2 * piFloat);
         const float fudgeFactor = 0.88f;
-        diameter = max(2, static_cast<int>(floorf(stdDev * gaussianKernelFactor * fudgeFactor + 0.5f)));
+        diameter = std::max(2, static_cast<int>(floorf(stdDev * gaussianKernelFactor * fudgeFactor + 0.5f)));
     }
 
-    enum {
-        leftLobe = 0,
-        rightLobe = 1
-    };
-
-    int lobes[3][2]; // indexed by pass, and left/right lobe
-    
     if (diameter & 1) {
         // if d is odd, use three box-blurs of size 'd', centered on the output pixel.
         int lobeSize = (diameter - 1) / 2;
@@ -199,6 +272,22 @@ void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, i
         lobes[2][leftLobe] = lobeSize;
         lobes[2][rightLobe] = lobeSize;
     }
+}
+
+void ShadowBlur::clear()
+{
+    m_type = NoShadow;
+    m_color = Color();
+    m_blurRadius = FloatSize();
+    m_offset = FloatSize();
+}
+
+void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride)
+{
+    const int channels[4] = { 3, 0, 1, 3 };
+
+    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;
@@ -209,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
@@ -263,51 +355,47 @@ 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::adjustBlurRadius(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 layerRect;
-    float inflation = 0;
+    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);
@@ -319,8 +407,9 @@ IntRect ShadowBlur::calculateLayerBoundingRect(GraphicsContext* context, const F
 
     // We expand the area by the blur radius to give extra space for the blur transition.
     if (m_type == BlurShadow) {
-        layerRect.inflate(roundedRadius);
-        inflation = roundedRadius;
+        layerRect.inflateX(edgeSize.width());
+        layerRect.inflateY(edgeSize.height());
+        inflation = edgeSize;
     }
 
     FloatRect unclippedLayerRect = layerRect;
@@ -333,14 +422,22 @@ IntRect ShadowBlur::calculateLayerBoundingRect(GraphicsContext* context, const F
         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);
+        }
         
         layerRect.intersect(inflatedClip);
     }
 
-    const float frameSize = inflation * 2;
-    m_sourceRect = FloatRect(0, 0, shadowedRect.width() + frameSize, shadowedRect.height() + frameSize);
+    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();
 
@@ -349,74 +446,43 @@ IntRect ShadowBlur::calculateLayerBoundingRect(GraphicsContext* context, const F
 
     // 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(layerRect);
 }
 
-GraphicsContext* ShadowBlur::beginShadowLayer(GraphicsContext* graphicsContext, const IntRect& layerRect)
-{
-    adjustBlurRadius(graphicsContext);
-
-    // 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)
+void ShadowBlur::drawShadowBuffer(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);
+    GraphicsContextStateSaver stateSaver(graphicsContext);
 
-    graphicsContext->clearShadow();
-    graphicsContext->fillRect(FloatRect(m_layerOrigin, m_sourceRect.size()));
-    
-    graphicsContext->restore();
-
-    m_layerImage = 0;
+    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);
 
-    // Schedule a purge of the scratch buffer. We do not need to destroy the surface.
-    ScratchBuffer::shared().scheduleScratchBufferPurge();
+    graphicsContext.clearShadow();
+    graphicsContext.fillRect(FloatRect(m_layerOrigin, m_sourceRect.size()));
 }
 
-static void computeSliceSizesFromRadii(int twiceRadius, const RoundedIntRect::Radii& radii, int& leftSlice, int& rightSlice, int& topSlice, int& bottomSlice)
+static void computeSliceSizesFromRadii(const IntSize& twiceRadius, const FloatRoundedRect::Radii& radii, int& leftSlice, int& rightSlice, int& topSlice, int& bottomSlice)
 {
-    leftSlice = twiceRadius + max(radii.topLeft().width(), radii.bottomLeft().width()); 
-    rightSlice = twiceRadius + max(radii.topRight().width(), radii.bottomRight().width()); 
+    leftSlice = twiceRadius.width() + std::max(radii.topLeft().width(), radii.bottomLeft().width());
+    rightSlice = twiceRadius.width() + std::max(radii.topRight().width(), radii.bottomRight().width());
 
-    topSlice = twiceRadius + max(radii.topLeft().height(), radii.topRight().height());
-    bottomSlice = twiceRadius + max(radii.bottomLeft().height(), radii.bottomRight().height());
+    topSlice = twiceRadius.height() + std::max(radii.topLeft().height(), radii.topRight().height());
+    bottomSlice = twiceRadius.height() + std::max(radii.bottomLeft().height(), radii.bottomRight().height());
 }
 
-IntSize ShadowBlur::templateSize(const RoundedIntRect::Radii& radii) const
+IntSize ShadowBlur::templateSize(const IntSize& radiusPadding, const FloatRoundedRect::Radii& radii) const
 {
     const int templateSideLength = 1;
 
@@ -424,90 +490,146 @@ IntSize ShadowBlur::templateSize(const RoundedIntRect::Radii& radii) const
     int rightSlice;
     int topSlice;
     int bottomSlice;
-    computeSliceSizesFromRadii(2 * ceilf(m_blurRadius), radii, leftSlice, rightSlice, topSlice, bottomSlice);
+    
+    IntSize blurExpansion = radiusPadding;
+    blurExpansion.scale(2);
+
+    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)
 {
-    IntRect layerRect = calculateLayerBoundingRect(graphicsContext, shadowedRect, graphicsContext->clipBounds());
+    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, layerRect);
+    if (!graphicsContext.getCTM().preservesAxisAlignment() || m_type != BlurShadow) {
+        drawRectShadowWithoutTiling(graphicsContext, shadowedRect, layerRect);
         return;
     }
 
-    IntSize templateSize = this->templateSize(radii);
+    IntSize edgeSize = blurredEdgeSize();
+    IntSize templateSize = this->templateSize(edgeSize, shadowedRect.radii());
+    const FloatRect& rect = shadowedRect.rect();
 
-    if (templateSize.width() > shadowedRect.width() || templateSize.height() > shadowedRect.height()
+    if (templateSize.width() > rect.width() || templateSize.height() > rect.height()
         || (templateSize.width() * templateSize.height() > m_sourceRect.width() * m_sourceRect.height())) {
-        drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, layerRect);
+        drawRectShadowWithoutTiling(graphicsContext, shadowedRect, layerRect);
         return;
     }
 
-    drawRectShadowWithTiling(graphicsContext, shadowedRect, radii, templateSize);
+    drawRectShadowWithTiling(graphicsContext, shadowedRect, templateSize, edgeSize);
 }
 
-void ShadowBlur::drawInsetShadow(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& holeRadii)
+void ShadowBlur::drawInsetShadow(GraphicsContext& graphicsContext, const FloatRect& rect, const FloatRoundedRect& holeRect)
 {
-    IntRect layerRect = calculateLayerBoundingRect(graphicsContext, rect, graphicsContext->clipBounds());
+    IntRect layerRect = calculateLayerBoundingRect(graphicsContext, rect, graphicsContext.clipBounds());
     if (layerRect.isEmpty())
         return;
 
+    adjustBlurRadius(graphicsContext);
+
     // drawInsetShadowWithTiling does not work with rotations.
     // https://bugs.webkit.org/show_bug.cgi?id=45042
-    if (!graphicsContext->getCTM().isIdentityOrTranslationOrFlipped() || m_type != BlurShadow) {
-        drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, holeRadii, layerRect);
+    if (!graphicsContext.getCTM().preservesAxisAlignment() || m_type != BlurShadow) {
+        drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, layerRect);
         return;
     }
 
-    IntSize templateSize = this->templateSize(holeRadii);
+    IntSize edgeSize = blurredEdgeSize();
+    IntSize templateSize = this->templateSize(edgeSize, holeRect.radii());
+    const FloatRect& hRect = holeRect.rect();
 
-    if (templateSize.width() > holeRect.width() || templateSize.height() > holeRect.height()
-        || (templateSize.width() * templateSize.height() > holeRect.width() * holeRect.height())) {
-        drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, holeRadii, layerRect);
+    if (templateSize.width() > hRect.width() || templateSize.height() > hRect.height()
+        || (templateSize.width() * templateSize.height() > hRect.width() * hRect.height())) {
+        drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, layerRect);
         return;
     }
 
-    drawInsetShadowWithTiling(graphicsContext, rect, holeRect, holeRadii, templateSize);
+    drawInsetShadowWithTiling(graphicsContext, rect, holeRect, templateSize, edgeSize);
 }
 
-void ShadowBlur::drawRectShadowWithoutTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii, const IntRect& layerRect)
+void ShadowBlur::drawRectShadowWithoutTiling(GraphicsContext& graphicsContext, const FloatRoundedRect& shadowedRect, const IntRect& layerRect)
 {
-    GraphicsContext* shadowContext = beginShadowLayer(graphicsContext, layerRect);
-    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 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->setFillColor(Color::black, ColorSpaceDeviceRGB);
-    shadowContext->fillPath(path);
+        blurShadowBuffer(expandedIntSize(m_layerSize));
+    }
     
-    endShadowLayer(graphicsContext);
+    drawShadowBuffer(graphicsContext);
+    m_layerImage = nullptr;
+    ScratchBuffer::singleton().scheduleScratchBufferPurge();
 }
 
-void ShadowBlur::drawInsetShadowWithoutTiling(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& holeRadii, const IntRect& layerRect)
+void ShadowBlur::drawInsetShadowWithoutTiling(GraphicsContext& graphicsContext, const FloatRect& rect, const FloatRoundedRect& holeRect, const IntRect& layerRect)
 {
-    GraphicsContext* shadowContext = beginShadowLayer(graphicsContext, layerRect);
-    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 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);
 
-    shadowContext->setFillRule(RULE_EVENODD);
-    shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
-    shadowContext->fillPath(path);
+        Path path;
+        path.addRect(rect);
+        if (holeRect.radii().isZero())
+            path.addRect(holeRect.rect());
+        else
+            path.addRoundedRect(holeRect);
 
-    endShadowLayer(graphicsContext);
+        shadowContext.setFillRule(RULE_EVENODD);
+        shadowContext.setFillColor(Color::black);
+        shadowContext.fillPath(path);
+
+        blurShadowBuffer(expandedIntSize(m_layerSize));
+    }
+    
+    drawShadowBuffer(graphicsContext);
+    m_layerImage = nullptr;
+    ScratchBuffer::singleton().scheduleScratchBufferPurge();
 }
 
 /*
@@ -542,101 +664,120 @@ void ShadowBlur::drawInsetShadowWithoutTiling(GraphicsContext* graphicsContext,
      the shadow.
  */
 
-void ShadowBlur::drawInsetShadowWithTiling(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& radii, const IntSize& templateSize)
+void ShadowBlur::drawInsetShadowWithTiling(GraphicsContext& graphicsContext, const FloatRect& rect, const FloatRoundedRect& holeRect, const IntSize& templateSize, const IntSize& edgeSize)
 {
-    graphicsContext->save();
-    graphicsContext->clearShadow();
-
-    const float roundedRadius = ceilf(m_blurRadius);
-    const float twiceRadius = roundedRadius * 2;
-
-    m_layerImage = ScratchBuffer::shared().getScratchBuffer(templateSize);
+    m_layerImage = ScratchBuffer::singleton().getScratchBuffer(templateSize);
+    if (!m_layerImage)
+        return;
 
     // Draw the rectangle with hole.
     FloatRect templateBounds(0, 0, templateSize.width(), templateSize.height());
-    FloatRect templateHole = FloatRect(roundedRadius, roundedRadius, templateSize.width() - twiceRadius, templateSize.height() - twiceRadius);
-    Path path;
-    path.addRect(templateBounds);
-    path.addRoundedRect(templateHole, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
-
-    // Draw shadow into a new ImageBuffer.
-    GraphicsContext* shadowContext = m_layerImage->context();
-    shadowContext->save();
-    shadowContext->clearRect(templateBounds);
-    shadowContext->setFillRule(RULE_EVENODD);
-    shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
-    shadowContext->fillPath(path);
-    blurAndColorShadowBuffer(templateSize);
-    shadowContext->restore();
+    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 boundingRect = rect;
-    boundingRect.move(m_offset);
+    boundingRect.move(offset);
 
-    FloatRect destHoleRect = holeRect;
-    destHoleRect.move(m_offset);
+    FloatRect destHoleRect = holeRect.rect();
+    destHoleRect.move(offset);
     FloatRect destHoleBounds = destHoleRect;
-    destHoleBounds.inflate(roundedRadius);
+    destHoleBounds.inflateX(edgeSize.width());
+    destHoleBounds.inflateY(edgeSize.height());
 
     // Fill the external part of the shadow (which may be visible because of offset).
     Path exteriorPath;
     exteriorPath.addRect(boundingRect);
     exteriorPath.addRect(destHoleBounds);
 
-    graphicsContext->save();
-    graphicsContext->setFillRule(RULE_EVENODD);
-    graphicsContext->setFillColor(m_color, m_colorSpace);
-    graphicsContext->fillPath(exteriorPath);
-    graphicsContext->restore();
+    {
+        GraphicsContextStateSaver fillStateSaver(graphicsContext);
+        graphicsContext.setFillRule(RULE_EVENODD);
+        graphicsContext.setFillColor(m_color);
+        graphicsContext.clearShadow();
+        graphicsContext.fillPath(exteriorPath);
+    }
     
-    drawLayerPieces(graphicsContext, destHoleBounds, radii, roundedRadius, templateSize, InnerShadow);
-
-    graphicsContext->restore();
+    drawLayerPieces(graphicsContext, destHoleBounds, holeRect.radii(), edgeSize, templateSize, InnerShadow);
 
-    m_layerImage = 0;
-    // Schedule a purge of the scratch buffer.
-    ScratchBuffer::shared().scheduleScratchBufferPurge();
+    m_layerImage = nullptr;
+    ScratchBuffer::singleton().scheduleScratchBufferPurge();
 }
 
-void ShadowBlur::drawRectShadowWithTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii, const IntSize& templateSize)
+void ShadowBlur::drawRectShadowWithTiling(GraphicsContext& graphicsContext, const FloatRoundedRect& shadowedRect, const IntSize& templateSize, const IntSize& edgeSize)
 {
-    graphicsContext->save();
-    graphicsContext->clearShadow();
-
-    const float roundedRadius = ceilf(m_blurRadius);
-    const float twiceRadius = roundedRadius * 2;
+    auto& scratchBuffer = ScratchBuffer::singleton();
+    m_layerImage = scratchBuffer.getScratchBuffer(templateSize);
+    if (!m_layerImage)
+        return;
 
-    m_layerImage = ScratchBuffer::shared().getScratchBuffer(templateSize);
+    FloatRect templateShadow = FloatRect(edgeSize.width(), edgeSize.height(), templateSize.width() - 2 * edgeSize.width(), templateSize.height() - 2 * edgeSize.height());
 
-    // Draw the rectangle.
-    FloatRect templateShadow = FloatRect(roundedRadius, roundedRadius, templateSize.width() - twiceRadius, templateSize.height() - twiceRadius);
-    Path path;
-    path.addRoundedRect(templateShadow, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
+    // 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);
 
-    // Draw shadow into the ImageBuffer.
-    GraphicsContext* shadowContext = m_layerImage->context();
-    shadowContext->save();
-    shadowContext->clearRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
-    shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
-    shadowContext->fillPath(path);
-    blurAndColorShadowBuffer(templateSize);
-    shadowContext->restore();
+        shadowContext.clearRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
+        shadowContext.setFillColor(Color::black);
+        
+        if (shadowedRect.radii().isZero())
+            shadowContext.fillRect(templateShadow);
+        else {
+            Path path;
+            path.addRoundedRect(FloatRoundedRect(templateShadow, shadowedRect.radii()));
+            shadowContext.fillPath(path);
+        }
 
-    FloatRect shadowBounds = shadowedRect;
-    shadowBounds.move(m_offset.width(), m_offset.height());
-    shadowBounds.inflate(roundedRadius);
+        blurAndColorShadowBuffer(templateSize);
+    }
+    FloatSize offset = m_offset;
+    if (shadowsIgnoreTransforms()) {
+        AffineTransform transform = graphicsContext.getCTM();
+        offset.scale(1 / transform.xScale(), 1 / transform.yScale());
+    }
 
-    drawLayerPieces(graphicsContext, shadowBounds, radii, roundedRadius, templateSize, OuterShadow);
+    FloatRect shadowBounds = shadowedRect.rect();
+    shadowBounds.move(offset);
+    shadowBounds.inflateX(edgeSize.width());
+    shadowBounds.inflateY(edgeSize.height());
 
-    graphicsContext->restore();
+    drawLayerPieces(graphicsContext, shadowBounds, shadowedRect.radii(), edgeSize, templateSize, OuterShadow);
 
-    m_layerImage = 0;
-    // Schedule a purge of the scratch buffer.
-    ScratchBuffer::shared().scheduleScratchBufferPurge();
+    m_layerImage = nullptr;
+    ScratchBuffer::singleton().scheduleScratchBufferPurge();
 }
 
-void ShadowBlur::drawLayerPieces(GraphicsContext* graphicsContext, const FloatRect& shadowBounds, const RoundedIntRect::Radii& radii, float roundedRadius, const IntSize& templateSize, ShadowDirection direction)
+void ShadowBlur::drawLayerPieces(GraphicsContext& graphicsContext, const FloatRect& shadowBounds, const FloatRoundedRect::Radii& radii, const IntSize& bufferPadding, const IntSize& templateSize, ShadowDirection direction)
 {
-    const float twiceRadius = roundedRadius * 2;
+    const IntSize twiceRadius = IntSize(bufferPadding.width() * 2, bufferPadding.height() * 2);
 
     int leftSlice;
     int rightSlice;
@@ -650,78 +791,128 @@ void ShadowBlur::drawLayerPieces(GraphicsContext* graphicsContext, const FloatRe
     if (direction == OuterShadow) {
         FloatRect shadowInterior(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight);
         if (!shadowInterior.isEmpty()) {
-            graphicsContext->save();
-            
-            graphicsContext->setFillColor(m_color, m_colorSpace);
-            graphicsContext->fillRect(shadowInterior);
-            
-            graphicsContext->restore();
+            GraphicsContextStateSaver stateSaver(graphicsContext);
+            graphicsContext.setFillColor(m_color);
+            graphicsContext.clearShadow();
+            graphicsContext.fillRect(shadowInterior);
         }
     }
 
+    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.
+    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(shadowBounds.x() + leftSlice, shadowBounds.y(), centerWidth, topSlice);
-    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+    FloatRect destRect = FloatRect(centerRect.x(), centerRect.y() - topSlice, centerRect.width(), topSlice);
+    graphicsContext.drawImageBuffer(*m_layerImage, destRect, tileRect);
 
     // Draw the bottom side.
     tileRect.setY(templateSize.height() - bottomSlice);
     tileRect.setHeight(bottomSlice);
-    destRect.setY(shadowBounds.maxY() - bottomSlice);
+    destRect.setY(centerRect.maxY());
     destRect.setHeight(bottomSlice);
-    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+    graphicsContext.drawImageBuffer(*m_layerImage, destRect, tileRect);
 
     // Left side.
     tileRect = FloatRect(0, topSlice, leftSlice, templateSideLength);
-    destRect = FloatRect(shadowBounds.x(), shadowBounds.y() + topSlice, leftSlice, centerHeight);
-    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+    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(shadowBounds.maxX() - rightSlice);
+    destRect.setX(centerRect.maxX());
     destRect.setWidth(rightSlice);
-    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+    graphicsContext.drawImageBuffer(*m_layerImage, destRect, tileRect);
 
     // Top left corner.
     tileRect = FloatRect(0, 0, leftSlice, topSlice);
-    destRect = FloatRect(shadowBounds.x(), shadowBounds.y(), leftSlice, topSlice);
-    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+    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(shadowBounds.maxX() - rightSlice, shadowBounds.y(), rightSlice, topSlice);
-    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+    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(shadowBounds.maxX() - rightSlice, shadowBounds.maxY() - bottomSlice, rightSlice, bottomSlice);
-    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+    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(shadowBounds.x(), shadowBounds.maxY() - bottomSlice, leftSlice, bottomSlice);
-    graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
+    destRect = FloatRect(centerRect.x() - leftSlice, centerRect.maxY(), leftSlice, bottomSlice);
+    graphicsContext.drawImageBuffer(*m_layerImage, destRect, tileRect);
 }
 
 
+void ShadowBlur::blurShadowBuffer(const IntSize& templateSize)
+{
+    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)
 {
-    {
-        IntRect blurRect(IntPoint(), templateSize);
-        RefPtr<ByteArray> layerData = m_layerImage->getUnmultipliedImageData(blurRect);
-        blurLayerImage(layerData->data(), blurRect.size(), blurRect.width() * 4);
-        m_layerImage->putUnmultipliedImageData(layerData.get(), blurRect.size(), blurRect, IntPoint());
-    }
+    blurShadowBuffer(templateSize);
 
     // Mask the image with the shadow color.
-    GraphicsContext* shadowContext = m_layerImage->context();
-    shadowContext->setCompositeOperation(CompositeSourceIn);
-    shadowContext->setFillColor(m_color, m_colorSpace);
-    shadowContext->fillRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
+    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();
 }
 
 } // namespace WebCore