| /* |
| * Copyright (C) 2011 Apple Inc. All rights reserved. |
| * Copyright (C) 2010 Sencha, Inc. All rights reserved. |
| * Copyright (C) 2010 Igalia S.L. All rights reserved. |
| * Copyright (C) Research In Motion Limited 2011. All rights reserved. |
| * Copyright (C) 2013 Digia Plc. and/or its subsidiary(-ies). |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "ShadowBlur.h" |
| |
| #include "AffineTransform.h" |
| #include "FloatQuad.h" |
| #include "GraphicsContext.h" |
| #include "ImageBuffer.h" |
| #include "PixelBuffer.h" |
| #include "Timer.h" |
| #include <wtf/Lock.h> |
| #include <wtf/MathExtras.h> |
| #include <wtf/NeverDestroyed.h> |
| #include <wtf/Noncopyable.h> |
| #include <wtf/RunLoop.h> |
| #include <wtf/Scope.h> |
| |
| namespace WebCore { |
| |
| enum { |
| LeftLobe = 0, |
| RightLobe = 1 |
| }; |
| |
| #if USE(CG) |
| static inline int roundUpToMultipleOf32(int d) |
| { |
| return (1 + (d >> 5)) << 5; |
| } |
| |
| // ShadowBlur needs a scratch image as the buffer for the blur filter. |
| // Instead of creating and destroying the buffer for every operation, |
| // we create a buffer which will be automatically purged via a timer. |
| class ScratchBuffer { |
| WTF_MAKE_FAST_ALLOCATED; |
| public: |
| ScratchBuffer() |
| : m_purgeTimer(RunLoop::main(), this, &ScratchBuffer::purgeTimerFired) |
| { |
| } |
| |
| RefPtr<ImageBuffer> getScratchBuffer(const IntSize& size) WTF_REQUIRES_LOCK(lock()) |
| { |
| ASSERT(lock().isHeld()); |
| auto releaseLayerImage = makeScopeExit([this] { |
| if (m_imageBuffer) |
| scheduleScratchBufferPurge(); |
| }); |
| |
| // We do not need to recreate the buffer if the current buffer is large enough. |
| if (m_imageBuffer && m_imageBuffer->truncatedLogicalSize().width() >= size.width() && m_imageBuffer->truncatedLogicalSize().height() >= size.height()) |
| return m_imageBuffer; |
| |
| // Round to the nearest 32 pixels so we do not grow the buffer for similar sized requests. |
| IntSize roundedSize(roundUpToMultipleOf32(size.width()), roundUpToMultipleOf32(size.height())); |
| |
| clearScratchBuffer(); |
| |
| // ShadowBlur is not used with accelerated drawing, so it's OK to make an unconditionally unaccelerated buffer. |
| m_imageBuffer = ImageBuffer::create(roundedSize, RenderingPurpose::Unspecified, 1, DestinationColorSpace::SRGB(), PixelFormat::BGRA8); |
| return m_imageBuffer; |
| } |
| |
| bool setCachedShadowValues(const FloatSize& radius, const Color& color, const FloatRect& shadowRect, const FloatRoundedRect::Radii& radii, const FloatSize& layerSize) WTF_REQUIRES_LOCK(lock()) |
| { |
| ASSERT(lock().isHeld()); |
| if (!m_lastWasInset && m_lastRadius == radius && m_lastColor == color && m_lastShadowRect == shadowRect && m_lastRadii == radii && m_lastLayerSize == layerSize) |
| return false; |
| |
| m_lastWasInset = false; |
| m_lastRadius = radius; |
| m_lastColor = color; |
| m_lastShadowRect = shadowRect; |
| m_lastRadii = radii; |
| m_lastLayerSize = layerSize; |
| |
| return true; |
| } |
| |
| bool setCachedInsetShadowValues(const FloatSize& radius, const Color& color, const FloatRect& bounds, const FloatRect& shadowRect, const FloatRoundedRect::Radii& radii) WTF_REQUIRES_LOCK(lock()) |
| { |
| ASSERT(lock().isHeld()); |
| if (m_lastWasInset && m_lastRadius == radius && m_lastColor == color && m_lastInsetBounds == bounds && shadowRect == m_lastShadowRect && radii == m_lastRadii) |
| return false; |
| |
| m_lastWasInset = true; |
| m_lastInsetBounds = bounds; |
| m_lastRadius = radius; |
| m_lastColor = color; |
| m_lastShadowRect = shadowRect; |
| m_lastRadii = radii; |
| |
| return true; |
| } |
| |
| static ScratchBuffer& singleton() WTF_REQUIRES_LOCK(lock()); |
| static Lock& lock() WTF_RETURNS_LOCK(s_lock) { return s_lock; } |
| |
| private: |
| void scheduleScratchBufferPurge() |
| { |
| ASSERT(lock().isHeld()); |
| const Seconds scratchBufferPurgeInterval { 2_s }; |
| m_purgeTimer.startOneShot(scratchBufferPurgeInterval); |
| } |
| |
| void purgeTimerFired() |
| { |
| ASSERT(isMainThread()); |
| if (lock().tryLock()) { |
| clearScratchBuffer(); |
| lock().unlock(); |
| } |
| } |
| |
| void clearScratchBuffer() WTF_REQUIRES_LOCK(lock()) |
| { |
| ASSERT(lock().isHeld()); |
| m_imageBuffer = nullptr; |
| m_lastRadius = FloatSize(); |
| m_lastLayerSize = FloatSize(); |
| } |
| |
| static Lock s_lock; |
| |
| RefPtr<ImageBuffer> m_imageBuffer; |
| RunLoop::Timer<ScratchBuffer> m_purgeTimer; |
| |
| FloatRect m_lastInsetBounds; |
| FloatRect m_lastShadowRect; |
| FloatRoundedRect::Radii m_lastRadii; |
| Color m_lastColor; |
| FloatSize m_lastRadius; |
| bool m_lastWasInset { false }; |
| FloatSize m_lastLayerSize; |
| }; |
| |
| Lock ScratchBuffer::s_lock; |
| |
| ScratchBuffer& ScratchBuffer::singleton() |
| { |
| ASSERT(lock().isHeld()); |
| static NeverDestroyed<ScratchBuffer> scratchBuffer; |
| return scratchBuffer; |
| } |
| |
| static float radiusToLegacyRadius(float radius) |
| { |
| return radius > 8 ? 8 + 4 * sqrt((radius - 8) / 2) : radius; |
| } |
| #endif |
| |
| static const int templateSideLength = 1; |
| |
| ShadowBlur::ShadowBlur() = default; |
| |
| ShadowBlur::ShadowBlur(const FloatSize& radius, const FloatSize& offset, const Color& color, bool shadowsIgnoreTransforms) |
| : m_color(color) |
| , m_blurRadius(radius) |
| , m_offset(offset) |
| , m_shadowsIgnoreTransforms(shadowsIgnoreTransforms) |
| { |
| updateShadowBlurValues(); |
| } |
| |
| ShadowBlur::ShadowBlur(const DropShadow& dropShadow, bool shadowsIgnoreTransforms) |
| : m_color(dropShadow.color) |
| , m_blurRadius(dropShadow.blurRadius, dropShadow.blurRadius) |
| , m_offset(dropShadow.offset) |
| , m_shadowsIgnoreTransforms(shadowsIgnoreTransforms) |
| { |
| #if USE(CG) |
| // Core Graphics incorrectly renders shadows with radius > 8px (<rdar://problem/8103442>), |
| // but we need to preserve this buggy behavior for canvas and -webkit-box-shadow. |
| if (dropShadow.radiusMode == ShadowRadiusMode::Legacy) { |
| float shadowBlur = radiusToLegacyRadius(dropShadow.blurRadius); |
| m_blurRadius = FloatSize(shadowBlur, shadowBlur); |
| } |
| #endif |
| updateShadowBlurValues(); |
| } |
| |
| void ShadowBlur::setShadowValues(const FloatSize& radius, const FloatSize& offset, const Color& color, bool ignoreTransforms) |
| { |
| m_blurRadius = radius; |
| m_offset = offset; |
| m_color = color; |
| m_shadowsIgnoreTransforms = ignoreTransforms; |
| |
| updateShadowBlurValues(); |
| } |
| |
| void ShadowBlur::updateShadowBlurValues() |
| { |
| // Limit blur radius to 128 to avoid lots of very expensive blurring. |
| m_blurRadius = m_blurRadius.shrunkTo(FloatSize(128, 128)); |
| |
| // The type of shadow is decided by the blur radius, shadow offset, and shadow color. |
| if (!m_color.isVisible()) { |
| // Can't paint the shadow with invalid or invisible color. |
| m_type = NoShadow; |
| } else if (m_blurRadius.width() > 0 || m_blurRadius.height() > 0) { |
| // Shadow is always blurred, even the offset is zero. |
| m_type = BlurShadow; |
| } else if (!m_offset.width() && !m_offset.height()) { |
| // Without blur and zero offset means the shadow is fully hidden. |
| m_type = NoShadow; |
| } else |
| m_type = SolidShadow; |
| } |
| |
| // Instead of integer division, we use 17.15 for fixed-point division. |
| static const int blurSumShift = 15; |
| |
| // Takes a two dimensional array with three rows and two columns for the lobes. |
| static void calculateLobes(int lobes[][2], float blurRadius, bool shadowsIgnoreTransforms) |
| { |
| int diameter; |
| if (shadowsIgnoreTransforms) |
| diameter = std::max(2, static_cast<int>(floorf((2 / 3.f) * blurRadius))); // Canvas shadow. FIXME: we should adjust the blur radius higher up. |
| else { |
| // http://dev.w3.org/csswg/css3-background/#box-shadow |
| // Approximate a Gaussian blur with a standard deviation equal to half the blur radius, |
| // which http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement tell us how to do. |
| // However, shadows rendered according to that spec will extend a little further than m_blurRadius, |
| // so we apply a fudge factor to bring the radius down slightly. |
| float stdDev = blurRadius / 2; |
| const float gaussianKernelFactor = 3 / 4.f * sqrtf(2 * piFloat); |
| const float fudgeFactor = 0.88f; |
| diameter = std::max(2, static_cast<int>(floorf(stdDev * gaussianKernelFactor * fudgeFactor + 0.5f))); |
| } |
| |
| if (diameter & 1) { |
| // if d is odd, use three box-blurs of size 'd', centered on the output pixel. |
| int lobeSize = (diameter - 1) / 2; |
| lobes[0][LeftLobe] = lobeSize; |
| lobes[0][RightLobe] = lobeSize; |
| lobes[1][LeftLobe] = lobeSize; |
| lobes[1][RightLobe] = lobeSize; |
| lobes[2][LeftLobe] = lobeSize; |
| lobes[2][RightLobe] = lobeSize; |
| } else { |
| // if d is even, two box-blurs of size 'd' (the first one centered on the pixel boundary |
| // between the output pixel and the one to the left, the second one centered on the pixel |
| // boundary between the output pixel and the one to the right) and one box blur of size 'd+1' centered on the output pixel |
| int lobeSize = diameter / 2; |
| lobes[0][LeftLobe] = lobeSize; |
| lobes[0][RightLobe] = lobeSize - 1; |
| lobes[1][LeftLobe] = lobeSize - 1; |
| lobes[1][RightLobe] = lobeSize; |
| lobes[2][LeftLobe] = lobeSize; |
| lobes[2][RightLobe] = lobeSize; |
| } |
| } |
| |
| void ShadowBlur::clear() |
| { |
| m_type = NoShadow; |
| m_color = Color(); |
| m_blurRadius = FloatSize(); |
| m_offset = FloatSize(); |
| } |
| |
| void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride) |
| { |
| const int channels[4] = { 3, 0, 1, 3 }; |
| |
| int lobes[3][2]; // indexed by pass, and left/right lobe |
| calculateLobes(lobes, m_blurRadius.width(), m_shadowsIgnoreTransforms); |
| |
| // First pass is horizontal. |
| int stride = 4; |
| int delta = rowStride; |
| int final = size.height(); |
| int dim = size.width(); |
| |
| // Two stages: horizontal and vertical |
| for (int pass = 0; pass < 2; ++pass) { |
| unsigned char* pixels = imageData; |
| |
| if (!pass && !m_blurRadius.width()) |
| final = 0; // Do no work if horizonal blur is zero. |
| |
| for (int j = 0; j < final; ++j, pixels += delta) { |
| // For each step, we blur the alpha in a channel and store the result |
| // in another channel for the subsequent step. |
| // We use sliding window algorithm to accumulate the alpha values. |
| // This is much more efficient than computing the sum of each pixels |
| // covered by the box kernel size for each x. |
| for (int step = 0; step < 3; ++step) { |
| int side1 = lobes[step][LeftLobe]; |
| int side2 = lobes[step][RightLobe]; |
| int pixelCount = side1 + 1 + side2; |
| int invCount = ((1 << blurSumShift) + pixelCount - 1) / pixelCount; |
| int ofs = 1 + side2; |
| int alpha1 = pixels[channels[step]]; |
| int alpha2 = pixels[(dim - 1) * stride + channels[step]]; |
| |
| unsigned char* ptr = pixels + channels[step + 1]; |
| unsigned char* prev = pixels + stride + channels[step]; |
| unsigned char* next = pixels + ofs * stride + channels[step]; |
| |
| int i; |
| int sum = side1 * alpha1 + alpha1; |
| int limit = (dim < side2 + 1) ? dim : side2 + 1; |
| |
| for (i = 1; i < limit; ++i, prev += stride) |
| sum += *prev; |
| |
| if (limit <= side2) |
| sum += (side2 - limit + 1) * alpha2; |
| |
| limit = (side1 < dim) ? side1 : dim; |
| for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) { |
| *ptr = (sum * invCount) >> blurSumShift; |
| sum += ((ofs < dim) ? *next : alpha2) - alpha1; |
| } |
| |
| prev = pixels + channels[step]; |
| for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) { |
| *ptr = (sum * invCount) >> blurSumShift; |
| sum += (*next) - (*prev); |
| } |
| |
| for (; i < dim; ptr += stride, prev += stride, ++i) { |
| *ptr = (sum * invCount) >> blurSumShift; |
| sum += alpha2 - (*prev); |
| } |
| } |
| } |
| |
| // Last pass is vertical. |
| stride = rowStride; |
| delta = 4; |
| final = size.width(); |
| dim = size.height(); |
| |
| if (!m_blurRadius.height()) |
| break; |
| |
| if (m_blurRadius.width() != m_blurRadius.height()) |
| calculateLobes(lobes, m_blurRadius.height(), m_shadowsIgnoreTransforms); |
| } |
| } |
| |
| void ShadowBlur::adjustBlurRadius(const AffineTransform& transform) |
| { |
| if (m_shadowsIgnoreTransforms) |
| m_blurRadius.scale(1 / static_cast<float>(transform.xScale()), 1 / static_cast<float>(transform.yScale())); |
| } |
| |
| IntSize ShadowBlur::blurredEdgeSize() const |
| { |
| IntSize edgeSize = expandedIntSize(m_blurRadius); |
| |
| // To avoid slowing down blurLayerImage() for radius == 1, we give it two empty pixels on each side. |
| if (edgeSize.width() == 1) |
| edgeSize.setWidth(2); |
| |
| if (edgeSize.height() == 1) |
| edgeSize.setHeight(2); |
| |
| return edgeSize; |
| } |
| |
| std::optional<ShadowBlur::LayerImageProperties> ShadowBlur::calculateLayerBoundingRect(const AffineTransform& transform, const FloatRect& shadowedRect, const IntRect& clipRect) |
| { |
| LayerImageProperties calculatedLayerImageProperties; |
| |
| IntSize edgeSize = blurredEdgeSize(); |
| |
| // Calculate the destination of the blurred and/or transformed layer. |
| FloatRect layerRect; |
| IntSize inflation; |
| |
| if (m_shadowsIgnoreTransforms && !transform.isIdentity()) { |
| FloatQuad transformedPolygon = transform.mapQuad(FloatQuad(shadowedRect)); |
| transformedPolygon.move(m_offset); |
| layerRect = valueOrDefault(transform.inverse()).mapQuad(transformedPolygon).boundingBox(); |
| } else { |
| layerRect = shadowedRect; |
| layerRect.move(m_offset); |
| } |
| |
| // We expand the area by the blur radius to give extra space for the blur transition. |
| if (m_type == BlurShadow) { |
| layerRect.inflateX(edgeSize.width()); |
| layerRect.inflateY(edgeSize.height()); |
| inflation = edgeSize; |
| } |
| |
| FloatRect unclippedLayerRect = layerRect; |
| |
| if (!clipRect.contains(enclosingIntRect(layerRect))) { |
| // If we are totally outside the clip region, we aren't painting at all. |
| if (intersection(layerRect, clipRect).isEmpty()) |
| return std::nullopt; |
| |
| IntRect inflatedClip = clipRect; |
| // Pixels at the edges can be affected by pixels outside the buffer, |
| // so intersect with the clip inflated by the blur. |
| if (m_type == BlurShadow) { |
| inflatedClip.inflateX(edgeSize.width()); |
| inflatedClip.inflateY(edgeSize.height()); |
| } else { |
| // Enlarge the clipping area 1 pixel so that the fill does not |
| // bleed (due to antialiasing) even if the unaligned clip rect occurred |
| inflatedClip.inflateX(1); |
| inflatedClip.inflateY(1); |
| } |
| |
| layerRect.intersect(inflatedClip); |
| } |
| |
| IntSize frameSize = inflation; |
| frameSize.scale(2); |
| calculatedLayerImageProperties.shadowedResultSize = FloatSize(shadowedRect.width() + frameSize.width(), shadowedRect.height() + frameSize.height()); |
| calculatedLayerImageProperties.layerOrigin = FloatPoint(layerRect.x(), layerRect.y()); |
| calculatedLayerImageProperties.layerSize = layerRect.size(); |
| |
| const FloatPoint unclippedLayerOrigin = FloatPoint(unclippedLayerRect.x(), unclippedLayerRect.y()); |
| const FloatSize clippedOut = unclippedLayerOrigin - calculatedLayerImageProperties.layerOrigin; |
| |
| // Set the origin as the top left corner of the scratch image, or, in case there's a clipped |
| // out region, set the origin accordingly to the full bounding rect's top-left corner. |
| float translationX = -shadowedRect.x() + inflation.width() - fabsf(clippedOut.width()); |
| float translationY = -shadowedRect.y() + inflation.height() - fabsf(clippedOut.height()); |
| calculatedLayerImageProperties.layerContextTranslation = FloatSize(translationX, translationY); |
| |
| return calculatedLayerImageProperties; |
| } |
| |
| void ShadowBlur::drawShadowBuffer(GraphicsContext& graphicsContext, ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize) |
| { |
| GraphicsContextStateSaver stateSaver(graphicsContext); |
| |
| IntSize bufferSize = layerImage.backendSize(); |
| if (bufferSize != layerSize) { |
| // The rect passed to clipToImageBuffer() has to be the size of the entire buffer, |
| // but we may not have cleared it all, so clip to the filled part first. |
| graphicsContext.clip(FloatRect(layerOrigin, layerSize)); |
| } |
| graphicsContext.clipToImageBuffer(layerImage, FloatRect(layerOrigin, bufferSize)); |
| graphicsContext.setFillColor(m_color); |
| |
| graphicsContext.clearShadow(); |
| graphicsContext.fillRect(FloatRect(layerOrigin, layerSize)); |
| } |
| |
| static void computeSliceSizesFromRadii(const IntSize& twiceRadius, const FloatRoundedRect::Radii& radii, int& leftSlice, int& rightSlice, int& topSlice, int& bottomSlice) |
| { |
| leftSlice = twiceRadius.width() + std::max(radii.topLeft().width(), radii.bottomLeft().width()); |
| rightSlice = twiceRadius.width() + std::max(radii.topRight().width(), radii.bottomRight().width()); |
| |
| topSlice = twiceRadius.height() + std::max(radii.topLeft().height(), radii.topRight().height()); |
| bottomSlice = twiceRadius.height() + std::max(radii.bottomLeft().height(), radii.bottomRight().height()); |
| } |
| |
| IntSize ShadowBlur::templateSize(const IntSize& radiusPadding, const FloatRoundedRect::Radii& radii) const |
| { |
| const int templateSideLength = 1; |
| |
| int leftSlice; |
| int rightSlice; |
| int topSlice; |
| int bottomSlice; |
| |
| IntSize blurExpansion = radiusPadding; |
| blurExpansion.scale(2); |
| |
| computeSliceSizesFromRadii(blurExpansion, radii, leftSlice, rightSlice, topSlice, bottomSlice); |
| |
| return IntSize(templateSideLength + leftSlice + rightSlice, templateSideLength + topSlice + bottomSlice); |
| } |
| |
| void ShadowBlur::drawRectShadow(GraphicsContext& graphicsContext, const FloatRoundedRect& shadowedRect) |
| { |
| drawRectShadow(graphicsContext.getCTM(), graphicsContext.clipBounds(), shadowedRect, |
| [this, &graphicsContext](ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize) { |
| drawShadowBuffer(graphicsContext, layerImage, layerOrigin, layerSize); |
| }, |
| [&graphicsContext](ImageBuffer& image, const FloatRect& destRect, const FloatRect& srcRect) { |
| GraphicsContextStateSaver stateSaver(graphicsContext); |
| graphicsContext.clearShadow(); |
| graphicsContext.drawImageBuffer(image, destRect, srcRect); |
| }, |
| [&graphicsContext](const FloatRect& rect, const Color& color) { |
| GraphicsContextStateSaver stateSaver(graphicsContext); |
| graphicsContext.setFillColor(color); |
| graphicsContext.clearShadow(); |
| graphicsContext.fillRect(rect); |
| }); |
| } |
| |
| void ShadowBlur::drawInsetShadow(GraphicsContext& graphicsContext, const FloatRect& fullRect, const FloatRoundedRect& holeRect) |
| { |
| drawInsetShadow(graphicsContext.getCTM(), graphicsContext.clipBounds(), fullRect, holeRect, |
| [this, &graphicsContext](ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize) { |
| drawShadowBuffer(graphicsContext, layerImage, layerOrigin, layerSize); |
| }, |
| [&graphicsContext](ImageBuffer& image, const FloatRect& destRect, const FloatRect& srcRect) { |
| // Note that drawing the ImageBuffer is faster than creating a Image and drawing that, |
| // because ImageBuffer::draw() knows that it doesn't have to copy the image bits. |
| GraphicsContextStateSaver stateSaver(graphicsContext); |
| graphicsContext.clearShadow(); |
| graphicsContext.drawImageBuffer(image, destRect, srcRect); |
| }, |
| [&graphicsContext](const FloatRect& rect, const FloatRect& holeRect, const Color& color) { |
| Path exteriorPath; |
| exteriorPath.addRect(rect); |
| exteriorPath.addRect(holeRect); |
| |
| GraphicsContextStateSaver fillStateSaver(graphicsContext); |
| graphicsContext.setFillRule(WindRule::EvenOdd); |
| graphicsContext.setFillColor(color); |
| graphicsContext.clearShadow(); |
| graphicsContext.fillPath(exteriorPath); |
| }); |
| } |
| |
| void ShadowBlur::drawRectShadow(const AffineTransform& transform, const IntRect& clipBounds, const FloatRoundedRect& shadowedRect, const DrawBufferCallback& drawBuffer, const DrawImageCallback& drawImage, const FillRectCallback& fillRect) |
| { |
| auto layerImageProperties = calculateLayerBoundingRect(transform, shadowedRect.rect(), clipBounds); |
| if (!layerImageProperties) |
| return; |
| |
| adjustBlurRadius(transform); |
| |
| bool canUseTilingTechnique = true; |
| |
| // drawRectShadowWithTiling does not work with rotations. |
| // https://bugs.webkit.org/show_bug.cgi?id=45042 |
| if (!transform.preservesAxisAlignment() || m_type != BlurShadow) |
| canUseTilingTechnique = false; |
| |
| IntSize edgeSize = blurredEdgeSize(); |
| IntSize templateSize = this->templateSize(edgeSize, shadowedRect.radii()); |
| const FloatRect& rect = shadowedRect.rect(); |
| |
| if (templateSize.width() > rect.width() || templateSize.height() > rect.height() |
| || (templateSize.unclampedArea() > layerImageProperties->shadowedResultSize.area())) |
| canUseTilingTechnique = false; |
| |
| if (canUseTilingTechnique) |
| drawRectShadowWithTiling(transform, shadowedRect, templateSize, edgeSize, drawImage, fillRect, *layerImageProperties); |
| else |
| drawRectShadowWithoutTiling(transform, shadowedRect, *layerImageProperties, drawBuffer); |
| } |
| |
| void ShadowBlur::drawInsetShadow(const AffineTransform& transform, const IntRect& clipBounds, const FloatRect& fullRect, const FloatRoundedRect& holeRect, const DrawBufferCallback& drawBuffer, const DrawImageCallback& drawImage, const FillRectWithHoleCallback& fillRectWithHole) |
| { |
| auto layerImageProperties = calculateLayerBoundingRect(transform, fullRect, clipBounds); |
| if (!layerImageProperties) |
| return; |
| |
| adjustBlurRadius(transform); |
| |
| bool canUseTilingTechnique = true; |
| |
| // drawRectShadowWithTiling does not work with rotations. |
| // https://bugs.webkit.org/show_bug.cgi?id=45042 |
| if (!transform.preservesAxisAlignment() || m_type != BlurShadow) |
| canUseTilingTechnique = false; |
| |
| IntSize edgeSize = blurredEdgeSize(); |
| IntSize templateSize = this->templateSize(edgeSize, holeRect.radii()); |
| const FloatRect& hRect = holeRect.rect(); |
| |
| if (templateSize.width() > hRect.width() || templateSize.height() > hRect.height() |
| || (templateSize.width() * templateSize.height() > hRect.width() * hRect.height())) |
| canUseTilingTechnique = false; |
| |
| if (canUseTilingTechnique) |
| drawInsetShadowWithTiling(transform, fullRect, holeRect, templateSize, edgeSize, drawImage, fillRectWithHole); |
| else |
| drawInsetShadowWithoutTiling(transform, fullRect, holeRect, *layerImageProperties, drawBuffer); |
| } |
| |
| void ShadowBlur::drawRectShadowWithoutTiling(const AffineTransform&, const FloatRoundedRect& shadowedRect, const LayerImageProperties& layerImageProperties, const DrawBufferCallback& drawBuffer) |
| { |
| auto layerImage = ImageBuffer::create(expandedIntSize(layerImageProperties.layerSize), RenderingPurpose::Unspecified, 1, DestinationColorSpace::SRGB(), PixelFormat::BGRA8); |
| if (!layerImage) |
| return; |
| |
| GraphicsContext& shadowContext = layerImage->context(); |
| GraphicsContextStateSaver stateSaver(shadowContext); |
| shadowContext.setFillColor(Color::black); |
| |
| { |
| GraphicsContext& shadowContext = layerImage->context(); |
| GraphicsContextStateSaver stateSaver(shadowContext); |
| shadowContext.translate(layerImageProperties.layerContextTranslation); |
| shadowContext.setFillColor(Color::black); |
| if (shadowedRect.radii().isZero()) |
| shadowContext.fillRect(shadowedRect.rect()); |
| else { |
| Path path; |
| path.addRoundedRect(shadowedRect); |
| shadowContext.fillPath(path); |
| } |
| |
| blurShadowBuffer(*layerImage, expandedIntSize(layerImageProperties.layerSize)); |
| } |
| drawBuffer(*layerImage, layerImageProperties.layerOrigin, layerImageProperties.layerSize); |
| } |
| |
| void ShadowBlur::drawInsetShadowWithoutTiling(const AffineTransform&, const FloatRect& fullRect, const FloatRoundedRect& holeRect, const LayerImageProperties& layerImageProperties, const DrawBufferCallback& drawBuffer) |
| { |
| auto layerImage = ImageBuffer::create(expandedIntSize(layerImageProperties.layerSize), RenderingPurpose::Unspecified, 1, DestinationColorSpace::SRGB(), PixelFormat::BGRA8); |
| if (!layerImage) |
| return; |
| |
| { |
| GraphicsContext& shadowContext = layerImage->context(); |
| GraphicsContextStateSaver stateSaver(shadowContext); |
| shadowContext.translate(layerImageProperties.layerContextTranslation); |
| |
| Path path; |
| path.addRect(fullRect); |
| if (holeRect.radii().isZero()) |
| path.addRect(holeRect.rect()); |
| else |
| path.addRoundedRect(holeRect); |
| |
| shadowContext.setFillRule(WindRule::EvenOdd); |
| shadowContext.setFillColor(Color::black); |
| shadowContext.fillPath(path); |
| |
| blurShadowBuffer(*layerImage, expandedIntSize(layerImageProperties.layerSize)); |
| } |
| |
| drawBuffer(*layerImage, layerImageProperties.layerOrigin, layerImageProperties.layerSize); |
| } |
| |
| /* |
| These functions use tiling to improve the performance of the shadow |
| drawing of rounded rectangles. The code basically does the following |
| steps: |
| |
| 1. Calculate the size of the shadow template, a rectangle that |
| contains all the necessary tiles to draw the complete shadow. |
| |
| 2. If that size is smaller than the real rectangle render the new |
| template rectangle and its shadow in a new surface, in other case |
| render the shadow of the real rectangle in the destination |
| surface. |
| |
| 3. Calculate the sizes and positions of the tiles and their |
| destinations and use drawPattern to render the final shadow. The |
| code divides the rendering in 8 tiles: |
| |
| 1 | 2 | 3 |
| ----------- |
| 4 | | 5 |
| ----------- |
| 6 | 7 | 8 |
| |
| The corners are directly copied from the template rectangle to the |
| real one and the side tiles are 1 pixel width, we use them as |
| tiles to cover the destination side. The corner tiles are bigger |
| than just the side of the rounded corner, we need to increase it |
| because the modifications caused by the corner over the blur |
| effect. We fill the central or outer part with solid color to complete |
| the shadow. |
| */ |
| |
| void ShadowBlur::drawRectShadowWithTiling(const AffineTransform& transform, const FloatRoundedRect& shadowedRect, const IntSize& templateSize, const IntSize& edgeSize, const DrawImageCallback& drawImage, const FillRectCallback& fillRect, const LayerImageProperties& layerImageProperties) |
| { |
| FloatRect templateShadow = FloatRect(edgeSize.width(), edgeSize.height(), templateSize.width() - 2 * edgeSize.width(), templateSize.height() - 2 * edgeSize.height()); |
| |
| #if USE(CG) |
| if (ScratchBuffer::lock().tryLock()) { |
| Locker locker { AdoptLock, ScratchBuffer::lock() }; |
| auto layerImageBuffer = ScratchBuffer::singleton().getScratchBuffer(templateSize); |
| if (!layerImageBuffer) |
| return; |
| bool redrawNeeded = ScratchBuffer::singleton().setCachedShadowValues(m_blurRadius, m_color, templateShadow, shadowedRect.radii(), layerImageProperties.layerSize); |
| drawRectShadowWithTilingWithLayerImageBuffer(*layerImageBuffer, transform, shadowedRect, templateSize, edgeSize, drawImage, fillRect, templateShadow, redrawNeeded); |
| return; |
| } |
| #else |
| UNUSED_PARAM(layerImageProperties); |
| #endif |
| |
| if (auto layerImageBuffer = ImageBuffer::create(templateSize, RenderingPurpose::Unspecified, 1, DestinationColorSpace::SRGB(), PixelFormat::BGRA8)) |
| drawRectShadowWithTilingWithLayerImageBuffer(*layerImageBuffer, transform, shadowedRect, templateSize, edgeSize, drawImage, fillRect, templateShadow); |
| } |
| |
| void ShadowBlur::drawRectShadowWithTilingWithLayerImageBuffer(ImageBuffer& layerImage, const AffineTransform& transform, const FloatRoundedRect& shadowedRect, const IntSize& templateSize, const IntSize& edgeSize, const DrawImageCallback& drawImage, const FillRectCallback& fillRect, const FloatRect& templateShadow, bool redrawNeeded) |
| { |
| if (redrawNeeded) { |
| // Draw shadow into the ImageBuffer. |
| GraphicsContext& shadowContext = layerImage.context(); |
| GraphicsContextStateSaver shadowStateSaver(shadowContext); |
| |
| shadowContext.clearRect(FloatRect(0, 0, templateSize.width(), templateSize.height())); |
| shadowContext.setFillColor(Color::black); |
| |
| if (shadowedRect.radii().isZero()) |
| shadowContext.fillRect(templateShadow); |
| else { |
| Path path; |
| path.addRoundedRect(FloatRoundedRect(templateShadow, shadowedRect.radii())); |
| shadowContext.fillPath(path); |
| } |
| blurAndColorShadowBuffer(layerImage, templateSize); |
| } |
| |
| FloatSize offset = m_offset; |
| if (shadowsIgnoreTransforms()) |
| offset.scale(1 / transform.xScale(), 1 / transform.yScale()); |
| |
| FloatRect shadowBounds = shadowedRect.rect(); |
| shadowBounds.move(offset); |
| shadowBounds.inflateX(edgeSize.width()); |
| shadowBounds.inflateY(edgeSize.height()); |
| |
| drawLayerPiecesAndFillCenter(layerImage, shadowBounds, shadowedRect.radii(), edgeSize, templateSize, drawImage, fillRect); |
| } |
| |
| void ShadowBlur::drawInsetShadowWithTiling(const AffineTransform& transform, const FloatRect& fullRect, const FloatRoundedRect& holeRect, const IntSize& templateSize, const IntSize& edgeSize, const DrawImageCallback& drawImage, const FillRectWithHoleCallback& fillRectWithHole) |
| { |
| // Draw the rectangle with hole. |
| FloatRect templateBounds(0, 0, templateSize.width(), templateSize.height()); |
| FloatRect templateHole = FloatRect(edgeSize.width(), edgeSize.height(), templateSize.width() - 2 * edgeSize.width(), templateSize.height() - 2 * edgeSize.height()); |
| |
| #if USE(CG) |
| if (ScratchBuffer::lock().tryLock()) { |
| Locker locker { AdoptLock, ScratchBuffer::lock() }; |
| auto layerImageBuffer = ScratchBuffer::singleton().getScratchBuffer(templateSize); |
| if (!layerImageBuffer) |
| return; |
| bool redrawNeeded = ScratchBuffer::singleton().setCachedInsetShadowValues(m_blurRadius, m_color, templateBounds, templateHole, holeRect.radii()); |
| drawInsetShadowWithTilingWithLayerImageBuffer(*layerImageBuffer, transform, fullRect, holeRect, templateSize, edgeSize, drawImage, fillRectWithHole, templateBounds, templateHole, redrawNeeded); |
| return; |
| } |
| #endif |
| |
| if (auto layerImageBuffer = ImageBuffer::create(templateSize, RenderingPurpose::Unspecified, 1, DestinationColorSpace::SRGB(), PixelFormat::BGRA8)) |
| drawInsetShadowWithTilingWithLayerImageBuffer(*layerImageBuffer, transform, fullRect, holeRect, templateSize, edgeSize, drawImage, fillRectWithHole, templateBounds, templateHole); |
| } |
| |
| void ShadowBlur::drawInsetShadowWithTilingWithLayerImageBuffer(ImageBuffer& layerImage, const AffineTransform& transform, const FloatRect& fullRect, const FloatRoundedRect& holeRect, const IntSize& templateSize, const IntSize& edgeSize, const DrawImageCallback& drawImage, const FillRectWithHoleCallback& fillRectWithHole, const FloatRect& templateBounds, const FloatRect& templateHole, bool redrawNeeded) |
| { |
| if (redrawNeeded) { |
| // Draw shadow into a new ImageBuffer. |
| GraphicsContext& shadowContext = layerImage.context(); |
| GraphicsContextStateSaver shadowStateSaver(shadowContext); |
| shadowContext.clearRect(templateBounds); |
| shadowContext.setFillRule(WindRule::EvenOdd); |
| shadowContext.setFillColor(Color::black); |
| |
| Path path; |
| path.addRect(templateBounds); |
| if (holeRect.radii().isZero()) |
| path.addRect(templateHole); |
| else |
| path.addRoundedRect(FloatRoundedRect(templateHole, holeRect.radii())); |
| |
| shadowContext.fillPath(path); |
| |
| blurAndColorShadowBuffer(layerImage, templateSize); |
| } |
| FloatSize offset = m_offset; |
| if (shadowsIgnoreTransforms()) |
| offset.scale(1 / transform.xScale(), 1 / transform.yScale()); |
| |
| FloatRect boundingRect = fullRect; |
| boundingRect.move(offset); |
| |
| FloatRect destHoleRect = holeRect.rect(); |
| destHoleRect.move(offset); |
| FloatRect destHoleBounds = destHoleRect; |
| destHoleBounds.inflateX(edgeSize.width()); |
| destHoleBounds.inflateY(edgeSize.height()); |
| |
| // Fill the external part of the shadow (which may be visible because of offset). |
| fillRectWithHole(boundingRect, destHoleBounds, m_color); |
| |
| drawLayerPieces(layerImage, destHoleBounds, holeRect.radii(), edgeSize, templateSize, drawImage); |
| } |
| |
| void ShadowBlur::drawLayerPieces(ImageBuffer& layerImage, const FloatRect& shadowBounds, const FloatRoundedRect::Radii& radii, const IntSize& bufferPadding, const IntSize& templateSize, const DrawImageCallback& drawImage) |
| { |
| const IntSize twiceRadius = IntSize(bufferPadding.width() * 2, bufferPadding.height() * 2); |
| |
| int leftSlice; |
| int rightSlice; |
| int topSlice; |
| int bottomSlice; |
| computeSliceSizesFromRadii(twiceRadius, radii, leftSlice, rightSlice, topSlice, bottomSlice); |
| |
| int centerWidth = shadowBounds.width() - leftSlice - rightSlice; |
| int centerHeight = shadowBounds.height() - topSlice - bottomSlice; |
| FloatRect centerRect(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight); |
| |
| // Top side. |
| FloatRect tileRect = FloatRect(leftSlice, 0, templateSideLength, topSlice); |
| FloatRect destRect = FloatRect(centerRect.x(), centerRect.y() - topSlice, centerRect.width(), topSlice); |
| drawImage(layerImage, destRect, tileRect); |
| |
| // Draw the bottom side. |
| tileRect.setY(templateSize.height() - bottomSlice); |
| tileRect.setHeight(bottomSlice); |
| destRect.setY(centerRect.maxY()); |
| destRect.setHeight(bottomSlice); |
| drawImage(layerImage, destRect, tileRect); |
| |
| // Left side. |
| tileRect = FloatRect(0, topSlice, leftSlice, templateSideLength); |
| destRect = FloatRect(centerRect.x() - leftSlice, centerRect.y(), leftSlice, centerRect.height()); |
| drawImage(layerImage, destRect, tileRect); |
| |
| // Right side. |
| tileRect.setX(templateSize.width() - rightSlice); |
| tileRect.setWidth(rightSlice); |
| destRect.setX(centerRect.maxX()); |
| destRect.setWidth(rightSlice); |
| drawImage(layerImage, destRect, tileRect); |
| |
| // Top left corner. |
| tileRect = FloatRect(0, 0, leftSlice, topSlice); |
| destRect = FloatRect(centerRect.x() - leftSlice, centerRect.y() - topSlice, leftSlice, topSlice); |
| drawImage(layerImage, destRect, tileRect); |
| |
| // Top right corner. |
| tileRect = FloatRect(templateSize.width() - rightSlice, 0, rightSlice, topSlice); |
| destRect = FloatRect(centerRect.maxX(), centerRect.y() - topSlice, rightSlice, topSlice); |
| drawImage(layerImage, destRect, tileRect); |
| |
| // Bottom right corner. |
| tileRect = FloatRect(templateSize.width() - rightSlice, templateSize.height() - bottomSlice, rightSlice, bottomSlice); |
| destRect = FloatRect(centerRect.maxX(), centerRect.maxY(), rightSlice, bottomSlice); |
| drawImage(layerImage, destRect, tileRect); |
| |
| // Bottom left corner. |
| tileRect = FloatRect(0, templateSize.height() - bottomSlice, leftSlice, bottomSlice); |
| destRect = FloatRect(centerRect.x() - leftSlice, centerRect.maxY(), leftSlice, bottomSlice); |
| drawImage(layerImage, destRect, tileRect); |
| } |
| |
| void ShadowBlur::drawLayerPiecesAndFillCenter(ImageBuffer& layerImage, const FloatRect& shadowBounds, const FloatRoundedRect::Radii& radii, const IntSize& bufferPadding, const IntSize& templateSize, const DrawImageCallback& drawImage, const FillRectCallback& fillRect) |
| { |
| const IntSize twiceRadius = IntSize(bufferPadding.width() * 2, bufferPadding.height() * 2); |
| |
| int leftSlice; |
| int rightSlice; |
| int topSlice; |
| int bottomSlice; |
| computeSliceSizesFromRadii(twiceRadius, radii, leftSlice, rightSlice, topSlice, bottomSlice); |
| |
| int centerWidth = shadowBounds.width() - leftSlice - rightSlice; |
| int centerHeight = shadowBounds.height() - topSlice - bottomSlice; |
| FloatRect centerRect(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight); |
| |
| // Fill center |
| if (!centerRect.isEmpty()) |
| fillRect(centerRect, m_color); |
| |
| drawLayerPieces(layerImage, shadowBounds, radii, bufferPadding, templateSize, drawImage); |
| } |
| |
| void ShadowBlur::blurShadowBuffer(ImageBuffer& layerImage, const IntSize& templateSize) |
| { |
| if (m_type != BlurShadow) |
| return; |
| |
| PixelBufferFormat format { AlphaPremultiplication::Unpremultiplied, PixelFormat::RGBA8, DestinationColorSpace::SRGB() }; |
| IntRect blurRect(IntPoint(), templateSize); |
| auto layerData = layerImage.getPixelBuffer(format, blurRect); |
| if (!layerData) |
| return; |
| |
| blurLayerImage(layerData->bytes(), blurRect.size(), blurRect.width() * 4); |
| layerImage.putPixelBuffer(*layerData, blurRect); |
| } |
| |
| void ShadowBlur::blurAndColorShadowBuffer(ImageBuffer& layerImage, const IntSize& templateSize) |
| { |
| blurShadowBuffer(layerImage, templateSize); |
| |
| // Mask the image with the shadow color. |
| GraphicsContext& shadowContext = layerImage.context(); |
| GraphicsContextStateSaver stateSaver(shadowContext); |
| shadowContext.setCompositeOperation(CompositeOperator::SourceIn); |
| shadowContext.setFillColor(m_color); |
| shadowContext.fillRect(FloatRect(0, 0, templateSize.width(), templateSize.height())); |
| } |
| |
| void ShadowBlur::drawShadowLayer(const AffineTransform& transform, const IntRect& clipBounds, const FloatRect& layerArea, const DrawShadowCallback& drawShadow, const DrawBufferCallback& drawBuffer) |
| { |
| auto layerImageProperties = calculateLayerBoundingRect(transform, layerArea, clipBounds); |
| if (!layerImageProperties) |
| return; |
| |
| adjustBlurRadius(transform); |
| |
| auto layerImage = ImageBuffer::create(expandedIntSize(layerImageProperties->layerSize), RenderingPurpose::Unspecified, 1, DestinationColorSpace::SRGB(), PixelFormat::BGRA8); |
| if (!layerImage) |
| return; |
| |
| { |
| GraphicsContext& shadowContext = layerImage->context(); |
| GraphicsContextStateSaver stateSaver(shadowContext); |
| shadowContext.translate(layerImageProperties->layerContextTranslation); |
| drawShadow(shadowContext); |
| } |
| |
| blurAndColorShadowBuffer(*layerImage, expandedIntSize(layerImageProperties->layerSize)); |
| drawBuffer(*layerImage, layerImageProperties->layerOrigin, layerImageProperties->layerSize); |
| } |
| |
| } // namespace WebCore |