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 | |
42 | namespace WebCore { |
43 | |
44 | enum { |
45 | LeftLobe = 0, |
46 | RightLobe = 1 |
47 | }; |
48 | |
49 | #if USE(CG) |
50 | static inline int roundUpToMultipleOf32(int d) |
51 | { |
52 | return (1 + (d >> 5)) << 5; |
53 | } |
54 | |
55 | // ShadowBlur needs a scratch image as the buffer for the blur filter. |
56 | // Instead of creating and destroying the buffer for every operation, |
57 | // we create a buffer which will be automatically purged via a timer. |
58 | class ScratchBuffer { |
59 | WTF_MAKE_FAST_ALLOCATED; |
60 | public: |
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 | |
134 | private: |
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 | |
158 | ScratchBuffer& ScratchBuffer::singleton() |
159 | { |
160 | static NeverDestroyed<ScratchBuffer> scratchBuffer; |
161 | return scratchBuffer; |
162 | } |
163 | |
164 | static float radiusToLegacyRadius(float radius) |
165 | { |
166 | return radius > 8 ? 8 + 4 * sqrt((radius - 8) / 2) : radius; |
167 | } |
168 | #endif |
169 | |
170 | static const int templateSideLength = 1; |
171 | |
172 | ShadowBlur::ShadowBlur() = default; |
173 | |
174 | ShadowBlur::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 | |
183 | ShadowBlur::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 | |
199 | void 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 | |
209 | void 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. |
229 | static const int blurSumShift = 15; |
230 | |
231 | // Takes a two dimensional array with three rows and two columns for the lobes. |
232 | static 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 | |
272 | void ShadowBlur::clear() |
273 | { |
274 | m_type = NoShadow; |
275 | m_color = Color(); |
276 | m_blurRadius = FloatSize(); |
277 | m_offset = FloatSize(); |
278 | } |
279 | |
280 | void 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 | |
362 | void 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 | |
368 | IntSize 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 | |
382 | IntSize 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 | |
447 | void 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 | |
467 | static 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 | |
476 | IntSize 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 | |
493 | void 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 | |
513 | void 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 | |
540 | void 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 | |
569 | void 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 | |
598 | void 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 | |
627 | void 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 | |
688 | void 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 | |
744 | void 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 | |
810 | void 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 | |
869 | void 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 | |
890 | void 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 | |
904 | void 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 | |
916 | void 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 | |