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
41 using WTF::max;
42
43 namespace WebCore {
44
45 static RefPtr<cairo_surface_t> gScratchBuffer;
purgeScratchBuffer()46 static 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.
54 class PurgeScratchBufferTimer : public TimerBase {
55 private:
fired()56 virtual void fired() { purgeScratchBuffer(); }
57 };
58 static PurgeScratchBufferTimer purgeScratchBufferTimer;
scheduleScratchBufferPurge()59 static void scheduleScratchBufferPurge()
60 {
61 if (purgeScratchBufferTimer.isActive())
62 purgeScratchBufferTimer.stop();
63 purgeScratchBufferTimer.startOneShot(2);
64 }
65
getScratchBuffer(const IntSize & size)66 static 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
beginShadowLayer(GraphicsContext * context,const FloatRect & layerArea)86 PlatformContext 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
endShadowLayer(GraphicsContext * context)110 void 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
drawRectShadowWithoutTiling(GraphicsContext * context,const IntRect & shadowRect,const IntSize & topLeftRadius,const IntSize & topRightRadius,const IntSize & bottomLeftRadius,const IntSize & bottomRightRadius,float alpha)133 void 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
getPhase(const FloatRect & dest,const FloatRect & tile)150 static 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 */
drawRectShadow(GraphicsContext * context,const IntRect & rect,const IntSize & topLeftRadius,const IntSize & topRightRadius,const IntSize & bottomLeftRadius,const IntSize & bottomRightRadius)190 void 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