a0e7ff873bcb35c3059bda6cd3f43e37016f9c7e
[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 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 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 #if USE(CAIRO)
32
33 #include "CairoUtilities.h"
34 #include "Gradient.h"
35 #include "GraphicsContext.h"
36 #include "Pattern.h"
37 #include <cairo.h>
38
39 namespace WebCore {
40
41 // In Cairo image masking is immediate, so to emulate image clipping we must save masking
42 // details as part of the context state and apply them during platform restore.
43 class ImageMaskInformation {
44 public:
45     void update(cairo_surface_t* maskSurface, const FloatRect& maskRect)
46     {
47         m_maskSurface = maskSurface;
48         m_maskRect = maskRect;
49     }
50
51     bool isValid() const { return m_maskSurface; }
52     cairo_surface_t* maskSurface() const { return m_maskSurface.get(); }
53     const FloatRect& maskRect() const { return m_maskRect; }
54
55 private:
56     RefPtr<cairo_surface_t> m_maskSurface;
57     FloatRect m_maskRect;
58 };
59
60
61 // Encapsulates the additional painting state information we store for each
62 // pushed graphics state.
63 class PlatformContextCairo::State {
64 public:
65     State()
66         : m_globalAlpha(1)
67         , m_imageInterpolationQuality(InterpolationDefault)
68     {
69     }
70
71     State(const State& state)
72         : m_globalAlpha(state.m_globalAlpha)
73         , m_imageInterpolationQuality(state.m_imageInterpolationQuality)
74     {
75         // We do not copy m_imageMaskInformation because otherwise it would be applied
76         // more than once during subsequent calls to restore().
77     }
78
79     ImageMaskInformation m_imageMaskInformation;
80     float m_globalAlpha;
81     InterpolationQuality m_imageInterpolationQuality;
82 };
83
84 PlatformContextCairo::PlatformContextCairo(cairo_t* cr)
85     : m_cr(cr)
86 {
87     m_stateStack.append(State());
88     m_state = &m_stateStack.last();
89 }
90
91 void PlatformContextCairo::restore()
92 {
93     const ImageMaskInformation& maskInformation = m_state->m_imageMaskInformation;
94     if (maskInformation.isValid()) {
95         const FloatRect& maskRect = maskInformation.maskRect();
96         cairo_pop_group_to_source(m_cr.get());
97         cairo_mask_surface(m_cr.get(), maskInformation.maskSurface(), maskRect.x(), maskRect.y());
98     }
99
100     m_stateStack.removeLast();
101     ASSERT(!m_stateStack.isEmpty());
102     m_state = &m_stateStack.last();
103
104     cairo_restore(m_cr.get());
105 }
106
107 PlatformContextCairo::~PlatformContextCairo()
108 {
109 }
110
111 void PlatformContextCairo::save()
112 {
113     m_stateStack.append(State(*m_state));
114     m_state = &m_stateStack.last();
115
116     cairo_save(m_cr.get());
117 }
118
119 void PlatformContextCairo::pushImageMask(cairo_surface_t* surface, const FloatRect& rect)
120 {
121     // We must call savePlatformState at least once before we can use image masking,
122     // since we actually apply the mask in restorePlatformState.
123     ASSERT(!m_stateStack.isEmpty());
124     m_state->m_imageMaskInformation.update(surface, rect);
125
126     // Cairo doesn't support the notion of an image clip, so we push a group here
127     // and then paint it to the surface with an image mask (which is an immediate
128     // operation) during restorePlatformState.
129
130     // We want to allow the clipped elements to composite with the surface as it
131     // is now, but they are isolated in another group. To make this work, we're
132     // going to blit the current surface contents onto the new group once we push it.
133     cairo_surface_t* currentTarget = cairo_get_target(m_cr.get());
134     cairo_surface_flush(currentTarget);
135
136     // Pushing a new group ensures that only things painted after this point are clipped.
137     cairo_push_group(m_cr.get());
138     cairo_set_operator(m_cr.get(), CAIRO_OPERATOR_SOURCE);
139
140     cairo_set_source_surface(m_cr.get(), currentTarget, 0, 0);
141     cairo_rectangle(m_cr.get(), rect.x(), rect.y(), rect.width(), rect.height());
142     cairo_fill(m_cr.get());
143 }
144
145 static void drawPatternToCairoContext(cairo_t* cr, cairo_pattern_t* pattern, const FloatRect& destRect, float alpha)
146 {
147     cairo_translate(cr, destRect.x(), destRect.y());
148     cairo_set_source(cr, pattern);
149     cairo_rectangle(cr, 0, 0, destRect.width(), destRect.height());
150
151     if (alpha < 1) {
152         cairo_clip(cr);
153         cairo_paint_with_alpha(cr, alpha);
154     } else
155         cairo_fill(cr);
156 }
157
158 void PlatformContextCairo::drawSurfaceToContext(cairo_surface_t* surface, const FloatRect& destRect, const FloatRect& originalSrcRect, GraphicsContext& context)
159 {
160     // Avoid invalid cairo matrix with small values.
161     if (std::fabs(destRect.width()) < 0.5f || std::fabs(destRect.height()) < 0.5f)
162         return;
163
164     FloatRect srcRect = originalSrcRect;
165
166     // We need to account for negative source dimensions by flipping the rectangle.
167     if (originalSrcRect.width() < 0) {
168         srcRect.setX(originalSrcRect.x() + originalSrcRect.width());
169         srcRect.setWidth(std::fabs(originalSrcRect.width()));
170     }
171     if (originalSrcRect.height() < 0) {
172         srcRect.setY(originalSrcRect.y() + originalSrcRect.height());
173         srcRect.setHeight(std::fabs(originalSrcRect.height()));
174     }
175
176     RefPtr<cairo_surface_t> patternSurface = surface;
177     float leftPadding = 0;
178     float topPadding = 0;
179     if (srcRect.x() || srcRect.y() || srcRect.size() != cairoSurfaceSize(surface)) {
180         // Cairo subsurfaces don't support floating point boundaries well, so we expand the rectangle.
181         IntRect expandedSrcRect(enclosingIntRect(srcRect));
182
183         // We use a subsurface here so that we don't end up sampling outside the originalSrcRect rectangle.
184         // See https://bugs.webkit.org/show_bug.cgi?id=58309
185         patternSurface = adoptRef(cairo_surface_create_for_rectangle(surface, expandedSrcRect.x(),
186             expandedSrcRect.y(), expandedSrcRect.width(), expandedSrcRect.height()));
187
188         leftPadding = static_cast<float>(expandedSrcRect.x()) - floorf(srcRect.x());
189         topPadding = static_cast<float>(expandedSrcRect.y()) - floorf(srcRect.y());
190     }
191
192     RefPtr<cairo_pattern_t> pattern = adoptRef(cairo_pattern_create_for_surface(patternSurface.get()));
193
194     ASSERT(m_state);
195     switch (m_state->m_imageInterpolationQuality) {
196     case InterpolationNone:
197     case InterpolationLow:
198         cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_FAST);
199         break;
200     case InterpolationMedium:
201     case InterpolationDefault:
202         cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_GOOD);
203         break;
204     case InterpolationHigh:
205         cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_BEST);
206         break;
207     }
208     cairo_pattern_set_extend(pattern.get(), CAIRO_EXTEND_PAD);
209
210     // The pattern transformation properly scales the pattern for when the source rectangle is a
211     // different size than the destination rectangle. We also account for any offset we introduced
212     // by expanding floating point source rectangle sizes. It's important to take the absolute value
213     // of the scale since the original width and height might be negative.
214     float scaleX = std::fabs(srcRect.width() / destRect.width());
215     float scaleY = std::fabs(srcRect.height() / destRect.height());
216     cairo_matrix_t matrix = { scaleX, 0, 0, scaleY, leftPadding, topPadding };
217     cairo_pattern_set_matrix(pattern.get(), &matrix);
218
219     ShadowBlur& shadow = context.platformContext()->shadowBlur();
220     if (shadow.type() != ShadowBlur::NoShadow) {
221         if (GraphicsContext* shadowContext = shadow.beginShadowLayer(context, destRect)) {
222             drawPatternToCairoContext(shadowContext->platformContext()->cr(), pattern.get(), destRect, 1);
223             shadow.endShadowLayer(context);
224         }
225     }
226
227     cairo_save(m_cr.get());
228     drawPatternToCairoContext(m_cr.get(), pattern.get(), destRect, globalAlpha());
229     cairo_restore(m_cr.get());
230 }
231
232 void PlatformContextCairo::setImageInterpolationQuality(InterpolationQuality quality)
233 {
234     ASSERT(m_state);
235     m_state->m_imageInterpolationQuality = quality;
236 }
237
238 InterpolationQuality PlatformContextCairo::imageInterpolationQuality() const
239 {
240     ASSERT(m_state);
241     return m_state->m_imageInterpolationQuality;
242 }
243
244
245 float PlatformContextCairo::globalAlpha() const
246 {
247     return m_state->m_globalAlpha;
248 }
249
250 void PlatformContextCairo::setGlobalAlpha(float globalAlpha)
251 {
252     m_state->m_globalAlpha = globalAlpha;
253 }
254
255 static inline void reduceSourceByAlpha(cairo_t* cr, float alpha)
256 {
257     if (alpha >= 1)
258         return;
259     cairo_push_group(cr);
260     cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
261     cairo_paint_with_alpha(cr, alpha);
262     cairo_pop_group_to_source(cr);
263 }
264
265 static void prepareCairoContextSource(cairo_t* cr, Pattern* pattern, Gradient* gradient, const Color& color, float globalAlpha)
266 {
267     if (pattern) {
268         RefPtr<cairo_pattern_t> cairoPattern(adoptRef(pattern->createPlatformPattern(AffineTransform())));
269         cairo_set_source(cr, cairoPattern.get());
270         reduceSourceByAlpha(cr, globalAlpha);
271     } else if (gradient)
272         cairo_set_source(cr, gradient->platformGradient(globalAlpha));
273     else { // Solid color source.
274         if (globalAlpha < 1)
275             setSourceRGBAFromColor(cr, colorWithOverrideAlpha(color.rgb(), color.alpha() / 255.f * globalAlpha));
276         else
277             setSourceRGBAFromColor(cr, color);
278     }
279 }
280
281 void PlatformContextCairo::prepareForFilling(const GraphicsContextState& state, PatternAdjustment patternAdjustment)
282 {
283     cairo_set_fill_rule(m_cr.get(), state.fillRule == RULE_EVENODD ?  CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
284     prepareCairoContextSource(m_cr.get(),
285                               state.fillPattern.get(),
286                               state.fillGradient.get(),
287                               state.fillColor,
288                               patternAdjustment == AdjustPatternForGlobalAlpha ? globalAlpha() : 1);
289
290     if (state.fillPattern)
291         clipForPatternFilling(state);
292 }
293
294 void PlatformContextCairo::prepareForStroking(const GraphicsContextState& state, AlphaPreservation alphaPreservation)
295 {
296     prepareCairoContextSource(m_cr.get(),
297                               state.strokePattern.get(),
298                               state.strokeGradient.get(),
299                               state.strokeColor,
300                               alphaPreservation == PreserveAlpha ? globalAlpha() : 1);
301 }
302
303 void PlatformContextCairo::clipForPatternFilling(const GraphicsContextState& state)
304 {
305     ASSERT(state.fillPattern);
306
307     // Hold current cairo path in a variable for restoring it after configuring the pattern clip rectangle.
308     auto currentPath = cairo_copy_path(m_cr.get());
309     cairo_new_path(m_cr.get());
310
311     // Initialize clipping extent from current cairo clip extents, then shrink if needed according to pattern.
312     // Inspired by GraphicsContextQt::drawRepeatPattern.
313     double x1, y1, x2, y2;
314     cairo_clip_extents(m_cr.get(), &x1, &y1, &x2, &y2);
315     FloatRect clipRect(x1, y1, x2 - x1, y2 - y1);
316
317     Image* patternImage = state.fillPattern->tileImage();
318     ASSERT(patternImage);
319     const AffineTransform& patternTransform = state.fillPattern->getPatternSpaceTransform();
320     FloatRect patternRect = patternTransform.mapRect(FloatRect(0, 0, patternImage->width(), patternImage->height()));
321
322     bool repeatX = state.fillPattern->repeatX();
323     bool repeatY = state.fillPattern->repeatY();
324
325     if (!repeatX) {
326         clipRect.setX(patternRect.x());
327         clipRect.setWidth(patternRect.width());
328     }
329     if (!repeatY) {
330         clipRect.setY(patternRect.y());
331         clipRect.setHeight(patternRect.height());
332     }
333     if (!repeatX || !repeatY) {
334         cairo_rectangle(m_cr.get(), clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());
335         cairo_clip(m_cr.get());
336     }
337
338     // Restoring cairo path.
339     cairo_append_path(m_cr.get(), currentPath);
340     cairo_path_destroy(currentPath);
341 }
342
343 } // namespace WebCore
344
345 #endif // USE(CAIRO)