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