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