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