2011-04-21 Dirk Schulze <krit@webkit.org>
[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 = 0;
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         if (pass && m_blurRadius.width() != m_blurRadius.height())
256             calculateLobes(lobes, m_blurRadius.height(), m_shadowsIgnoreTransforms);
257         unsigned char* pixels = imageData;
258
259         for (int j = 0; j < final; ++j, pixels += delta) {
260             // For each step, we blur the alpha in a channel and store the result
261             // in another channel for the subsequent step.
262             // We use sliding window algorithm to accumulate the alpha values.
263             // This is much more efficient than computing the sum of each pixels
264             // covered by the box kernel size for each x.
265             for (int step = 0; step < 3; ++step) {
266                 int side1 = lobes[step][leftLobe];
267                 int side2 = lobes[step][rightLobe];
268                 int pixelCount = side1 + 1 + side2;
269                 int invCount = ((1 << blurSumShift) + pixelCount - 1) / pixelCount;
270                 int ofs = 1 + side2;
271                 int alpha1 = pixels[channels[step]];
272                 int alpha2 = pixels[(dim - 1) * stride + channels[step]];
273
274                 unsigned char* ptr = pixels + channels[step + 1];
275                 unsigned char* prev = pixels + stride + channels[step];
276                 unsigned char* next = pixels + ofs * stride + channels[step];
277
278                 int i;
279                 int sum = side1 * alpha1 + alpha1;
280                 int limit = (dim < side2 + 1) ? dim : side2 + 1;
281
282                 for (i = 1; i < limit; ++i, prev += stride)
283                     sum += *prev;
284
285                 if (limit <= side2)
286                     sum += (side2 - limit + 1) * alpha2;
287
288                 limit = (side1 < dim) ? side1 : dim;
289                 for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) {
290                     *ptr = (sum * invCount) >> blurSumShift;
291                     sum += ((ofs < dim) ? *next : alpha2) - alpha1;
292                 }
293                 
294                 prev = pixels + channels[step];
295                 for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) {
296                     *ptr = (sum * invCount) >> blurSumShift;
297                     sum += (*next) - (*prev);
298                 }
299                 
300                 for (; i < dim; ptr += stride, prev += stride, ++i) {
301                     *ptr = (sum * invCount) >> blurSumShift;
302                     sum += alpha2 - (*prev);
303                 }
304             }
305         }
306
307         // Last pass is vertical.
308         stride = rowStride;
309         delta = 4;
310         final = size.width();
311         dim = size.height();
312     }
313 }
314
315 void ShadowBlur::adjustBlurRadius(GraphicsContext* context)
316 {
317     if (!m_shadowsIgnoreTransforms)
318         return;
319
320     const AffineTransform transform = context->getCTM();
321
322     // Adjust blur if we're scaling, since the radius must not be affected by transformations.
323     // FIXME: use AffineTransform::isIdentityOrTranslationOrFlipped()?
324     if (transform.isIdentity())
325         return;
326
327     // Calculate transformed unit vectors.
328     const FloatQuad unitQuad(FloatPoint(0, 0), FloatPoint(1, 0),
329                              FloatPoint(0, 1), FloatPoint(1, 1));
330     const FloatQuad transformedUnitQuad = transform.mapQuad(unitQuad);
331
332     // Calculate X axis scale factor.
333     const FloatSize xUnitChange = transformedUnitQuad.p2() - transformedUnitQuad.p1();
334     const float xAxisScale = sqrtf(xUnitChange.width() * xUnitChange.width()
335                                    + xUnitChange.height() * xUnitChange.height());
336
337     // Calculate Y axis scale factor.
338     const FloatSize yUnitChange = transformedUnitQuad.p3() - transformedUnitQuad.p1();
339     const float yAxisScale = sqrtf(yUnitChange.width() * yUnitChange.width()
340                                    + yUnitChange.height() * yUnitChange.height());
341
342     // Scale blur radius
343     m_blurRadius.scale(1 / xAxisScale, 1 / yAxisScale);
344 }
345
346 IntRect ShadowBlur::calculateLayerBoundingRect(GraphicsContext* context, const FloatRect& shadowedRect, const IntRect& clipRect)
347 {
348     const IntSize roundedRadius = expandedIntSize(m_blurRadius);
349
350     // Calculate the destination of the blurred and/or transformed layer.
351     FloatRect layerRect;
352     IntSize inflation;
353
354     const AffineTransform transform = context->getCTM();
355     if (m_shadowsIgnoreTransforms && !transform.isIdentity()) {
356         FloatQuad transformedPolygon = transform.mapQuad(FloatQuad(shadowedRect));
357         transformedPolygon.move(m_offset);
358         layerRect = transform.inverse().mapQuad(transformedPolygon).boundingBox();
359     } else {
360         layerRect = shadowedRect;
361         layerRect.move(m_offset);
362     }
363
364     // We expand the area by the blur radius to give extra space for the blur transition.
365     if (m_type == BlurShadow) {
366         layerRect.inflateX(roundedRadius.width());
367         layerRect.inflateY(roundedRadius.height());
368         inflation = roundedRadius;
369     }
370
371     FloatRect unclippedLayerRect = layerRect;
372
373     if (!clipRect.contains(enclosingIntRect(layerRect))) {
374         // If we are totally outside the clip region, we aren't painting at all.
375         if (intersection(layerRect, clipRect).isEmpty())
376             return IntRect();
377
378         IntRect inflatedClip = clipRect;
379         // Pixels at the edges can be affected by pixels outside the buffer,
380         // so intersect with the clip inflated by the blur.
381         if (m_type == BlurShadow) {
382             inflatedClip.inflateX(roundedRadius.width());
383             inflatedClip.inflateY(roundedRadius.height());
384         }
385         
386         layerRect.intersect(inflatedClip);
387     }
388
389     IntSize frameSize = inflation;
390     frameSize.scale(2);
391     m_sourceRect = FloatRect(0, 0, shadowedRect.width() + frameSize.width(), shadowedRect.height() + frameSize.height());
392     m_layerOrigin = FloatPoint(layerRect.x(), layerRect.y());
393     m_layerSize = layerRect.size();
394
395     const FloatPoint unclippedLayerOrigin = FloatPoint(unclippedLayerRect.x(), unclippedLayerRect.y());
396     const FloatSize clippedOut = unclippedLayerOrigin - m_layerOrigin;
397
398     // Set the origin as the top left corner of the scratch image, or, in case there's a clipped
399     // out region, set the origin accordingly to the full bounding rect's top-left corner.
400     float translationX = -shadowedRect.x() + inflation.width() - fabsf(clippedOut.width());
401     float translationY = -shadowedRect.y() + inflation.height() - fabsf(clippedOut.height());
402     m_layerContextTranslation = FloatSize(translationX, translationY);
403
404     return enclosingIntRect(layerRect);
405 }
406
407 void ShadowBlur::drawShadowBuffer(GraphicsContext* graphicsContext)
408 {
409     if (!m_layerImage)
410         return;
411
412     GraphicsContextStateSaver stateSaver(*graphicsContext);
413
414     IntSize bufferSize = m_layerImage->size();
415     if (bufferSize != m_layerSize) {
416         // The rect passed to clipToImageBuffer() has to be the size of the entire buffer,
417         // but we may not have cleared it all, so clip to the filled part first.
418         graphicsContext->clip(FloatRect(m_layerOrigin, m_layerSize));
419     }
420     graphicsContext->clipToImageBuffer(m_layerImage, FloatRect(m_layerOrigin, bufferSize));
421     graphicsContext->setFillColor(m_color, m_colorSpace);
422
423     graphicsContext->clearShadow();
424     graphicsContext->fillRect(FloatRect(m_layerOrigin, m_sourceRect.size()));
425 }
426
427 static void computeSliceSizesFromRadii(const IntSize& twiceRadius, const RoundedIntRect::Radii& radii, int& leftSlice, int& rightSlice, int& topSlice, int& bottomSlice)
428 {
429     leftSlice = twiceRadius.width() + max(radii.topLeft().width(), radii.bottomLeft().width()); 
430     rightSlice = twiceRadius.width() + max(radii.topRight().width(), radii.bottomRight().width()); 
431
432     topSlice = twiceRadius.height() + max(radii.topLeft().height(), radii.topRight().height());
433     bottomSlice = twiceRadius.height() + max(radii.bottomLeft().height(), radii.bottomRight().height());
434 }
435
436 IntSize ShadowBlur::templateSize(const RoundedIntRect::Radii& radii) const
437 {
438     const int templateSideLength = 1;
439
440     int leftSlice;
441     int rightSlice;
442     int topSlice;
443     int bottomSlice;
444     IntSize twiceRadius = expandedIntSize(m_blurRadius);
445     twiceRadius.scale(2);
446
447     computeSliceSizesFromRadii(twiceRadius, radii, leftSlice, rightSlice, topSlice, bottomSlice);
448     
449     return IntSize(templateSideLength + leftSlice + rightSlice,
450                    templateSideLength + topSlice + bottomSlice);
451 }
452
453 void ShadowBlur::drawRectShadow(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii)
454 {
455     IntRect layerRect = calculateLayerBoundingRect(graphicsContext, shadowedRect, graphicsContext->clipBounds());
456     if (layerRect.isEmpty())
457         return;
458
459     adjustBlurRadius(graphicsContext);
460
461     // drawRectShadowWithTiling does not work with rotations.
462     // https://bugs.webkit.org/show_bug.cgi?id=45042
463     if (!graphicsContext->getCTM().isIdentityOrTranslationOrFlipped() || m_type != BlurShadow) {
464         drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, layerRect);
465         return;
466     }
467
468     IntSize templateSize = this->templateSize(radii);
469
470     if (templateSize.width() > shadowedRect.width() || templateSize.height() > shadowedRect.height()
471         || (templateSize.width() * templateSize.height() > m_sourceRect.width() * m_sourceRect.height())) {
472         drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, layerRect);
473         return;
474     }
475
476     drawRectShadowWithTiling(graphicsContext, shadowedRect, radii, templateSize);
477 }
478
479 void ShadowBlur::drawInsetShadow(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& holeRadii)
480 {
481     IntRect layerRect = calculateLayerBoundingRect(graphicsContext, rect, graphicsContext->clipBounds());
482     if (layerRect.isEmpty())
483         return;
484
485     adjustBlurRadius(graphicsContext);
486
487     // drawInsetShadowWithTiling does not work with rotations.
488     // https://bugs.webkit.org/show_bug.cgi?id=45042
489     if (!graphicsContext->getCTM().isIdentityOrTranslationOrFlipped() || m_type != BlurShadow) {
490         drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, holeRadii, layerRect);
491         return;
492     }
493
494     IntSize templateSize = this->templateSize(holeRadii);
495
496     if (templateSize.width() > holeRect.width() || templateSize.height() > holeRect.height()
497         || (templateSize.width() * templateSize.height() > holeRect.width() * holeRect.height())) {
498         drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, holeRadii, layerRect);
499         return;
500     }
501
502     drawInsetShadowWithTiling(graphicsContext, rect, holeRect, holeRadii, templateSize);
503 }
504
505 void ShadowBlur::drawRectShadowWithoutTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii, const IntRect& layerRect)
506 {
507     m_layerImage = ScratchBuffer::shared().getScratchBuffer(layerRect.size());
508     if (!m_layerImage)
509         return;
510
511     FloatRect bufferRelativeShadowedRect = shadowedRect;
512     bufferRelativeShadowedRect.move(m_layerContextTranslation);
513     if (!ScratchBuffer::shared().matchesLastShadow(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeShadowedRect, radii)) {
514         GraphicsContext* shadowContext = m_layerImage->context();
515         GraphicsContextStateSaver stateSaver(*shadowContext);
516
517         // Add a pixel to avoid later edge aliasing when rotated.
518         shadowContext->clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1));
519         shadowContext->translate(m_layerContextTranslation);
520         shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
521         if (radii.isZero())
522             shadowContext->fillRect(shadowedRect);
523         else {
524             Path path;
525             path.addRoundedRect(shadowedRect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
526             shadowContext->fillPath(path);
527         }
528
529         blurShadowBuffer(expandedIntSize(m_layerSize));
530         
531         ScratchBuffer::shared().setLastShadowValues(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeShadowedRect, radii);
532     }
533     
534     drawShadowBuffer(graphicsContext);
535     m_layerImage = 0;
536     ScratchBuffer::shared().scheduleScratchBufferPurge();
537 }
538
539 void ShadowBlur::drawInsetShadowWithoutTiling(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& holeRadii, const IntRect& layerRect)
540 {
541     m_layerImage = ScratchBuffer::shared().getScratchBuffer(layerRect.size());
542     if (!m_layerImage)
543         return;
544
545     FloatRect bufferRelativeRect = rect;
546     bufferRelativeRect.move(m_layerContextTranslation);
547
548     FloatRect bufferRelativeHoleRect = holeRect;
549     bufferRelativeHoleRect.move(m_layerContextTranslation);
550
551     if (!ScratchBuffer::shared().matchesLastInsetShadow(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeRect, bufferRelativeHoleRect, holeRadii)) {
552         GraphicsContext* shadowContext = m_layerImage->context();
553         GraphicsContextStateSaver stateSaver(*shadowContext);
554
555         // Add a pixel to avoid later edge aliasing when rotated.
556         shadowContext->clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1));
557         shadowContext->translate(m_layerContextTranslation);
558
559         Path path;
560         path.addRect(rect);
561         if (holeRadii.isZero())
562             path.addRect(holeRect);
563         else
564             path.addRoundedRect(holeRect, holeRadii.topLeft(), holeRadii.topRight(), holeRadii.bottomLeft(), holeRadii.bottomRight());
565
566         shadowContext->setFillRule(RULE_EVENODD);
567         shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
568         shadowContext->fillPath(path);
569
570         blurShadowBuffer(expandedIntSize(m_layerSize));
571
572         ScratchBuffer::shared().setLastInsetShadowValues(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeRect, bufferRelativeHoleRect, holeRadii);
573     }
574     
575     drawShadowBuffer(graphicsContext);
576     m_layerImage = 0;
577     ScratchBuffer::shared().scheduleScratchBufferPurge();
578 }
579
580 /*
581   These functions use tiling to improve the performance of the shadow
582   drawing of rounded rectangles. The code basically does the following
583   steps:
584
585      1. Calculate the size of the shadow template, a rectangle that
586      contains all the necessary tiles to draw the complete shadow.
587
588      2. If that size is smaller than the real rectangle render the new
589      template rectangle and its shadow in a new surface, in other case
590      render the shadow of the real rectangle in the destination
591      surface.
592
593      3. Calculate the sizes and positions of the tiles and their
594      destinations and use drawPattern to render the final shadow. The
595      code divides the rendering in 8 tiles:
596
597         1 | 2 | 3
598        -----------
599         4 |   | 5
600        -----------
601         6 | 7 | 8
602
603      The corners are directly copied from the template rectangle to the
604      real one and the side tiles are 1 pixel width, we use them as
605      tiles to cover the destination side. The corner tiles are bigger
606      than just the side of the rounded corner, we need to increase it
607      because the modifications caused by the corner over the blur
608      effect. We fill the central or outer part with solid color to complete
609      the shadow.
610  */
611
612 void ShadowBlur::drawInsetShadowWithTiling(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& radii, const IntSize& templateSize)
613 {
614     GraphicsContextStateSaver stateSaver(*graphicsContext);
615     graphicsContext->clearShadow();
616
617     const IntSize roundedRadius = expandedIntSize(m_blurRadius);
618     const IntSize twiceRadius = IntSize(roundedRadius.width() * 2, roundedRadius.height() * 2);
619
620     m_layerImage = ScratchBuffer::shared().getScratchBuffer(templateSize);
621     if (!m_layerImage)
622         return;
623
624     // Draw the rectangle with hole.
625     FloatRect templateBounds(0, 0, templateSize.width(), templateSize.height());
626     FloatRect templateHole = FloatRect(roundedRadius.width(), roundedRadius.height(), templateSize.width() - twiceRadius.width(), templateSize.height() - twiceRadius.height());
627
628     if (!ScratchBuffer::shared().matchesLastInsetShadow(m_blurRadius, m_color, m_colorSpace, templateBounds, templateHole, radii)) {
629         // Draw shadow into a new ImageBuffer.
630         GraphicsContext* shadowContext = m_layerImage->context();
631         GraphicsContextStateSaver shadowStateSaver(*shadowContext);
632         shadowContext->clearRect(templateBounds);
633         shadowContext->setFillRule(RULE_EVENODD);
634         shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
635
636         Path path;
637         path.addRect(templateBounds);
638         if (radii.isZero())
639             path.addRect(templateHole);
640         else
641             path.addRoundedRect(templateHole, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
642
643         shadowContext->fillPath(path);
644
645         blurAndColorShadowBuffer(templateSize);
646     
647         ScratchBuffer::shared().setLastInsetShadowValues(m_blurRadius, m_color, m_colorSpace, templateBounds, templateHole, radii);
648     }
649
650     FloatRect boundingRect = rect;
651     boundingRect.move(m_offset);
652
653     FloatRect destHoleRect = holeRect;
654     destHoleRect.move(m_offset);
655     FloatRect destHoleBounds = destHoleRect;
656     destHoleBounds.inflateX(roundedRadius.width());
657     destHoleBounds.inflateY(roundedRadius.height());
658
659     // Fill the external part of the shadow (which may be visible because of offset).
660     Path exteriorPath;
661     exteriorPath.addRect(boundingRect);
662     exteriorPath.addRect(destHoleBounds);
663
664     {
665         GraphicsContextStateSaver fillStateSaver(*graphicsContext);
666         graphicsContext->setFillRule(RULE_EVENODD);
667         graphicsContext->setFillColor(m_color, m_colorSpace);
668         graphicsContext->fillPath(exteriorPath);
669     }
670     
671     drawLayerPieces(graphicsContext, destHoleBounds, radii, roundedRadius, templateSize, InnerShadow);
672
673     m_layerImage = 0;
674     ScratchBuffer::shared().scheduleScratchBufferPurge();
675 }
676
677 void ShadowBlur::drawRectShadowWithTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii, const IntSize& templateSize)
678 {
679     GraphicsContextStateSaver stateSaver(*graphicsContext);
680     graphicsContext->clearShadow();
681
682     const IntSize roundedRadius = expandedIntSize(m_blurRadius);
683     const IntSize twiceRadius = IntSize(roundedRadius.width() * 2, roundedRadius.height() * 2);
684
685     m_layerImage = ScratchBuffer::shared().getScratchBuffer(templateSize);
686     if (!m_layerImage)
687         return;
688
689     FloatRect templateShadow = FloatRect(roundedRadius.width(), roundedRadius.height(), templateSize.width() - twiceRadius.width(), templateSize.height() - twiceRadius.height());
690
691     if (!ScratchBuffer::shared().matchesLastShadow(m_blurRadius, m_color, m_colorSpace, templateShadow, radii)) {
692         // Draw shadow into the ImageBuffer.
693         GraphicsContext* shadowContext = m_layerImage->context();
694         GraphicsContextStateSaver shadowStateSaver(*shadowContext);
695
696         shadowContext->clearRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
697         shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
698         
699         if (radii.isZero())
700             shadowContext->fillRect(templateShadow);
701         else {
702             Path path;
703             path.addRoundedRect(templateShadow, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
704             shadowContext->fillPath(path);
705         }
706
707         blurAndColorShadowBuffer(templateSize);
708
709         ScratchBuffer::shared().setLastShadowValues(m_blurRadius, m_color, m_colorSpace, templateShadow, radii);
710     }
711
712     FloatRect shadowBounds = shadowedRect;
713     shadowBounds.move(m_offset.width(), m_offset.height());
714     shadowBounds.inflateX(roundedRadius.width());
715     shadowBounds.inflateY(roundedRadius.height());
716
717     drawLayerPieces(graphicsContext, shadowBounds, radii, roundedRadius, templateSize, OuterShadow);
718
719     m_layerImage = 0;
720     ScratchBuffer::shared().scheduleScratchBufferPurge();
721 }
722
723 void ShadowBlur::drawLayerPieces(GraphicsContext* graphicsContext, const FloatRect& shadowBounds, const RoundedIntRect::Radii& radii, const IntSize& roundedRadius, const IntSize& templateSize, ShadowDirection direction)
724 {
725     const IntSize twiceRadius = IntSize(roundedRadius.width() * 2, roundedRadius.height() * 2);
726
727     int leftSlice;
728     int rightSlice;
729     int topSlice;
730     int bottomSlice;
731     computeSliceSizesFromRadii(twiceRadius, radii, leftSlice, rightSlice, topSlice, bottomSlice);
732
733     int centerWidth = shadowBounds.width() - leftSlice - rightSlice;
734     int centerHeight = shadowBounds.height() - topSlice - bottomSlice;
735
736     if (direction == OuterShadow) {
737         FloatRect shadowInterior(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight);
738         if (!shadowInterior.isEmpty()) {
739             GraphicsContextStateSaver stateSaver(*graphicsContext);
740             graphicsContext->setFillColor(m_color, m_colorSpace);
741             graphicsContext->fillRect(shadowInterior);
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