2011-04-10 Simon Fraser <simon.fraser@apple.com>
[WebKit-https.git] / Source / WebCore / platform / graphics / ShadowBlur.cpp
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