1/*
2 * Copyright (C) 2011 Apple Inc. All rights reserved.
3 * Copyright (C) 2010 Sencha, Inc. All rights reserved.
4 * Copyright (C) 2010 Igalia S.L. All rights reserved.
5 * Copyright (C) Research In Motion Limited 2011. All rights reserved.
6 * Copyright (C) 2013 Digia Plc. and/or its subsidiary(-ies).
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#include "config.h"
31#include "ShadowBlur.h"
32
33#include "AffineTransform.h"
34#include "FloatQuad.h"
35#include "GraphicsContext.h"
36#include "ImageBuffer.h"
37#include "Timer.h"
38#include <wtf/MathExtras.h>
39#include <wtf/NeverDestroyed.h>
40#include <wtf/Noncopyable.h>
41
42namespace WebCore {
43
44enum {
45 LeftLobe = 0,
46 RightLobe = 1
47};
48
49#if USE(CG)
50static inline int roundUpToMultipleOf32(int d)
51{
52 return (1 + (d >> 5)) << 5;
53}
54
55// ShadowBlur needs a scratch image as the buffer for the blur filter.
56// Instead of creating and destroying the buffer for every operation,
57// we create a buffer which will be automatically purged via a timer.
58class ScratchBuffer {
59 WTF_MAKE_FAST_ALLOCATED;
60public:
61 ScratchBuffer()
62 : m_purgeTimer(*this, &ScratchBuffer::clearScratchBuffer)
63 , m_lastWasInset(false)
64#if !ASSERT_DISABLED
65 , m_bufferInUse(false)
66#endif
67 {
68 }
69
70 ImageBuffer* getScratchBuffer(const IntSize& size)
71 {
72 ASSERT(!m_bufferInUse);
73#if !ASSERT_DISABLED
74 m_bufferInUse = true;
75#endif
76 // We do not need to recreate the buffer if the current buffer is large enough.
77 if (m_imageBuffer && m_imageBuffer->logicalSize().width() >= size.width() && m_imageBuffer->logicalSize().height() >= size.height())
78 return m_imageBuffer.get();
79
80 // Round to the nearest 32 pixels so we do not grow the buffer for similar sized requests.
81 IntSize roundedSize(roundUpToMultipleOf32(size.width()), roundUpToMultipleOf32(size.height()));
82
83 clearScratchBuffer();
84
85 // ShadowBlur is not used with accelerated drawing, so it's OK to make an unconditionally unaccelerated buffer.
86 m_imageBuffer = ImageBuffer::create(roundedSize, Unaccelerated, 1);
87 return m_imageBuffer.get();
88 }
89
90 bool setCachedShadowValues(const FloatSize& radius, const Color& color, const FloatRect& shadowRect, const FloatRoundedRect::Radii& radii, const FloatSize& layerSize)
91 {
92 if (!m_lastWasInset && m_lastRadius == radius && m_lastColor == color && m_lastShadowRect == shadowRect && m_lastRadii == radii && m_lastLayerSize == layerSize)
93 return false;
94
95 m_lastWasInset = false;
96 m_lastRadius = radius;
97 m_lastColor = color;
98 m_lastShadowRect = shadowRect;
99 m_lastRadii = radii;
100 m_lastLayerSize = layerSize;
101
102 return true;
103 }
104
105 bool setCachedInsetShadowValues(const FloatSize& radius, const Color& color, const FloatRect& bounds, const FloatRect& shadowRect, const FloatRoundedRect::Radii& radii)
106 {
107 if (m_lastWasInset && m_lastRadius == radius && m_lastColor == color && m_lastInsetBounds == bounds && shadowRect == m_lastShadowRect && radii == m_lastRadii)
108 return false;
109
110 m_lastWasInset = true;
111 m_lastInsetBounds = bounds;
112 m_lastRadius = radius;
113 m_lastColor = color;
114 m_lastShadowRect = shadowRect;
115 m_lastRadii = radii;
116
117 return true;
118 }
119
120 void scheduleScratchBufferPurge()
121 {
122#if !ASSERT_DISABLED
123 m_bufferInUse = false;
124#endif
125 if (m_purgeTimer.isActive())
126 m_purgeTimer.stop();
127
128 const Seconds scratchBufferPurgeInterval { 2_s };
129 m_purgeTimer.startOneShot(scratchBufferPurgeInterval);
130 }
131
132 static ScratchBuffer& singleton();
133
134private:
135 void clearScratchBuffer()
136 {
137 m_imageBuffer = nullptr;
138 m_lastRadius = FloatSize();
139 m_lastLayerSize = FloatSize();
140 }
141
142 std::unique_ptr<ImageBuffer> m_imageBuffer;
143 Timer m_purgeTimer;
144
145 FloatRect m_lastInsetBounds;
146 FloatRect m_lastShadowRect;
147 FloatRoundedRect::Radii m_lastRadii;
148 Color m_lastColor;
149 FloatSize m_lastRadius;
150 bool m_lastWasInset;
151 FloatSize m_lastLayerSize;
152
153#if !ASSERT_DISABLED
154 bool m_bufferInUse;
155#endif
156};
157
158ScratchBuffer& ScratchBuffer::singleton()
159{
160 static NeverDestroyed<ScratchBuffer> scratchBuffer;
161 return scratchBuffer;
162}
163
164static float radiusToLegacyRadius(float radius)
165{
166 return radius > 8 ? 8 + 4 * sqrt((radius - 8) / 2) : radius;
167}
168#endif
169
170static const int templateSideLength = 1;
171
172ShadowBlur::ShadowBlur() = default;
173
174ShadowBlur::ShadowBlur(const FloatSize& radius, const FloatSize& offset, const Color& color, bool shadowsIgnoreTransforms)
175 : m_color(color)
176 , m_blurRadius(radius)
177 , m_offset(offset)
178 , m_shadowsIgnoreTransforms(shadowsIgnoreTransforms)
179{
180 updateShadowBlurValues();
181}
182
183ShadowBlur::ShadowBlur(const GraphicsContextState& state)
184 : m_color(state.shadowColor)
185 , m_blurRadius(state.shadowBlur, state.shadowBlur)
186 , m_offset(state.shadowOffset)
187 , m_layerImage(0)
188 , m_shadowsIgnoreTransforms(state.shadowsIgnoreTransforms)
189{
190#if USE(CG)
191 if (state.shadowsUseLegacyRadius) {
192 float shadowBlur = radiusToLegacyRadius(state.shadowBlur);
193 m_blurRadius = FloatSize(shadowBlur, shadowBlur);
194 }
195#endif
196 updateShadowBlurValues();
197}
198
199void ShadowBlur::setShadowValues(const FloatSize& radius, const FloatSize& offset, const Color& color, bool ignoreTransforms)
200{
201 m_blurRadius = radius;
202 m_offset = offset;
203 m_color = color;
204 m_shadowsIgnoreTransforms = ignoreTransforms;
205
206 updateShadowBlurValues();
207}
208
209void ShadowBlur::updateShadowBlurValues()
210{
211 // Limit blur radius to 128 to avoid lots of very expensive blurring.
212 m_blurRadius = m_blurRadius.shrunkTo(FloatSize(128, 128));
213
214 // The type of shadow is decided by the blur radius, shadow offset, and shadow color.
215 if (!m_color.isVisible()) {
216 // Can't paint the shadow with invalid or invisible color.
217 m_type = NoShadow;
218 } else if (m_blurRadius.width() > 0 || m_blurRadius.height() > 0) {
219 // Shadow is always blurred, even the offset is zero.
220 m_type = BlurShadow;
221 } else if (!m_offset.width() && !m_offset.height()) {
222 // Without blur and zero offset means the shadow is fully hidden.
223 m_type = NoShadow;
224 } else
225 m_type = SolidShadow;
226}
227
228// Instead of integer division, we use 17.15 for fixed-point division.
229static const int blurSumShift = 15;
230
231// Takes a two dimensional array with three rows and two columns for the lobes.
232static void calculateLobes(int lobes[][2], float blurRadius, bool shadowsIgnoreTransforms)
233{
234 int diameter;
235 if (shadowsIgnoreTransforms)
236 diameter = std::max(2, static_cast<int>(floorf((2 / 3.f) * blurRadius))); // Canvas shadow. FIXME: we should adjust the blur radius higher up.
237 else {
238 // http://dev.w3.org/csswg/css3-background/#box-shadow
239 // Approximate a Gaussian blur with a standard deviation equal to half the blur radius,
240 // which http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement tell us how to do.
241 // However, shadows rendered according to that spec will extend a little further than m_blurRadius,
242 // so we apply a fudge factor to bring the radius down slightly.
243 float stdDev = blurRadius / 2;
244 const float gaussianKernelFactor = 3 / 4.f * sqrtf(2 * piFloat);
245 const float fudgeFactor = 0.88f;
246 diameter = std::max(2, static_cast<int>(floorf(stdDev * gaussianKernelFactor * fudgeFactor + 0.5f)));
247 }
248
249 if (diameter & 1) {
250 // if d is odd, use three box-blurs of size 'd', centered on the output pixel.
251 int lobeSize = (diameter - 1) / 2;
252 lobes[0][LeftLobe] = lobeSize;
253 lobes[0][RightLobe] = lobeSize;
254 lobes[1][LeftLobe] = lobeSize;
255 lobes[1][RightLobe] = lobeSize;
256 lobes[2][LeftLobe] = lobeSize;
257 lobes[2][RightLobe] = lobeSize;
258 } else {
259 // if d is even, two box-blurs of size 'd' (the first one centered on the pixel boundary
260 // between the output pixel and the one to the left, the second one centered on the pixel
261 // boundary between the output pixel and the one to the right) and one box blur of size 'd+1' centered on the output pixel
262 int lobeSize = diameter / 2;
263 lobes[0][LeftLobe] = lobeSize;
264 lobes[0][RightLobe] = lobeSize - 1;
265 lobes[1][LeftLobe] = lobeSize - 1;
266 lobes[1][RightLobe] = lobeSize;
267 lobes[2][LeftLobe] = lobeSize;
268 lobes[2][RightLobe] = lobeSize;
269 }
270}
271
272void ShadowBlur::clear()
273{
274 m_type = NoShadow;
275 m_color = Color();
276 m_blurRadius = FloatSize();
277 m_offset = FloatSize();
278}
279
280void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride)
281{
282 const int channels[4] = { 3, 0, 1, 3 };
283
284 int lobes[3][2]; // indexed by pass, and left/right lobe
285 calculateLobes(lobes, m_blurRadius.width(), m_shadowsIgnoreTransforms);
286
287 // First pass is horizontal.
288 int stride = 4;
289 int delta = rowStride;
290 int final = size.height();
291 int dim = size.width();
292
293 // Two stages: horizontal and vertical
294 for (int pass = 0; pass < 2; ++pass) {
295 unsigned char* pixels = imageData;
296
297 if (!pass && !m_blurRadius.width())
298 final = 0; // Do no work if horizonal blur is zero.
299
300 for (int j = 0; j < final; ++j, pixels += delta) {
301 // For each step, we blur the alpha in a channel and store the result
302 // in another channel for the subsequent step.
303 // We use sliding window algorithm to accumulate the alpha values.
304 // This is much more efficient than computing the sum of each pixels
305 // covered by the box kernel size for each x.
306 for (int step = 0; step < 3; ++step) {
307 int side1 = lobes[step][LeftLobe];
308 int side2 = lobes[step][RightLobe];
309 int pixelCount = side1 + 1 + side2;
310 int invCount = ((1 << blurSumShift) + pixelCount - 1) / pixelCount;
311 int ofs = 1 + side2;
312 int alpha1 = pixels[channels[step]];
313 int alpha2 = pixels[(dim - 1) * stride + channels[step]];
314
315 unsigned char* ptr = pixels + channels[step + 1];
316 unsigned char* prev = pixels + stride + channels[step];
317 unsigned char* next = pixels + ofs * stride + channels[step];
318
319 int i;
320 int sum = side1 * alpha1 + alpha1;
321 int limit = (dim < side2 + 1) ? dim : side2 + 1;
322
323 for (i = 1; i < limit; ++i, prev += stride)
324 sum += *prev;
325
326 if (limit <= side2)
327 sum += (side2 - limit + 1) * alpha2;
328
329 limit = (side1 < dim) ? side1 : dim;
330 for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) {
331 *ptr = (sum * invCount) >> blurSumShift;
332 sum += ((ofs < dim) ? *next : alpha2) - alpha1;
333 }
334
335 prev = pixels + channels[step];
336 for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) {
337 *ptr = (sum * invCount) >> blurSumShift;
338 sum += (*next) - (*prev);
339 }
340
341 for (; i < dim; ptr += stride, prev += stride, ++i) {
342 *ptr = (sum * invCount) >> blurSumShift;
343 sum += alpha2 - (*prev);
344 }
345 }
346 }
347
348 // Last pass is vertical.
349 stride = rowStride;
350 delta = 4;
351 final = size.width();
352 dim = size.height();
353
354 if (!m_blurRadius.height())
355 break;
356
357 if (m_blurRadius.width() != m_blurRadius.height())
358 calculateLobes(lobes, m_blurRadius.height(), m_shadowsIgnoreTransforms);
359 }
360}
361
362void ShadowBlur::adjustBlurRadius(const AffineTransform& transform)
363{
364 if (m_shadowsIgnoreTransforms)
365 m_blurRadius.scale(1 / static_cast<float>(transform.xScale()), 1 / static_cast<float>(transform.yScale()));
366}
367
368IntSize ShadowBlur::blurredEdgeSize() const
369{
370 IntSize edgeSize = expandedIntSize(m_blurRadius);
371
372 // To avoid slowing down blurLayerImage() for radius == 1, we give it two empty pixels on each side.
373 if (edgeSize.width() == 1)
374 edgeSize.setWidth(2);
375
376 if (edgeSize.height() == 1)
377 edgeSize.setHeight(2);
378
379 return edgeSize;
380}
381
382IntSize ShadowBlur::calculateLayerBoundingRect(const AffineTransform& transform, const FloatRect& shadowedRect, const IntRect& clipRect)
383{
384 IntSize edgeSize = blurredEdgeSize();
385
386 // Calculate the destination of the blurred and/or transformed layer.
387 FloatRect layerRect;
388 IntSize inflation;
389
390 if (m_shadowsIgnoreTransforms && !transform.isIdentity()) {
391 FloatQuad transformedPolygon = transform.mapQuad(FloatQuad(shadowedRect));
392 transformedPolygon.move(m_offset);
393 layerRect = transform.inverse().valueOr(AffineTransform()).mapQuad(transformedPolygon).boundingBox();
394 } else {
395 layerRect = shadowedRect;
396 layerRect.move(m_offset);
397 }
398
399 // We expand the area by the blur radius to give extra space for the blur transition.
400 if (m_type == BlurShadow) {
401 layerRect.inflateX(edgeSize.width());
402 layerRect.inflateY(edgeSize.height());
403 inflation = edgeSize;
404 }
405
406 FloatRect unclippedLayerRect = layerRect;
407
408 if (!clipRect.contains(enclosingIntRect(layerRect))) {
409 // If we are totally outside the clip region, we aren't painting at all.
410 if (intersection(layerRect, clipRect).isEmpty())
411 return IntSize();
412
413 IntRect inflatedClip = clipRect;
414 // Pixels at the edges can be affected by pixels outside the buffer,
415 // so intersect with the clip inflated by the blur.
416 if (m_type == BlurShadow) {
417 inflatedClip.inflateX(edgeSize.width());
418 inflatedClip.inflateY(edgeSize.height());
419 } else {
420 // Enlarge the clipping area 1 pixel so that the fill does not
421 // bleed (due to antialiasing) even if the unaligned clip rect occurred
422 inflatedClip.inflateX(1);
423 inflatedClip.inflateY(1);
424 }
425
426 layerRect.intersect(inflatedClip);
427 }
428
429 IntSize frameSize = inflation;
430 frameSize.scale(2);
431 m_shadowedResultSize = FloatSize(shadowedRect.width() + frameSize.width(), shadowedRect.height() + frameSize.height());
432 m_layerOrigin = FloatPoint(layerRect.x(), layerRect.y());
433 m_layerSize = layerRect.size();
434
435 const FloatPoint unclippedLayerOrigin = FloatPoint(unclippedLayerRect.x(), unclippedLayerRect.y());
436 const FloatSize clippedOut = unclippedLayerOrigin - m_layerOrigin;
437
438 // Set the origin as the top left corner of the scratch image, or, in case there's a clipped
439 // out region, set the origin accordingly to the full bounding rect's top-left corner.
440 float translationX = -shadowedRect.x() + inflation.width() - fabsf(clippedOut.width());
441 float translationY = -shadowedRect.y() + inflation.height() - fabsf(clippedOut.height());
442 m_layerContextTranslation = FloatSize(translationX, translationY);
443
444 return expandedIntSize(layerRect.size());
445}
446
447void ShadowBlur::drawShadowBuffer(GraphicsContext& graphicsContext)
448{
449 if (!m_layerImage)
450 return;
451
452 GraphicsContextStateSaver stateSaver(graphicsContext);
453
454 IntSize bufferSize = m_layerImage->internalSize();
455 if (bufferSize != m_layerSize) {
456 // The rect passed to clipToImageBuffer() has to be the size of the entire buffer,
457 // but we may not have cleared it all, so clip to the filled part first.
458 graphicsContext.clip(FloatRect(m_layerOrigin, m_layerSize));
459 }
460 graphicsContext.clipToImageBuffer(*m_layerImage, FloatRect(m_layerOrigin, bufferSize));
461 graphicsContext.setFillColor(m_color);
462
463 graphicsContext.clearShadow();
464 graphicsContext.fillRect(FloatRect(m_layerOrigin, m_layerSize));
465}
466
467static void computeSliceSizesFromRadii(const IntSize& twiceRadius, const FloatRoundedRect::Radii& radii, int& leftSlice, int& rightSlice, int& topSlice, int& bottomSlice)
468{
469 leftSlice = twiceRadius.width() + std::max(radii.topLeft().width(), radii.bottomLeft().width());
470 rightSlice = twiceRadius.width() + std::max(radii.topRight().width(), radii.bottomRight().width());
471
472 topSlice = twiceRadius.height() + std::max(radii.topLeft().height(), radii.topRight().height());
473 bottomSlice = twiceRadius.height() + std::max(radii.bottomLeft().height(), radii.bottomRight().height());
474}
475
476IntSize ShadowBlur::templateSize(const IntSize& radiusPadding, const FloatRoundedRect::Radii& radii) const
477{
478 const int templateSideLength = 1;
479
480 int leftSlice;
481 int rightSlice;
482 int topSlice;
483 int bottomSlice;
484
485 IntSize blurExpansion = radiusPadding;
486 blurExpansion.scale(2);
487
488 computeSliceSizesFromRadii(blurExpansion, radii, leftSlice, rightSlice, topSlice, bottomSlice);
489
490 return IntSize(templateSideLength + leftSlice + rightSlice, templateSideLength + topSlice + bottomSlice);
491}
492
493void ShadowBlur::drawRectShadow(GraphicsContext& graphicsContext, const FloatRoundedRect& shadowedRect)
494{
495 drawRectShadow(graphicsContext.getCTM(), graphicsContext.clipBounds(), shadowedRect,
496 [this, &graphicsContext](ImageBuffer&, const FloatPoint&, const FloatSize&) {
497 // FIXME: Use parameters instead of implicit parameters defined as class variables.
498 drawShadowBuffer(graphicsContext);
499 },
500 [&graphicsContext](ImageBuffer& image, const FloatRect& destRect, const FloatRect& srcRect) {
501 GraphicsContextStateSaver stateSaver(graphicsContext);
502 graphicsContext.clearShadow();
503 graphicsContext.drawImageBuffer(image, destRect, srcRect);
504 },
505 [&graphicsContext](const FloatRect& rect, const Color& color) {
506 GraphicsContextStateSaver stateSaver(graphicsContext);
507 graphicsContext.setFillColor(color);
508 graphicsContext.clearShadow();
509 graphicsContext.fillRect(rect);
510 });
511}
512
513void ShadowBlur::drawInsetShadow(GraphicsContext& graphicsContext, const FloatRect& fullRect, const FloatRoundedRect& holeRect)
514{
515 drawInsetShadow(graphicsContext.getCTM(), graphicsContext.clipBounds(), fullRect, holeRect,
516 [this, &graphicsContext](ImageBuffer&, const FloatPoint&, const FloatSize&) {
517 // FIXME: Use parameters instead of implicit parameters defined as class variables.
518 drawShadowBuffer(graphicsContext);
519 },
520 [&graphicsContext](ImageBuffer& image, const FloatRect& destRect, const FloatRect& srcRect) {
521 // Note that drawing the ImageBuffer is faster than creating a Image and drawing that,
522 // because ImageBuffer::draw() knows that it doesn't have to copy the image bits.
523 GraphicsContextStateSaver stateSaver(graphicsContext);
524 graphicsContext.clearShadow();
525 graphicsContext.drawImageBuffer(image, destRect, srcRect);
526 },
527 [&graphicsContext](const FloatRect& rect, const FloatRect& holeRect, const Color& color) {
528 Path exteriorPath;
529 exteriorPath.addRect(rect);
530 exteriorPath.addRect(holeRect);
531
532 GraphicsContextStateSaver fillStateSaver(graphicsContext);
533 graphicsContext.setFillRule(WindRule::EvenOdd);
534 graphicsContext.setFillColor(color);
535 graphicsContext.clearShadow();
536 graphicsContext.fillPath(exteriorPath);
537 });
538}
539
540void ShadowBlur::drawRectShadow(const AffineTransform& transform, const IntRect& clipBounds, const FloatRoundedRect& shadowedRect, const DrawBufferCallback& drawBuffer, const DrawImageCallback& drawImage, const FillRectCallback& fillRect)
541{
542 IntSize layerSize = calculateLayerBoundingRect(transform, shadowedRect.rect(), clipBounds);
543 if (layerSize.isEmpty())
544 return;
545
546 adjustBlurRadius(transform);
547
548 bool canUseTilingTechnique = true;
549
550 // drawRectShadowWithTiling does not work with rotations.
551 // https://bugs.webkit.org/show_bug.cgi?id=45042
552 if (!transform.preservesAxisAlignment() || m_type != BlurShadow)
553 canUseTilingTechnique = false;
554
555 IntSize edgeSize = blurredEdgeSize();
556 IntSize templateSize = this->templateSize(edgeSize, shadowedRect.radii());
557 const FloatRect& rect = shadowedRect.rect();
558
559 if (templateSize.width() > rect.width() || templateSize.height() > rect.height()
560 || (templateSize.width() * templateSize.height() > m_shadowedResultSize.width() * m_shadowedResultSize.height()))
561 canUseTilingTechnique = false;
562
563 if (canUseTilingTechnique)
564 drawRectShadowWithTiling(transform, shadowedRect, templateSize, edgeSize, drawImage, fillRect);
565 else
566 drawRectShadowWithoutTiling(transform, shadowedRect, layerSize, drawBuffer);
567}
568
569void ShadowBlur::drawInsetShadow(const AffineTransform& transform, const IntRect& clipBounds, const FloatRect& fullRect, const FloatRoundedRect& holeRect, const DrawBufferCallback& drawBuffer, const DrawImageCallback& drawImage, const FillRectWithHoleCallback& fillRectWithHole)
570{
571 IntSize layerSize = calculateLayerBoundingRect(transform, fullRect, clipBounds);
572 if (layerSize.isEmpty())
573 return;
574
575 adjustBlurRadius(transform);
576
577 bool canUseTilingTechnique = true;
578
579 // drawRectShadowWithTiling does not work with rotations.
580 // https://bugs.webkit.org/show_bug.cgi?id=45042
581 if (!transform.preservesAxisAlignment() || m_type != BlurShadow)
582 canUseTilingTechnique = false;
583
584 IntSize edgeSize = blurredEdgeSize();
585 IntSize templateSize = this->templateSize(edgeSize, holeRect.radii());
586 const FloatRect& hRect = holeRect.rect();
587
588 if (templateSize.width() > hRect.width() || templateSize.height() > hRect.height()
589 || (templateSize.width() * templateSize.height() > hRect.width() * hRect.height()))
590 canUseTilingTechnique = false;
591
592 if (canUseTilingTechnique)
593 drawInsetShadowWithTiling(transform, fullRect, holeRect, templateSize, edgeSize, drawImage, fillRectWithHole);
594 else
595 drawInsetShadowWithoutTiling(transform, fullRect, holeRect, layerSize, drawBuffer);
596}
597
598void ShadowBlur::drawRectShadowWithoutTiling(const AffineTransform&, const FloatRoundedRect& shadowedRect, const IntSize& layerSize, const DrawBufferCallback& drawBuffer)
599{
600 auto layerImage = ImageBuffer::create(layerSize, Unaccelerated, 1);
601 if (!layerImage)
602 return;
603 m_layerImage = layerImage.get();
604
605 GraphicsContext& shadowContext = layerImage->context();
606 GraphicsContextStateSaver stateSaver(shadowContext);
607 shadowContext.setFillColor(Color::black);
608
609 {
610 GraphicsContext& shadowContext = layerImage->context();
611 GraphicsContextStateSaver stateSaver(shadowContext);
612 shadowContext.translate(m_layerContextTranslation);
613 shadowContext.setFillColor(Color::black);
614 if (shadowedRect.radii().isZero())
615 shadowContext.fillRect(shadowedRect.rect());
616 else {
617 Path path;
618 path.addRoundedRect(shadowedRect);
619 shadowContext.fillPath(path);
620 }
621
622 blurShadowBuffer(layerSize);
623 }
624 drawBuffer(*layerImage, m_layerOrigin, m_layerSize);
625}
626
627void ShadowBlur::drawInsetShadowWithoutTiling(const AffineTransform&, const FloatRect& fullRect, const FloatRoundedRect& holeRect, const IntSize& layerSize, const DrawBufferCallback& drawBuffer)
628{
629 auto layerImage = ImageBuffer::create(layerSize, Unaccelerated, 1);
630 if (!layerImage)
631 return;
632 m_layerImage = layerImage.get();
633
634 {
635 GraphicsContext& shadowContext = layerImage->context();
636 GraphicsContextStateSaver stateSaver(shadowContext);
637 shadowContext.translate(m_layerContextTranslation);
638
639 Path path;
640 path.addRect(fullRect);
641 if (holeRect.radii().isZero())
642 path.addRect(holeRect.rect());
643 else
644 path.addRoundedRect(holeRect);
645
646 shadowContext.setFillRule(WindRule::EvenOdd);
647 shadowContext.setFillColor(Color::black);
648 shadowContext.fillPath(path);
649
650 blurShadowBuffer(layerSize);
651 }
652
653 drawBuffer(*layerImage, m_layerOrigin, m_layerSize);
654}
655
656/*
657 These functions use tiling to improve the performance of the shadow
658 drawing of rounded rectangles. The code basically does the following
659 steps:
660
661 1. Calculate the size of the shadow template, a rectangle that
662 contains all the necessary tiles to draw the complete shadow.
663
664 2. If that size is smaller than the real rectangle render the new
665 template rectangle and its shadow in a new surface, in other case
666 render the shadow of the real rectangle in the destination
667 surface.
668
669 3. Calculate the sizes and positions of the tiles and their
670 destinations and use drawPattern to render the final shadow. The
671 code divides the rendering in 8 tiles:
672
673 1 | 2 | 3
674 -----------
675 4 | | 5
676 -----------
677 6 | 7 | 8
678
679 The corners are directly copied from the template rectangle to the
680 real one and the side tiles are 1 pixel width, we use them as
681 tiles to cover the destination side. The corner tiles are bigger
682 than just the side of the rounded corner, we need to increase it
683 because the modifications caused by the corner over the blur
684 effect. We fill the central or outer part with solid color to complete
685 the shadow.
686 */
687
688void ShadowBlur::drawRectShadowWithTiling(const AffineTransform& transform, const FloatRoundedRect& shadowedRect, const IntSize& templateSize, const IntSize& edgeSize, const DrawImageCallback& drawImage, const FillRectCallback& fillRect)
689{
690#if USE(CG)
691 m_layerImage = ScratchBuffer::singleton().getScratchBuffer(templateSize);
692#else
693 auto layerImage = ImageBuffer::create(templateSize, Unaccelerated, 1);
694 m_layerImage = layerImage.get();
695#endif
696
697 if (!m_layerImage)
698 return;
699
700 FloatRect templateShadow = FloatRect(edgeSize.width(), edgeSize.height(), templateSize.width() - 2 * edgeSize.width(), templateSize.height() - 2 * edgeSize.height());
701
702 bool redrawNeeded = true;
703#if USE(CG)
704 // Only redraw in the scratch buffer if its cached contents don't match our needs
705 redrawNeeded = ScratchBuffer::singleton().setCachedShadowValues(m_blurRadius, m_color, templateShadow, shadowedRect.radii(), m_layerSize);
706#endif
707
708 if (redrawNeeded) {
709 // Draw shadow into the ImageBuffer.
710 GraphicsContext& shadowContext = m_layerImage->context();
711 GraphicsContextStateSaver shadowStateSaver(shadowContext);
712
713 shadowContext.clearRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
714 shadowContext.setFillColor(Color::black);
715
716 if (shadowedRect.radii().isZero())
717 shadowContext.fillRect(templateShadow);
718 else {
719 Path path;
720 path.addRoundedRect(FloatRoundedRect(templateShadow, shadowedRect.radii()));
721 shadowContext.fillPath(path);
722 }
723 blurAndColorShadowBuffer(templateSize);
724 }
725
726 FloatSize offset = m_offset;
727 if (shadowsIgnoreTransforms())
728 offset.scale(1 / transform.xScale(), 1 / transform.yScale());
729
730 FloatRect shadowBounds = shadowedRect.rect();
731 shadowBounds.move(offset);
732 shadowBounds.inflateX(edgeSize.width());
733 shadowBounds.inflateY(edgeSize.height());
734
735 drawLayerPiecesAndFillCenter(shadowBounds, shadowedRect.radii(), edgeSize, templateSize, drawImage, fillRect);
736
737 m_layerImage = nullptr;
738
739#if USE(CG)
740 ScratchBuffer::singleton().scheduleScratchBufferPurge();
741#endif
742}
743
744void ShadowBlur::drawInsetShadowWithTiling(const AffineTransform& transform, const FloatRect& fullRect, const FloatRoundedRect& holeRect, const IntSize& templateSize, const IntSize& edgeSize, const DrawImageCallback& drawImage, const FillRectWithHoleCallback& fillRectWithHole)
745{
746#if USE(CG)
747 m_layerImage = ScratchBuffer::singleton().getScratchBuffer(templateSize);
748#else
749 auto layerImage = ImageBuffer::create(templateSize, Unaccelerated, 1);
750 m_layerImage = layerImage.get();
751#endif
752
753 if (!m_layerImage)
754 return;
755
756 // Draw the rectangle with hole.
757 FloatRect templateBounds(0, 0, templateSize.width(), templateSize.height());
758 FloatRect templateHole = FloatRect(edgeSize.width(), edgeSize.height(), templateSize.width() - 2 * edgeSize.width(), templateSize.height() - 2 * edgeSize.height());
759
760 bool redrawNeeded = true;
761#if USE(CG)
762 // Only redraw in the scratch buffer if its cached contents don't match our needs
763 redrawNeeded = ScratchBuffer::singleton().setCachedInsetShadowValues(m_blurRadius, m_color, templateBounds, templateHole, holeRect.radii());
764#endif
765
766 if (redrawNeeded) {
767 // Draw shadow into a new ImageBuffer.
768 GraphicsContext& shadowContext = m_layerImage->context();
769 GraphicsContextStateSaver shadowStateSaver(shadowContext);
770 shadowContext.clearRect(templateBounds);
771 shadowContext.setFillRule(WindRule::EvenOdd);
772 shadowContext.setFillColor(Color::black);
773
774 Path path;
775 path.addRect(templateBounds);
776 if (holeRect.radii().isZero())
777 path.addRect(templateHole);
778 else
779 path.addRoundedRect(FloatRoundedRect(templateHole, holeRect.radii()));
780
781 shadowContext.fillPath(path);
782
783 blurAndColorShadowBuffer(templateSize);
784 }
785 FloatSize offset = m_offset;
786 if (shadowsIgnoreTransforms())
787 offset.scale(1 / transform.xScale(), 1 / transform.yScale());
788
789 FloatRect boundingRect = fullRect;
790 boundingRect.move(offset);
791
792 FloatRect destHoleRect = holeRect.rect();
793 destHoleRect.move(offset);
794 FloatRect destHoleBounds = destHoleRect;
795 destHoleBounds.inflateX(edgeSize.width());
796 destHoleBounds.inflateY(edgeSize.height());
797
798 // Fill the external part of the shadow (which may be visible because of offset).
799 fillRectWithHole(boundingRect, destHoleBounds, m_color);
800
801 drawLayerPieces(destHoleBounds, holeRect.radii(), edgeSize, templateSize, drawImage);
802
803 m_layerImage = nullptr;
804
805#if USE(CG)
806 ScratchBuffer::singleton().scheduleScratchBufferPurge();
807#endif
808}
809
810void ShadowBlur::drawLayerPieces(const FloatRect& shadowBounds, const FloatRoundedRect::Radii& radii, const IntSize& bufferPadding, const IntSize& templateSize, const DrawImageCallback& drawImage)
811{
812 const IntSize twiceRadius = IntSize(bufferPadding.width() * 2, bufferPadding.height() * 2);
813
814 int leftSlice;
815 int rightSlice;
816 int topSlice;
817 int bottomSlice;
818 computeSliceSizesFromRadii(twiceRadius, radii, leftSlice, rightSlice, topSlice, bottomSlice);
819
820 int centerWidth = shadowBounds.width() - leftSlice - rightSlice;
821 int centerHeight = shadowBounds.height() - topSlice - bottomSlice;
822 FloatRect centerRect(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight);
823
824 // Top side.
825 FloatRect tileRect = FloatRect(leftSlice, 0, templateSideLength, topSlice);
826 FloatRect destRect = FloatRect(centerRect.x(), centerRect.y() - topSlice, centerRect.width(), topSlice);
827 drawImage(*m_layerImage, destRect, tileRect);
828
829 // Draw the bottom side.
830 tileRect.setY(templateSize.height() - bottomSlice);
831 tileRect.setHeight(bottomSlice);
832 destRect.setY(centerRect.maxY());
833 destRect.setHeight(bottomSlice);
834 drawImage(*m_layerImage, destRect, tileRect);
835
836 // Left side.
837 tileRect = FloatRect(0, topSlice, leftSlice, templateSideLength);
838 destRect = FloatRect(centerRect.x() - leftSlice, centerRect.y(), leftSlice, centerRect.height());
839 drawImage(*m_layerImage, destRect, tileRect);
840
841 // Right side.
842 tileRect.setX(templateSize.width() - rightSlice);
843 tileRect.setWidth(rightSlice);
844 destRect.setX(centerRect.maxX());
845 destRect.setWidth(rightSlice);
846 drawImage(*m_layerImage, destRect, tileRect);
847
848 // Top left corner.
849 tileRect = FloatRect(0, 0, leftSlice, topSlice);
850 destRect = FloatRect(centerRect.x() - leftSlice, centerRect.y() - topSlice, leftSlice, topSlice);
851 drawImage(*m_layerImage, destRect, tileRect);
852
853 // Top right corner.
854 tileRect = FloatRect(templateSize.width() - rightSlice, 0, rightSlice, topSlice);
855 destRect = FloatRect(centerRect.maxX(), centerRect.y() - topSlice, rightSlice, topSlice);
856 drawImage(*m_layerImage, destRect, tileRect);
857
858 // Bottom right corner.
859 tileRect = FloatRect(templateSize.width() - rightSlice, templateSize.height() - bottomSlice, rightSlice, bottomSlice);
860 destRect = FloatRect(centerRect.maxX(), centerRect.maxY(), rightSlice, bottomSlice);
861 drawImage(*m_layerImage, destRect, tileRect);
862
863 // Bottom left corner.
864 tileRect = FloatRect(0, templateSize.height() - bottomSlice, leftSlice, bottomSlice);
865 destRect = FloatRect(centerRect.x() - leftSlice, centerRect.maxY(), leftSlice, bottomSlice);
866 drawImage(*m_layerImage, destRect, tileRect);
867}
868
869void ShadowBlur::drawLayerPiecesAndFillCenter(const FloatRect& shadowBounds, const FloatRoundedRect::Radii& radii, const IntSize& bufferPadding, const IntSize& templateSize, const DrawImageCallback& drawImage, const FillRectCallback& fillRect)
870{
871 const IntSize twiceRadius = IntSize(bufferPadding.width() * 2, bufferPadding.height() * 2);
872
873 int leftSlice;
874 int rightSlice;
875 int topSlice;
876 int bottomSlice;
877 computeSliceSizesFromRadii(twiceRadius, radii, leftSlice, rightSlice, topSlice, bottomSlice);
878
879 int centerWidth = shadowBounds.width() - leftSlice - rightSlice;
880 int centerHeight = shadowBounds.height() - topSlice - bottomSlice;
881 FloatRect centerRect(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight);
882
883 // Fill center
884 if (!centerRect.isEmpty())
885 fillRect(centerRect, m_color);
886
887 drawLayerPieces(shadowBounds, radii, bufferPadding, templateSize, drawImage);
888}
889
890void ShadowBlur::blurShadowBuffer(const IntSize& templateSize)
891{
892 if (m_type != BlurShadow)
893 return;
894
895 IntRect blurRect(IntPoint(), templateSize);
896 auto layerData = m_layerImage->getUnmultipliedImageData(blurRect);
897 if (!layerData)
898 return;
899
900 blurLayerImage(layerData->data(), blurRect.size(), blurRect.width() * 4);
901 m_layerImage->putByteArray(*layerData, AlphaPremultiplication::Unpremultiplied, blurRect.size(), blurRect, { });
902}
903
904void ShadowBlur::blurAndColorShadowBuffer(const IntSize& templateSize)
905{
906 blurShadowBuffer(templateSize);
907
908 // Mask the image with the shadow color.
909 GraphicsContext& shadowContext = m_layerImage->context();
910 GraphicsContextStateSaver stateSaver(shadowContext);
911 shadowContext.setCompositeOperation(CompositeSourceIn);
912 shadowContext.setFillColor(m_color);
913 shadowContext.fillRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
914}
915
916void ShadowBlur::drawShadowLayer(const AffineTransform& transform, const IntRect& clipBounds, const FloatRect& layerArea, const DrawShadowCallback& drawShadow, const DrawBufferCallback& drawBuffer)
917{
918 IntSize layerSize = calculateLayerBoundingRect(transform, layerArea, clipBounds);
919 if (layerSize.isEmpty())
920 return;
921
922 adjustBlurRadius(transform);
923
924 auto layerImage = ImageBuffer::create(layerSize, Unaccelerated, 1);
925 if (!layerImage)
926 return;
927 m_layerImage = layerImage.get();
928
929 {
930 GraphicsContext& shadowContext = layerImage->context();
931 GraphicsContextStateSaver stateSaver(shadowContext);
932 shadowContext.translate(m_layerContextTranslation);
933 drawShadow(shadowContext);
934 }
935
936 blurAndColorShadowBuffer(expandedIntSize(m_layerSize));
937 drawBuffer(*layerImage, m_layerOrigin, m_layerSize);
938}
939
940} // namespace WebCore
941