Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2011 Apple Inc.
      3  * Copyright (C) 2010 Sencha, Inc.
      4  * Copyright (C) 2010 Igalia S.L.
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
     17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     20  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     21  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     23  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     24  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 #include "config.h"
     30 #include "ShadowBlur.h"
     31 
     32 #include "AffineTransform.h"
     33 #include "FloatQuad.h"
     34 #include "GraphicsContext.h"
     35 #include "ImageBuffer.h"
     36 #include "Timer.h"
     37 #include <wtf/MathExtras.h>
     38 #include <wtf/Noncopyable.h>
     39 #include <wtf/UnusedParam.h>
     40 
     41 using namespace std;
     42 
     43 namespace WebCore {
     44 
     45 static inline int roundUpToMultipleOf32(int d)
     46 {
     47     return (1 + (d >> 5)) << 5;
     48 }
     49 
     50 // ShadowBlur needs a scratch image as the buffer for the blur filter.
     51 // Instead of creating and destroying the buffer for every operation,
     52 // we create a buffer which will be automatically purged via a timer.
     53 class ScratchBuffer {
     54 public:
     55     ScratchBuffer()
     56         : m_purgeTimer(this, &ScratchBuffer::timerFired)
     57         , m_lastRadius(0)
     58         , m_lastWasInset(false)
     59 #if !ASSERT_DISABLED
     60         , m_bufferInUse(false)
     61 #endif
     62     {
     63     }
     64 
     65     ImageBuffer* getScratchBuffer(const IntSize& size)
     66     {
     67         ASSERT(!m_bufferInUse);
     68 #if !ASSERT_DISABLED
     69         m_bufferInUse = true;
     70 #endif
     71         // We do not need to recreate the buffer if the current buffer is large enough.
     72         if (m_imageBuffer && m_imageBuffer->width() >= size.width() && m_imageBuffer->height() >= size.height())
     73             return m_imageBuffer.get();
     74 
     75         // Round to the nearest 32 pixels so we do not grow the buffer for similar sized requests.
     76         IntSize roundedSize(roundUpToMultipleOf32(size.width()), roundUpToMultipleOf32(size.height()));
     77 
     78         m_imageBuffer = ImageBuffer::create(roundedSize);
     79         return m_imageBuffer.get();
     80     }
     81 
     82     void setLastShadowValues(float radius, const Color& color, ColorSpace colorSpace, const FloatRect& shadowRect, const RoundedIntRect::Radii& radii)
     83     {
     84         m_lastWasInset = false;
     85         m_lastRadius = radius;
     86         m_lastColor = color;
     87         m_lastColorSpace = colorSpace;
     88         m_lastShadowRect = shadowRect;
     89         m_lastRadii = radii;
     90     }
     91 
     92     void setLastInsetShadowValues(float radius, const Color& color, ColorSpace colorSpace, const FloatRect& bounds, const FloatRect& shadowRect, const RoundedIntRect::Radii& radii)
     93     {
     94         m_lastWasInset = true;
     95         m_lastInsetBounds = bounds;
     96         m_lastRadius = radius;
     97         m_lastColor = color;
     98         m_lastColorSpace = colorSpace;
     99         m_lastShadowRect = shadowRect;
    100         m_lastRadii = radii;
    101     }
    102 
    103     bool matchesLastShadow(float radius, const Color& color, ColorSpace colorSpace, const FloatRect& shadowRect, const RoundedIntRect::Radii& radii) const
    104     {
    105         if (m_lastWasInset)
    106             return false;
    107         return m_lastRadius == radius && m_lastColor == color && m_lastColorSpace == colorSpace && shadowRect == m_lastShadowRect && radii == m_lastRadii;
    108     }
    109 
    110     bool matchesLastInsetShadow(float radius, const Color& color, ColorSpace colorSpace, const FloatRect& bounds, const FloatRect& shadowRect, const RoundedIntRect::Radii& radii) const
    111     {
    112         if (!m_lastWasInset)
    113             return false;
    114         return m_lastRadius == radius && m_lastColor == color && m_lastColorSpace == colorSpace && m_lastInsetBounds == bounds && shadowRect == m_lastShadowRect && radii == m_lastRadii;
    115     }
    116 
    117     void scheduleScratchBufferPurge()
    118     {
    119 #if !ASSERT_DISABLED
    120         m_bufferInUse = false;
    121 #endif
    122         if (m_purgeTimer.isActive())
    123             m_purgeTimer.stop();
    124 
    125         const double scratchBufferPurgeInterval = 2;
    126         m_purgeTimer.startOneShot(scratchBufferPurgeInterval);
    127     }
    128 
    129     static ScratchBuffer& shared();
    130 
    131 private:
    132     void timerFired(Timer<ScratchBuffer>*)
    133     {
    134         clearScratchBuffer();
    135     }
    136 
    137     void clearScratchBuffer()
    138     {
    139         m_imageBuffer = 0;
    140         m_lastRadius = 0;
    141     }
    142 
    143     OwnPtr<ImageBuffer> m_imageBuffer;
    144     Timer<ScratchBuffer> m_purgeTimer;
    145 
    146     FloatRect m_lastInsetBounds;
    147     FloatRect m_lastShadowRect;
    148     RoundedIntRect::Radii m_lastRadii;
    149     Color m_lastColor;
    150     ColorSpace m_lastColorSpace;
    151     float m_lastRadius;
    152     bool m_lastWasInset;
    153 
    154 #if !ASSERT_DISABLED
    155     bool m_bufferInUse;
    156 #endif
    157 };
    158 
    159 ScratchBuffer& ScratchBuffer::shared()
    160 {
    161     DEFINE_STATIC_LOCAL(ScratchBuffer, scratchBuffer, ());
    162     return scratchBuffer;
    163 }
    164 
    165 static const int templateSideLength = 1;
    166 
    167 ShadowBlur::ShadowBlur(float radius, const FloatSize& offset, const Color& color, ColorSpace colorSpace)
    168     : m_color(color)
    169     , m_colorSpace(colorSpace)
    170     , m_blurRadius(radius)
    171     , m_offset(offset)
    172     , m_layerImage(0)
    173     , m_shadowsIgnoreTransforms(false)
    174 {
    175     // Limit blur radius to 128 to avoid lots of very expensive blurring.
    176     m_blurRadius = min<float>(m_blurRadius, 128);
    177 
    178     // The type of shadow is decided by the blur radius, shadow offset, and shadow color.
    179     if (!m_color.isValid() || !color.alpha()) {
    180         // Can't paint the shadow with invalid or invisible color.
    181         m_type = NoShadow;
    182     } else if (m_blurRadius > 0) {
    183         // Shadow is always blurred, even the offset is zero.
    184         m_type = BlurShadow;
    185     } else if (!m_offset.width() && !m_offset.height()) {
    186         // Without blur and zero offset means the shadow is fully hidden.
    187         m_type = NoShadow;
    188     } else
    189         m_type = SolidShadow;
    190 }
    191 
    192 // Instead of integer division, we use 17.15 for fixed-point division.
    193 static const int blurSumShift = 15;
    194 
    195 void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride)
    196 {
    197     const int channels[4] = { 3, 0, 1, 3 };
    198 
    199     int diameter;
    200     if (m_shadowsIgnoreTransforms)
    201         diameter = max(2, static_cast<int>(floorf((2 / 3.f) * m_blurRadius))); // Canvas shadow. FIXME: we should adjust the blur radius higher up.
    202     else {
    203         // http://dev.w3.org/csswg/css3-background/#box-shadow
    204         // Approximate a Gaussian blur with a standard deviation equal to half the blur radius,
    205         // which http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement tell us how to do.
    206         // However, shadows rendered according to that spec will extend a little further than m_blurRadius,
    207         // so we apply a fudge factor to bring the radius down slightly.
    208         float stdDev = m_blurRadius / 2;
    209         const float gaussianKernelFactor = 3 / 4.f * sqrtf(2 * piFloat);
    210         const float fudgeFactor = 0.88f;
    211         diameter = max(2, static_cast<int>(floorf(stdDev * gaussianKernelFactor * fudgeFactor + 0.5f)));
    212     }
    213 
    214     enum {
    215         leftLobe = 0,
    216         rightLobe = 1
    217     };
    218 
    219     int lobes[3][2]; // indexed by pass, and left/right lobe
    220 
    221     if (diameter & 1) {
    222         // if d is odd, use three box-blurs of size 'd', centered on the output pixel.
    223         int lobeSize = (diameter - 1) / 2;
    224         lobes[0][leftLobe] = lobeSize;
    225         lobes[0][rightLobe] = lobeSize;
    226         lobes[1][leftLobe] = lobeSize;
    227         lobes[1][rightLobe] = lobeSize;
    228         lobes[2][leftLobe] = lobeSize;
    229         lobes[2][rightLobe] = lobeSize;
    230     } else {
    231         // if d is even, two box-blurs of size 'd' (the first one centered on the pixel boundary
    232         // between the output pixel and the one to the left, the second one centered on the pixel
    233         // boundary between the output pixel and the one to the right) and one box blur of size 'd+1' centered on the output pixel
    234         int lobeSize = diameter / 2;
    235         lobes[0][leftLobe] = lobeSize;
    236         lobes[0][rightLobe] = lobeSize - 1;
    237         lobes[1][leftLobe] = lobeSize - 1;
    238         lobes[1][rightLobe] = lobeSize;
    239         lobes[2][leftLobe] = lobeSize;
    240         lobes[2][rightLobe] = lobeSize;
    241     }
    242 
    243     // First pass is horizontal.
    244     int stride = 4;
    245     int delta = rowStride;
    246     int final = size.height();
    247     int dim = size.width();
    248 
    249     // Two stages: horizontal and vertical
    250     for (int pass = 0; pass < 2; ++pass) {
    251         unsigned char* pixels = imageData;
    252 
    253         for (int j = 0; j < final; ++j, pixels += delta) {
    254             // For each step, we blur the alpha in a channel and store the result
    255             // in another channel for the subsequent step.
    256             // We use sliding window algorithm to accumulate the alpha values.
    257             // This is much more efficient than computing the sum of each pixels
    258             // covered by the box kernel size for each x.
    259             for (int step = 0; step < 3; ++step) {
    260                 int side1 = lobes[step][leftLobe];
    261                 int side2 = lobes[step][rightLobe];
    262                 int pixelCount = side1 + 1 + side2;
    263                 int invCount = ((1 << blurSumShift) + pixelCount - 1) / pixelCount;
    264                 int ofs = 1 + side2;
    265                 int alpha1 = pixels[channels[step]];
    266                 int alpha2 = pixels[(dim - 1) * stride + channels[step]];
    267 
    268                 unsigned char* ptr = pixels + channels[step + 1];
    269                 unsigned char* prev = pixels + stride + channels[step];
    270                 unsigned char* next = pixels + ofs * stride + channels[step];
    271 
    272                 int i;
    273                 int sum = side1 * alpha1 + alpha1;
    274                 int limit = (dim < side2 + 1) ? dim : side2 + 1;
    275 
    276                 for (i = 1; i < limit; ++i, prev += stride)
    277                     sum += *prev;
    278 
    279                 if (limit <= side2)
    280                     sum += (side2 - limit + 1) * alpha2;
    281 
    282                 limit = (side1 < dim) ? side1 : dim;
    283                 for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) {
    284                     *ptr = (sum * invCount) >> blurSumShift;
    285                     sum += ((ofs < dim) ? *next : alpha2) - alpha1;
    286                 }
    287 
    288                 prev = pixels + channels[step];
    289                 for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) {
    290                     *ptr = (sum * invCount) >> blurSumShift;
    291                     sum += (*next) - (*prev);
    292                 }
    293 
    294                 for (; i < dim; ptr += stride, prev += stride, ++i) {
    295                     *ptr = (sum * invCount) >> blurSumShift;
    296                     sum += alpha2 - (*prev);
    297                 }
    298             }
    299         }
    300 
    301         // Last pass is vertical.
    302         stride = rowStride;
    303         delta = 4;
    304         final = size.width();
    305         dim = size.height();
    306     }
    307 }
    308 
    309 void ShadowBlur::adjustBlurRadius(GraphicsContext* context)
    310 {
    311     if (!m_shadowsIgnoreTransforms)
    312         return;
    313 
    314     const AffineTransform transform = context->getCTM();
    315 
    316     // Adjust blur if we're scaling, since the radius must not be affected by transformations.
    317     // FIXME: use AffineTransform::isIdentityOrTranslationOrFlipped()?
    318     if (transform.isIdentity())
    319         return;
    320 
    321     // Calculate transformed unit vectors.
    322     const FloatQuad unitQuad(FloatPoint(0, 0), FloatPoint(1, 0),
    323                              FloatPoint(0, 1), FloatPoint(1, 1));
    324     const FloatQuad transformedUnitQuad = transform.mapQuad(unitQuad);
    325 
    326     // Calculate X axis scale factor.
    327     const FloatSize xUnitChange = transformedUnitQuad.p2() - transformedUnitQuad.p1();
    328     const float xAxisScale = sqrtf(xUnitChange.width() * xUnitChange.width()
    329                                    + xUnitChange.height() * xUnitChange.height());
    330 
    331     // Calculate Y axis scale factor.
    332     const FloatSize yUnitChange = transformedUnitQuad.p3() - transformedUnitQuad.p1();
    333     const float yAxisScale = sqrtf(yUnitChange.width() * yUnitChange.width()
    334                                    + yUnitChange.height() * yUnitChange.height());
    335 
    336     // blurLayerImage() does not support per-axis blurring, so calculate a balanced scaling.
    337     // FIXME: does AffineTransform.xScale()/yScale() help?
    338     const float scale = sqrtf(xAxisScale * yAxisScale);
    339     m_blurRadius = roundf(m_blurRadius / scale);
    340 }
    341 
    342 IntRect ShadowBlur::calculateLayerBoundingRect(GraphicsContext* context, const FloatRect& shadowedRect, const IntRect& clipRect)
    343 {
    344     const float roundedRadius = ceilf(m_blurRadius);
    345 
    346     // Calculate the destination of the blurred and/or transformed layer.
    347     FloatRect layerRect;
    348     float inflation = 0;
    349 
    350     const AffineTransform transform = context->getCTM();
    351     if (m_shadowsIgnoreTransforms && !transform.isIdentity()) {
    352         FloatQuad transformedPolygon = transform.mapQuad(FloatQuad(shadowedRect));
    353         transformedPolygon.move(m_offset);
    354         layerRect = transform.inverse().mapQuad(transformedPolygon).boundingBox();
    355     } else {
    356         layerRect = shadowedRect;
    357         layerRect.move(m_offset);
    358     }
    359 
    360     // We expand the area by the blur radius to give extra space for the blur transition.
    361     if (m_type == BlurShadow) {
    362         layerRect.inflate(roundedRadius);
    363         inflation = roundedRadius;
    364     }
    365 
    366     FloatRect unclippedLayerRect = layerRect;
    367 
    368     if (!clipRect.contains(enclosingIntRect(layerRect))) {
    369         // If we are totally outside the clip region, we aren't painting at all.
    370         if (intersection(layerRect, clipRect).isEmpty())
    371             return IntRect();
    372 
    373         IntRect inflatedClip = clipRect;
    374         // Pixels at the edges can be affected by pixels outside the buffer,
    375         // so intersect with the clip inflated by the blur.
    376         if (m_type == BlurShadow)
    377             inflatedClip.inflate(roundedRadius);
    378 
    379         layerRect.intersect(inflatedClip);
    380     }
    381 
    382     const float frameSize = inflation * 2;
    383     m_sourceRect = FloatRect(0, 0, shadowedRect.width() + frameSize, shadowedRect.height() + frameSize);
    384     m_layerOrigin = FloatPoint(layerRect.x(), layerRect.y());
    385     m_layerSize = layerRect.size();
    386 
    387     const FloatPoint unclippedLayerOrigin = FloatPoint(unclippedLayerRect.x(), unclippedLayerRect.y());
    388     const FloatSize clippedOut = unclippedLayerOrigin - m_layerOrigin;
    389 
    390     // Set the origin as the top left corner of the scratch image, or, in case there's a clipped
    391     // out region, set the origin accordingly to the full bounding rect's top-left corner.
    392     float translationX = -shadowedRect.x() + inflation - fabsf(clippedOut.width());
    393     float translationY = -shadowedRect.y() + inflation - fabsf(clippedOut.height());
    394     m_layerContextTranslation = FloatSize(translationX, translationY);
    395 
    396     return enclosingIntRect(layerRect);
    397 }
    398 
    399 void ShadowBlur::drawShadowBuffer(GraphicsContext* graphicsContext)
    400 {
    401     if (!m_layerImage)
    402         return;
    403 
    404     graphicsContext->save();
    405 
    406     IntSize bufferSize = m_layerImage->size();
    407     if (bufferSize != m_layerSize) {
    408         // The rect passed to clipToImageBuffer() has to be the size of the entire buffer,
    409         // but we may not have cleared it all, so clip to the filled part first.
    410         graphicsContext->clip(FloatRect(m_layerOrigin, m_layerSize));
    411     }
    412     graphicsContext->clipToImageBuffer(m_layerImage, FloatRect(m_layerOrigin, bufferSize));
    413     graphicsContext->setFillColor(m_color, m_colorSpace);
    414 
    415     graphicsContext->clearShadow();
    416     graphicsContext->fillRect(FloatRect(m_layerOrigin, m_sourceRect.size()));
    417 
    418     graphicsContext->restore();
    419 }
    420 
    421 static void computeSliceSizesFromRadii(int twiceRadius, const RoundedIntRect::Radii& radii, int& leftSlice, int& rightSlice, int& topSlice, int& bottomSlice)
    422 {
    423     leftSlice = twiceRadius + max(radii.topLeft().width(), radii.bottomLeft().width());
    424     rightSlice = twiceRadius + max(radii.topRight().width(), radii.bottomRight().width());
    425 
    426     topSlice = twiceRadius + max(radii.topLeft().height(), radii.topRight().height());
    427     bottomSlice = twiceRadius + max(radii.bottomLeft().height(), radii.bottomRight().height());
    428 }
    429 
    430 IntSize ShadowBlur::templateSize(const RoundedIntRect::Radii& radii) const
    431 {
    432     const int templateSideLength = 1;
    433 
    434     int leftSlice;
    435     int rightSlice;
    436     int topSlice;
    437     int bottomSlice;
    438     computeSliceSizesFromRadii(2 * ceilf(m_blurRadius), radii, leftSlice, rightSlice, topSlice, bottomSlice);
    439 
    440     return IntSize(templateSideLength + leftSlice + rightSlice,
    441                    templateSideLength + topSlice + bottomSlice);
    442 }
    443 
    444 void ShadowBlur::drawRectShadow(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii)
    445 {
    446     IntRect layerRect = calculateLayerBoundingRect(graphicsContext, shadowedRect, graphicsContext->clipBounds());
    447     if (layerRect.isEmpty())
    448         return;
    449 
    450     adjustBlurRadius(graphicsContext);
    451 
    452     // drawRectShadowWithTiling does not work with rotations.
    453     // https://bugs.webkit.org/show_bug.cgi?id=45042
    454     if (!graphicsContext->getCTM().isIdentityOrTranslationOrFlipped() || m_type != BlurShadow) {
    455         drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, layerRect);
    456         return;
    457     }
    458 
    459     IntSize templateSize = this->templateSize(radii);
    460 
    461     if (templateSize.width() > shadowedRect.width() || templateSize.height() > shadowedRect.height()
    462         || (templateSize.width() * templateSize.height() > m_sourceRect.width() * m_sourceRect.height())) {
    463         drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, layerRect);
    464         return;
    465     }
    466 
    467     drawRectShadowWithTiling(graphicsContext, shadowedRect, radii, templateSize);
    468 }
    469 
    470 void ShadowBlur::drawInsetShadow(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& holeRadii)
    471 {
    472     IntRect layerRect = calculateLayerBoundingRect(graphicsContext, rect, graphicsContext->clipBounds());
    473     if (layerRect.isEmpty())
    474         return;
    475 
    476     adjustBlurRadius(graphicsContext);
    477 
    478     // drawInsetShadowWithTiling does not work with rotations.
    479     // https://bugs.webkit.org/show_bug.cgi?id=45042
    480     if (!graphicsContext->getCTM().isIdentityOrTranslationOrFlipped() || m_type != BlurShadow) {
    481         drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, holeRadii, layerRect);
    482         return;
    483     }
    484 
    485     IntSize templateSize = this->templateSize(holeRadii);
    486 
    487     if (templateSize.width() > holeRect.width() || templateSize.height() > holeRect.height()
    488         || (templateSize.width() * templateSize.height() > holeRect.width() * holeRect.height())) {
    489         drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, holeRadii, layerRect);
    490         return;
    491     }
    492 
    493     drawInsetShadowWithTiling(graphicsContext, rect, holeRect, holeRadii, templateSize);
    494 }
    495 
    496 void ShadowBlur::drawRectShadowWithoutTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii, const IntRect& layerRect)
    497 {
    498     m_layerImage = ScratchBuffer::shared().getScratchBuffer(layerRect.size());
    499     if (!m_layerImage)
    500         return;
    501 
    502     FloatRect bufferRelativeShadowedRect = shadowedRect;
    503     bufferRelativeShadowedRect.move(m_layerContextTranslation);
    504     if (!ScratchBuffer::shared().matchesLastShadow(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeShadowedRect, radii)) {
    505         GraphicsContext* shadowContext = m_layerImage->context();
    506         shadowContext->save();
    507 
    508         // Add a pixel to avoid later edge aliasing when rotated.
    509         shadowContext->clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1));
    510         shadowContext->translate(m_layerContextTranslation);
    511         shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
    512         if (radii.isZero())
    513             shadowContext->fillRect(shadowedRect);
    514         else {
    515             Path path;
    516             path.addRoundedRect(shadowedRect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
    517             shadowContext->fillPath(path);
    518         }
    519 
    520         blurShadowBuffer(expandedIntSize(m_layerSize));
    521 
    522         shadowContext->restore();
    523 
    524         ScratchBuffer::shared().setLastShadowValues(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeShadowedRect, radii);
    525     }
    526 
    527     drawShadowBuffer(graphicsContext);
    528     m_layerImage = 0;
    529     ScratchBuffer::shared().scheduleScratchBufferPurge();
    530 }
    531 
    532 void ShadowBlur::drawInsetShadowWithoutTiling(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& holeRadii, const IntRect& layerRect)
    533 {
    534     m_layerImage = ScratchBuffer::shared().getScratchBuffer(layerRect.size());
    535     if (!m_layerImage)
    536         return;
    537 
    538     FloatRect bufferRelativeRect = rect;
    539     bufferRelativeRect.move(m_layerContextTranslation);
    540 
    541     FloatRect bufferRelativeHoleRect = holeRect;
    542     bufferRelativeHoleRect.move(m_layerContextTranslation);
    543 
    544     if (!ScratchBuffer::shared().matchesLastInsetShadow(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeRect, bufferRelativeHoleRect, holeRadii)) {
    545         GraphicsContext* shadowContext = m_layerImage->context();
    546         shadowContext->save();
    547 
    548         // Add a pixel to avoid later edge aliasing when rotated.
    549         shadowContext->clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1));
    550         shadowContext->translate(m_layerContextTranslation);
    551 
    552         Path path;
    553         path.addRect(rect);
    554         if (holeRadii.isZero())
    555             path.addRect(holeRect);
    556         else
    557             path.addRoundedRect(holeRect, holeRadii.topLeft(), holeRadii.topRight(), holeRadii.bottomLeft(), holeRadii.bottomRight());
    558 
    559         shadowContext->setFillRule(RULE_EVENODD);
    560         shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
    561         shadowContext->fillPath(path);
    562 
    563         blurShadowBuffer(expandedIntSize(m_layerSize));
    564 
    565         shadowContext->restore();
    566 
    567         ScratchBuffer::shared().setLastInsetShadowValues(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeRect, bufferRelativeHoleRect, holeRadii);
    568     }
    569 
    570     drawShadowBuffer(graphicsContext);
    571     m_layerImage = 0;
    572     ScratchBuffer::shared().scheduleScratchBufferPurge();
    573 }
    574 
    575 /*
    576   These functions use tiling to improve the performance of the shadow
    577   drawing of rounded rectangles. The code basically does the following
    578   steps:
    579 
    580      1. Calculate the size of the shadow template, a rectangle that
    581      contains all the necessary tiles to draw the complete shadow.
    582 
    583      2. If that size is smaller than the real rectangle render the new
    584      template rectangle and its shadow in a new surface, in other case
    585      render the shadow of the real rectangle in the destination
    586      surface.
    587 
    588      3. Calculate the sizes and positions of the tiles and their
    589      destinations and use drawPattern to render the final shadow. The
    590      code divides the rendering in 8 tiles:
    591 
    592         1 | 2 | 3
    593        -----------
    594         4 |   | 5
    595        -----------
    596         6 | 7 | 8
    597 
    598      The corners are directly copied from the template rectangle to the
    599      real one and the side tiles are 1 pixel width, we use them as
    600      tiles to cover the destination side. The corner tiles are bigger
    601      than just the side of the rounded corner, we need to increase it
    602      because the modifications caused by the corner over the blur
    603      effect. We fill the central or outer part with solid color to complete
    604      the shadow.
    605  */
    606 
    607 void ShadowBlur::drawInsetShadowWithTiling(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& radii, const IntSize& templateSize)
    608 {
    609     graphicsContext->save();
    610     graphicsContext->clearShadow();
    611 
    612     const float roundedRadius = ceilf(m_blurRadius);
    613     const float twiceRadius = roundedRadius * 2;
    614 
    615     m_layerImage = ScratchBuffer::shared().getScratchBuffer(templateSize);
    616     if (!m_layerImage)
    617         return;
    618 
    619     // Draw the rectangle with hole.
    620     FloatRect templateBounds(0, 0, templateSize.width(), templateSize.height());
    621     FloatRect templateHole = FloatRect(roundedRadius, roundedRadius, templateSize.width() - twiceRadius, templateSize.height() - twiceRadius);
    622 
    623     if (!ScratchBuffer::shared().matchesLastInsetShadow(m_blurRadius, m_color, m_colorSpace, templateBounds, templateHole, radii)) {
    624         // Draw shadow into a new ImageBuffer.
    625         GraphicsContext* shadowContext = m_layerImage->context();
    626         shadowContext->save();
    627         shadowContext->clearRect(templateBounds);
    628         shadowContext->setFillRule(RULE_EVENODD);
    629         shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
    630 
    631         Path path;
    632         path.addRect(templateBounds);
    633         if (radii.isZero())
    634             path.addRect(templateHole);
    635         else
    636             path.addRoundedRect(templateHole, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
    637 
    638         shadowContext->fillPath(path);
    639 
    640         blurAndColorShadowBuffer(templateSize);
    641         shadowContext->restore();
    642 
    643         ScratchBuffer::shared().setLastInsetShadowValues(m_blurRadius, m_color, m_colorSpace, templateBounds, templateHole, radii);
    644     }
    645 
    646     FloatRect boundingRect = rect;
    647     boundingRect.move(m_offset);
    648 
    649     FloatRect destHoleRect = holeRect;
    650     destHoleRect.move(m_offset);
    651     FloatRect destHoleBounds = destHoleRect;
    652     destHoleBounds.inflate(roundedRadius);
    653 
    654     // Fill the external part of the shadow (which may be visible because of offset).
    655     Path exteriorPath;
    656     exteriorPath.addRect(boundingRect);
    657     exteriorPath.addRect(destHoleBounds);
    658 
    659     graphicsContext->save();
    660     graphicsContext->setFillRule(RULE_EVENODD);
    661     graphicsContext->setFillColor(m_color, m_colorSpace);
    662     graphicsContext->fillPath(exteriorPath);
    663     graphicsContext->restore();
    664 
    665     drawLayerPieces(graphicsContext, destHoleBounds, radii, roundedRadius, templateSize, InnerShadow);
    666 
    667     graphicsContext->restore();
    668 
    669     m_layerImage = 0;
    670     ScratchBuffer::shared().scheduleScratchBufferPurge();
    671 }
    672 
    673 void ShadowBlur::drawRectShadowWithTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii, const IntSize& templateSize)
    674 {
    675     graphicsContext->save();
    676     graphicsContext->clearShadow();
    677 
    678     const float roundedRadius = ceilf(m_blurRadius);
    679     const float twiceRadius = roundedRadius * 2;
    680 
    681     m_layerImage = ScratchBuffer::shared().getScratchBuffer(templateSize);
    682     if (!m_layerImage)
    683         return;
    684 
    685     FloatRect templateShadow = FloatRect(roundedRadius, roundedRadius, templateSize.width() - twiceRadius, templateSize.height() - twiceRadius);
    686 
    687     if (!ScratchBuffer::shared().matchesLastShadow(m_blurRadius, m_color, m_colorSpace, templateShadow, radii)) {
    688         // Draw shadow into the ImageBuffer.
    689         GraphicsContext* shadowContext = m_layerImage->context();
    690         shadowContext->save();
    691         shadowContext->clearRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
    692         shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
    693 
    694         if (radii.isZero())
    695             shadowContext->fillRect(templateShadow);
    696         else {
    697             Path path;
    698             path.addRoundedRect(templateShadow, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
    699             shadowContext->fillPath(path);
    700         }
    701 
    702         blurAndColorShadowBuffer(templateSize);
    703         shadowContext->restore();
    704 
    705         ScratchBuffer::shared().setLastShadowValues(m_blurRadius, m_color, m_colorSpace, templateShadow, radii);
    706     }
    707 
    708     FloatRect shadowBounds = shadowedRect;
    709     shadowBounds.move(m_offset.width(), m_offset.height());
    710     shadowBounds.inflate(roundedRadius);
    711 
    712     drawLayerPieces(graphicsContext, shadowBounds, radii, roundedRadius, templateSize, OuterShadow);
    713 
    714     graphicsContext->restore();
    715 
    716     m_layerImage = 0;
    717     ScratchBuffer::shared().scheduleScratchBufferPurge();
    718 }
    719 
    720 void ShadowBlur::drawLayerPieces(GraphicsContext* graphicsContext, const FloatRect& shadowBounds, const RoundedIntRect::Radii& radii, float roundedRadius, const IntSize& templateSize, ShadowDirection direction)
    721 {
    722     const float twiceRadius = roundedRadius * 2;
    723 
    724     int leftSlice;
    725     int rightSlice;
    726     int topSlice;
    727     int bottomSlice;
    728     computeSliceSizesFromRadii(twiceRadius, radii, leftSlice, rightSlice, topSlice, bottomSlice);
    729 
    730     int centerWidth = shadowBounds.width() - leftSlice - rightSlice;
    731     int centerHeight = shadowBounds.height() - topSlice - bottomSlice;
    732 
    733     if (direction == OuterShadow) {
    734         FloatRect shadowInterior(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight);
    735         if (!shadowInterior.isEmpty()) {
    736             graphicsContext->save();
    737 
    738             graphicsContext->setFillColor(m_color, m_colorSpace);
    739             graphicsContext->fillRect(shadowInterior);
    740 
    741             graphicsContext->restore();
    742         }
    743     }
    744 
    745     // Note that drawing the ImageBuffer is faster than creating a Image and drawing that,
    746     // because ImageBuffer::draw() knows that it doesn't have to copy the image bits.
    747 
    748     // Top side.
    749     FloatRect tileRect = FloatRect(leftSlice, 0, templateSideLength, topSlice);
    750     FloatRect destRect = FloatRect(shadowBounds.x() + leftSlice, shadowBounds.y(), centerWidth, topSlice);
    751     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
    752 
    753     // Draw the bottom side.
    754     tileRect.setY(templateSize.height() - bottomSlice);
    755     tileRect.setHeight(bottomSlice);
    756     destRect.setY(shadowBounds.maxY() - bottomSlice);
    757     destRect.setHeight(bottomSlice);
    758     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
    759 
    760     // Left side.
    761     tileRect = FloatRect(0, topSlice, leftSlice, templateSideLength);
    762     destRect = FloatRect(shadowBounds.x(), shadowBounds.y() + topSlice, leftSlice, centerHeight);
    763     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
    764 
    765     // Right side.
    766     tileRect.setX(templateSize.width() - rightSlice);
    767     tileRect.setWidth(rightSlice);
    768     destRect.setX(shadowBounds.maxX() - rightSlice);
    769     destRect.setWidth(rightSlice);
    770     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
    771 
    772     // Top left corner.
    773     tileRect = FloatRect(0, 0, leftSlice, topSlice);
    774     destRect = FloatRect(shadowBounds.x(), shadowBounds.y(), leftSlice, topSlice);
    775     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
    776 
    777     // Top right corner.
    778     tileRect = FloatRect(templateSize.width() - rightSlice, 0, rightSlice, topSlice);
    779     destRect = FloatRect(shadowBounds.maxX() - rightSlice, shadowBounds.y(), rightSlice, topSlice);
    780     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
    781 
    782     // Bottom right corner.
    783     tileRect = FloatRect(templateSize.width() - rightSlice, templateSize.height() - bottomSlice, rightSlice, bottomSlice);
    784     destRect = FloatRect(shadowBounds.maxX() - rightSlice, shadowBounds.maxY() - bottomSlice, rightSlice, bottomSlice);
    785     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
    786 
    787     // Bottom left corner.
    788     tileRect = FloatRect(0, templateSize.height() - bottomSlice, leftSlice, bottomSlice);
    789     destRect = FloatRect(shadowBounds.x(), shadowBounds.maxY() - bottomSlice, leftSlice, bottomSlice);
    790     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
    791 }
    792 
    793 
    794 void ShadowBlur::blurShadowBuffer(const IntSize& templateSize)
    795 {
    796     if (m_type != BlurShadow)
    797         return;
    798 
    799     IntRect blurRect(IntPoint(), templateSize);
    800     RefPtr<ByteArray> layerData = m_layerImage->getUnmultipliedImageData(blurRect);
    801     blurLayerImage(layerData->data(), blurRect.size(), blurRect.width() * 4);
    802     m_layerImage->putUnmultipliedImageData(layerData.get(), blurRect.size(), blurRect, IntPoint());
    803 }
    804 
    805 void ShadowBlur::blurAndColorShadowBuffer(const IntSize& templateSize)
    806 {
    807     blurShadowBuffer(templateSize);
    808 
    809     // Mask the image with the shadow color.
    810     GraphicsContext* shadowContext = m_layerImage->context();
    811     shadowContext->setCompositeOperation(CompositeSourceIn);
    812     shadowContext->setFillColor(m_color, m_colorSpace);
    813     shadowContext->fillRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
    814 }
    815 
    816 } // namespace WebCore
    817