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