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#include "AffineTransform.h"
31#include "Color.h"
32#include "FloatPoint.h"
33#include "FloatRect.h"
34#include "IntRect.h"
35#include "OwnPtrCairo.h"
36#include "Path.h"
37#include "PlatformPathCairo.h"
38#include "RefPtrCairo.h"
39#include <wtf/Assertions.h>
40#include <wtf/Vector.h>
41
42#if ENABLE(ACCELERATED_2D_CANVAS)
43#include <cairo-gl.h>
44#endif
45
46namespace WebCore {
47
48void copyContextProperties(cairo_t* srcCr, cairo_t* dstCr)
49{
50    cairo_set_antialias(dstCr, cairo_get_antialias(srcCr));
51
52    size_t dashCount = cairo_get_dash_count(srcCr);
53    Vector<double> dashes(dashCount);
54
55    double offset;
56    cairo_get_dash(srcCr, dashes.data(), &offset);
57    cairo_set_dash(dstCr, dashes.data(), dashCount, offset);
58    cairo_set_line_cap(dstCr, cairo_get_line_cap(srcCr));
59    cairo_set_line_join(dstCr, cairo_get_line_join(srcCr));
60    cairo_set_line_width(dstCr, cairo_get_line_width(srcCr));
61    cairo_set_miter_limit(dstCr, cairo_get_miter_limit(srcCr));
62    cairo_set_fill_rule(dstCr, cairo_get_fill_rule(srcCr));
63}
64
65void setSourceRGBAFromColor(cairo_t* context, const Color& color)
66{
67    float red, green, blue, alpha;
68    color.getRGBA(red, green, blue, alpha);
69    cairo_set_source_rgba(context, red, green, blue, alpha);
70}
71
72void appendPathToCairoContext(cairo_t* to, cairo_t* from)
73{
74    OwnPtr<cairo_path_t> cairoPath = adoptPtr(cairo_copy_path(from));
75    cairo_append_path(to, cairoPath.get());
76}
77
78void setPathOnCairoContext(cairo_t* to, cairo_t* from)
79{
80    cairo_new_path(to);
81    appendPathToCairoContext(to, from);
82}
83
84void appendWebCorePathToCairoContext(cairo_t* context, const Path& path)
85{
86    if (path.isEmpty())
87        return;
88    appendPathToCairoContext(context, path.platformPath()->context());
89}
90
91void appendRegionToCairoContext(cairo_t* to, const cairo_region_t* region)
92{
93    if (!region)
94        return;
95
96    const int rectCount = cairo_region_num_rectangles(region);
97    for (int i = 0; i < rectCount; ++i) {
98        cairo_rectangle_int_t rect;
99        cairo_region_get_rectangle(region, i, &rect);
100        cairo_rectangle(to, rect.x, rect.y, rect.width, rect.height);
101    }
102}
103
104cairo_operator_t toCairoOperator(CompositeOperator op)
105{
106    switch (op) {
107    case CompositeClear:
108        return CAIRO_OPERATOR_CLEAR;
109    case CompositeCopy:
110        return CAIRO_OPERATOR_SOURCE;
111    case CompositeSourceOver:
112        return CAIRO_OPERATOR_OVER;
113    case CompositeSourceIn:
114        return CAIRO_OPERATOR_IN;
115    case CompositeSourceOut:
116        return CAIRO_OPERATOR_OUT;
117    case CompositeSourceAtop:
118        return CAIRO_OPERATOR_ATOP;
119    case CompositeDestinationOver:
120        return CAIRO_OPERATOR_DEST_OVER;
121    case CompositeDestinationIn:
122        return CAIRO_OPERATOR_DEST_IN;
123    case CompositeDestinationOut:
124        return CAIRO_OPERATOR_DEST_OUT;
125    case CompositeDestinationAtop:
126        return CAIRO_OPERATOR_DEST_ATOP;
127    case CompositeXOR:
128        return CAIRO_OPERATOR_XOR;
129    case CompositePlusDarker:
130        return CAIRO_OPERATOR_DARKEN;
131    case CompositePlusLighter:
132        return CAIRO_OPERATOR_ADD;
133    case CompositeDifference:
134        return CAIRO_OPERATOR_DIFFERENCE;
135    default:
136        return CAIRO_OPERATOR_SOURCE;
137    }
138}
139cairo_operator_t toCairoOperator(BlendMode blendOp)
140{
141    switch (blendOp) {
142    case BlendModeNormal:
143        return CAIRO_OPERATOR_OVER;
144    case BlendModeMultiply:
145        return CAIRO_OPERATOR_MULTIPLY;
146    case BlendModeScreen:
147        return CAIRO_OPERATOR_SCREEN;
148    case BlendModeOverlay:
149        return CAIRO_OPERATOR_OVERLAY;
150    case BlendModeDarken:
151        return CAIRO_OPERATOR_DARKEN;
152    case BlendModeLighten:
153        return CAIRO_OPERATOR_LIGHTEN;
154    case BlendModeColorDodge:
155        return CAIRO_OPERATOR_COLOR_DODGE;
156    case BlendModeColorBurn:
157        return CAIRO_OPERATOR_COLOR_BURN;
158    case BlendModeHardLight:
159        return CAIRO_OPERATOR_HARD_LIGHT;
160    case BlendModeSoftLight:
161        return CAIRO_OPERATOR_SOFT_LIGHT;
162    case BlendModeDifference:
163        return CAIRO_OPERATOR_DIFFERENCE;
164    case BlendModeExclusion:
165        return CAIRO_OPERATOR_EXCLUSION;
166    case BlendModeHue:
167        return CAIRO_OPERATOR_HSL_HUE;
168    case BlendModeSaturation:
169        return CAIRO_OPERATOR_HSL_SATURATION;
170    case BlendModeColor:
171        return CAIRO_OPERATOR_HSL_COLOR;
172    case BlendModeLuminosity:
173        return CAIRO_OPERATOR_HSL_LUMINOSITY;
174    default:
175        return CAIRO_OPERATOR_OVER;
176    }
177}
178
179void drawPatternToCairoContext(cairo_t* cr, cairo_surface_t* image, const IntSize& imageSize, const FloatRect& tileRect,
180                               const AffineTransform& patternTransform, const FloatPoint& phase, cairo_operator_t op, const FloatRect& destRect)
181{
182    // Avoid NaN
183    if (!std::isfinite(phase.x()) || !std::isfinite(phase.y()))
184       return;
185
186    cairo_save(cr);
187
188    RefPtr<cairo_surface_t> clippedImageSurface = 0;
189    if (tileRect.size() != imageSize) {
190        IntRect imageRect = enclosingIntRect(tileRect);
191        clippedImageSurface = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, imageRect.width(), imageRect.height()));
192        RefPtr<cairo_t> clippedImageContext = adoptRef(cairo_create(clippedImageSurface.get()));
193        cairo_set_source_surface(clippedImageContext.get(), image, -tileRect.x(), -tileRect.y());
194        cairo_paint(clippedImageContext.get());
195        image = clippedImageSurface.get();
196    }
197
198    cairo_pattern_t* pattern = cairo_pattern_create_for_surface(image);
199    cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
200
201    cairo_matrix_t patternMatrix = cairo_matrix_t(patternTransform);
202    cairo_matrix_t phaseMatrix = {1, 0, 0, 1, phase.x() + tileRect.x() * patternTransform.a(), phase.y() + tileRect.y() * patternTransform.d()};
203    cairo_matrix_t combined;
204    cairo_matrix_multiply(&combined, &patternMatrix, &phaseMatrix);
205    cairo_matrix_invert(&combined);
206    cairo_pattern_set_matrix(pattern, &combined);
207
208    cairo_set_operator(cr, op);
209    cairo_set_source(cr, pattern);
210    cairo_pattern_destroy(pattern);
211    cairo_rectangle(cr, destRect.x(), destRect.y(), destRect.width(), destRect.height());
212    cairo_fill(cr);
213
214    cairo_restore(cr);
215}
216
217PassRefPtr<cairo_surface_t> copyCairoImageSurface(cairo_surface_t* originalSurface)
218{
219    // Cairo doesn't provide a way to copy a cairo_surface_t.
220    // See http://lists.cairographics.org/archives/cairo/2007-June/010877.html
221    // Once cairo provides the way, use the function instead of this.
222    IntSize size = cairoSurfaceSize(originalSurface);
223    RefPtr<cairo_surface_t> newSurface = adoptRef(cairo_surface_create_similar(originalSurface,
224        cairo_surface_get_content(originalSurface), size.width(), size.height()));
225
226    RefPtr<cairo_t> cr = adoptRef(cairo_create(newSurface.get()));
227    cairo_set_source_surface(cr.get(), originalSurface, 0, 0);
228    cairo_set_operator(cr.get(), CAIRO_OPERATOR_SOURCE);
229    cairo_paint(cr.get());
230    return newSurface.release();
231}
232
233void copyRectFromCairoSurfaceToContext(cairo_surface_t* from, cairo_t* to, const IntSize& offset, const IntRect& rect)
234{
235    cairo_set_source_surface(to, from, offset.width(), offset.height());
236    cairo_rectangle(to, rect.x(), rect.y(), rect.width(), rect.height());
237    cairo_fill(to);
238}
239
240void copyRectFromOneSurfaceToAnother(cairo_surface_t* from, cairo_surface_t* to, const IntSize& sourceOffset, const IntRect& rect, const IntSize& destOffset, cairo_operator_t cairoOperator)
241{
242    RefPtr<cairo_t> context = adoptRef(cairo_create(to));
243    cairo_translate(context.get(), destOffset.width(), destOffset.height());
244    cairo_set_operator(context.get(), cairoOperator);
245    copyRectFromCairoSurfaceToContext(from, context.get(), sourceOffset, rect);
246}
247
248IntSize cairoSurfaceSize(cairo_surface_t* surface)
249{
250    switch (cairo_surface_get_type(surface)) {
251    case CAIRO_SURFACE_TYPE_IMAGE:
252        return IntSize(cairo_image_surface_get_width(surface), cairo_image_surface_get_height(surface));
253#if ENABLE(ACCELERATED_2D_CANVAS)
254    case CAIRO_SURFACE_TYPE_GL:
255        return IntSize(cairo_gl_surface_get_width(surface), cairo_gl_surface_get_height(surface));
256#endif
257    default:
258        ASSERT_NOT_REACHED();
259        return IntSize();
260    }
261}
262
263void flipImageSurfaceVertically(cairo_surface_t* surface)
264{
265    ASSERT(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE);
266
267    IntSize size = cairoSurfaceSize(surface);
268    ASSERT(!size.isEmpty());
269
270    int stride = cairo_image_surface_get_stride(surface);
271    int halfHeight = size.height() / 2;
272
273    uint8_t* source = static_cast<uint8_t*>(cairo_image_surface_get_data(surface));
274    std::unique_ptr<uint8_t[]> tmp = std::make_unique<uint8_t[]>(stride);
275
276    for (int i = 0; i < halfHeight; ++i) {
277        uint8_t* top = source + (i * stride);
278        uint8_t* bottom = source + ((size.height()-i-1) * stride);
279
280        memcpy(tmp.get(), top, stride);
281        memcpy(top, bottom, stride);
282        memcpy(bottom, tmp.get(), stride);
283    }
284}
285
286void cairoSurfaceSetDeviceScale(cairo_surface_t* surface, double xScale, double yScale)
287{
288    // This function was added pretty much simultaneous to when 1.13 was branched.
289#if HAVE(CAIRO_SURFACE_SET_DEVICE_SCALE)
290    cairo_surface_set_device_scale(surface, xScale, yScale);
291#else
292    UNUSED_PARAM(surface);
293    ASSERT_UNUSED(xScale, 1 == xScale);
294    ASSERT_UNUSED(yScale, 1 == yScale);
295#endif
296}
297} // namespace WebCore
298