1 | /* |
2 | * Copyright (C) 2010 Igalia S.L. |
3 | * Copyright (C) 2011 ProFUSION embedded systems |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
8 | * 1. Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * 2. Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * |
14 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
15 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
17 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
18 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
19 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
20 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
21 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
22 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 | */ |
26 | |
27 | #include "config.h" |
28 | #include "CairoUtilities.h" |
29 | |
30 | #if USE(CAIRO) |
31 | |
32 | #include "AffineTransform.h" |
33 | #include "Color.h" |
34 | #include "FloatPoint.h" |
35 | #include "FloatRect.h" |
36 | #include "IntRect.h" |
37 | #include "Path.h" |
38 | #include "PlatformPathCairo.h" |
39 | #include "RefPtrCairo.h" |
40 | #include "Region.h" |
41 | #include <wtf/Assertions.h> |
42 | #include <wtf/NeverDestroyed.h> |
43 | #include <wtf/UniqueArray.h> |
44 | #include <wtf/Vector.h> |
45 | |
46 | #if ENABLE(ACCELERATED_2D_CANVAS) |
47 | #if USE(EGL) && USE(LIBEPOXY) |
48 | #include "EpoxyEGL.h" |
49 | #endif |
50 | #include <cairo-gl.h> |
51 | #endif |
52 | |
53 | #if OS(WINDOWS) |
54 | #include <cairo-win32.h> |
55 | #endif |
56 | |
57 | namespace WebCore { |
58 | |
59 | #if USE(FREETYPE) && !PLATFORM(GTK) |
60 | const cairo_font_options_t* getDefaultCairoFontOptions() |
61 | { |
62 | static NeverDestroyed<cairo_font_options_t*> options = cairo_font_options_create(); |
63 | return options; |
64 | } |
65 | #endif |
66 | |
67 | void copyContextProperties(cairo_t* srcCr, cairo_t* dstCr) |
68 | { |
69 | cairo_set_antialias(dstCr, cairo_get_antialias(srcCr)); |
70 | |
71 | size_t dashCount = cairo_get_dash_count(srcCr); |
72 | Vector<double> dashes(dashCount); |
73 | |
74 | double offset; |
75 | cairo_get_dash(srcCr, dashes.data(), &offset); |
76 | cairo_set_dash(dstCr, dashes.data(), dashCount, offset); |
77 | cairo_set_line_cap(dstCr, cairo_get_line_cap(srcCr)); |
78 | cairo_set_line_join(dstCr, cairo_get_line_join(srcCr)); |
79 | cairo_set_line_width(dstCr, cairo_get_line_width(srcCr)); |
80 | cairo_set_miter_limit(dstCr, cairo_get_miter_limit(srcCr)); |
81 | cairo_set_fill_rule(dstCr, cairo_get_fill_rule(srcCr)); |
82 | } |
83 | |
84 | void setSourceRGBAFromColor(cairo_t* context, const Color& color) |
85 | { |
86 | if (color.isExtended()) |
87 | cairo_set_source_rgba(context, color.asExtended().red(), color.asExtended().green(), color.asExtended().blue(), color.asExtended().alpha()); |
88 | else { |
89 | float red, green, blue, alpha; |
90 | color.getRGBA(red, green, blue, alpha); |
91 | cairo_set_source_rgba(context, red, green, blue, alpha); |
92 | } |
93 | } |
94 | |
95 | void appendPathToCairoContext(cairo_t* to, cairo_t* from) |
96 | { |
97 | auto cairoPath = cairo_copy_path(from); |
98 | cairo_append_path(to, cairoPath); |
99 | cairo_path_destroy(cairoPath); |
100 | } |
101 | |
102 | void setPathOnCairoContext(cairo_t* to, cairo_t* from) |
103 | { |
104 | cairo_new_path(to); |
105 | appendPathToCairoContext(to, from); |
106 | } |
107 | |
108 | void appendWebCorePathToCairoContext(cairo_t* context, const Path& path) |
109 | { |
110 | if (path.isEmpty()) |
111 | return; |
112 | appendPathToCairoContext(context, path.platformPath()->context()); |
113 | } |
114 | |
115 | void appendRegionToCairoContext(cairo_t* to, const cairo_region_t* region) |
116 | { |
117 | if (!region) |
118 | return; |
119 | |
120 | const int rectCount = cairo_region_num_rectangles(region); |
121 | for (int i = 0; i < rectCount; ++i) { |
122 | cairo_rectangle_int_t rect; |
123 | cairo_region_get_rectangle(region, i, &rect); |
124 | cairo_rectangle(to, rect.x, rect.y, rect.width, rect.height); |
125 | } |
126 | } |
127 | |
128 | static cairo_operator_t toCairoCompositeOperator(CompositeOperator op) |
129 | { |
130 | switch (op) { |
131 | case CompositeClear: |
132 | return CAIRO_OPERATOR_CLEAR; |
133 | case CompositeCopy: |
134 | return CAIRO_OPERATOR_SOURCE; |
135 | case CompositeSourceOver: |
136 | return CAIRO_OPERATOR_OVER; |
137 | case CompositeSourceIn: |
138 | return CAIRO_OPERATOR_IN; |
139 | case CompositeSourceOut: |
140 | return CAIRO_OPERATOR_OUT; |
141 | case CompositeSourceAtop: |
142 | return CAIRO_OPERATOR_ATOP; |
143 | case CompositeDestinationOver: |
144 | return CAIRO_OPERATOR_DEST_OVER; |
145 | case CompositeDestinationIn: |
146 | return CAIRO_OPERATOR_DEST_IN; |
147 | case CompositeDestinationOut: |
148 | return CAIRO_OPERATOR_DEST_OUT; |
149 | case CompositeDestinationAtop: |
150 | return CAIRO_OPERATOR_DEST_ATOP; |
151 | case CompositeXOR: |
152 | return CAIRO_OPERATOR_XOR; |
153 | case CompositePlusDarker: |
154 | return CAIRO_OPERATOR_DARKEN; |
155 | case CompositePlusLighter: |
156 | return CAIRO_OPERATOR_ADD; |
157 | case CompositeDifference: |
158 | return CAIRO_OPERATOR_DIFFERENCE; |
159 | default: |
160 | return CAIRO_OPERATOR_SOURCE; |
161 | } |
162 | } |
163 | |
164 | cairo_operator_t toCairoOperator(CompositeOperator op, BlendMode blendOp) |
165 | { |
166 | switch (blendOp) { |
167 | case BlendMode::Normal: |
168 | return toCairoCompositeOperator(op); |
169 | case BlendMode::Multiply: |
170 | return CAIRO_OPERATOR_MULTIPLY; |
171 | case BlendMode::Screen: |
172 | return CAIRO_OPERATOR_SCREEN; |
173 | case BlendMode::Overlay: |
174 | return CAIRO_OPERATOR_OVERLAY; |
175 | case BlendMode::Darken: |
176 | return CAIRO_OPERATOR_DARKEN; |
177 | case BlendMode::Lighten: |
178 | return CAIRO_OPERATOR_LIGHTEN; |
179 | case BlendMode::ColorDodge: |
180 | return CAIRO_OPERATOR_COLOR_DODGE; |
181 | case BlendMode::ColorBurn: |
182 | return CAIRO_OPERATOR_COLOR_BURN; |
183 | case BlendMode::HardLight: |
184 | return CAIRO_OPERATOR_HARD_LIGHT; |
185 | case BlendMode::SoftLight: |
186 | return CAIRO_OPERATOR_SOFT_LIGHT; |
187 | case BlendMode::Difference: |
188 | return CAIRO_OPERATOR_DIFFERENCE; |
189 | case BlendMode::Exclusion: |
190 | return CAIRO_OPERATOR_EXCLUSION; |
191 | case BlendMode::Hue: |
192 | return CAIRO_OPERATOR_HSL_HUE; |
193 | case BlendMode::Saturation: |
194 | return CAIRO_OPERATOR_HSL_SATURATION; |
195 | case BlendMode::Color: |
196 | return CAIRO_OPERATOR_HSL_COLOR; |
197 | case BlendMode::Luminosity: |
198 | return CAIRO_OPERATOR_HSL_LUMINOSITY; |
199 | default: |
200 | return CAIRO_OPERATOR_OVER; |
201 | } |
202 | } |
203 | |
204 | void drawPatternToCairoContext(cairo_t* cr, cairo_surface_t* image, const IntSize& imageSize, const FloatRect& tileRect, |
205 | const AffineTransform& patternTransform, const FloatPoint& phase, cairo_operator_t op, const FloatRect& destRect) |
206 | { |
207 | // Avoid NaN |
208 | if (!std::isfinite(phase.x()) || !std::isfinite(phase.y())) |
209 | return; |
210 | |
211 | cairo_save(cr); |
212 | |
213 | RefPtr<cairo_surface_t> clippedImageSurface = 0; |
214 | if (tileRect.size() != imageSize) { |
215 | IntRect imageRect = enclosingIntRect(tileRect); |
216 | clippedImageSurface = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, imageRect.width(), imageRect.height())); |
217 | RefPtr<cairo_t> clippedImageContext = adoptRef(cairo_create(clippedImageSurface.get())); |
218 | cairo_set_source_surface(clippedImageContext.get(), image, -tileRect.x(), -tileRect.y()); |
219 | cairo_paint(clippedImageContext.get()); |
220 | image = clippedImageSurface.get(); |
221 | } |
222 | |
223 | cairo_pattern_t* pattern = cairo_pattern_create_for_surface(image); |
224 | cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); |
225 | |
226 | // Due to a limitation in pixman, cairo cannot handle transformation matrices with values bigger than 32768. If the value is |
227 | // bigger, cairo is not able to paint anything, and this is the reason for the missing backgrounds reported in |
228 | // https://bugs.webkit.org/show_bug.cgi?id=154283. |
229 | |
230 | // When drawing a pattern there are 2 matrices that can overflow this limitation, and they are the current transformation |
231 | // matrix (which translates user space coordinates to coordinates of the output device) and the pattern matrix (which translates |
232 | // user space coordinates to pattern coordinates). The overflow happens only in the translation components of the matrices. |
233 | |
234 | // To avoid the problem in the transformation matrix what we do is remove the translation components of the transformation matrix |
235 | // and perform the translation by moving the destination rectangle instead. For this, we calculate such a translation amount (dx, dy) |
236 | // that its opposite translate (-dx, -dy) will zero the translation components of the transformation matrix. We move the current |
237 | // transformation matrix by (-dx, -dy) and move the destination rectangle by (dx, dy). We also need to apply the same translation to |
238 | // the pattern matrix, so we get the same pattern coordinates for the new destination rectangle. (dx, dy) is caclucated by transforming |
239 | // the current translation components by the inverse matrix of the current transformation matrix. |
240 | |
241 | cairo_matrix_t ctm; |
242 | cairo_get_matrix(cr, &ctm); |
243 | double dx = 0, dy = 0; |
244 | cairo_matrix_transform_point(&ctm, &dx, &dy); |
245 | cairo_matrix_t inv = ctm; |
246 | if (cairo_matrix_invert(&inv) == CAIRO_STATUS_SUCCESS) |
247 | cairo_matrix_transform_distance(&inv, &dx, &dy); |
248 | |
249 | cairo_translate(cr, -dx, -dy); |
250 | FloatRect adjustedDestRect(destRect); |
251 | adjustedDestRect.move(dx, dy); |
252 | |
253 | // Regarding the pattern matrix, what we do is reduce the translation component of the matrix taking advantage of the fact that we |
254 | // are drawing a repeated pattern. This means that, assuming that (w, h) is the size of the pattern, samplig it at (x, y) is the same |
255 | // than sampling it at (x mod w, y mod h), so we transform the translation component of the pattern matrix in that way. |
256 | |
257 | cairo_matrix_t patternMatrix = toCairoMatrix(patternTransform); |
258 | // dx and dy are added here as well to compensate the previous translation of the destination rectangle. |
259 | double phaseOffsetX = phase.x() + tileRect.x() * patternTransform.a() + dx; |
260 | double phaseOffsetY = phase.y() + tileRect.y() * patternTransform.d() + dy; |
261 | // this is where we perform the (x mod w, y mod h) metioned above, but with floats instead of integers. |
262 | phaseOffsetX -= std::trunc(phaseOffsetX / (tileRect.width() * patternTransform.a())) * tileRect.width() * patternTransform.a(); |
263 | phaseOffsetY -= std::trunc(phaseOffsetY / (tileRect.height() * patternTransform.d())) * tileRect.height() * patternTransform.d(); |
264 | cairo_matrix_t phaseMatrix = {1, 0, 0, 1, phaseOffsetX, phaseOffsetY}; |
265 | cairo_matrix_t combined; |
266 | cairo_matrix_multiply(&combined, &patternMatrix, &phaseMatrix); |
267 | cairo_matrix_invert(&combined); |
268 | cairo_pattern_set_matrix(pattern, &combined); |
269 | |
270 | cairo_set_operator(cr, op); |
271 | cairo_set_source(cr, pattern); |
272 | cairo_pattern_destroy(pattern); |
273 | cairo_rectangle(cr, adjustedDestRect.x(), adjustedDestRect.y(), adjustedDestRect.width(), adjustedDestRect.height()); |
274 | cairo_fill(cr); |
275 | |
276 | cairo_restore(cr); |
277 | } |
278 | |
279 | RefPtr<cairo_surface_t> copyCairoImageSurface(cairo_surface_t* originalSurface) |
280 | { |
281 | // Cairo doesn't provide a way to copy a cairo_surface_t. |
282 | // See http://lists.cairographics.org/archives/cairo/2007-June/010877.html |
283 | // Once cairo provides the way, use the function instead of this. |
284 | IntSize size = cairoSurfaceSize(originalSurface); |
285 | RefPtr<cairo_surface_t> newSurface = adoptRef(cairo_surface_create_similar(originalSurface, |
286 | cairo_surface_get_content(originalSurface), size.width(), size.height())); |
287 | |
288 | RefPtr<cairo_t> cr = adoptRef(cairo_create(newSurface.get())); |
289 | cairo_set_source_surface(cr.get(), originalSurface, 0, 0); |
290 | cairo_set_operator(cr.get(), CAIRO_OPERATOR_SOURCE); |
291 | cairo_paint(cr.get()); |
292 | return newSurface; |
293 | } |
294 | |
295 | void copyRectFromCairoSurfaceToContext(cairo_surface_t* from, cairo_t* to, const IntSize& offset, const IntRect& rect) |
296 | { |
297 | cairo_set_source_surface(to, from, offset.width(), offset.height()); |
298 | cairo_rectangle(to, rect.x(), rect.y(), rect.width(), rect.height()); |
299 | cairo_fill(to); |
300 | } |
301 | |
302 | void copyRectFromOneSurfaceToAnother(cairo_surface_t* from, cairo_surface_t* to, const IntSize& sourceOffset, const IntRect& rect, const IntSize& destOffset, cairo_operator_t cairoOperator) |
303 | { |
304 | RefPtr<cairo_t> context = adoptRef(cairo_create(to)); |
305 | cairo_translate(context.get(), destOffset.width(), destOffset.height()); |
306 | cairo_set_operator(context.get(), cairoOperator); |
307 | copyRectFromCairoSurfaceToContext(from, context.get(), sourceOffset, rect); |
308 | } |
309 | |
310 | IntSize cairoSurfaceSize(cairo_surface_t* surface) |
311 | { |
312 | switch (cairo_surface_get_type(surface)) { |
313 | case CAIRO_SURFACE_TYPE_IMAGE: |
314 | return IntSize(cairo_image_surface_get_width(surface), cairo_image_surface_get_height(surface)); |
315 | #if ENABLE(ACCELERATED_2D_CANVAS) |
316 | case CAIRO_SURFACE_TYPE_GL: |
317 | return IntSize(cairo_gl_surface_get_width(surface), cairo_gl_surface_get_height(surface)); |
318 | #endif |
319 | #if OS(WINDOWS) |
320 | case CAIRO_SURFACE_TYPE_WIN32: |
321 | surface = cairo_win32_surface_get_image(surface); |
322 | ASSERT(surface); |
323 | ASSERT(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE); |
324 | return IntSize(cairo_image_surface_get_width(surface), cairo_image_surface_get_height(surface)); |
325 | #endif |
326 | default: |
327 | ASSERT_NOT_REACHED(); |
328 | return IntSize(); |
329 | } |
330 | } |
331 | |
332 | void flipImageSurfaceVertically(cairo_surface_t* surface) |
333 | { |
334 | ASSERT(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE); |
335 | |
336 | IntSize size = cairoSurfaceSize(surface); |
337 | ASSERT(!size.isEmpty()); |
338 | |
339 | int stride = cairo_image_surface_get_stride(surface); |
340 | int halfHeight = size.height() / 2; |
341 | |
342 | uint8_t* source = static_cast<uint8_t*>(cairo_image_surface_get_data(surface)); |
343 | auto tmp = makeUniqueArray<uint8_t>(stride); |
344 | |
345 | for (int i = 0; i < halfHeight; ++i) { |
346 | uint8_t* top = source + (i * stride); |
347 | uint8_t* bottom = source + ((size.height()-i-1) * stride); |
348 | |
349 | memcpy(tmp.get(), top, stride); |
350 | memcpy(top, bottom, stride); |
351 | memcpy(bottom, tmp.get(), stride); |
352 | } |
353 | } |
354 | |
355 | void cairoSurfaceSetDeviceScale(cairo_surface_t* surface, double xScale, double yScale) |
356 | { |
357 | // This function was added pretty much simultaneous to when 1.13 was branched. |
358 | #if HAVE(CAIRO_SURFACE_SET_DEVICE_SCALE) |
359 | cairo_surface_set_device_scale(surface, xScale, yScale); |
360 | #else |
361 | UNUSED_PARAM(surface); |
362 | ASSERT_UNUSED(xScale, 1 == xScale); |
363 | ASSERT_UNUSED(yScale, 1 == yScale); |
364 | #endif |
365 | } |
366 | |
367 | void cairoSurfaceGetDeviceScale(cairo_surface_t* surface, double& xScale, double& yScale) |
368 | { |
369 | #if HAVE(CAIRO_SURFACE_SET_DEVICE_SCALE) |
370 | cairo_surface_get_device_scale(surface, &xScale, &yScale); |
371 | #else |
372 | UNUSED_PARAM(surface); |
373 | xScale = 1; |
374 | yScale = 1; |
375 | #endif |
376 | } |
377 | |
378 | RefPtr<cairo_region_t> toCairoRegion(const Region& region) |
379 | { |
380 | RefPtr<cairo_region_t> cairoRegion = adoptRef(cairo_region_create()); |
381 | for (const auto& rect : region.rects()) { |
382 | cairo_rectangle_int_t cairoRect = rect; |
383 | cairo_region_union_rectangle(cairoRegion.get(), &cairoRect); |
384 | } |
385 | return cairoRegion; |
386 | } |
387 | |
388 | cairo_matrix_t toCairoMatrix(const AffineTransform& transform) |
389 | { |
390 | return cairo_matrix_t { transform.a(), transform.b(), transform.c(), transform.d(), transform.e(), transform.f() }; |
391 | } |
392 | |
393 | } // namespace WebCore |
394 | |
395 | #endif // USE(CAIRO) |
396 | |