[Cairo] Canvas-shadow behavior is not being as expected
[WebKit-https.git] / Source / WebCore / platform / graphics / cairo / PlatformContextCairo.cpp
1 /*
2  * Copyright (C) 2011 Igalia S.L.
3  * Copyright (c) 2008, Google Inc. All rights reserved.
4  * Copyright (c) 2012, Intel Corporation
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
26  */
27
28 #include "config.h"
29 #include "PlatformContextCairo.h"
30
31 #include "CairoUtilities.h"
32 #include "Gradient.h"
33 #include "GraphicsContext.h"
34 #include "OwnPtrCairo.h"
35 #include "Pattern.h"
36 #include <cairo.h>
37
38 namespace WebCore {
39
40 // In Cairo image masking is immediate, so to emulate image clipping we must save masking
41 // details as part of the context state and apply them during platform restore.
42 class ImageMaskInformation {
43 public:
44     void update(cairo_surface_t* maskSurface, const FloatRect& maskRect)
45     {
46         m_maskSurface = maskSurface;
47         m_maskRect = maskRect;
48     }
49
50     bool isValid() const { return m_maskSurface; }
51     cairo_surface_t* maskSurface() const { return m_maskSurface.get(); }
52     const FloatRect& maskRect() const { return m_maskRect; }
53
54 private:
55     RefPtr<cairo_surface_t> m_maskSurface;
56     FloatRect m_maskRect;
57 };
58
59
60 // Encapsulates the additional painting state information we store for each
61 // pushed graphics state.
62 class PlatformContextCairo::State {
63 public:
64     State()
65         : m_globalAlpha(1)
66         , m_imageInterpolationQuality(InterpolationDefault)
67     {
68     }
69
70     State(const State& state)
71         : m_globalAlpha(state.m_globalAlpha)
72         , m_imageInterpolationQuality(state.m_imageInterpolationQuality)
73     {
74         // We do not copy m_imageMaskInformation because otherwise it would be applied
75         // more than once during subsequent calls to restore().
76     }
77
78     ImageMaskInformation m_imageMaskInformation;
79     float m_globalAlpha;
80     InterpolationQuality m_imageInterpolationQuality;
81 };
82
83 PlatformContextCairo::PlatformContextCairo(cairo_t* cr)
84     : m_cr(cr)
85 {
86     m_stateStack.append(State());
87     m_state = &m_stateStack.last();
88 }
89
90 void PlatformContextCairo::restore()
91 {
92     const ImageMaskInformation& maskInformation = m_state->m_imageMaskInformation;
93     if (maskInformation.isValid()) {
94         const FloatRect& maskRect = maskInformation.maskRect();
95         cairo_pop_group_to_source(m_cr.get());
96         cairo_mask_surface(m_cr.get(), maskInformation.maskSurface(), maskRect.x(), maskRect.y());
97     }
98
99     m_stateStack.removeLast();
100     ASSERT(!m_stateStack.isEmpty());
101     m_state = &m_stateStack.last();
102
103     cairo_restore(m_cr.get());
104 }
105
106 PlatformContextCairo::~PlatformContextCairo()
107 {
108 }
109
110 void PlatformContextCairo::save()
111 {
112     m_stateStack.append(State(*m_state));
113     m_state = &m_stateStack.last();
114
115     cairo_save(m_cr.get());
116 }
117
118 void PlatformContextCairo::pushImageMask(cairo_surface_t* surface, const FloatRect& rect)
119 {
120     // We must call savePlatformState at least once before we can use image masking,
121     // since we actually apply the mask in restorePlatformState.
122     ASSERT(!m_stateStack.isEmpty());
123     m_state->m_imageMaskInformation.update(surface, rect);
124
125     // Cairo doesn't support the notion of an image clip, so we push a group here
126     // and then paint it to the surface with an image mask (which is an immediate
127     // operation) during restorePlatformState.
128
129     // We want to allow the clipped elements to composite with the surface as it
130     // is now, but they are isolated in another group. To make this work, we're
131     // going to blit the current surface contents onto the new group once we push it.
132     cairo_surface_t* currentTarget = cairo_get_target(m_cr.get());
133     cairo_surface_flush(currentTarget);
134
135     // Pushing a new group ensures that only things painted after this point are clipped.
136     cairo_push_group(m_cr.get());
137     cairo_set_operator(m_cr.get(), CAIRO_OPERATOR_SOURCE);
138
139     cairo_set_source_surface(m_cr.get(), currentTarget, 0, 0);
140     cairo_rectangle(m_cr.get(), rect.x(), rect.y(), rect.width(), rect.height());
141     cairo_fill(m_cr.get());
142 }
143
144 static void drawPatternToCairoContext(cairo_t* cr, cairo_pattern_t* pattern, const FloatRect& destRect, float alpha)
145 {
146     cairo_translate(cr, destRect.x(), destRect.y());
147     cairo_set_source(cr, pattern);
148     cairo_rectangle(cr, 0, 0, destRect.width(), destRect.height());
149
150     if (alpha < 1) {
151         cairo_clip(cr);
152         cairo_paint_with_alpha(cr, alpha);
153     } else
154         cairo_fill(cr);
155 }
156
157 void PlatformContextCairo::drawSurfaceToContext(cairo_surface_t* surface, const FloatRect& destRect, const FloatRect& originalSrcRect, GraphicsContext* context)
158 {
159     FloatRect srcRect = originalSrcRect;
160
161     // We need to account for negative source dimensions by flipping the rectangle.
162     if (originalSrcRect.width() < 0) {
163         srcRect.setX(originalSrcRect.x() + originalSrcRect.width());
164         srcRect.setWidth(std::fabs(originalSrcRect.width()));
165     }
166     if (originalSrcRect.height() < 0) {
167         srcRect.setY(originalSrcRect.y() + originalSrcRect.height());
168         srcRect.setHeight(std::fabs(originalSrcRect.height()));
169     }
170
171     // Cairo subsurfaces don't support floating point boundaries well, so we expand the rectangle.
172     IntRect expandedSrcRect(enclosingIntRect(srcRect));
173
174     // We use a subsurface here so that we don't end up sampling outside the originalSrcRect rectangle.
175     // See https://bugs.webkit.org/show_bug.cgi?id=58309
176     RefPtr<cairo_surface_t> subsurface = adoptRef(cairo_surface_create_for_rectangle(
177         surface, expandedSrcRect.x(), expandedSrcRect.y(), expandedSrcRect.width(), expandedSrcRect.height()));
178     RefPtr<cairo_pattern_t> pattern = adoptRef(cairo_pattern_create_for_surface(subsurface.get()));
179
180     ASSERT(m_state);
181     switch (m_state->m_imageInterpolationQuality) {
182     case InterpolationNone:
183     case InterpolationLow:
184         cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_FAST);
185         break;
186     case InterpolationMedium:
187     case InterpolationHigh:
188         cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_BILINEAR);
189         break;
190     case InterpolationDefault:
191         cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_BILINEAR);
192         break;
193     }
194     cairo_pattern_set_extend(pattern.get(), CAIRO_EXTEND_NONE);
195
196     // The pattern transformation properly scales the pattern for when the source rectangle is a
197     // different size than the destination rectangle. We also account for any offset we introduced
198     // by expanding floating point source rectangle sizes. It's important to take the absolute value
199     // of the scale since the original width and height might be negative.
200     float scaleX = std::fabs(srcRect.width() / destRect.width());
201     float scaleY = std::fabs(srcRect.height() / destRect.height());
202     float leftPadding = static_cast<float>(expandedSrcRect.x()) - floorf(srcRect.x());
203     float topPadding = static_cast<float>(expandedSrcRect.y()) - floorf(srcRect.y());
204     cairo_matrix_t matrix = { scaleX, 0, 0, scaleY, leftPadding, topPadding };
205     cairo_pattern_set_matrix(pattern.get(), &matrix);
206
207     ShadowBlur& shadow = context->platformContext()->shadowBlur();
208     if (shadow.type() != ShadowBlur::NoShadow) {
209         if (GraphicsContext* shadowContext = shadow.beginShadowLayer(context, destRect)) {
210             drawPatternToCairoContext(shadowContext->platformContext()->cr(), pattern.get(), destRect, 1);
211             shadow.endShadowLayer(context);
212         }
213     }
214
215     cairo_save(m_cr.get());
216     drawPatternToCairoContext(m_cr.get(), pattern.get(), destRect, globalAlpha());
217     cairo_restore(m_cr.get());
218 }
219
220 void PlatformContextCairo::setImageInterpolationQuality(InterpolationQuality quality)
221 {
222     ASSERT(m_state);
223     m_state->m_imageInterpolationQuality = quality;
224 }
225
226 InterpolationQuality PlatformContextCairo::imageInterpolationQuality() const
227 {
228     ASSERT(m_state);
229     return m_state->m_imageInterpolationQuality;
230 }
231
232
233 float PlatformContextCairo::globalAlpha() const
234 {
235     return m_state->m_globalAlpha;
236 }
237
238 void PlatformContextCairo::setGlobalAlpha(float globalAlpha)
239 {
240     m_state->m_globalAlpha = globalAlpha;
241 }
242
243 static inline void reduceSourceByAlpha(cairo_t* cr, float alpha)
244 {
245     if (alpha >= 1)
246         return;
247     cairo_push_group(cr);
248     cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
249     cairo_paint_with_alpha(cr, alpha);
250     cairo_pop_group_to_source(cr);
251 }
252
253 static void prepareCairoContextSource(cairo_t* cr, Pattern* pattern, Gradient* gradient, const Color& color, float globalAlpha)
254 {
255     if (pattern) {
256         RefPtr<cairo_pattern_t> cairoPattern(adoptRef(pattern->createPlatformPattern(AffineTransform())));
257         cairo_set_source(cr, cairoPattern.get());
258         reduceSourceByAlpha(cr, globalAlpha);
259     } else if (gradient)
260         cairo_set_source(cr, gradient->platformGradient(globalAlpha));
261     else { // Solid color source.
262         if (globalAlpha < 1)
263             setSourceRGBAFromColor(cr, colorWithOverrideAlpha(color.rgb(), color.alpha() / 255.f * globalAlpha));
264         else
265             setSourceRGBAFromColor(cr, color);
266     }
267 }
268
269 void PlatformContextCairo::prepareForFilling(const GraphicsContextState& state, PatternAdjustment patternAdjustment)
270 {
271     cairo_set_fill_rule(m_cr.get(), state.fillRule == RULE_EVENODD ?  CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
272     prepareCairoContextSource(m_cr.get(),
273                               state.fillPattern.get(),
274                               state.fillGradient.get(),
275                               state.fillColor,
276                               patternAdjustment == AdjustPatternForGlobalAlpha ? globalAlpha() : 1);
277
278     if (state.fillPattern)
279         clipForPatternFilling(state);
280 }
281
282 void PlatformContextCairo::prepareForStroking(const GraphicsContextState& state, AlphaPreservation alphaPreservation)
283 {
284     prepareCairoContextSource(m_cr.get(),
285                               state.strokePattern.get(),
286                               state.strokeGradient.get(),
287                               state.strokeColor,
288                               alphaPreservation == PreserveAlpha ? globalAlpha() : 1);
289 }
290
291 void PlatformContextCairo::clipForPatternFilling(const GraphicsContextState& state)
292 {
293     ASSERT(state.fillPattern);
294
295     // Hold current cairo path in a variable for restoring it after configuring the pattern clip rectangle.
296     OwnPtr<cairo_path_t> currentPath = adoptPtr(cairo_copy_path(m_cr.get()));
297     cairo_new_path(m_cr.get());
298
299     // Initialize clipping extent from current cairo clip extents, then shrink if needed according to pattern.
300     // Inspired by GraphicsContextQt::drawRepeatPattern.
301     double x1, y1, x2, y2;
302     cairo_clip_extents(m_cr.get(), &x1, &y1, &x2, &y2);
303     FloatRect clipRect(x1, y1, x2 - x1, y2 - y1);
304
305     Image* patternImage = state.fillPattern->tileImage();
306     ASSERT(patternImage);
307     const AffineTransform& patternTransform = state.fillPattern->getPatternSpaceTransform();
308     FloatRect patternRect = patternTransform.mapRect(FloatRect(0, 0, patternImage->width(), patternImage->height()));
309
310     bool repeatX = state.fillPattern->repeatX();
311     bool repeatY = state.fillPattern->repeatY();
312
313     if (!repeatX) {
314         clipRect.setX(patternRect.x());
315         clipRect.setWidth(patternRect.width());
316     }
317     if (!repeatY) {
318         clipRect.setY(patternRect.y());
319         clipRect.setHeight(patternRect.height());
320     }
321     if (!repeatX || !repeatY) {
322         cairo_rectangle(m_cr.get(), clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());
323         cairo_clip(m_cr.get());
324     }
325
326     // Restoring cairo path.
327     cairo_append_path(m_cr.get(), currentPath.get());
328 }
329
330 } // namespace WebCore