1 /* 2 * Copyright (C) 2011 Apple Inc. 3 * Copyright (C) 2010 Sencha, Inc. 4 * Copyright (C) 2010 Igalia S.L. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 20 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 24 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "config.h" 30 #include "ShadowBlur.h" 31 32 #include "AffineTransform.h" 33 #include "FloatQuad.h" 34 #include "GraphicsContext.h" 35 #include "ImageBuffer.h" 36 #include "Timer.h" 37 #include <wtf/MathExtras.h> 38 #include <wtf/Noncopyable.h> 39 #include <wtf/UnusedParam.h> 40 41 using namespace std; 42 43 namespace WebCore { 44 45 static inline int roundUpToMultipleOf32(int d) 46 { 47 return (1 + (d >> 5)) << 5; 48 } 49 50 // ShadowBlur needs a scratch image as the buffer for the blur filter. 51 // Instead of creating and destroying the buffer for every operation, 52 // we create a buffer which will be automatically purged via a timer. 53 class ScratchBuffer { 54 public: 55 ScratchBuffer() 56 : m_purgeTimer(this, &ScratchBuffer::timerFired) 57 , m_lastRadius(0) 58 , m_lastWasInset(false) 59 #if !ASSERT_DISABLED 60 , m_bufferInUse(false) 61 #endif 62 { 63 } 64 65 ImageBuffer* getScratchBuffer(const IntSize& size) 66 { 67 ASSERT(!m_bufferInUse); 68 #if !ASSERT_DISABLED 69 m_bufferInUse = true; 70 #endif 71 // We do not need to recreate the buffer if the current buffer is large enough. 72 if (m_imageBuffer && m_imageBuffer->width() >= size.width() && m_imageBuffer->height() >= size.height()) 73 return m_imageBuffer.get(); 74 75 // Round to the nearest 32 pixels so we do not grow the buffer for similar sized requests. 76 IntSize roundedSize(roundUpToMultipleOf32(size.width()), roundUpToMultipleOf32(size.height())); 77 78 m_imageBuffer = ImageBuffer::create(roundedSize); 79 return m_imageBuffer.get(); 80 } 81 82 void setLastShadowValues(float radius, const Color& color, ColorSpace colorSpace, const FloatRect& shadowRect, const RoundedIntRect::Radii& radii) 83 { 84 m_lastWasInset = false; 85 m_lastRadius = radius; 86 m_lastColor = color; 87 m_lastColorSpace = colorSpace; 88 m_lastShadowRect = shadowRect; 89 m_lastRadii = radii; 90 } 91 92 void setLastInsetShadowValues(float radius, const Color& color, ColorSpace colorSpace, const FloatRect& bounds, const FloatRect& shadowRect, const RoundedIntRect::Radii& radii) 93 { 94 m_lastWasInset = true; 95 m_lastInsetBounds = bounds; 96 m_lastRadius = radius; 97 m_lastColor = color; 98 m_lastColorSpace = colorSpace; 99 m_lastShadowRect = shadowRect; 100 m_lastRadii = radii; 101 } 102 103 bool matchesLastShadow(float radius, const Color& color, ColorSpace colorSpace, const FloatRect& shadowRect, const RoundedIntRect::Radii& radii) const 104 { 105 if (m_lastWasInset) 106 return false; 107 return m_lastRadius == radius && m_lastColor == color && m_lastColorSpace == colorSpace && shadowRect == m_lastShadowRect && radii == m_lastRadii; 108 } 109 110 bool matchesLastInsetShadow(float radius, const Color& color, ColorSpace colorSpace, const FloatRect& bounds, const FloatRect& shadowRect, const RoundedIntRect::Radii& radii) const 111 { 112 if (!m_lastWasInset) 113 return false; 114 return m_lastRadius == radius && m_lastColor == color && m_lastColorSpace == colorSpace && m_lastInsetBounds == bounds && shadowRect == m_lastShadowRect && radii == m_lastRadii; 115 } 116 117 void scheduleScratchBufferPurge() 118 { 119 #if !ASSERT_DISABLED 120 m_bufferInUse = false; 121 #endif 122 if (m_purgeTimer.isActive()) 123 m_purgeTimer.stop(); 124 125 const double scratchBufferPurgeInterval = 2; 126 m_purgeTimer.startOneShot(scratchBufferPurgeInterval); 127 } 128 129 static ScratchBuffer& shared(); 130 131 private: 132 void timerFired(Timer<ScratchBuffer>*) 133 { 134 clearScratchBuffer(); 135 } 136 137 void clearScratchBuffer() 138 { 139 m_imageBuffer = 0; 140 m_lastRadius = 0; 141 } 142 143 OwnPtr<ImageBuffer> m_imageBuffer; 144 Timer<ScratchBuffer> m_purgeTimer; 145 146 FloatRect m_lastInsetBounds; 147 FloatRect m_lastShadowRect; 148 RoundedIntRect::Radii m_lastRadii; 149 Color m_lastColor; 150 ColorSpace m_lastColorSpace; 151 float m_lastRadius; 152 bool m_lastWasInset; 153 154 #if !ASSERT_DISABLED 155 bool m_bufferInUse; 156 #endif 157 }; 158 159 ScratchBuffer& ScratchBuffer::shared() 160 { 161 DEFINE_STATIC_LOCAL(ScratchBuffer, scratchBuffer, ()); 162 return scratchBuffer; 163 } 164 165 static const int templateSideLength = 1; 166 167 ShadowBlur::ShadowBlur(float radius, const FloatSize& offset, const Color& color, ColorSpace colorSpace) 168 : m_color(color) 169 , m_colorSpace(colorSpace) 170 , m_blurRadius(radius) 171 , m_offset(offset) 172 , m_layerImage(0) 173 , m_shadowsIgnoreTransforms(false) 174 { 175 // Limit blur radius to 128 to avoid lots of very expensive blurring. 176 m_blurRadius = min<float>(m_blurRadius, 128); 177 178 // The type of shadow is decided by the blur radius, shadow offset, and shadow color. 179 if (!m_color.isValid() || !color.alpha()) { 180 // Can't paint the shadow with invalid or invisible color. 181 m_type = NoShadow; 182 } else if (m_blurRadius > 0) { 183 // Shadow is always blurred, even the offset is zero. 184 m_type = BlurShadow; 185 } else if (!m_offset.width() && !m_offset.height()) { 186 // Without blur and zero offset means the shadow is fully hidden. 187 m_type = NoShadow; 188 } else 189 m_type = SolidShadow; 190 } 191 192 // Instead of integer division, we use 17.15 for fixed-point division. 193 static const int blurSumShift = 15; 194 195 void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride) 196 { 197 const int channels[4] = { 3, 0, 1, 3 }; 198 199 int diameter; 200 if (m_shadowsIgnoreTransforms) 201 diameter = max(2, static_cast<int>(floorf((2 / 3.f) * m_blurRadius))); // Canvas shadow. FIXME: we should adjust the blur radius higher up. 202 else { 203 // http://dev.w3.org/csswg/css3-background/#box-shadow 204 // Approximate a Gaussian blur with a standard deviation equal to half the blur radius, 205 // which http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement tell us how to do. 206 // However, shadows rendered according to that spec will extend a little further than m_blurRadius, 207 // so we apply a fudge factor to bring the radius down slightly. 208 float stdDev = m_blurRadius / 2; 209 const float gaussianKernelFactor = 3 / 4.f * sqrtf(2 * piFloat); 210 const float fudgeFactor = 0.88f; 211 diameter = max(2, static_cast<int>(floorf(stdDev * gaussianKernelFactor * fudgeFactor + 0.5f))); 212 } 213 214 enum { 215 leftLobe = 0, 216 rightLobe = 1 217 }; 218 219 int lobes[3][2]; // indexed by pass, and left/right lobe 220 221 if (diameter & 1) { 222 // if d is odd, use three box-blurs of size 'd', centered on the output pixel. 223 int lobeSize = (diameter - 1) / 2; 224 lobes[0][leftLobe] = lobeSize; 225 lobes[0][rightLobe] = lobeSize; 226 lobes[1][leftLobe] = lobeSize; 227 lobes[1][rightLobe] = lobeSize; 228 lobes[2][leftLobe] = lobeSize; 229 lobes[2][rightLobe] = lobeSize; 230 } else { 231 // if d is even, two box-blurs of size 'd' (the first one centered on the pixel boundary 232 // between the output pixel and the one to the left, the second one centered on the pixel 233 // boundary between the output pixel and the one to the right) and one box blur of size 'd+1' centered on the output pixel 234 int lobeSize = diameter / 2; 235 lobes[0][leftLobe] = lobeSize; 236 lobes[0][rightLobe] = lobeSize - 1; 237 lobes[1][leftLobe] = lobeSize - 1; 238 lobes[1][rightLobe] = lobeSize; 239 lobes[2][leftLobe] = lobeSize; 240 lobes[2][rightLobe] = lobeSize; 241 } 242 243 // First pass is horizontal. 244 int stride = 4; 245 int delta = rowStride; 246 int final = size.height(); 247 int dim = size.width(); 248 249 // Two stages: horizontal and vertical 250 for (int pass = 0; pass < 2; ++pass) { 251 unsigned char* pixels = imageData; 252 253 for (int j = 0; j < final; ++j, pixels += delta) { 254 // For each step, we blur the alpha in a channel and store the result 255 // in another channel for the subsequent step. 256 // We use sliding window algorithm to accumulate the alpha values. 257 // This is much more efficient than computing the sum of each pixels 258 // covered by the box kernel size for each x. 259 for (int step = 0; step < 3; ++step) { 260 int side1 = lobes[step][leftLobe]; 261 int side2 = lobes[step][rightLobe]; 262 int pixelCount = side1 + 1 + side2; 263 int invCount = ((1 << blurSumShift) + pixelCount - 1) / pixelCount; 264 int ofs = 1 + side2; 265 int alpha1 = pixels[channels[step]]; 266 int alpha2 = pixels[(dim - 1) * stride + channels[step]]; 267 268 unsigned char* ptr = pixels + channels[step + 1]; 269 unsigned char* prev = pixels + stride + channels[step]; 270 unsigned char* next = pixels + ofs * stride + channels[step]; 271 272 int i; 273 int sum = side1 * alpha1 + alpha1; 274 int limit = (dim < side2 + 1) ? dim : side2 + 1; 275 276 for (i = 1; i < limit; ++i, prev += stride) 277 sum += *prev; 278 279 if (limit <= side2) 280 sum += (side2 - limit + 1) * alpha2; 281 282 limit = (side1 < dim) ? side1 : dim; 283 for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) { 284 *ptr = (sum * invCount) >> blurSumShift; 285 sum += ((ofs < dim) ? *next : alpha2) - alpha1; 286 } 287 288 prev = pixels + channels[step]; 289 for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) { 290 *ptr = (sum * invCount) >> blurSumShift; 291 sum += (*next) - (*prev); 292 } 293 294 for (; i < dim; ptr += stride, prev += stride, ++i) { 295 *ptr = (sum * invCount) >> blurSumShift; 296 sum += alpha2 - (*prev); 297 } 298 } 299 } 300 301 // Last pass is vertical. 302 stride = rowStride; 303 delta = 4; 304 final = size.width(); 305 dim = size.height(); 306 } 307 } 308 309 void ShadowBlur::adjustBlurRadius(GraphicsContext* context) 310 { 311 if (!m_shadowsIgnoreTransforms) 312 return; 313 314 const AffineTransform transform = context->getCTM(); 315 316 // Adjust blur if we're scaling, since the radius must not be affected by transformations. 317 // FIXME: use AffineTransform::isIdentityOrTranslationOrFlipped()? 318 if (transform.isIdentity()) 319 return; 320 321 // Calculate transformed unit vectors. 322 const FloatQuad unitQuad(FloatPoint(0, 0), FloatPoint(1, 0), 323 FloatPoint(0, 1), FloatPoint(1, 1)); 324 const FloatQuad transformedUnitQuad = transform.mapQuad(unitQuad); 325 326 // Calculate X axis scale factor. 327 const FloatSize xUnitChange = transformedUnitQuad.p2() - transformedUnitQuad.p1(); 328 const float xAxisScale = sqrtf(xUnitChange.width() * xUnitChange.width() 329 + xUnitChange.height() * xUnitChange.height()); 330 331 // Calculate Y axis scale factor. 332 const FloatSize yUnitChange = transformedUnitQuad.p3() - transformedUnitQuad.p1(); 333 const float yAxisScale = sqrtf(yUnitChange.width() * yUnitChange.width() 334 + yUnitChange.height() * yUnitChange.height()); 335 336 // blurLayerImage() does not support per-axis blurring, so calculate a balanced scaling. 337 // FIXME: does AffineTransform.xScale()/yScale() help? 338 const float scale = sqrtf(xAxisScale * yAxisScale); 339 m_blurRadius = roundf(m_blurRadius / scale); 340 } 341 342 IntRect ShadowBlur::calculateLayerBoundingRect(GraphicsContext* context, const FloatRect& shadowedRect, const IntRect& clipRect) 343 { 344 const float roundedRadius = ceilf(m_blurRadius); 345 346 // Calculate the destination of the blurred and/or transformed layer. 347 FloatRect layerRect; 348 float inflation = 0; 349 350 const AffineTransform transform = context->getCTM(); 351 if (m_shadowsIgnoreTransforms && !transform.isIdentity()) { 352 FloatQuad transformedPolygon = transform.mapQuad(FloatQuad(shadowedRect)); 353 transformedPolygon.move(m_offset); 354 layerRect = transform.inverse().mapQuad(transformedPolygon).boundingBox(); 355 } else { 356 layerRect = shadowedRect; 357 layerRect.move(m_offset); 358 } 359 360 // We expand the area by the blur radius to give extra space for the blur transition. 361 if (m_type == BlurShadow) { 362 layerRect.inflate(roundedRadius); 363 inflation = roundedRadius; 364 } 365 366 FloatRect unclippedLayerRect = layerRect; 367 368 if (!clipRect.contains(enclosingIntRect(layerRect))) { 369 // If we are totally outside the clip region, we aren't painting at all. 370 if (intersection(layerRect, clipRect).isEmpty()) 371 return IntRect(); 372 373 IntRect inflatedClip = clipRect; 374 // Pixels at the edges can be affected by pixels outside the buffer, 375 // so intersect with the clip inflated by the blur. 376 if (m_type == BlurShadow) 377 inflatedClip.inflate(roundedRadius); 378 379 layerRect.intersect(inflatedClip); 380 } 381 382 const float frameSize = inflation * 2; 383 m_sourceRect = FloatRect(0, 0, shadowedRect.width() + frameSize, shadowedRect.height() + frameSize); 384 m_layerOrigin = FloatPoint(layerRect.x(), layerRect.y()); 385 m_layerSize = layerRect.size(); 386 387 const FloatPoint unclippedLayerOrigin = FloatPoint(unclippedLayerRect.x(), unclippedLayerRect.y()); 388 const FloatSize clippedOut = unclippedLayerOrigin - m_layerOrigin; 389 390 // Set the origin as the top left corner of the scratch image, or, in case there's a clipped 391 // out region, set the origin accordingly to the full bounding rect's top-left corner. 392 float translationX = -shadowedRect.x() + inflation - fabsf(clippedOut.width()); 393 float translationY = -shadowedRect.y() + inflation - fabsf(clippedOut.height()); 394 m_layerContextTranslation = FloatSize(translationX, translationY); 395 396 return enclosingIntRect(layerRect); 397 } 398 399 void ShadowBlur::drawShadowBuffer(GraphicsContext* graphicsContext) 400 { 401 if (!m_layerImage) 402 return; 403 404 graphicsContext->save(); 405 406 IntSize bufferSize = m_layerImage->size(); 407 if (bufferSize != m_layerSize) { 408 // The rect passed to clipToImageBuffer() has to be the size of the entire buffer, 409 // but we may not have cleared it all, so clip to the filled part first. 410 graphicsContext->clip(FloatRect(m_layerOrigin, m_layerSize)); 411 } 412 graphicsContext->clipToImageBuffer(m_layerImage, FloatRect(m_layerOrigin, bufferSize)); 413 graphicsContext->setFillColor(m_color, m_colorSpace); 414 415 graphicsContext->clearShadow(); 416 graphicsContext->fillRect(FloatRect(m_layerOrigin, m_sourceRect.size())); 417 418 graphicsContext->restore(); 419 } 420 421 static void computeSliceSizesFromRadii(int twiceRadius, const RoundedIntRect::Radii& radii, int& leftSlice, int& rightSlice, int& topSlice, int& bottomSlice) 422 { 423 leftSlice = twiceRadius + max(radii.topLeft().width(), radii.bottomLeft().width()); 424 rightSlice = twiceRadius + max(radii.topRight().width(), radii.bottomRight().width()); 425 426 topSlice = twiceRadius + max(radii.topLeft().height(), radii.topRight().height()); 427 bottomSlice = twiceRadius + max(radii.bottomLeft().height(), radii.bottomRight().height()); 428 } 429 430 IntSize ShadowBlur::templateSize(const RoundedIntRect::Radii& radii) const 431 { 432 const int templateSideLength = 1; 433 434 int leftSlice; 435 int rightSlice; 436 int topSlice; 437 int bottomSlice; 438 computeSliceSizesFromRadii(2 * ceilf(m_blurRadius), radii, leftSlice, rightSlice, topSlice, bottomSlice); 439 440 return IntSize(templateSideLength + leftSlice + rightSlice, 441 templateSideLength + topSlice + bottomSlice); 442 } 443 444 void ShadowBlur::drawRectShadow(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii) 445 { 446 IntRect layerRect = calculateLayerBoundingRect(graphicsContext, shadowedRect, graphicsContext->clipBounds()); 447 if (layerRect.isEmpty()) 448 return; 449 450 adjustBlurRadius(graphicsContext); 451 452 // drawRectShadowWithTiling does not work with rotations. 453 // https://bugs.webkit.org/show_bug.cgi?id=45042 454 if (!graphicsContext->getCTM().isIdentityOrTranslationOrFlipped() || m_type != BlurShadow) { 455 drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, layerRect); 456 return; 457 } 458 459 IntSize templateSize = this->templateSize(radii); 460 461 if (templateSize.width() > shadowedRect.width() || templateSize.height() > shadowedRect.height() 462 || (templateSize.width() * templateSize.height() > m_sourceRect.width() * m_sourceRect.height())) { 463 drawRectShadowWithoutTiling(graphicsContext, shadowedRect, radii, layerRect); 464 return; 465 } 466 467 drawRectShadowWithTiling(graphicsContext, shadowedRect, radii, templateSize); 468 } 469 470 void ShadowBlur::drawInsetShadow(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& holeRadii) 471 { 472 IntRect layerRect = calculateLayerBoundingRect(graphicsContext, rect, graphicsContext->clipBounds()); 473 if (layerRect.isEmpty()) 474 return; 475 476 adjustBlurRadius(graphicsContext); 477 478 // drawInsetShadowWithTiling does not work with rotations. 479 // https://bugs.webkit.org/show_bug.cgi?id=45042 480 if (!graphicsContext->getCTM().isIdentityOrTranslationOrFlipped() || m_type != BlurShadow) { 481 drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, holeRadii, layerRect); 482 return; 483 } 484 485 IntSize templateSize = this->templateSize(holeRadii); 486 487 if (templateSize.width() > holeRect.width() || templateSize.height() > holeRect.height() 488 || (templateSize.width() * templateSize.height() > holeRect.width() * holeRect.height())) { 489 drawInsetShadowWithoutTiling(graphicsContext, rect, holeRect, holeRadii, layerRect); 490 return; 491 } 492 493 drawInsetShadowWithTiling(graphicsContext, rect, holeRect, holeRadii, templateSize); 494 } 495 496 void ShadowBlur::drawRectShadowWithoutTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii, const IntRect& layerRect) 497 { 498 m_layerImage = ScratchBuffer::shared().getScratchBuffer(layerRect.size()); 499 if (!m_layerImage) 500 return; 501 502 FloatRect bufferRelativeShadowedRect = shadowedRect; 503 bufferRelativeShadowedRect.move(m_layerContextTranslation); 504 if (!ScratchBuffer::shared().matchesLastShadow(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeShadowedRect, radii)) { 505 GraphicsContext* shadowContext = m_layerImage->context(); 506 shadowContext->save(); 507 508 // Add a pixel to avoid later edge aliasing when rotated. 509 shadowContext->clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1)); 510 shadowContext->translate(m_layerContextTranslation); 511 shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB); 512 if (radii.isZero()) 513 shadowContext->fillRect(shadowedRect); 514 else { 515 Path path; 516 path.addRoundedRect(shadowedRect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight()); 517 shadowContext->fillPath(path); 518 } 519 520 blurShadowBuffer(expandedIntSize(m_layerSize)); 521 522 shadowContext->restore(); 523 524 ScratchBuffer::shared().setLastShadowValues(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeShadowedRect, radii); 525 } 526 527 drawShadowBuffer(graphicsContext); 528 m_layerImage = 0; 529 ScratchBuffer::shared().scheduleScratchBufferPurge(); 530 } 531 532 void ShadowBlur::drawInsetShadowWithoutTiling(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& holeRadii, const IntRect& layerRect) 533 { 534 m_layerImage = ScratchBuffer::shared().getScratchBuffer(layerRect.size()); 535 if (!m_layerImage) 536 return; 537 538 FloatRect bufferRelativeRect = rect; 539 bufferRelativeRect.move(m_layerContextTranslation); 540 541 FloatRect bufferRelativeHoleRect = holeRect; 542 bufferRelativeHoleRect.move(m_layerContextTranslation); 543 544 if (!ScratchBuffer::shared().matchesLastInsetShadow(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeRect, bufferRelativeHoleRect, holeRadii)) { 545 GraphicsContext* shadowContext = m_layerImage->context(); 546 shadowContext->save(); 547 548 // Add a pixel to avoid later edge aliasing when rotated. 549 shadowContext->clearRect(FloatRect(0, 0, m_layerSize.width() + 1, m_layerSize.height() + 1)); 550 shadowContext->translate(m_layerContextTranslation); 551 552 Path path; 553 path.addRect(rect); 554 if (holeRadii.isZero()) 555 path.addRect(holeRect); 556 else 557 path.addRoundedRect(holeRect, holeRadii.topLeft(), holeRadii.topRight(), holeRadii.bottomLeft(), holeRadii.bottomRight()); 558 559 shadowContext->setFillRule(RULE_EVENODD); 560 shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB); 561 shadowContext->fillPath(path); 562 563 blurShadowBuffer(expandedIntSize(m_layerSize)); 564 565 shadowContext->restore(); 566 567 ScratchBuffer::shared().setLastInsetShadowValues(m_blurRadius, Color::black, ColorSpaceDeviceRGB, bufferRelativeRect, bufferRelativeHoleRect, holeRadii); 568 } 569 570 drawShadowBuffer(graphicsContext); 571 m_layerImage = 0; 572 ScratchBuffer::shared().scheduleScratchBufferPurge(); 573 } 574 575 /* 576 These functions use tiling to improve the performance of the shadow 577 drawing of rounded rectangles. The code basically does the following 578 steps: 579 580 1. Calculate the size of the shadow template, a rectangle that 581 contains all the necessary tiles to draw the complete shadow. 582 583 2. If that size is smaller than the real rectangle render the new 584 template rectangle and its shadow in a new surface, in other case 585 render the shadow of the real rectangle in the destination 586 surface. 587 588 3. Calculate the sizes and positions of the tiles and their 589 destinations and use drawPattern to render the final shadow. The 590 code divides the rendering in 8 tiles: 591 592 1 | 2 | 3 593 ----------- 594 4 | | 5 595 ----------- 596 6 | 7 | 8 597 598 The corners are directly copied from the template rectangle to the 599 real one and the side tiles are 1 pixel width, we use them as 600 tiles to cover the destination side. The corner tiles are bigger 601 than just the side of the rounded corner, we need to increase it 602 because the modifications caused by the corner over the blur 603 effect. We fill the central or outer part with solid color to complete 604 the shadow. 605 */ 606 607 void ShadowBlur::drawInsetShadowWithTiling(GraphicsContext* graphicsContext, const FloatRect& rect, const FloatRect& holeRect, const RoundedIntRect::Radii& radii, const IntSize& templateSize) 608 { 609 graphicsContext->save(); 610 graphicsContext->clearShadow(); 611 612 const float roundedRadius = ceilf(m_blurRadius); 613 const float twiceRadius = roundedRadius * 2; 614 615 m_layerImage = ScratchBuffer::shared().getScratchBuffer(templateSize); 616 if (!m_layerImage) 617 return; 618 619 // Draw the rectangle with hole. 620 FloatRect templateBounds(0, 0, templateSize.width(), templateSize.height()); 621 FloatRect templateHole = FloatRect(roundedRadius, roundedRadius, templateSize.width() - twiceRadius, templateSize.height() - twiceRadius); 622 623 if (!ScratchBuffer::shared().matchesLastInsetShadow(m_blurRadius, m_color, m_colorSpace, templateBounds, templateHole, radii)) { 624 // Draw shadow into a new ImageBuffer. 625 GraphicsContext* shadowContext = m_layerImage->context(); 626 shadowContext->save(); 627 shadowContext->clearRect(templateBounds); 628 shadowContext->setFillRule(RULE_EVENODD); 629 shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB); 630 631 Path path; 632 path.addRect(templateBounds); 633 if (radii.isZero()) 634 path.addRect(templateHole); 635 else 636 path.addRoundedRect(templateHole, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight()); 637 638 shadowContext->fillPath(path); 639 640 blurAndColorShadowBuffer(templateSize); 641 shadowContext->restore(); 642 643 ScratchBuffer::shared().setLastInsetShadowValues(m_blurRadius, m_color, m_colorSpace, templateBounds, templateHole, radii); 644 } 645 646 FloatRect boundingRect = rect; 647 boundingRect.move(m_offset); 648 649 FloatRect destHoleRect = holeRect; 650 destHoleRect.move(m_offset); 651 FloatRect destHoleBounds = destHoleRect; 652 destHoleBounds.inflate(roundedRadius); 653 654 // Fill the external part of the shadow (which may be visible because of offset). 655 Path exteriorPath; 656 exteriorPath.addRect(boundingRect); 657 exteriorPath.addRect(destHoleBounds); 658 659 graphicsContext->save(); 660 graphicsContext->setFillRule(RULE_EVENODD); 661 graphicsContext->setFillColor(m_color, m_colorSpace); 662 graphicsContext->fillPath(exteriorPath); 663 graphicsContext->restore(); 664 665 drawLayerPieces(graphicsContext, destHoleBounds, radii, roundedRadius, templateSize, InnerShadow); 666 667 graphicsContext->restore(); 668 669 m_layerImage = 0; 670 ScratchBuffer::shared().scheduleScratchBufferPurge(); 671 } 672 673 void ShadowBlur::drawRectShadowWithTiling(GraphicsContext* graphicsContext, const FloatRect& shadowedRect, const RoundedIntRect::Radii& radii, const IntSize& templateSize) 674 { 675 graphicsContext->save(); 676 graphicsContext->clearShadow(); 677 678 const float roundedRadius = ceilf(m_blurRadius); 679 const float twiceRadius = roundedRadius * 2; 680 681 m_layerImage = ScratchBuffer::shared().getScratchBuffer(templateSize); 682 if (!m_layerImage) 683 return; 684 685 FloatRect templateShadow = FloatRect(roundedRadius, roundedRadius, templateSize.width() - twiceRadius, templateSize.height() - twiceRadius); 686 687 if (!ScratchBuffer::shared().matchesLastShadow(m_blurRadius, m_color, m_colorSpace, templateShadow, radii)) { 688 // Draw shadow into the ImageBuffer. 689 GraphicsContext* shadowContext = m_layerImage->context(); 690 shadowContext->save(); 691 shadowContext->clearRect(FloatRect(0, 0, templateSize.width(), templateSize.height())); 692 shadowContext->setFillColor(Color::black, ColorSpaceDeviceRGB); 693 694 if (radii.isZero()) 695 shadowContext->fillRect(templateShadow); 696 else { 697 Path path; 698 path.addRoundedRect(templateShadow, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight()); 699 shadowContext->fillPath(path); 700 } 701 702 blurAndColorShadowBuffer(templateSize); 703 shadowContext->restore(); 704 705 ScratchBuffer::shared().setLastShadowValues(m_blurRadius, m_color, m_colorSpace, templateShadow, radii); 706 } 707 708 FloatRect shadowBounds = shadowedRect; 709 shadowBounds.move(m_offset.width(), m_offset.height()); 710 shadowBounds.inflate(roundedRadius); 711 712 drawLayerPieces(graphicsContext, shadowBounds, radii, roundedRadius, templateSize, OuterShadow); 713 714 graphicsContext->restore(); 715 716 m_layerImage = 0; 717 ScratchBuffer::shared().scheduleScratchBufferPurge(); 718 } 719 720 void ShadowBlur::drawLayerPieces(GraphicsContext* graphicsContext, const FloatRect& shadowBounds, const RoundedIntRect::Radii& radii, float roundedRadius, const IntSize& templateSize, ShadowDirection direction) 721 { 722 const float twiceRadius = roundedRadius * 2; 723 724 int leftSlice; 725 int rightSlice; 726 int topSlice; 727 int bottomSlice; 728 computeSliceSizesFromRadii(twiceRadius, radii, leftSlice, rightSlice, topSlice, bottomSlice); 729 730 int centerWidth = shadowBounds.width() - leftSlice - rightSlice; 731 int centerHeight = shadowBounds.height() - topSlice - bottomSlice; 732 733 if (direction == OuterShadow) { 734 FloatRect shadowInterior(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight); 735 if (!shadowInterior.isEmpty()) { 736 graphicsContext->save(); 737 738 graphicsContext->setFillColor(m_color, m_colorSpace); 739 graphicsContext->fillRect(shadowInterior); 740 741 graphicsContext->restore(); 742 } 743 } 744 745 // Note that drawing the ImageBuffer is faster than creating a Image and drawing that, 746 // because ImageBuffer::draw() knows that it doesn't have to copy the image bits. 747 748 // Top side. 749 FloatRect tileRect = FloatRect(leftSlice, 0, templateSideLength, topSlice); 750 FloatRect destRect = FloatRect(shadowBounds.x() + leftSlice, shadowBounds.y(), centerWidth, topSlice); 751 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect); 752 753 // Draw the bottom side. 754 tileRect.setY(templateSize.height() - bottomSlice); 755 tileRect.setHeight(bottomSlice); 756 destRect.setY(shadowBounds.maxY() - bottomSlice); 757 destRect.setHeight(bottomSlice); 758 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect); 759 760 // Left side. 761 tileRect = FloatRect(0, topSlice, leftSlice, templateSideLength); 762 destRect = FloatRect(shadowBounds.x(), shadowBounds.y() + topSlice, leftSlice, centerHeight); 763 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect); 764 765 // Right side. 766 tileRect.setX(templateSize.width() - rightSlice); 767 tileRect.setWidth(rightSlice); 768 destRect.setX(shadowBounds.maxX() - rightSlice); 769 destRect.setWidth(rightSlice); 770 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect); 771 772 // Top left corner. 773 tileRect = FloatRect(0, 0, leftSlice, topSlice); 774 destRect = FloatRect(shadowBounds.x(), shadowBounds.y(), leftSlice, topSlice); 775 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect); 776 777 // Top right corner. 778 tileRect = FloatRect(templateSize.width() - rightSlice, 0, rightSlice, topSlice); 779 destRect = FloatRect(shadowBounds.maxX() - rightSlice, shadowBounds.y(), rightSlice, topSlice); 780 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect); 781 782 // Bottom right corner. 783 tileRect = FloatRect(templateSize.width() - rightSlice, templateSize.height() - bottomSlice, rightSlice, bottomSlice); 784 destRect = FloatRect(shadowBounds.maxX() - rightSlice, shadowBounds.maxY() - bottomSlice, rightSlice, bottomSlice); 785 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect); 786 787 // Bottom left corner. 788 tileRect = FloatRect(0, templateSize.height() - bottomSlice, leftSlice, bottomSlice); 789 destRect = FloatRect(shadowBounds.x(), shadowBounds.maxY() - bottomSlice, leftSlice, bottomSlice); 790 graphicsContext->drawImageBuffer(m_layerImage, ColorSpaceDeviceRGB, destRect, tileRect); 791 } 792 793 794 void ShadowBlur::blurShadowBuffer(const IntSize& templateSize) 795 { 796 if (m_type != BlurShadow) 797 return; 798 799 IntRect blurRect(IntPoint(), templateSize); 800 RefPtr<ByteArray> layerData = m_layerImage->getUnmultipliedImageData(blurRect); 801 blurLayerImage(layerData->data(), blurRect.size(), blurRect.width() * 4); 802 m_layerImage->putUnmultipliedImageData(layerData.get(), blurRect.size(), blurRect, IntPoint()); 803 } 804 805 void ShadowBlur::blurAndColorShadowBuffer(const IntSize& templateSize) 806 { 807 blurShadowBuffer(templateSize); 808 809 // Mask the image with the shadow color. 810 GraphicsContext* shadowContext = m_layerImage->context(); 811 shadowContext->setCompositeOperation(CompositeSourceIn); 812 shadowContext->setFillColor(m_color, m_colorSpace); 813 shadowContext->fillRect(FloatRect(0, 0, templateSize.width(), templateSize.height())); 814 } 815 816 } // namespace WebCore 817