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