• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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