1/* 2 * Copyright (C) 2010 Sencha, Inc. 3 * Copyright (C) 2010 Igalia S.L. 4 * 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 COMPUTER, 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 "ContextShadow.h" 31 32#include "AffineTransform.h" 33#include "CairoUtilities.h" 34#include "GraphicsContext.h" 35#include "OwnPtrCairo.h" 36#include "Path.h" 37#include "PlatformContextCairo.h" 38#include "Timer.h" 39#include <cairo.h> 40 41using WTF::max; 42 43namespace WebCore { 44 45static RefPtr<cairo_surface_t> gScratchBuffer; 46static void purgeScratchBuffer() 47{ 48 gScratchBuffer.clear(); 49} 50 51// ContextShadow needs a scratch image as the buffer for the blur filter. 52// Instead of creating and destroying the buffer for every operation, 53// we create a buffer which will be automatically purged via a timer. 54class PurgeScratchBufferTimer : public TimerBase { 55private: 56 virtual void fired() { purgeScratchBuffer(); } 57}; 58static PurgeScratchBufferTimer purgeScratchBufferTimer; 59static void scheduleScratchBufferPurge() 60{ 61 if (purgeScratchBufferTimer.isActive()) 62 purgeScratchBufferTimer.stop(); 63 purgeScratchBufferTimer.startOneShot(2); 64} 65 66static cairo_surface_t* getScratchBuffer(const IntSize& size) 67{ 68 int width = size.width(); 69 int height = size.height(); 70 int scratchWidth = gScratchBuffer.get() ? cairo_image_surface_get_width(gScratchBuffer.get()) : 0; 71 int scratchHeight = gScratchBuffer.get() ? cairo_image_surface_get_height(gScratchBuffer.get()) : 0; 72 73 // We do not need to recreate the buffer if the current buffer is large enough. 74 if (gScratchBuffer.get() && scratchWidth >= width && scratchHeight >= height) 75 return gScratchBuffer.get(); 76 77 purgeScratchBuffer(); 78 79 // Round to the nearest 32 pixels so we do not grow the buffer for similar sized requests. 80 width = (1 + (width >> 5)) << 5; 81 height = (1 + (height >> 5)) << 5; 82 gScratchBuffer = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height)); 83 return gScratchBuffer.get(); 84} 85 86PlatformContext ContextShadow::beginShadowLayer(GraphicsContext* context, const FloatRect& layerArea) 87{ 88 adjustBlurDistance(context); 89 90 double x1, x2, y1, y2; 91 cairo_clip_extents(context->platformContext()->cr(), &x1, &y1, &x2, &y2); 92 IntRect layerRect = calculateLayerBoundingRect(context, layerArea, IntRect(x1, y1, x2 - x1, y2 - y1)); 93 94 // Don't paint if we are totally outside the clip region. 95 if (layerRect.isEmpty()) 96 return 0; 97 98 m_layerImage = getScratchBuffer(layerRect.size()); 99 m_layerContext = cairo_create(m_layerImage); 100 101 // Always clear the surface first. 102 cairo_set_operator(m_layerContext, CAIRO_OPERATOR_CLEAR); 103 cairo_paint(m_layerContext); 104 cairo_set_operator(m_layerContext, CAIRO_OPERATOR_OVER); 105 106 cairo_translate(m_layerContext, m_layerContextTranslation.x(), m_layerContextTranslation.y()); 107 return m_layerContext; 108} 109 110void ContextShadow::endShadowLayer(GraphicsContext* context) 111{ 112 cairo_destroy(m_layerContext); 113 m_layerContext = 0; 114 115 if (m_type == BlurShadow) { 116 cairo_surface_flush(m_layerImage); 117 blurLayerImage(cairo_image_surface_get_data(m_layerImage), 118 IntSize(cairo_image_surface_get_width(m_layerImage), cairo_image_surface_get_height(m_layerImage)), 119 cairo_image_surface_get_stride(m_layerImage)); 120 cairo_surface_mark_dirty(m_layerImage); 121 } 122 123 cairo_t* cr = context->platformContext()->cr(); 124 cairo_save(cr); 125 setSourceRGBAFromColor(cr, m_color); 126 cairo_mask_surface(cr, m_layerImage, m_layerOrigin.x(), m_layerOrigin.y()); 127 cairo_restore(cr); 128 129 // Schedule a purge of the scratch buffer. We do not need to destroy the surface. 130 scheduleScratchBufferPurge(); 131} 132 133void ContextShadow::drawRectShadowWithoutTiling(GraphicsContext* context, const IntRect& shadowRect, const IntSize& topLeftRadius, const IntSize& topRightRadius, const IntSize& bottomLeftRadius, const IntSize& bottomRightRadius, float alpha) 134{ 135 beginShadowLayer(context, shadowRect); 136 137 if (!m_layerContext) 138 return; 139 140 Path path; 141 path.addRoundedRect(shadowRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); 142 143 appendWebCorePathToCairoContext(m_layerContext, path); 144 cairo_set_source_rgba(m_layerContext, 0, 0, 0, alpha); 145 cairo_fill(m_layerContext); 146 147 endShadowLayer(context); 148} 149 150static inline FloatPoint getPhase(const FloatRect& dest, const FloatRect& tile) 151{ 152 FloatPoint phase = dest.location(); 153 phase.move(-tile.x(), -tile.y()); 154 155 return phase; 156} 157 158/* 159 This function uses tiling to improve the performance of the shadow 160 drawing of rounded rectangles. The code basically does the following 161 steps: 162 163 1. Calculate the size of the shadow template, a rectangle that 164 contains all the necessary tiles to draw the complete shadow. 165 166 2. If that size is smaller than the real rectangle render the new 167 template rectangle and its shadow in a new surface, in other case 168 render the shadow of the real rectangle in the destination 169 surface. 170 171 3. Calculate the sizes and positions of the tiles and their 172 destinations and use drawPattern to render the final shadow. The 173 code divides the rendering in 8 tiles: 174 175 1 | 2 | 3 176 ----------- 177 4 | | 5 178 ----------- 179 6 | 7 | 8 180 181 The corners are directly copied from the template rectangle to the 182 real one and the side tiles are 1 pixel width, we use them as 183 184 tiles to cover the destination side. The corner tiles are bigger 185 than just the side of the rounded corner, we need to increase it 186 because the modifications caused by the corner over the blur 187 effect. We fill the central part with solid color to complete the 188 shadow. 189 */ 190void ContextShadow::drawRectShadow(GraphicsContext* context, const IntRect& rect, const IntSize& topLeftRadius, const IntSize& topRightRadius, const IntSize& bottomLeftRadius, const IntSize& bottomRightRadius) 191{ 192 193 float radiusTwice = m_blurDistance * 2; 194 195 // Find the space the corners need inside the rect for its shadows. 196 int internalShadowWidth = radiusTwice + max(topLeftRadius.width(), bottomLeftRadius.width()) + 197 max(topRightRadius.width(), bottomRightRadius.width()); 198 int internalShadowHeight = radiusTwice + max(topLeftRadius.height(), topRightRadius.height()) + 199 max(bottomLeftRadius.height(), bottomRightRadius.height()); 200 201 cairo_t* cr = context->platformContext()->cr(); 202 203 // drawShadowedRect still does not work with rotations. 204 // https://bugs.webkit.org/show_bug.cgi?id=45042 205 if ((!context->getCTM().isIdentityOrTranslationOrFlipped()) || (internalShadowWidth > rect.width()) 206 || (internalShadowHeight > rect.height()) || (m_type != BlurShadow)) { 207 drawRectShadowWithoutTiling(context, rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius, context->getAlpha()); 208 return; 209 } 210 211 // Calculate size of the template shadow buffer. 212 IntSize shadowBufferSize = IntSize(rect.width() + radiusTwice, rect.height() + radiusTwice); 213 214 // Determine dimensions of shadow rect. 215 FloatRect shadowRect = FloatRect(rect.location(), shadowBufferSize); 216 shadowRect.move(- m_blurDistance, - m_blurDistance); 217 218 // Size of the tiling side. 219 int sideTileWidth = 1; 220 221 // The length of a side of the buffer is the enough space for four blur radii, 222 // the radii of the corners, and then 1 pixel to draw the side tiles. 223 IntSize shadowTemplateSize = IntSize(sideTileWidth + radiusTwice + internalShadowWidth, 224 sideTileWidth + radiusTwice + internalShadowHeight); 225 226 // Reduce the size of what we have to draw with the clip area. 227 double x1, x2, y1, y2; 228 cairo_clip_extents(cr, &x1, &y1, &x2, &y2); 229 calculateLayerBoundingRect(context, shadowRect, IntRect(x1, y1, x2 - x1, y2 - y1)); 230 231 if ((shadowTemplateSize.width() * shadowTemplateSize.height() > m_sourceRect.width() * m_sourceRect.height())) { 232 drawRectShadowWithoutTiling(context, rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius, context->getAlpha()); 233 return; 234 } 235 236 shadowRect.move(m_offset.width(), m_offset.height()); 237 238 m_layerImage = getScratchBuffer(shadowTemplateSize); 239 240 // Draw shadow into a new ImageBuffer. 241 m_layerContext = cairo_create(m_layerImage); 242 243 // Clear the surface first. 244 cairo_set_operator(m_layerContext, CAIRO_OPERATOR_CLEAR); 245 cairo_paint(m_layerContext); 246 cairo_set_operator(m_layerContext, CAIRO_OPERATOR_OVER); 247 248 // Draw the rectangle. 249 IntRect templateRect = IntRect(m_blurDistance, m_blurDistance, shadowTemplateSize.width() - radiusTwice, shadowTemplateSize.height() - radiusTwice); 250 Path path; 251 path.addRoundedRect(templateRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); 252 appendWebCorePathToCairoContext(m_layerContext, path); 253 254 cairo_set_source_rgba(m_layerContext, 0, 0, 0, context->getAlpha()); 255 cairo_fill(m_layerContext); 256 257 // Blur the image. 258 cairo_surface_flush(m_layerImage); 259 blurLayerImage(cairo_image_surface_get_data(m_layerImage), shadowTemplateSize, cairo_image_surface_get_stride(m_layerImage)); 260 cairo_surface_mark_dirty(m_layerImage); 261 262 // Mask the image with the shadow color. 263 cairo_set_operator(m_layerContext, CAIRO_OPERATOR_IN); 264 setSourceRGBAFromColor(m_layerContext, m_color); 265 cairo_paint(m_layerContext); 266 267 cairo_destroy(m_layerContext); 268 m_layerContext = 0; 269 270 // Fill the internal part of the shadow. 271 shadowRect.inflate(-radiusTwice); 272 if (!shadowRect.isEmpty()) { 273 cairo_save(cr); 274 path.clear(); 275 path.addRoundedRect(shadowRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); 276 appendWebCorePathToCairoContext(cr, path); 277 setSourceRGBAFromColor(cr, m_color); 278 cairo_fill(cr); 279 cairo_restore(cr); 280 } 281 shadowRect.inflate(radiusTwice); 282 283 // Draw top side. 284 FloatRect tileRect = FloatRect(radiusTwice + topLeftRadius.width(), 0, sideTileWidth, radiusTwice); 285 FloatRect destRect = tileRect; 286 destRect.move(shadowRect.x(), shadowRect.y()); 287 destRect.setWidth(shadowRect.width() - topLeftRadius.width() - topRightRadius.width() - m_blurDistance * 4); 288 FloatPoint phase = getPhase(destRect, tileRect); 289 AffineTransform patternTransform; 290 patternTransform.makeIdentity(); 291 drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect); 292 293 // Draw the bottom side. 294 tileRect = FloatRect(radiusTwice + bottomLeftRadius.width(), shadowTemplateSize.height() - radiusTwice, sideTileWidth, radiusTwice); 295 destRect = tileRect; 296 destRect.move(shadowRect.x(), shadowRect.y() + radiusTwice + rect.height() - shadowTemplateSize.height()); 297 destRect.setWidth(shadowRect.width() - bottomLeftRadius.width() - bottomRightRadius.width() - m_blurDistance * 4); 298 phase = getPhase(destRect, tileRect); 299 drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect); 300 301 // Draw the right side. 302 tileRect = FloatRect(shadowTemplateSize.width() - radiusTwice, radiusTwice + topRightRadius.height(), radiusTwice, sideTileWidth); 303 destRect = tileRect; 304 destRect.move(shadowRect.x() + radiusTwice + rect.width() - shadowTemplateSize.width(), shadowRect.y()); 305 destRect.setHeight(shadowRect.height() - topRightRadius.height() - bottomRightRadius.height() - m_blurDistance * 4); 306 phase = getPhase(destRect, tileRect); 307 drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect); 308 309 // Draw the left side. 310 tileRect = FloatRect(0, radiusTwice + topLeftRadius.height(), radiusTwice, sideTileWidth); 311 destRect = tileRect; 312 destRect.move(shadowRect.x(), shadowRect.y()); 313 destRect.setHeight(shadowRect.height() - topLeftRadius.height() - bottomLeftRadius.height() - m_blurDistance * 4); 314 phase = FloatPoint(destRect.x() - tileRect.x(), destRect.y() - tileRect.y()); 315 drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect); 316 317 // Draw the top left corner. 318 tileRect = FloatRect(0, 0, radiusTwice + topLeftRadius.width(), radiusTwice + topLeftRadius.height()); 319 destRect = tileRect; 320 destRect.move(shadowRect.x(), shadowRect.y()); 321 phase = getPhase(destRect, tileRect); 322 drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect); 323 324 // Draw the top right corner. 325 tileRect = FloatRect(shadowTemplateSize.width() - radiusTwice - topRightRadius.width(), 0, radiusTwice + topRightRadius.width(), 326 radiusTwice + topRightRadius.height()); 327 destRect = tileRect; 328 destRect.move(shadowRect.x() + rect.width() - shadowTemplateSize.width() + radiusTwice, shadowRect.y()); 329 phase = getPhase(destRect, tileRect); 330 drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect); 331 332 // Draw the bottom right corner. 333 tileRect = FloatRect(shadowTemplateSize.width() - radiusTwice - bottomRightRadius.width(), 334 shadowTemplateSize.height() - radiusTwice - bottomRightRadius.height(), 335 radiusTwice + bottomRightRadius.width(), radiusTwice + bottomRightRadius.height()); 336 destRect = tileRect; 337 destRect.move(shadowRect.x() + rect.width() - shadowTemplateSize.width() + radiusTwice, 338 shadowRect.y() + rect.height() - shadowTemplateSize.height() + radiusTwice); 339 phase = getPhase(destRect, tileRect); 340 drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect); 341 342 // Draw the bottom left corner. 343 tileRect = FloatRect(0, shadowTemplateSize.height() - radiusTwice - bottomLeftRadius.height(), 344 radiusTwice + bottomLeftRadius.width(), radiusTwice + bottomLeftRadius.height()); 345 destRect = tileRect; 346 destRect.move(shadowRect.x(), shadowRect.y() + rect.height() - shadowTemplateSize.height() + radiusTwice); 347 phase = getPhase(destRect, tileRect); 348 drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect); 349 350 // Schedule a purge of the scratch buffer. 351 scheduleScratchBufferPurge(); 352} 353 354} 355