[ iOS wk2 ] compositing/absolute-inside-out-of-view-fixed.html is flaky timing out...
[WebKit-https.git] / Source / WebCore / platform / graphics / cairo / GradientCairo.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
3  * Copyright (C) 2007 Alp Toker <alp@atoker.com>
4  * Copyright (C) 2019 Igalia S.L.
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 "Gradient.h"
30
31 #if USE(CAIRO)
32
33 #include "CairoOperations.h"
34 #include "CairoUtilities.h"
35 #include "GraphicsContext.h"
36 #include "PlatformContextCairo.h"
37
38 namespace WebCore {
39
40 void Gradient::platformDestroy()
41 {
42 }
43
44 static void addColorStopRGBA(cairo_pattern_t *gradient, Gradient::ColorStop stop, float globalAlpha)
45 {
46     if (stop.color.isExtended()) {
47         cairo_pattern_add_color_stop_rgba(gradient, stop.offset, stop.color.asExtended().red(), stop.color.asExtended().green(),
48             stop.color.asExtended().blue(), stop.color.asExtended().alpha() * globalAlpha);
49     } else {
50         float r, g, b, a;
51         stop.color.getRGBA(r, g, b, a);
52         cairo_pattern_add_color_stop_rgba(gradient, stop.offset, r, g, b, a * globalAlpha);
53     }
54 }
55
56 #if PLATFORM(GTK) || PLATFORM(WPE)
57
58 typedef struct point_t {
59     double x, y;
60 } point_t;
61
62 static void setCornerColorRGBA(cairo_pattern_t* gradient, int id, Gradient::ColorStop stop, float globalAlpha)
63 {
64     if (stop.color.isExtended()) {
65         cairo_mesh_pattern_set_corner_color_rgba(gradient, id, stop.color.asExtended().red(), stop.color.asExtended().green(),
66             stop.color.asExtended().blue(), stop.color.asExtended().alpha() * globalAlpha);
67     } else {
68         float r, g, b, a;
69         stop.color.getRGBA(r, g, b, a);
70         cairo_mesh_pattern_set_corner_color_rgba(gradient, id, r, g, b, a * globalAlpha);
71     }
72 }
73
74 static void addConicSector(cairo_pattern_t *gradient, float cx, float cy, float r, float angleRadians,
75     Gradient::ColorStop from, Gradient::ColorStop to, float globalAlpha)
76 {
77     const double angOffset = 0.25; // 90 degrees.
78
79     // Substract 90 degrees so angles start from top left.
80     // Convert to radians and add angleRadians offset.
81     double angleStart = ((from.offset - angOffset) * 2 * M_PI) + angleRadians;
82     double angleEnd = ((to.offset - angOffset) * 2 * M_PI) + angleRadians;
83
84     // Calculate center offset depending on quadrant.
85     //
86     // All sections belonging to the same quadrant share a common center. As we move
87     // along the circle, sections belonging to a new quadrant will have a different
88     // center. If all sections had the same center, the center will get overridden as
89     // the sections get painted.
90     double cxOffset, cyOffset;
91     if (from.offset >= 0 && from.offset < 0.25) {
92         cxOffset = 0;
93         cyOffset = -1;
94     } else if (from.offset >= 0.25 && from.offset < 0.50) {
95         cxOffset = 0;
96         cyOffset = 0;
97     } else if (from.offset >= 0.50 && from.offset < 0.75) {
98         cxOffset = -1;
99         cyOffset = 0;
100     } else if (from.offset >= 0.75 && from.offset < 1) {
101         cxOffset = -1;
102         cyOffset = -1;
103     } else {
104         cxOffset = 0;
105         cyOffset = -1;
106     }
107     // The center offset for each of the sections is 1 pixel, since in theory nothing
108     // can be smaller than 1 pixel. However, in high-resolution displays 1 pixel is
109     // too wide, and that makes the separation between sections clearly visible by a
110     // straight white line. To fix this issue, I set the size of the offset not to
111     // 1 pixel but 0.10. This has proved to work OK both in low-resolution displays
112     // as well as high-resolution displays.
113     const double offsetWidth = 0.1;
114     cx = cx + cxOffset * offsetWidth;
115     cy = cy + cyOffset * offsetWidth;
116
117     // Calculate starting point, ending point and control points of Bezier curve.
118     double f = 4 * tan((angleEnd - angleStart) / 4) / 3;
119     point_t p0 = {
120         x: cx + (r * cos(angleStart)),
121         y: cy + (r * sin(angleStart))
122     };
123     point_t p1 = {
124         x: cx + (r * cos(angleStart)) - f * (r * sin(angleStart)),
125         y: cy + (r * sin(angleStart)) + f * (r * cos(angleStart))
126     };
127     point_t p2 = {
128         x: cx + (r * cos(angleEnd)) + f * (r * sin(angleEnd)),
129         y: cy + (r * sin(angleEnd)) - f * (r * cos(angleEnd))
130     };
131     point_t p3 = {
132         x: cx + (r * cos(angleEnd)),
133         y: cy + (r * sin(angleEnd))
134     };
135
136     // Add patch with shape of the sector and gradient colors.
137     cairo_mesh_pattern_begin_patch(gradient);
138     cairo_mesh_pattern_move_to(gradient, cx, cy);
139     cairo_mesh_pattern_line_to(gradient, p0.x, p0.y);
140     cairo_mesh_pattern_curve_to(gradient, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
141     setCornerColorRGBA(gradient, 0, from, globalAlpha);
142     setCornerColorRGBA(gradient, 1, from, globalAlpha);
143     setCornerColorRGBA(gradient, 2, to, globalAlpha);
144     setCornerColorRGBA(gradient, 3, to, globalAlpha);
145     cairo_mesh_pattern_end_patch(gradient);
146 }
147
148 static Gradient::ColorStop interpolateColorStop(Gradient::ColorStop from, Gradient::ColorStop to)
149 {
150     float r1, g1, b1, a1;
151     float r2, g2, b2, a2;
152
153     if (from.color.isExtended()) {
154         r1 = from.color.asExtended().red();
155         g1 = from.color.asExtended().green();
156         b1 = from.color.asExtended().blue();
157         a1 = from.color.asExtended().alpha();
158     } else
159         from.color.getRGBA(r1, g1, b1, a1);
160
161     if (to.color.isExtended()) {
162         r2 = to.color.asExtended().red();
163         g2 = to.color.asExtended().green();
164         b2 = to.color.asExtended().blue();
165         a2 = to.color.asExtended().alpha();
166     } else
167         to.color.getRGBA(r2, g2, b2, a2);
168
169     float offset = from.offset + (to.offset - from.offset) * 0.5f;
170     float r = r1 + (r2 - r1) * 0.5f;
171     float g = g1 + (g2 - g1) * 0.5f;
172     float b = b1 + (b2 - b1) * 0.5f;
173     float a = a1 + (a2 - a1) * 0.5f;
174
175     return Gradient::ColorStop(offset, Color(r, g, b, a));
176 }
177
178 static cairo_pattern_t* createConic(float xo, float yo, float r, float angleRadians,
179     Gradient::ColorStopVector stops, float globalAlpha)
180 {
181     cairo_pattern_t* gradient = cairo_pattern_create_mesh();
182     Gradient::ColorStop from, to;
183
184     /* It's not possible to paint an entire circle with a single Bezier curve.
185      * To have a good approximation to a circle it's necessary to use at least
186      * four Bezier curves. So three additional stops with interpolated colors
187      * are added to force painting of four Bezier curves. */
188     if (stops.size() == 2) {
189         Gradient::ColorStop first = stops.at(0);
190         Gradient::ColorStop last = stops.at(1);
191         Gradient::ColorStop third = interpolateColorStop(first, last);
192         Gradient::ColorStop second = interpolateColorStop(first, third);
193         Gradient::ColorStop fourth = interpolateColorStop(third, last);
194         stops.insert(1, fourth);
195         stops.insert(1, third);
196         stops.insert(1, second);
197     }
198
199     // Add extra color stop at the beginning if first element offset is not zero.
200     if (stops.at(0).offset > 0)
201         stops.insert(0, Gradient::ColorStop(0, stops.at(0).color));
202     // Add extra color stop at the end if last element offset is not zero.
203     if (stops.at(stops.size() - 1).offset < 1)
204         stops.append(Gradient::ColorStop(1, stops.at(stops.size() - 1).color));
205
206     for (size_t i = 0; i < stops.size() - 1; i++) {
207         from = stops.at(i), to = stops.at(i + 1);
208         addConicSector(gradient, xo, yo, r, angleRadians, from, to, globalAlpha);
209     }
210
211     return gradient;
212 }
213
214 #endif
215
216 cairo_pattern_t* Gradient::createPlatformGradient(float globalAlpha)
217 {
218     cairo_pattern_t* gradient = WTF::switchOn(m_data,
219         [&] (const LinearData& data) -> cairo_pattern_t* {
220             return cairo_pattern_create_linear(data.point0.x(), data.point0.y(), data.point1.x(), data.point1.y());
221         },
222         [&] (const RadialData& data) -> cairo_pattern_t* {
223             return cairo_pattern_create_radial(data.point0.x(), data.point0.y(), data.startRadius, data.point1.x(), data.point1.y(), data.endRadius);
224         },
225 #if PLATFORM(GTK) || PLATFORM(WPE)
226         [&] (const ConicData& data)  -> cairo_pattern_t* {
227             // FIXME: data passed for a Conic gradient doesn't contain a radius. That's apparently correct because the W3C spec
228             // (https://www.w3.org/TR/css-images-4/#conic-gradients) states a conic gradient is only defined by its position and angle.
229             // Thus, here I give the radius an extremely large value. The resulting gradient will be later clipped by fillRect.
230             // An alternative solution could be to change the API and pass a rect's width and height to optimize the computation of the radius.
231             const float radius = 4096;
232             return createConic(data.point0.x(), data.point0.y(), radius, data.angleRadians, stops(), globalAlpha);
233 #else
234         [&] (const ConicData&)  -> cairo_pattern_t* {
235             // FIXME: implement conic gradient rendering.
236             return nullptr;
237 #endif
238         }
239     );
240
241     if (type() != Type::Conic) {
242         for (const auto& stop : stops()) {
243             addColorStopRGBA(gradient, stop, globalAlpha);
244         }
245     }
246
247     switch (m_spreadMethod) {
248     case SpreadMethodPad:
249         cairo_pattern_set_extend(gradient, CAIRO_EXTEND_PAD);
250         break;
251     case SpreadMethodReflect:
252         cairo_pattern_set_extend(gradient, CAIRO_EXTEND_REFLECT);
253         break;
254     case SpreadMethodRepeat:
255         cairo_pattern_set_extend(gradient, CAIRO_EXTEND_REPEAT);
256         break;
257     }
258
259     cairo_matrix_t matrix = toCairoMatrix(m_gradientSpaceTransformation);
260     cairo_matrix_invert(&matrix);
261     cairo_pattern_set_matrix(gradient, &matrix);
262
263     return gradient;
264 }
265
266 void Gradient::fill(GraphicsContext& context, const FloatRect& rect)
267 {
268     RefPtr<cairo_pattern_t> platformGradient = adoptRef(createPlatformGradient(1.0));
269     if (!platformGradient)
270         return;
271
272     ASSERT(context.hasPlatformContext());
273     auto& platformContext = *context.platformContext();
274
275     Cairo::save(platformContext);
276     Cairo::fillRect(platformContext, rect, platformGradient.get());
277     Cairo::restore(platformContext);
278 }
279
280 } // namespace WebCore
281
282 #endif // USE(CAIRO)