• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
roundUpToMultipleOf32(int d)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:
ScratchBuffer()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 
getScratchBuffer(const IntSize & size)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 
setLastShadowValues(float radius,const Color & color,ColorSpace colorSpace,const FloatRect & shadowRect,const RoundedIntRect::Radii & radii)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 
setLastInsetShadowValues(float radius,const Color & color,ColorSpace colorSpace,const FloatRect & bounds,const FloatRect & shadowRect,const RoundedIntRect::Radii & radii)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 
matchesLastShadow(float radius,const Color & color,ColorSpace colorSpace,const FloatRect & shadowRect,const RoundedIntRect::Radii & radii) const103     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 
matchesLastInsetShadow(float radius,const Color & color,ColorSpace colorSpace,const FloatRect & bounds,const FloatRect & shadowRect,const RoundedIntRect::Radii & radii) const110     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 
scheduleScratchBufferPurge()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:
timerFired(Timer<ScratchBuffer> *)132     void timerFired(Timer<ScratchBuffer>*)
133     {
134         clearScratchBuffer();
135     }
136 
clearScratchBuffer()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 
shared()159 ScratchBuffer& ScratchBuffer::shared()
160 {
161     DEFINE_STATIC_LOCAL(ScratchBuffer, scratchBuffer, ());
162     return scratchBuffer;
163 }
164 
165 static const int templateSideLength = 1;
166 
ShadowBlur(float radius,const FloatSize & offset,const Color & color,ColorSpace colorSpace)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 
blurLayerImage(unsigned char * imageData,const IntSize & size,int rowStride)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 
adjustBlurRadius(GraphicsContext * context)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 
calculateLayerBoundingRect(GraphicsContext * context,const FloatRect & shadowedRect,const IntRect & clipRect)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 
drawShadowBuffer(GraphicsContext * graphicsContext)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 
computeSliceSizesFromRadii(int twiceRadius,const RoundedIntRect::Radii & radii,int & leftSlice,int & rightSlice,int & topSlice,int & bottomSlice)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 
templateSize(const RoundedIntRect::Radii & radii) const430 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 
drawRectShadow(GraphicsContext * graphicsContext,const FloatRect & shadowedRect,const RoundedIntRect::Radii & radii)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 
drawInsetShadow(GraphicsContext * graphicsContext,const FloatRect & rect,const FloatRect & holeRect,const RoundedIntRect::Radii & holeRadii)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 
drawRectShadowWithoutTiling(GraphicsContext * graphicsContext,const FloatRect & shadowedRect,const RoundedIntRect::Radii & radii,const IntRect & layerRect)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 
drawInsetShadowWithoutTiling(GraphicsContext * graphicsContext,const FloatRect & rect,const FloatRect & holeRect,const RoundedIntRect::Radii & holeRadii,const IntRect & layerRect)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 
drawInsetShadowWithTiling(GraphicsContext * graphicsContext,const FloatRect & rect,const FloatRect & holeRect,const RoundedIntRect::Radii & radii,const IntSize & templateSize)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 
drawRectShadowWithTiling(GraphicsContext * graphicsContext,const FloatRect & shadowedRect,const RoundedIntRect::Radii & radii,const IntSize & templateSize)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 
drawLayerPieces(GraphicsContext * graphicsContext,const FloatRect & shadowBounds,const RoundedIntRect::Radii & radii,float roundedRadius,const IntSize & templateSize,ShadowDirection direction)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 
blurShadowBuffer(const IntSize & templateSize)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 
blurAndColorShadowBuffer(const IntSize & templateSize)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
817