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