2011-01-30 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
40 using namespace std;
41
42 namespace WebCore {
43
44 static inline int roundUpToMultipleOf32(int d)
45 {
46     return (1 + (d >> 5)) << 5;
47 }
48
49 // ShadowBlur needs a scratch image as the buffer for the blur filter.
50 // Instead of creating and destroying the buffer for every operation,
51 // we create a buffer which will be automatically purged via a timer.
52 class ScratchBuffer {
53 public:
54     ScratchBuffer()
55         : m_purgeTimer(this, &ScratchBuffer::timerFired)
56     {
57     }
58     
59     ImageBuffer* getScratchBuffer(const IntSize& size)
60     {
61         // We do not need to recreate the buffer if the current buffer is large enough.
62         if (m_imageBuffer && m_imageBuffer->width() >= size.width() && m_imageBuffer->height() >= size.height())
63             return m_imageBuffer.get();
64
65         // Round to the nearest 32 pixels so we do not grow the buffer for similar sized requests.
66         IntSize roundedSize(roundUpToMultipleOf32(size.width()), roundUpToMultipleOf32(size.height()));
67
68         m_imageBuffer = ImageBuffer::create(roundedSize);
69         return m_imageBuffer.get();
70     }
71
72     void scheduleScratchBufferPurge()
73     {
74         if (m_purgeTimer.isActive())
75             m_purgeTimer.stop();
76
77         const double scratchBufferPurgeInterval = 2;
78         m_purgeTimer.startOneShot(scratchBufferPurgeInterval);
79     }
80     
81     static ScratchBuffer& shared();
82
83 private:
84     void timerFired(Timer<ScratchBuffer>*)
85     {
86         clearScratchBuffer();
87     }
88     
89     void clearScratchBuffer()
90     {
91         m_imageBuffer = 0;
92     }
93
94     OwnPtr<ImageBuffer> m_imageBuffer;
95     Timer<ScratchBuffer> m_purgeTimer;
96 };
97
98 ScratchBuffer& ScratchBuffer::shared()
99 {
100     DEFINE_STATIC_LOCAL(ScratchBuffer, scratchBuffer, ());
101     return scratchBuffer;
102 }
103
104 ShadowBlur::ShadowBlur(float radius, const FloatSize& offset, const Color& color, ColorSpace colorSpace)
105     : m_color(color)
106     , m_colorSpace(colorSpace)
107     , m_blurRadius(radius)
108     , m_offset(offset)
109     , m_shadowsIgnoreTransforms(false)
110 {
111     // Limit blur radius to 128 to avoid lots of very expensive blurring.
112     m_blurRadius = min<float>(m_blurRadius, 128);
113
114     // The type of shadow is decided by the blur radius, shadow offset, and shadow color.
115     if (!m_color.isValid() || !color.alpha()) {
116         // Can't paint the shadow with invalid or invisible color.
117         m_type = NoShadow;
118     } else if (radius > 0) {
119         // Shadow is always blurred, even the offset is zero.
120         m_type = BlurShadow;
121     } else if (!m_offset.width() && !m_offset.height()) {
122         // Without blur and zero offset means the shadow is fully hidden.
123         m_type = NoShadow;
124     } else
125         m_type = SolidShadow;
126 }
127
128 // Instead of integer division, we use 17.15 for fixed-point division.
129 static const int blurSumShift = 15;
130 static const float gaussianKernelFactor = 3 / 4.f * sqrtf(2 * piFloat);
131
132 // Check http://www.w3.org/TR/SVG/filters.html#feGaussianBlur.
133 // As noted in the SVG filter specification, running box blur 3x
134 // approximates a real gaussian blur nicely.
135
136 void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride)
137 {
138     const int channels[4] =
139 #if CPU(BIG_ENDIAN)
140         { 0, 3, 2, 0 };
141 #elif CPU(MIDDLE_ENDIAN)
142         { 1, 2, 3, 1 };
143 #else
144         { 3, 0, 1, 3 };
145 #endif
146
147     int diameter;
148     if (m_shadowsIgnoreTransforms)
149         diameter = max(2, static_cast<int>(floorf((2 / 3.f) * m_blurRadius))); // Canvas shadow. FIXME: we should adjust the blur radius higher up.
150     else
151         diameter = max(2, static_cast<int>(floorf(m_blurRadius / 2 * gaussianKernelFactor + 0.5f))); // CSS
152
153     int dMax = diameter >> 1;
154     int dMin = dMax - 1 + (diameter & 1);
155     if (dMin < 0)
156         dMin = 0;
157
158     // First pass is horizontal.
159     int stride = 4;
160     int delta = rowStride;
161     int final = size.height();
162     int dim = size.width();
163
164     // Two stages: horizontal and vertical
165     for (int pass = 0; pass < 2; ++pass) {
166         unsigned char* pixels = imageData;
167
168         for (int j = 0; j < final; ++j, pixels += delta) {
169             // For each step, we blur the alpha in a channel and store the result
170             // in another channel for the subsequent step.
171             // We use sliding window algorithm to accumulate the alpha values.
172             // This is much more efficient than computing the sum of each pixels
173             // covered by the box kernel size for each x.
174             for (int step = 0; step < 3; ++step) {
175                 int side1 = (!step) ? dMin : dMax;
176                 int side2 = (step == 1) ? dMin : dMax;
177                 int pixelCount = side1 + 1 + side2;
178                 int invCount = ((1 << blurSumShift) + pixelCount - 1) / pixelCount;
179                 int ofs = 1 + side2;
180                 int alpha1 = pixels[channels[step]];
181                 int alpha2 = pixels[(dim - 1) * stride + channels[step]];
182
183                 unsigned char* ptr = pixels + channels[step + 1];
184                 unsigned char* prev = pixels + stride + channels[step];
185                 unsigned char* next = pixels + ofs * stride + channels[step];
186
187                 int i;
188                 int sum = side1 * alpha1 + alpha1;
189                 int limit = (dim < side2 + 1) ? dim : side2 + 1;
190
191                 for (i = 1; i < limit; ++i, prev += stride)
192                     sum += *prev;
193
194                 if (limit <= side2)
195                     sum += (side2 - limit + 1) * alpha2;
196
197                 limit = (side1 < dim) ? side1 : dim;
198                 for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) {
199                     *ptr = (sum * invCount) >> blurSumShift;
200                     sum += ((ofs < dim) ? *next : alpha2) - alpha1;
201                 }
202                 
203                 prev = pixels + channels[step];
204                 for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) {
205                     *ptr = (sum * invCount) >> blurSumShift;
206                     sum += (*next) - (*prev);
207                 }
208                 
209                 for (; i < dim; ptr += stride, prev += stride, ++i) {
210                     *ptr = (sum * invCount) >> blurSumShift;
211                     sum += alpha2 - (*prev);
212                 }
213             }
214         }
215
216         // Last pass is vertical.
217         stride = rowStride;
218         delta = 4;
219         final = size.width();
220         dim = size.height();
221     }
222 }
223
224 void ShadowBlur::adjustBlurDistance(GraphicsContext* context)
225 {
226     if (!m_shadowsIgnoreTransforms)
227         return;
228
229     const AffineTransform transform = context->getCTM();
230
231     // Adjust blur if we're scaling, since the radius must not be affected by transformations.
232     // FIXME: use AffineTransform::isIdentityOrTranslationOrFlipped()?
233     if (transform.isIdentity())
234         return;
235
236     // Calculate transformed unit vectors.
237     const FloatQuad unitQuad(FloatPoint(0, 0), FloatPoint(1, 0),
238                              FloatPoint(0, 1), FloatPoint(1, 1));
239     const FloatQuad transformedUnitQuad = transform.mapQuad(unitQuad);
240
241     // Calculate X axis scale factor.
242     const FloatSize xUnitChange = transformedUnitQuad.p2() - transformedUnitQuad.p1();
243     const float xAxisScale = sqrtf(xUnitChange.width() * xUnitChange.width()
244                                    + xUnitChange.height() * xUnitChange.height());
245
246     // Calculate Y axis scale factor.
247     const FloatSize yUnitChange = transformedUnitQuad.p3() - transformedUnitQuad.p1();
248     const float yAxisScale = sqrtf(yUnitChange.width() * yUnitChange.width()
249                                    + yUnitChange.height() * yUnitChange.height());
250
251     // blurLayerImage() does not support per-axis blurring, so calculate a balanced scaling.
252     // FIXME: does AffineTransform.xScale()/yScale() help?
253     const float scale = sqrtf(xAxisScale * yAxisScale);
254     m_blurRadius = roundf(m_blurRadius / scale);
255 }
256
257 IntRect ShadowBlur::calculateLayerBoundingRect(GraphicsContext* context, const FloatRect& shadowedRect, const IntRect& clipRect)
258 {
259     const float roundedRadius = ceilf(m_blurRadius);
260
261     // Calculate the destination of the blurred and/or transformed layer.
262     FloatRect layerFloatRect;
263     float inflation = 0;
264
265     const AffineTransform transform = context->getCTM();
266     if (m_shadowsIgnoreTransforms && !transform.isIdentity()) {
267         FloatQuad transformedPolygon = transform.mapQuad(FloatQuad(shadowedRect));
268         transformedPolygon.move(m_offset);
269         layerFloatRect = transform.inverse().mapQuad(transformedPolygon).boundingBox();
270     } else {
271         layerFloatRect = shadowedRect;
272         layerFloatRect.move(m_offset);
273     }
274
275     // We expand the area by the blur radius to give extra space for the blur transition.
276     if (m_type == BlurShadow) {
277         layerFloatRect.inflate(roundedRadius);
278         inflation += roundedRadius;
279     }
280
281     FloatRect unclippedLayerRect = layerFloatRect;
282
283     if (!clipRect.contains(enclosingIntRect(layerFloatRect))) {
284         // If we are totally outside the clip region, we aren't painting at all.
285         if (intersection(layerFloatRect, clipRect).isEmpty())
286             return IntRect();
287
288         IntRect inflatedClip = clipRect;
289         // Pixels at the edges can be affected by pixels outside the buffer,
290         // so intersect with the clip inflated by the blur.
291         if (m_type == BlurShadow)
292             inflatedClip.inflate(roundedRadius);
293         
294         layerFloatRect.intersect(inflatedClip);
295     }
296
297     const int frameSize = inflation * 2;
298     m_sourceRect = FloatRect(0, 0, shadowedRect.width() + frameSize, shadowedRect.height() + frameSize);
299     m_layerOrigin = FloatPoint(layerFloatRect.x(), layerFloatRect.y());
300     m_layerSize = layerFloatRect.size();
301
302     const FloatPoint unclippedLayerOrigin = FloatPoint(unclippedLayerRect.x(), unclippedLayerRect.y());
303     const FloatSize clippedOut = unclippedLayerOrigin - m_layerOrigin;
304
305     // Set the origin as the top left corner of the scratch image, or, in case there's a clipped
306     // out region, set the origin accordingly to the full bounding rect's top-left corner.
307     float translationX = -shadowedRect.x() + inflation - fabsf(clippedOut.width());
308     float translationY = -shadowedRect.y() + inflation - fabsf(clippedOut.height());
309     m_layerContextTranslation = FloatSize(translationX, translationY);
310
311     return enclosingIntRect(layerFloatRect);
312 }
313
314 GraphicsContext* ShadowBlur::beginShadowLayer(GraphicsContext* graphicsContext, const FloatRect& shadowedRect)
315 {
316     adjustBlurDistance(graphicsContext);
317
318     IntRect layerRect = calculateLayerBoundingRect(graphicsContext, shadowedRect, graphicsContext->clipBounds());
319     // Don't paint if we are totally outside the clip region.
320     if (layerRect.isEmpty())
321         return 0;
322
323     m_layerImage = ScratchBuffer::shared().getScratchBuffer(layerRect.size());
324     GraphicsContext* layerContext = m_layerImage->context();
325
326     layerContext->save(); // Balanced by restore() in endShadowLayer().
327
328     // Always clear the surface first. FIXME: we could avoid the clear on first allocation.
329     // Add a pixel to avoid later edge aliasing when rotated.
330     layerContext->clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1));
331     layerContext->translate(m_layerContextTranslation);
332
333     return layerContext;
334 }
335
336 void ShadowBlur::endShadowLayer(GraphicsContext* graphicsContext)
337 {
338     if (!m_layerImage)
339         return;
340         
341     m_layerImage->context()->restore();
342
343     if (m_type == BlurShadow) {
344         IntRect blurRect = enclosingIntRect(FloatRect(FloatPoint(), m_layerSize));
345         RefPtr<ByteArray> layerData = m_layerImage->getUnmultipliedImageData(blurRect);
346         blurLayerImage(layerData->data(), blurRect.size(), blurRect.width() * 4);
347         m_layerImage->putUnmultipliedImageData(layerData.get(), blurRect.size(), blurRect, IntPoint());
348     }
349
350     graphicsContext->save();
351
352     graphicsContext->clipToImageBuffer(m_layerImage, FloatRect(m_layerOrigin, m_layerImage->size()));
353     graphicsContext->setFillColor(m_color, m_colorSpace);
354
355     graphicsContext->clearShadow();
356     graphicsContext->fillRect(FloatRect(m_layerOrigin, m_sourceRect.size()));
357     
358     graphicsContext->restore();
359
360     m_layerImage = 0;
361
362     // Schedule a purge of the scratch buffer. We do not need to destroy the surface.
363     ScratchBuffer::shared().scheduleScratchBufferPurge();
364 }
365
366 void ShadowBlur::drawRectShadow(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii)
367 {
368     // drawShadowedRect does not work with rotations.
369     // https://bugs.webkit.org/show_bug.cgi?id=45042
370     if (!graphicsContext->getCTM().isIdentityOrTranslationOrFlipped() || m_type != BlurShadow) {
371         drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, 1);
372         return;
373     }
374
375     const float roundedRadius = ceilf(m_blurRadius);
376     const float twiceRadius = roundedRadius * 2;
377     
378     const int templateSideLength = 1;
379     
380     // Find the extra space needed from the curve of the corners.
381     int extraWidthFromCornerRadii = twiceRadius + max(radii.topLeft().width(), radii.bottomLeft().width()) + twiceRadius + max(radii.topRight().width(), radii.bottomRight().width());
382     int extraHeightFromCornerRadii = twiceRadius + max(radii.topLeft().height(), radii.topRight().height()) + twiceRadius + max(radii.bottomLeft().height(), radii.bottomRight().height());
383
384     // The length of a side of the buffer is the enough space for four blur radii,
385     // the radii of the corners, and then 1 pixel to draw the side tiles.
386     IntSize shadowTemplateSize = IntSize(templateSideLength + extraWidthFromCornerRadii, templateSideLength + extraHeightFromCornerRadii);
387
388     if (shadowTemplateSize.width() > shadowedRect.width() || shadowTemplateSize.height() > shadowedRect.height()) {
389         drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, 1);
390         return;
391     }
392
393     // Determine dimensions of shadow rect.
394     FloatRect shadowRect = shadowedRect;
395     shadowRect.inflate(roundedRadius); // FIXME: duplicating code with calculateLayerBoundingRect.
396
397     // Reduce the size of what we have to draw with the clip area.
398     calculateLayerBoundingRect(graphicsContext, shadowedRect, graphicsContext->clipBounds());
399
400     // If the template area ends up being larger than the area to be blurred, use the simple case.
401     // FIXME: when does this happen?
402     if ((shadowTemplateSize.width() * shadowTemplateSize.height() > m_sourceRect.width() * m_sourceRect.height())) {
403         drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, 1);
404         return;
405     }
406     
407     drawRectShadowWithTiling(graphicsContext, shadowedRect, radii, 1, shadowTemplateSize);
408 }
409
410 void ShadowBlur::drawInsetShadow(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& holeRadii)
411 {
412     // FIXME: add a tiling code path here.
413     GraphicsContext* shadowContext = beginShadowLayer(graphicsContext, rect);
414     if (!shadowContext)
415         return;
416
417     Path path;
418     path.addRect(rect);
419     path.addRoundedRect(holeRect, holeRadii.topLeft(), holeRadii.topRight(), holeRadii.bottomLeft(), holeRadii.bottomRight());
420
421     shadowContext->setFillRule(RULE_EVENODD);
422     shadowContext->setFillColor(Color(.0f, .0f, .0f, 1.f), ColorSpaceDeviceRGB);
423     shadowContext->fillPath(path);
424     
425     endShadowLayer(graphicsContext);
426 }
427
428 void ShadowBlur::drawRectShadowWithoutTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii, float alpha)
429 {
430     GraphicsContext* shadowContext = beginShadowLayer(graphicsContext, shadowedRect);
431     if (!shadowContext)
432         return;
433
434     Path path;
435     path.addRoundedRect(shadowedRect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
436
437     shadowContext->setFillColor(Color(.0f, .0f, .0f, alpha), ColorSpaceDeviceRGB);
438     shadowContext->fillPath(path);
439
440     endShadowLayer(graphicsContext);
441 }
442
443 /*
444   This function uses tiling to improve the performance of the shadow
445   drawing of rounded rectangles. The code basically does the following
446   steps:
447
448      1. Calculate the size of the shadow template, a rectangle that
449      contains all the necessary tiles to draw the complete shadow.
450
451      2. If that size is smaller than the real rectangle render the new
452      template rectangle and its shadow in a new surface, in other case
453      render the shadow of the real rectangle in the destination
454      surface.
455
456      3. Calculate the sizes and positions of the tiles and their
457      destinations and use drawPattern to render the final shadow. The
458      code divides the rendering in 8 tiles:
459
460         1 | 2 | 3
461        -----------
462         4 |   | 5
463        -----------
464         6 | 7 | 8
465
466      The corners are directly copied from the template rectangle to the
467      real one and the side tiles are 1 pixel width, we use them as
468
469      tiles to cover the destination side. The corner tiles are bigger
470      than just the side of the rounded corner, we need to increase it
471      because the modifications caused by the corner over the blur
472      effect. We fill the central part with solid color to complete the
473      shadow.
474  */
475
476 void ShadowBlur::drawRectShadowWithTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii, float alpha, const IntSize& shadowTemplateSize)
477 {
478     const float roundedRadius = ceilf(m_blurRadius);
479     const float twiceRadius = roundedRadius * 2;
480
481     // Size of the tiling side.
482     const int templateSideLength = 1;
483
484     FloatRect shadowRect = shadowedRect;
485     shadowRect.inflate(roundedRadius); // FIXME: duplicating code with calculateLayerBoundingRect.
486
487     shadowRect.move(m_offset.width(), m_offset.height());
488
489     m_layerImage = ScratchBuffer::shared().getScratchBuffer(shadowTemplateSize);
490
491     // Draw shadow into a new ImageBuffer.
492     GraphicsContext* shadowContext = m_layerImage->context();
493     shadowContext->save();
494     
495     shadowContext->clearRect(FloatRect(0, 0, shadowTemplateSize.width(), shadowTemplateSize.height()));
496
497     // Draw the rectangle.
498     FloatRect templateRect = FloatRect(roundedRadius, roundedRadius, shadowTemplateSize.width() - twiceRadius, shadowTemplateSize.height() - twiceRadius);
499     Path path;
500     path.addRoundedRect(templateRect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
501
502     shadowContext->setFillColor(Color(.0f, .0f, .0f, alpha), ColorSpaceDeviceRGB);
503     shadowContext->fillPath(path);
504
505     // Blur the image.
506     {
507         IntRect blurRect(IntPoint(), shadowTemplateSize);
508         RefPtr<ByteArray> layerData = m_layerImage->getUnmultipliedImageData(blurRect);
509         blurLayerImage(layerData->data(), blurRect.size(), blurRect.width() * 4);
510         m_layerImage->putUnmultipliedImageData(layerData.get(), blurRect.size(), blurRect, IntPoint());
511     }
512
513     // Mask the image with the shadow color.
514     shadowContext->setCompositeOperation(CompositeSourceIn);
515     shadowContext->setFillColor(m_color, m_colorSpace);
516     shadowContext->fillRect(FloatRect(0, 0, shadowTemplateSize.width(), shadowTemplateSize.height()));
517     
518     shadowContext->restore();
519
520     // Fill the internal part of the shadow.
521     shadowRect.inflate(-twiceRadius);
522     if (!shadowRect.isEmpty()) {
523         graphicsContext->save();
524         
525         path.clear();
526         path.addRoundedRect(shadowRect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
527
528         graphicsContext->setFillColor(m_color, m_colorSpace);
529         graphicsContext->fillPath(path);
530         
531         graphicsContext->restore();
532     }
533     shadowRect.inflate(twiceRadius);
534
535     // Note that drawing the ImageBuffer is faster than creating a Image and drawing that,
536     // because ImageBuffer::draw() knows that it doesn't have to copy the image bits.
537         
538     // Draw top side.
539     FloatRect tileRect = FloatRect(twiceRadius + radii.topLeft().width(), 0, templateSideLength, twiceRadius);
540     FloatRect destRect = tileRect;
541     destRect.move(shadowRect.x(), shadowRect.y());
542     destRect.setWidth(shadowRect.width() - radii.topLeft().width() - radii.topRight().width() - roundedRadius * 4);
543     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
544
545     // Draw the bottom side.
546     tileRect = FloatRect(twiceRadius + radii.bottomLeft().width(), shadowTemplateSize.height() - twiceRadius, templateSideLength, twiceRadius);
547     destRect = tileRect;
548     destRect.move(shadowRect.x(), shadowRect.y() + twiceRadius + shadowedRect.height() - shadowTemplateSize.height());
549     destRect.setWidth(shadowRect.width() - radii.bottomLeft().width() - radii.bottomRight().width() - roundedRadius * 4);
550     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
551
552     // Draw the right side.
553     tileRect = FloatRect(shadowTemplateSize.width() - twiceRadius, twiceRadius + radii.topRight().height(), twiceRadius, templateSideLength);
554     destRect = tileRect;
555     destRect.move(shadowRect.x() + twiceRadius + shadowedRect.width() - shadowTemplateSize.width(), shadowRect.y());
556     destRect.setHeight(shadowRect.height() - radii.topRight().height() - radii.bottomRight().height() - roundedRadius * 4);
557     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
558
559     // Draw the left side.
560     tileRect = FloatRect(0, twiceRadius + radii.topLeft().height(), twiceRadius, templateSideLength);
561     destRect = tileRect;
562     destRect.move(shadowRect.x(), shadowRect.y());
563     destRect.setHeight(shadowRect.height() - radii.topLeft().height() - radii.bottomLeft().height() - roundedRadius * 4);
564     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
565
566     // Draw the top left corner.
567     tileRect = FloatRect(0, 0, twiceRadius + radii.topLeft().width(), twiceRadius + radii.topLeft().height());
568     destRect = tileRect;
569     destRect.move(shadowRect.x(), shadowRect.y());
570     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
571
572     // Draw the top right corner.
573     tileRect = FloatRect(shadowTemplateSize.width() - twiceRadius - radii.topRight().width(), 0, twiceRadius + radii.topRight().width(),
574                          twiceRadius + radii.topRight().height());
575     destRect = tileRect;
576     destRect.move(shadowRect.x() + shadowedRect.width() - shadowTemplateSize.width() + twiceRadius, shadowRect.y());
577     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
578
579     // Draw the bottom right corner.
580     tileRect = FloatRect(shadowTemplateSize.width() - twiceRadius - radii.bottomRight().width(),
581                          shadowTemplateSize.height() - twiceRadius - radii.bottomRight().height(),
582                          twiceRadius + radii.bottomRight().width(), twiceRadius + radii.bottomRight().height());
583     destRect = tileRect;
584     destRect.move(shadowRect.x() + shadowedRect.width() - shadowTemplateSize.width() + twiceRadius,
585                   shadowRect.y() + shadowedRect.height() - shadowTemplateSize.height() + twiceRadius);
586     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
587
588     // Draw the bottom left corner.
589     tileRect = FloatRect(0, shadowTemplateSize.height() - twiceRadius - radii.bottomLeft().height(),
590                          twiceRadius + radii.bottomLeft().width(), twiceRadius + radii.bottomLeft().height());
591     destRect = tileRect;
592     destRect.move(shadowRect.x(), shadowRect.y() + shadowedRect.height() - shadowTemplateSize.height() + twiceRadius);
593     graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
594
595     m_layerImage = 0;
596     // Schedule a purge of the scratch buffer.
597     ScratchBuffer::shared().scheduleScratchBufferPurge();
598 }
599
600 #if !PLATFORM(CG)
601 IntRect ShadowBlur::clipBounds(GraphicsContext*)
602 {
603     // FIXME: add clipBounds() to GraphicsContext.
604     ASSERT_NOT_REACHED();
605     return IntRect();
606 }
607 #endif
608
609 } // namespace WebCore