| 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 | |