| /* |
| * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. |
| * Copyright (C) 2007 Alp Toker <alp@atoker.com> |
| * Copyright (C) 2019 Igalia S.L. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "Gradient.h" |
| |
| #if USE(CAIRO) |
| |
| #include "CairoOperations.h" |
| #include "CairoUtilities.h" |
| #include "GraphicsContext.h" |
| #include "PlatformContextCairo.h" |
| |
| namespace WebCore { |
| |
| void Gradient::platformDestroy() |
| { |
| } |
| |
| static void addColorStopRGBA(cairo_pattern_t *gradient, Gradient::ColorStop stop, float globalAlpha) |
| { |
| if (stop.color.isExtended()) { |
| cairo_pattern_add_color_stop_rgba(gradient, stop.offset, stop.color.asExtended().red(), stop.color.asExtended().green(), |
| stop.color.asExtended().blue(), stop.color.asExtended().alpha() * globalAlpha); |
| } else { |
| float r, g, b, a; |
| stop.color.getRGBA(r, g, b, a); |
| cairo_pattern_add_color_stop_rgba(gradient, stop.offset, r, g, b, a * globalAlpha); |
| } |
| } |
| |
| #if PLATFORM(GTK) || PLATFORM(WPE) |
| |
| typedef struct point_t { |
| double x, y; |
| } point_t; |
| |
| static void setCornerColorRGBA(cairo_pattern_t* gradient, int id, Gradient::ColorStop stop, float globalAlpha) |
| { |
| if (stop.color.isExtended()) { |
| cairo_mesh_pattern_set_corner_color_rgba(gradient, id, stop.color.asExtended().red(), stop.color.asExtended().green(), |
| stop.color.asExtended().blue(), stop.color.asExtended().alpha() * globalAlpha); |
| } else { |
| float r, g, b, a; |
| stop.color.getRGBA(r, g, b, a); |
| cairo_mesh_pattern_set_corner_color_rgba(gradient, id, r, g, b, a * globalAlpha); |
| } |
| } |
| |
| static void addConicSector(cairo_pattern_t *gradient, float cx, float cy, float r, float angleRadians, |
| Gradient::ColorStop from, Gradient::ColorStop to, float globalAlpha) |
| { |
| const double angOffset = 0.25; // 90 degrees. |
| |
| // Substract 90 degrees so angles start from top left. |
| // Convert to radians and add angleRadians offset. |
| double angleStart = ((from.offset - angOffset) * 2 * M_PI) + angleRadians; |
| double angleEnd = ((to.offset - angOffset) * 2 * M_PI) + angleRadians; |
| |
| // Calculate center offset depending on quadrant. |
| // |
| // All sections belonging to the same quadrant share a common center. As we move |
| // along the circle, sections belonging to a new quadrant will have a different |
| // center. If all sections had the same center, the center will get overridden as |
| // the sections get painted. |
| double cxOffset, cyOffset; |
| if (from.offset >= 0 && from.offset < 0.25) { |
| cxOffset = 0; |
| cyOffset = -1; |
| } else if (from.offset >= 0.25 && from.offset < 0.50) { |
| cxOffset = 0; |
| cyOffset = 0; |
| } else if (from.offset >= 0.50 && from.offset < 0.75) { |
| cxOffset = -1; |
| cyOffset = 0; |
| } else if (from.offset >= 0.75 && from.offset < 1) { |
| cxOffset = -1; |
| cyOffset = -1; |
| } else { |
| cxOffset = 0; |
| cyOffset = -1; |
| } |
| // The center offset for each of the sections is 1 pixel, since in theory nothing |
| // can be smaller than 1 pixel. However, in high-resolution displays 1 pixel is |
| // too wide, and that makes the separation between sections clearly visible by a |
| // straight white line. To fix this issue, I set the size of the offset not to |
| // 1 pixel but 0.10. This has proved to work OK both in low-resolution displays |
| // as well as high-resolution displays. |
| const double offsetWidth = 0.1; |
| cx = cx + cxOffset * offsetWidth; |
| cy = cy + cyOffset * offsetWidth; |
| |
| // Calculate starting point, ending point and control points of Bezier curve. |
| double f = 4 * tan((angleEnd - angleStart) / 4) / 3; |
| point_t p0 = { |
| x: cx + (r * cos(angleStart)), |
| y: cy + (r * sin(angleStart)) |
| }; |
| point_t p1 = { |
| x: cx + (r * cos(angleStart)) - f * (r * sin(angleStart)), |
| y: cy + (r * sin(angleStart)) + f * (r * cos(angleStart)) |
| }; |
| point_t p2 = { |
| x: cx + (r * cos(angleEnd)) + f * (r * sin(angleEnd)), |
| y: cy + (r * sin(angleEnd)) - f * (r * cos(angleEnd)) |
| }; |
| point_t p3 = { |
| x: cx + (r * cos(angleEnd)), |
| y: cy + (r * sin(angleEnd)) |
| }; |
| |
| // Add patch with shape of the sector and gradient colors. |
| cairo_mesh_pattern_begin_patch(gradient); |
| cairo_mesh_pattern_move_to(gradient, cx, cy); |
| cairo_mesh_pattern_line_to(gradient, p0.x, p0.y); |
| cairo_mesh_pattern_curve_to(gradient, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); |
| setCornerColorRGBA(gradient, 0, from, globalAlpha); |
| setCornerColorRGBA(gradient, 1, from, globalAlpha); |
| setCornerColorRGBA(gradient, 2, to, globalAlpha); |
| setCornerColorRGBA(gradient, 3, to, globalAlpha); |
| cairo_mesh_pattern_end_patch(gradient); |
| } |
| |
| static Gradient::ColorStop interpolateColorStop(Gradient::ColorStop from, Gradient::ColorStop to) |
| { |
| float r1, g1, b1, a1; |
| float r2, g2, b2, a2; |
| |
| if (from.color.isExtended()) { |
| r1 = from.color.asExtended().red(); |
| g1 = from.color.asExtended().green(); |
| b1 = from.color.asExtended().blue(); |
| a1 = from.color.asExtended().alpha(); |
| } else |
| from.color.getRGBA(r1, g1, b1, a1); |
| |
| if (to.color.isExtended()) { |
| r2 = to.color.asExtended().red(); |
| g2 = to.color.asExtended().green(); |
| b2 = to.color.asExtended().blue(); |
| a2 = to.color.asExtended().alpha(); |
| } else |
| to.color.getRGBA(r2, g2, b2, a2); |
| |
| float offset = from.offset + (to.offset - from.offset) * 0.5f; |
| float r = r1 + (r2 - r1) * 0.5f; |
| float g = g1 + (g2 - g1) * 0.5f; |
| float b = b1 + (b2 - b1) * 0.5f; |
| float a = a1 + (a2 - a1) * 0.5f; |
| |
| return Gradient::ColorStop(offset, Color(r, g, b, a)); |
| } |
| |
| static cairo_pattern_t* createConic(float xo, float yo, float r, float angleRadians, |
| Gradient::ColorStopVector stops, float globalAlpha) |
| { |
| cairo_pattern_t* gradient = cairo_pattern_create_mesh(); |
| Gradient::ColorStop from, to; |
| |
| /* It's not possible to paint an entire circle with a single Bezier curve. |
| * To have a good approximation to a circle it's necessary to use at least |
| * four Bezier curves. So three additional stops with interpolated colors |
| * are added to force painting of four Bezier curves. */ |
| if (stops.size() == 2) { |
| Gradient::ColorStop first = stops.at(0); |
| Gradient::ColorStop last = stops.at(1); |
| Gradient::ColorStop third = interpolateColorStop(first, last); |
| Gradient::ColorStop second = interpolateColorStop(first, third); |
| Gradient::ColorStop fourth = interpolateColorStop(third, last); |
| stops.insert(1, fourth); |
| stops.insert(1, third); |
| stops.insert(1, second); |
| } |
| |
| // Add extra color stop at the beginning if first element offset is not zero. |
| if (stops.at(0).offset > 0) |
| stops.insert(0, Gradient::ColorStop(0, stops.at(0).color)); |
| // Add extra color stop at the end if last element offset is not zero. |
| if (stops.at(stops.size() - 1).offset < 1) |
| stops.append(Gradient::ColorStop(1, stops.at(stops.size() - 1).color)); |
| |
| for (size_t i = 0; i < stops.size() - 1; i++) { |
| from = stops.at(i), to = stops.at(i + 1); |
| addConicSector(gradient, xo, yo, r, angleRadians, from, to, globalAlpha); |
| } |
| |
| return gradient; |
| } |
| |
| #endif |
| |
| cairo_pattern_t* Gradient::createPlatformGradient(float globalAlpha) |
| { |
| cairo_pattern_t* gradient = WTF::switchOn(m_data, |
| [&] (const LinearData& data) -> cairo_pattern_t* { |
| return cairo_pattern_create_linear(data.point0.x(), data.point0.y(), data.point1.x(), data.point1.y()); |
| }, |
| [&] (const RadialData& data) -> cairo_pattern_t* { |
| return cairo_pattern_create_radial(data.point0.x(), data.point0.y(), data.startRadius, data.point1.x(), data.point1.y(), data.endRadius); |
| }, |
| #if PLATFORM(GTK) || PLATFORM(WPE) |
| [&] (const ConicData& data) -> cairo_pattern_t* { |
| // FIXME: data passed for a Conic gradient doesn't contain a radius. That's apparently correct because the W3C spec |
| // (https://www.w3.org/TR/css-images-4/#conic-gradients) states a conic gradient is only defined by its position and angle. |
| // Thus, here I give the radius an extremely large value. The resulting gradient will be later clipped by fillRect. |
| // An alternative solution could be to change the API and pass a rect's width and height to optimize the computation of the radius. |
| const float radius = 4096; |
| return createConic(data.point0.x(), data.point0.y(), radius, data.angleRadians, stops(), globalAlpha); |
| #else |
| [&] (const ConicData&) -> cairo_pattern_t* { |
| // FIXME: implement conic gradient rendering. |
| return nullptr; |
| #endif |
| } |
| ); |
| |
| if (type() != Type::Conic) { |
| for (const auto& stop : stops()) { |
| addColorStopRGBA(gradient, stop, globalAlpha); |
| } |
| } |
| |
| switch (m_spreadMethod) { |
| case SpreadMethodPad: |
| cairo_pattern_set_extend(gradient, CAIRO_EXTEND_PAD); |
| break; |
| case SpreadMethodReflect: |
| cairo_pattern_set_extend(gradient, CAIRO_EXTEND_REFLECT); |
| break; |
| case SpreadMethodRepeat: |
| cairo_pattern_set_extend(gradient, CAIRO_EXTEND_REPEAT); |
| break; |
| } |
| |
| cairo_matrix_t matrix = toCairoMatrix(m_gradientSpaceTransformation); |
| cairo_matrix_invert(&matrix); |
| cairo_pattern_set_matrix(gradient, &matrix); |
| |
| return gradient; |
| } |
| |
| void Gradient::fill(GraphicsContext& context, const FloatRect& rect) |
| { |
| RefPtr<cairo_pattern_t> platformGradient = adoptRef(createPlatformGradient(1.0)); |
| if (!platformGradient) |
| return; |
| |
| ASSERT(context.hasPlatformContext()); |
| auto& platformContext = *context.platformContext(); |
| |
| Cairo::save(platformContext); |
| Cairo::fillRect(platformContext, rect, platformGradient.get()); |
| Cairo::restore(platformContext); |
| } |
| |
| } // namespace WebCore |
| |
| #endif // USE(CAIRO) |