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.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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.
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.
30 #include "ShadowBlur.h"
32 #include "AffineTransform.h"
33 #include "FloatQuad.h"
34 #include "GraphicsContext.h"
35 #include "ImageBuffer.h"
37 #include <wtf/MathExtras.h>
38 #include <wtf/Noncopyable.h>
39 #include <wtf/UnusedParam.h>
50 static inline int roundUpToMultipleOf32(int d)
52 return (1 + (d >> 5)) << 5;
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.
61 : m_purgeTimer(this, &ScratchBuffer::timerFired)
62 , m_lastWasInset(false)
64 , m_bufferInUse(false)
69 ImageBuffer* getScratchBuffer(const IntSize& size)
71 ASSERT(!m_bufferInUse);
75 // We do not need to recreate the buffer if the current buffer is large enough.
76 if (m_imageBuffer && m_imageBuffer->logicalSize().width() >= size.width() && m_imageBuffer->logicalSize().height() >= size.height())
77 return m_imageBuffer.get();
79 // Round to the nearest 32 pixels so we do not grow the buffer for similar sized requests.
80 IntSize roundedSize(roundUpToMultipleOf32(size.width()), roundUpToMultipleOf32(size.height()));
83 m_imageBuffer = ImageBuffer::create(roundedSize, 1);
84 return m_imageBuffer.get();
87 bool setCachedShadowValues(const FloatSize& radius, const Color& color, ColorSpace colorSpace, const FloatRect& shadowRect, const RoundedRect::Radii& radii, const FloatSize& layerSize)
89 if (!m_lastWasInset && m_lastRadius == radius && m_lastColor == color && m_lastColorSpace == colorSpace && m_lastShadowRect == shadowRect && m_lastRadii == radii && m_lastLayerSize == layerSize)
92 m_lastWasInset = false;
93 m_lastRadius = radius;
95 m_lastColorSpace = colorSpace;
96 m_lastShadowRect = shadowRect;
98 m_lastLayerSize = layerSize;
103 #if ENABLE(TIZEN_SHADOW_BLUR_SCRATCH_BUFFER_WITH_LAYER_BOUNDS)
104 bool setCachedShadowValues(const FloatSize& radius, const Color& color, ColorSpace colorSpace, const FloatRect& shadowRect, const RoundedRect::Radii& radii, const FloatRect& layerBounds)
106 if (!m_lastWasInset && m_lastRadius == radius && m_lastColor == color && m_lastColorSpace == colorSpace && m_lastShadowRect == shadowRect && m_lastRadii == radii && m_lastLayerBounds == layerBounds)
109 m_lastWasInset = false;
110 m_lastRadius = radius;
112 m_lastColorSpace = colorSpace;
113 m_lastShadowRect = shadowRect;
115 m_lastLayerSize = layerBounds.size();
116 m_lastLayerBounds = layerBounds;
122 bool setCachedInsetShadowValues(const FloatSize& radius, const Color& color, ColorSpace colorSpace, const FloatRect& bounds, const FloatRect& shadowRect, const RoundedRect::Radii& radii)
124 if (m_lastWasInset && m_lastRadius == radius && m_lastColor == color && m_lastColorSpace == colorSpace && m_lastInsetBounds == bounds && shadowRect == m_lastShadowRect && radii == m_lastRadii)
127 m_lastWasInset = true;
128 m_lastInsetBounds = bounds;
129 m_lastRadius = radius;
131 m_lastColorSpace = colorSpace;
132 m_lastShadowRect = shadowRect;
138 void scheduleScratchBufferPurge()
141 m_bufferInUse = false;
143 if (m_purgeTimer.isActive())
146 const double scratchBufferPurgeInterval = 2;
147 m_purgeTimer.startOneShot(scratchBufferPurgeInterval);
150 static ScratchBuffer& shared();
153 void timerFired(Timer<ScratchBuffer>*)
155 clearScratchBuffer();
158 void clearScratchBuffer()
160 m_imageBuffer = nullptr;
161 m_lastRadius = FloatSize();
164 OwnPtr<ImageBuffer> m_imageBuffer;
165 Timer<ScratchBuffer> m_purgeTimer;
167 FloatRect m_lastInsetBounds;
168 FloatRect m_lastShadowRect;
169 RoundedRect::Radii m_lastRadii;
171 ColorSpace m_lastColorSpace;
172 FloatSize m_lastRadius;
174 FloatSize m_lastLayerSize;
176 #if ENABLE(TIZEN_SHADOW_BLUR_SCRATCH_BUFFER_WITH_LAYER_BOUNDS)
177 FloatRect m_lastLayerBounds;
185 ScratchBuffer& ScratchBuffer::shared()
187 DEFINE_STATIC_LOCAL(ScratchBuffer, scratchBuffer, ());
188 return scratchBuffer;
191 static const int templateSideLength = 1;
193 ShadowBlur::ShadowBlur(const FloatSize& radius, const FloatSize& offset, const Color& color, ColorSpace colorSpace)
195 , m_colorSpace(colorSpace)
196 , m_blurRadius(radius)
199 , m_shadowsIgnoreTransforms(false)
201 updateShadowBlurValues();
204 ShadowBlur::ShadowBlur()
207 , m_shadowsIgnoreTransforms(false)
211 void ShadowBlur::setShadowValues(const FloatSize& radius, const FloatSize& offset, const Color& color, ColorSpace colorSpace, bool ignoreTransforms)
213 m_blurRadius = radius;
216 m_colorSpace = colorSpace;
217 m_shadowsIgnoreTransforms = ignoreTransforms;
219 updateShadowBlurValues();
222 void ShadowBlur::updateShadowBlurValues()
224 // Limit blur radius to 128 to avoid lots of very expensive blurring.
225 m_blurRadius = m_blurRadius.shrunkTo(FloatSize(128, 128));
227 // The type of shadow is decided by the blur radius, shadow offset, and shadow color.
228 if (!m_color.isValid() || !m_color.alpha()) {
229 // Can't paint the shadow with invalid or invisible color.
231 } else if (m_blurRadius.width() > 0 || m_blurRadius.height() > 0) {
232 // Shadow is always blurred, even the offset is zero.
234 } else if (!m_offset.width() && !m_offset.height()) {
235 // Without blur and zero offset means the shadow is fully hidden.
238 m_type = SolidShadow;
241 // Instead of integer division, we use 17.15 for fixed-point division.
242 static const int blurSumShift = 15;
244 // Takes a two dimensional array with three rows and two columns for the lobes.
245 static void calculateLobes(int lobes[][2], float blurRadius, bool shadowsIgnoreTransforms)
248 if (shadowsIgnoreTransforms)
249 diameter = max(2, static_cast<int>(floorf((2 / 3.f) * blurRadius))); // Canvas shadow. FIXME: we should adjust the blur radius higher up.
251 // http://dev.w3.org/csswg/css3-background/#box-shadow
252 // Approximate a Gaussian blur with a standard deviation equal to half the blur radius,
253 // which http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement tell us how to do.
254 // However, shadows rendered according to that spec will extend a little further than m_blurRadius,
255 // so we apply a fudge factor to bring the radius down slightly.
256 float stdDev = blurRadius / 2;
257 const float gaussianKernelFactor = 3 / 4.f * sqrtf(2 * piFloat);
258 const float fudgeFactor = 0.88f;
259 diameter = max(2, static_cast<int>(floorf(stdDev * gaussianKernelFactor * fudgeFactor + 0.5f)));
263 // if d is odd, use three box-blurs of size 'd', centered on the output pixel.
264 int lobeSize = (diameter - 1) / 2;
265 lobes[0][leftLobe] = lobeSize;
266 lobes[0][rightLobe] = lobeSize;
267 lobes[1][leftLobe] = lobeSize;
268 lobes[1][rightLobe] = lobeSize;
269 lobes[2][leftLobe] = lobeSize;
270 lobes[2][rightLobe] = lobeSize;
272 // if d is even, two box-blurs of size 'd' (the first one centered on the pixel boundary
273 // between the output pixel and the one to the left, the second one centered on the pixel
274 // boundary between the output pixel and the one to the right) and one box blur of size 'd+1' centered on the output pixel
275 int lobeSize = diameter / 2;
276 lobes[0][leftLobe] = lobeSize;
277 lobes[0][rightLobe] = lobeSize - 1;
278 lobes[1][leftLobe] = lobeSize - 1;
279 lobes[1][rightLobe] = lobeSize;
280 lobes[2][leftLobe] = lobeSize;
281 lobes[2][rightLobe] = lobeSize;
285 void ShadowBlur::clear()
289 m_blurRadius = FloatSize();
290 m_offset = FloatSize();
293 void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride)
295 const int channels[4] = { 3, 0, 1, 3 };
297 int lobes[3][2]; // indexed by pass, and left/right lobe
298 calculateLobes(lobes, m_blurRadius.width(), m_shadowsIgnoreTransforms);
300 // First pass is horizontal.
302 int delta = rowStride;
303 int final = size.height();
304 int dim = size.width();
306 // Two stages: horizontal and vertical
307 for (int pass = 0; pass < 2; ++pass) {
308 unsigned char* pixels = imageData;
310 if (!pass && !m_blurRadius.width())
311 final = 0; // Do no work if horizonal blur is zero.
313 for (int j = 0; j < final; ++j, pixels += delta) {
314 // For each step, we blur the alpha in a channel and store the result
315 // in another channel for the subsequent step.
316 // We use sliding window algorithm to accumulate the alpha values.
317 // This is much more efficient than computing the sum of each pixels
318 // covered by the box kernel size for each x.
319 for (int step = 0; step < 3; ++step) {
320 int side1 = lobes[step][leftLobe];
321 int side2 = lobes[step][rightLobe];
322 int pixelCount = side1 + 1 + side2;
323 int invCount = ((1 << blurSumShift) + pixelCount - 1) / pixelCount;
325 int alpha1 = pixels[channels[step]];
326 int alpha2 = pixels[(dim - 1) * stride + channels[step]];
328 unsigned char* ptr = pixels + channels[step + 1];
329 unsigned char* prev = pixels + stride + channels[step];
330 unsigned char* next = pixels + ofs * stride + channels[step];
333 int sum = side1 * alpha1 + alpha1;
334 int limit = (dim < side2 + 1) ? dim : side2 + 1;
336 for (i = 1; i < limit; ++i, prev += stride)
340 sum += (side2 - limit + 1) * alpha2;
342 limit = (side1 < dim) ? side1 : dim;
343 for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) {
344 *ptr = (sum * invCount) >> blurSumShift;
345 sum += ((ofs < dim) ? *next : alpha2) - alpha1;
348 prev = pixels + channels[step];
349 for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) {
350 *ptr = (sum * invCount) >> blurSumShift;
351 sum += (*next) - (*prev);
354 for (; i < dim; ptr += stride, prev += stride, ++i) {
355 *ptr = (sum * invCount) >> blurSumShift;
356 sum += alpha2 - (*prev);
361 // Last pass is vertical.
364 final = size.width();
367 if (!m_blurRadius.height())
370 if (m_blurRadius.width() != m_blurRadius.height())
371 calculateLobes(lobes, m_blurRadius.height(), m_shadowsIgnoreTransforms);
375 void ShadowBlur::adjustBlurRadius(GraphicsContext* context)
377 if (!m_shadowsIgnoreTransforms)
380 AffineTransform transform = context->getCTM();
381 m_blurRadius.scale(1 / static_cast<float>(transform.xScale()), 1 / static_cast<float>(transform.yScale()));
384 IntSize ShadowBlur::blurredEdgeSize() const
386 IntSize edgeSize = expandedIntSize(m_blurRadius);
388 // To avoid slowing down blurLayerImage() for radius == 1, we give it two empty pixels on each side.
389 if (edgeSize.width() == 1)
390 edgeSize.setWidth(2);
392 if (edgeSize.height() == 1)
393 edgeSize.setHeight(2);
398 IntRect ShadowBlur::calculateLayerBoundingRect(GraphicsContext* context, const FloatRect& shadowedRect, const IntRect& clipRect)
400 IntSize edgeSize = blurredEdgeSize();
402 // Calculate the destination of the blurred and/or transformed layer.
406 const AffineTransform transform = context->getCTM();
407 if (m_shadowsIgnoreTransforms && !transform.isIdentity()) {
408 FloatQuad transformedPolygon = transform.mapQuad(FloatQuad(shadowedRect));
409 transformedPolygon.move(m_offset);
410 layerRect = transform.inverse().mapQuad(transformedPolygon).boundingBox();
412 layerRect = shadowedRect;
413 layerRect.move(m_offset);
416 // We expand the area by the blur radius to give extra space for the blur transition.
417 if (m_type == BlurShadow) {
418 layerRect.inflateX(edgeSize.width());
419 layerRect.inflateY(edgeSize.height());
420 inflation = edgeSize;
423 FloatRect unclippedLayerRect = layerRect;
425 if (!clipRect.contains(enclosingIntRect(layerRect))) {
426 // If we are totally outside the clip region, we aren't painting at all.
427 if (intersection(layerRect, clipRect).isEmpty())
430 IntRect inflatedClip = clipRect;
431 // Pixels at the edges can be affected by pixels outside the buffer,
432 // so intersect with the clip inflated by the blur.
433 if (m_type == BlurShadow) {
434 inflatedClip.inflateX(edgeSize.width());
435 inflatedClip.inflateY(edgeSize.height());
437 #if ENABLE(TIZEN_INFLATE_NONE_BLUR_SHADOW_AREA)
438 // Enlarge the clipping area 1 pixel so that the fill does not
439 // bleed (due to antialiasing) if the context is transformed.
441 inflatedClip.inflateX(1);
442 inflatedClip.inflateY(1);
445 layerRect.intersect(inflatedClip);
448 IntSize frameSize = inflation;
450 m_sourceRect = FloatRect(0, 0, shadowedRect.width() + frameSize.width(), shadowedRect.height() + frameSize.height());
451 m_layerOrigin = FloatPoint(layerRect.x(), layerRect.y());
452 m_layerSize = layerRect.size();
454 const FloatPoint unclippedLayerOrigin = FloatPoint(unclippedLayerRect.x(), unclippedLayerRect.y());
455 const FloatSize clippedOut = unclippedLayerOrigin - m_layerOrigin;
457 // Set the origin as the top left corner of the scratch image, or, in case there's a clipped
458 // out region, set the origin accordingly to the full bounding rect's top-left corner.
459 float translationX = -shadowedRect.x() + inflation.width() - fabsf(clippedOut.width());
460 float translationY = -shadowedRect.y() + inflation.height() - fabsf(clippedOut.height());
461 m_layerContextTranslation = FloatSize(translationX, translationY);
463 return enclosingIntRect(layerRect);
466 void ShadowBlur::drawShadowBuffer(GraphicsContext* graphicsContext)
471 GraphicsContextStateSaver stateSaver(*graphicsContext);
473 IntSize bufferSize = m_layerImage->internalSize();
474 if (bufferSize != m_layerSize) {
475 // The rect passed to clipToImageBuffer() has to be the size of the entire buffer,
476 // but we may not have cleared it all, so clip to the filled part first.
477 graphicsContext->clip(FloatRect(m_layerOrigin, m_layerSize));
479 graphicsContext->clipToImageBuffer(m_layerImage, FloatRect(m_layerOrigin, bufferSize));
480 graphicsContext->setFillColor(m_color, m_colorSpace);
482 graphicsContext->clearShadow();
483 graphicsContext->fillRect(FloatRect(m_layerOrigin, m_sourceRect.size()));
486 static void computeSliceSizesFromRadii(const IntSize& twiceRadius, const RoundedRect::Radii& radii, int& leftSlice, int& rightSlice, int& topSlice, int& bottomSlice)
488 leftSlice = twiceRadius.width() + max(radii.topLeft().width(), radii.bottomLeft().width());
489 rightSlice = twiceRadius.width() + max(radii.topRight().width(), radii.bottomRight().width());
491 topSlice = twiceRadius.height() + max(radii.topLeft().height(), radii.topRight().height());
492 bottomSlice = twiceRadius.height() + max(radii.bottomLeft().height(), radii.bottomRight().height());
495 IntSize ShadowBlur::templateSize(const IntSize& radiusPadding, const RoundedRect::Radii& radii) const
497 const int templateSideLength = 1;
504 IntSize blurExpansion = radiusPadding;
505 blurExpansion.scale(2);
507 computeSliceSizesFromRadii(blurExpansion, radii, leftSlice, rightSlice, topSlice, bottomSlice);
509 return IntSize(templateSideLength + leftSlice + rightSlice,
510 templateSideLength + topSlice + bottomSlice);
513 void ShadowBlur::drawRectShadow(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedRect::Radii& radii)
515 IntRect layerRect = calculateLayerBoundingRect(graphicsContext, shadowedRect, graphicsContext->clipBounds());
516 if (layerRect.isEmpty())
519 adjustBlurRadius(graphicsContext);
521 // drawRectShadowWithTiling does not work with rotations.
522 // https://bugs.webkit.org/show_bug.cgi?id=45042
523 if (!graphicsContext->getCTM().preservesAxisAlignment() || m_type != BlurShadow) {
524 drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, layerRect);
528 IntSize edgeSize = blurredEdgeSize();
529 IntSize templateSize = this->templateSize(edgeSize, radii);
531 if (templateSize.width() > shadowedRect.width() || templateSize.height() > shadowedRect.height()
532 || (templateSize.width() * templateSize.height() > m_sourceRect.width() * m_sourceRect.height())) {
533 drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, layerRect);
537 drawRectShadowWithTiling(graphicsContext, shadowedRect, radii, templateSize, edgeSize);
540 void ShadowBlur::drawInsetShadow(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedRect::Radii& holeRadii)
542 IntRect layerRect = calculateLayerBoundingRect(graphicsContext, rect, graphicsContext->clipBounds());
543 if (layerRect.isEmpty())
546 adjustBlurRadius(graphicsContext);
548 // drawInsetShadowWithTiling does not work with rotations.
549 // https://bugs.webkit.org/show_bug.cgi?id=45042
550 if (!graphicsContext->getCTM().preservesAxisAlignment() || m_type != BlurShadow) {
551 drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, holeRadii, layerRect);
555 IntSize edgeSize = blurredEdgeSize();
556 IntSize templateSize = this->templateSize(edgeSize, holeRadii);
558 if (templateSize.width() > holeRect.width() || templateSize.height() > holeRect.height()
559 || (templateSize.width() * templateSize.height() > holeRect.width() * holeRect.height())) {
560 drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, holeRadii, layerRect);
564 drawInsetShadowWithTiling(graphicsContext, rect, holeRect, holeRadii, templateSize, edgeSize);
567 void ShadowBlur::drawRectShadowWithoutTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedRect::Radii& radii, const IntRect& layerRect)
569 m_layerImage = ScratchBuffer::shared().getScratchBuffer(layerRect.size());
573 FloatRect bufferRelativeShadowedRect = shadowedRect;
574 bufferRelativeShadowedRect.move(m_layerContextTranslation);
576 // Only redraw in the scratch buffer if its cached contents don't match our needs
577 #if ENABLE(TIZEN_SHADOW_BLUR_SCRATCH_BUFFER_WITH_LAYER_BOUNDS)
578 bool redrawNeeded = ScratchBuffer::shared().setCachedShadowValues(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeShadowedRect, radii, FloatRect(m_layerOrigin, m_layerSize));
580 bool redrawNeeded = ScratchBuffer::shared().setCachedShadowValues(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeShadowedRect, radii, m_layerSize);
584 GraphicsContext* shadowContext = m_layerImage->context();
585 GraphicsContextStateSaver stateSaver(*shadowContext);
587 // Add a pixel to avoid later edge aliasing when rotated.
588 shadowContext->clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1));
589 shadowContext->translate(m_layerContextTranslation);
590 shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
592 shadowContext->fillRect(shadowedRect);
595 path.addRoundedRect(shadowedRect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
596 shadowContext->fillPath(path);
599 blurShadowBuffer(expandedIntSize(m_layerSize));
602 drawShadowBuffer(graphicsContext);
604 ScratchBuffer::shared().scheduleScratchBufferPurge();
607 void ShadowBlur::drawInsetShadowWithoutTiling(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedRect::Radii& holeRadii, const IntRect& layerRect)
609 m_layerImage = ScratchBuffer::shared().getScratchBuffer(layerRect.size());
613 FloatRect bufferRelativeRect = rect;
614 bufferRelativeRect.move(m_layerContextTranslation);
616 FloatRect bufferRelativeHoleRect = holeRect;
617 bufferRelativeHoleRect.move(m_layerContextTranslation);
619 // Only redraw in the scratch buffer if its cached contents don't match our needs
620 bool redrawNeeded = ScratchBuffer::shared().setCachedInsetShadowValues(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeRect, bufferRelativeHoleRect, holeRadii);
622 GraphicsContext* shadowContext = m_layerImage->context();
623 GraphicsContextStateSaver stateSaver(*shadowContext);
625 // Add a pixel to avoid later edge aliasing when rotated.
626 shadowContext->clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1));
627 shadowContext->translate(m_layerContextTranslation);
631 if (holeRadii.isZero())
632 path.addRect(holeRect);
634 path.addRoundedRect(holeRect, holeRadii.topLeft(), holeRadii.topRight(), holeRadii.bottomLeft(), holeRadii.bottomRight());
636 shadowContext->setFillRule(RULE_EVENODD);
637 shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
638 shadowContext->fillPath(path);
640 blurShadowBuffer(expandedIntSize(m_layerSize));
643 drawShadowBuffer(graphicsContext);
645 ScratchBuffer::shared().scheduleScratchBufferPurge();
649 These functions use tiling to improve the performance of the shadow
650 drawing of rounded rectangles. The code basically does the following
653 1. Calculate the size of the shadow template, a rectangle that
654 contains all the necessary tiles to draw the complete shadow.
656 2. If that size is smaller than the real rectangle render the new
657 template rectangle and its shadow in a new surface, in other case
658 render the shadow of the real rectangle in the destination
661 3. Calculate the sizes and positions of the tiles and their
662 destinations and use drawPattern to render the final shadow. The
663 code divides the rendering in 8 tiles:
671 The corners are directly copied from the template rectangle to the
672 real one and the side tiles are 1 pixel width, we use them as
673 tiles to cover the destination side. The corner tiles are bigger
674 than just the side of the rounded corner, we need to increase it
675 because the modifications caused by the corner over the blur
676 effect. We fill the central or outer part with solid color to complete
680 void ShadowBlur::drawInsetShadowWithTiling(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedRect::Radii& radii, const IntSize& templateSize, const IntSize& edgeSize)
682 m_layerImage = ScratchBuffer::shared().getScratchBuffer(templateSize);
686 // Draw the rectangle with hole.
687 FloatRect templateBounds(0, 0, templateSize.width(), templateSize.height());
688 FloatRect templateHole = FloatRect(edgeSize.width(), edgeSize.height(), templateSize.width() - 2 * edgeSize.width(), templateSize.height() - 2 * edgeSize.height());
690 // Only redraw in the scratch buffer if its cached contents don't match our needs
691 bool redrawNeeded = ScratchBuffer::shared().setCachedInsetShadowValues(m_blurRadius, m_color, m_colorSpace, templateBounds, templateHole, radii);
693 // Draw shadow into a new ImageBuffer.
694 GraphicsContext* shadowContext = m_layerImage->context();
695 GraphicsContextStateSaver shadowStateSaver(*shadowContext);
696 shadowContext->clearRect(templateBounds);
697 shadowContext->setFillRule(RULE_EVENODD);
698 shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
701 path.addRect(templateBounds);
703 path.addRect(templateHole);
705 path.addRoundedRect(templateHole, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
707 shadowContext->fillPath(path);
709 blurAndColorShadowBuffer(templateSize);
712 FloatRect boundingRect = rect;
713 boundingRect.move(m_offset);
715 FloatRect destHoleRect = holeRect;
716 destHoleRect.move(m_offset);
717 FloatRect destHoleBounds = destHoleRect;
718 destHoleBounds.inflateX(edgeSize.width());
719 destHoleBounds.inflateY(edgeSize.height());
721 // Fill the external part of the shadow (which may be visible because of offset).
723 exteriorPath.addRect(boundingRect);
724 exteriorPath.addRect(destHoleBounds);
727 GraphicsContextStateSaver fillStateSaver(*graphicsContext);
728 graphicsContext->clearShadow();
729 graphicsContext->setFillRule(RULE_EVENODD);
730 graphicsContext->setFillColor(m_color, m_colorSpace);
731 graphicsContext->fillPath(exteriorPath);
734 drawLayerPieces(graphicsContext, destHoleBounds, radii, edgeSize, templateSize, InnerShadow);
737 ScratchBuffer::shared().scheduleScratchBufferPurge();
740 void ShadowBlur::drawRectShadowWithTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedRect::Radii& radii, const IntSize& templateSize, const IntSize& edgeSize)
742 m_layerImage = ScratchBuffer::shared().getScratchBuffer(templateSize);
746 FloatRect templateShadow = FloatRect(edgeSize.width(), edgeSize.height(), templateSize.width() - 2 * edgeSize.width(), templateSize.height() - 2 * edgeSize.height());
748 // Only redraw in the scratch buffer if its cached contents don't match our needs
749 #if ENABLE(TIZEN_SHADOW_BLUR_SCRATCH_BUFFER_WITH_LAYER_BOUNDS)
750 bool redrawNeeded = ScratchBuffer::shared().setCachedShadowValues(m_blurRadius, m_color, m_colorSpace, templateShadow, radii, FloatRect(m_layerOrigin, m_layerSize));
752 bool redrawNeeded = ScratchBuffer::shared().setCachedShadowValues(m_blurRadius, m_color, m_colorSpace, templateShadow, radii, m_layerSize);
756 // Draw shadow into the ImageBuffer.
757 GraphicsContext* shadowContext = m_layerImage->context();
758 GraphicsContextStateSaver shadowStateSaver(*shadowContext);
760 shadowContext->clearRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
761 shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB);
764 shadowContext->fillRect(templateShadow);
767 path.addRoundedRect(templateShadow, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
768 shadowContext->fillPath(path);
771 blurAndColorShadowBuffer(templateSize);
774 FloatRect shadowBounds = shadowedRect;
775 shadowBounds.move(m_offset.width(), m_offset.height());
776 shadowBounds.inflateX(edgeSize.width());
777 shadowBounds.inflateY(edgeSize.height());
779 drawLayerPieces(graphicsContext, shadowBounds, radii, edgeSize, templateSize, OuterShadow);
782 ScratchBuffer::shared().scheduleScratchBufferPurge();
785 void ShadowBlur::drawLayerPieces(GraphicsContext* graphicsContext, const FloatRect& shadowBounds, const RoundedRect::Radii& radii, const IntSize& bufferPadding, const IntSize& templateSize, ShadowDirection direction)
787 const IntSize twiceRadius = IntSize(bufferPadding.width() * 2, bufferPadding.height() * 2);
793 computeSliceSizesFromRadii(twiceRadius, radii, leftSlice, rightSlice, topSlice, bottomSlice);
795 int centerWidth = shadowBounds.width() - leftSlice - rightSlice;
796 int centerHeight = shadowBounds.height() - topSlice - bottomSlice;
798 if (direction == OuterShadow) {
799 FloatRect shadowInterior(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight);
800 if (!shadowInterior.isEmpty()) {
801 GraphicsContextStateSaver stateSaver(*graphicsContext);
802 graphicsContext->setFillColor(m_color, m_colorSpace);
803 graphicsContext->clearShadow();
804 graphicsContext->fillRect(shadowInterior);
808 GraphicsContextStateSaver stateSaver(*graphicsContext);
809 graphicsContext->clearShadow();
810 graphicsContext->setFillColor(m_color, m_colorSpace);
812 // Note that drawing the ImageBuffer is faster than creating a Image and drawing that,
813 // because ImageBuffer::draw() knows that it doesn't have to copy the image bits.
814 FloatRect centerRect(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight);
815 centerRect = graphicsContext->roundToDevicePixels(centerRect);
818 FloatRect tileRect = FloatRect(leftSlice, 0, templateSideLength, topSlice);
819 FloatRect destRect = FloatRect(centerRect.x(), centerRect.y() - topSlice, centerRect.width(), topSlice);
820 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
822 // Draw the bottom side.
823 tileRect.setY(templateSize.height() - bottomSlice);
824 tileRect.setHeight(bottomSlice);
825 destRect.setY(centerRect.maxY());
826 destRect.setHeight(bottomSlice);
827 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
830 tileRect = FloatRect(0, topSlice, leftSlice, templateSideLength);
831 destRect = FloatRect(centerRect.x() - leftSlice, centerRect.y(), leftSlice, centerRect.height());
832 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
835 tileRect.setX(templateSize.width() - rightSlice);
836 tileRect.setWidth(rightSlice);
837 destRect.setX(centerRect.maxX());
838 destRect.setWidth(rightSlice);
839 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
842 tileRect = FloatRect(0, 0, leftSlice, topSlice);
843 destRect = FloatRect(centerRect.x() - leftSlice, centerRect.y() - topSlice, leftSlice, topSlice);
844 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
847 tileRect = FloatRect(templateSize.width() - rightSlice, 0, rightSlice, topSlice);
848 destRect = FloatRect(centerRect.maxX(), centerRect.y() - topSlice, rightSlice, topSlice);
849 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
851 // Bottom right corner.
852 tileRect = FloatRect(templateSize.width() - rightSlice, templateSize.height() - bottomSlice, rightSlice, bottomSlice);
853 destRect = FloatRect(centerRect.maxX(), centerRect.maxY(), rightSlice, bottomSlice);
854 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
856 // Bottom left corner.
857 tileRect = FloatRect(0, templateSize.height() - bottomSlice, leftSlice, bottomSlice);
858 destRect = FloatRect(centerRect.x() - leftSlice, centerRect.maxY(), leftSlice, bottomSlice);
859 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect);
863 void ShadowBlur::blurShadowBuffer(const IntSize& templateSize)
865 if (m_type != BlurShadow)
868 IntRect blurRect(IntPoint(), templateSize);
869 RefPtr<Uint8ClampedArray> layerData = m_layerImage->getUnmultipliedImageData(blurRect);
870 blurLayerImage(layerData->data(), blurRect.size(), blurRect.width() * 4);
871 m_layerImage->putByteArray(Unmultiplied, layerData.get(), blurRect.size(), blurRect, IntPoint());
874 void ShadowBlur::blurAndColorShadowBuffer(const IntSize& templateSize)
876 blurShadowBuffer(templateSize);
878 // Mask the image with the shadow color.
879 GraphicsContext* shadowContext = m_layerImage->context();
880 GraphicsContextStateSaver stateSaver(*shadowContext);
881 shadowContext->setCompositeOperation(CompositeSourceIn);
882 shadowContext->setFillColor(m_color, m_colorSpace);
883 shadowContext->fillRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
886 GraphicsContext* ShadowBlur::beginShadowLayer(GraphicsContext *context, const FloatRect& layerArea)
888 adjustBlurRadius(context);
890 IntRect layerRect = calculateLayerBoundingRect(context, layerArea, context->clipBounds());
892 if (layerRect.isEmpty())
895 // We reset the scratch buffer values here, because the buffer will no longer contain
896 // data from any previous rectangle or inset shadows drawn via the tiling path.
897 #if ENABLE(TIZEN_SHADOW_BLUR_SCRATCH_BUFFER_WITH_LAYER_BOUNDS)
898 ScratchBuffer::shared().setCachedShadowValues(FloatSize(), Color::black, ColorSpaceDeviceRGB, IntRect(), RoundedRect::Radii(), FloatRect(m_layerOrigin, m_layerSize));
900 ScratchBuffer::shared().setCachedShadowValues(FloatSize(), Color::black, ColorSpaceDeviceRGB, IntRect(), RoundedRect::Radii(), m_layerSize);
902 m_layerImage = ScratchBuffer::shared().getScratchBuffer(layerRect.size());
904 GraphicsContext* shadowContext = m_layerImage->context();
905 shadowContext->save();
907 // Add a pixel to avoid later edge aliasing when rotated.
908 shadowContext->clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1));
910 shadowContext->translate(m_layerContextTranslation);
911 return shadowContext;
914 void ShadowBlur::endShadowLayer(GraphicsContext* context)
916 m_layerImage->context()->restore();
918 blurAndColorShadowBuffer(expandedIntSize(m_layerSize));
919 GraphicsContextStateSaver stateSave(*context);
921 context->clearShadow();
922 context->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, roundedIntPoint(m_layerOrigin), IntRect(0, 0, m_layerSize.width(), m_layerSize.height()), context->compositeOperation());
925 ScratchBuffer::shared().scheduleScratchBufferPurge();
928 #if PLATFORM(QT) || USE(CAIRO)
929 bool ShadowBlur::mustUseShadowBlur(GraphicsContext* context) const
931 // We can't avoid ShadowBlur, since the shadow has blur.
932 if (type() == BlurShadow)
934 // We can avoid ShadowBlur and optimize, since we're not drawing on a
935 // canvas and box shadows are affected by the transformation matrix.
936 if (!shadowsIgnoreTransforms())
938 // We can avoid ShadowBlur, since there are no transformations to apply to the canvas.
939 if (context->getCTM().isIdentity())
941 // Otherwise, no chance avoiding ShadowBlur.
946 } // namespace WebCore