/src/serenity/Userland/Libraries/LibGfx/GradientPainting.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/Math/Constants.h> |
8 | | #include <AK/Math/Exponentials.h> |
9 | | #include <AK/Math/Rounding.h> |
10 | | #include <AK/Math/Sqrt.h> |
11 | | #include <AK/Math/Trigonometry.h> |
12 | | #include <LibGfx/Gradients.h> |
13 | | #include <LibGfx/PaintStyle.h> |
14 | | #include <LibGfx/Painter.h> |
15 | | |
16 | | #if defined(AK_COMPILER_GCC) |
17 | | # pragma GCC optimize("O3") |
18 | | #endif |
19 | | |
20 | | namespace Gfx { |
21 | | |
22 | | // Note: This file implements the CSS/Canvas gradients for LibWeb according to the spec. |
23 | | // Please do not make ad-hoc changes that may break spec compliance! |
24 | | |
25 | | static float color_stop_step(ColorStop const& previous_stop, ColorStop const& next_stop, float position) |
26 | 348k | { |
27 | 348k | if (position < previous_stop.position) |
28 | 0 | return 0; |
29 | 348k | if (position > next_stop.position) |
30 | 0 | return 1; |
31 | | // For any given point between the two color stops, |
32 | | // determine the point’s location as a percentage of the distance between the two color stops. |
33 | | // Let this percentage be P. |
34 | 348k | auto stop_length = next_stop.position - previous_stop.position; |
35 | | // FIXME: Avoids NaNs... Still not quite correct? |
36 | 348k | if (stop_length <= 0) |
37 | 0 | return 1; |
38 | 348k | auto p = (position - previous_stop.position) / stop_length; |
39 | 348k | if (!next_stop.transition_hint.has_value()) |
40 | 348k | return p; |
41 | 0 | if (*next_stop.transition_hint >= 1) |
42 | 0 | return 0; |
43 | 0 | if (*next_stop.transition_hint <= 0) |
44 | 0 | return 1; |
45 | | // Let C, the color weighting at that point, be equal to P^(logH(.5)). |
46 | 0 | auto c = AK::pow(p, AK::log<float>(0.5) / AK::log(*next_stop.transition_hint)); |
47 | | // The color at that point is then a linear blend between the colors of the two color stops, |
48 | | // blending (1 - C) of the first stop and C of the second stop. |
49 | 0 | return c; |
50 | 0 | } |
51 | | |
52 | | enum class UsePremultipliedAlpha { |
53 | | Yes, |
54 | | No |
55 | | }; |
56 | | |
57 | | class GradientLine { |
58 | | public: |
59 | | GradientLine(int gradient_length, ReadonlySpan<ColorStop> color_stops, Optional<float> repeat_length, UsePremultipliedAlpha use_premultiplied_alpha = UsePremultipliedAlpha::Yes) |
60 | 9.20k | : m_repeat_mode(repeat_length.has_value() ? RepeatMode::Repeat : RepeatMode::None) |
61 | 9.20k | , m_start_offset(round_to<int>((repeating() ? color_stops.first().position : 0.0f) * gradient_length)) |
62 | 9.20k | , m_color_stops(color_stops) |
63 | 9.20k | , m_use_premultiplied_alpha(use_premultiplied_alpha) |
64 | 9.20k | { |
65 | | // Avoid generating excessive amounts of colors when the not enough shades to fill that length. |
66 | 9.20k | auto necessary_length = min<int>((color_stops.size() - 1) * 255, gradient_length); |
67 | 9.20k | m_sample_scale = float(necessary_length) / gradient_length; |
68 | | // Note: color_count will be < gradient_length for repeating gradients. |
69 | 9.20k | auto color_count = round_to<int>(repeat_length.value_or(1.0f) * necessary_length); |
70 | 9.20k | m_gradient_line_colors.resize(color_count); |
71 | | |
72 | 357k | for (int loc = 0; loc < color_count; loc++) { |
73 | 348k | auto relative_loc = float(loc + m_start_offset) / necessary_length; |
74 | 348k | Color gradient_color = color_blend(color_stops[0].color, color_stops[1].color, |
75 | 348k | color_stop_step(color_stops[0], color_stops[1], relative_loc)); |
76 | 348k | for (size_t i = 1; i < color_stops.size() - 1; i++) { |
77 | 0 | gradient_color = color_blend(gradient_color, color_stops[i + 1].color, |
78 | 0 | color_stop_step(color_stops[i], color_stops[i + 1], relative_loc)); |
79 | 0 | } |
80 | 348k | m_gradient_line_colors[loc] = gradient_color; |
81 | 348k | if (gradient_color.alpha() < 255) |
82 | 84.2k | m_requires_blending = true; |
83 | 348k | } |
84 | 9.20k | } |
85 | | |
86 | | Color color_blend(Color a, Color b, float amount) const |
87 | 65.5M | { |
88 | | // Note: color.mixed_with() performs premultiplied alpha mixing when necessary as defined in: |
89 | | // https://drafts.csswg.org/css-images/#coloring-gradient-line |
90 | 65.5M | if (m_use_premultiplied_alpha == UsePremultipliedAlpha::Yes) |
91 | 0 | return a.mixed_with(b, amount); |
92 | 65.5M | return a.interpolate(b, amount); |
93 | 65.5M | } |
94 | | |
95 | | Color get_color(i64 index) const |
96 | 131M | { |
97 | 131M | if (index < 0) |
98 | 543k | return m_color_stops.first().color; |
99 | 130M | if (index >= static_cast<i64>(m_gradient_line_colors.size())) |
100 | 86.8M | return m_color_stops.last().color; |
101 | 44.1M | return m_gradient_line_colors[index]; |
102 | 130M | } |
103 | | |
104 | | Color sample_color(float loc) const |
105 | 66.3M | { |
106 | 66.3M | if (!isfinite(loc)) |
107 | 0 | return Color(); |
108 | 66.3M | if (m_sample_scale != 1.0f) |
109 | 43.2M | loc *= m_sample_scale; |
110 | 131M | auto repeat_wrap_if_required = [&](i64 loc) { |
111 | 131M | if (m_repeat_mode != RepeatMode::None) { |
112 | 0 | auto current_loc = loc + m_start_offset; |
113 | 0 | auto gradient_len = static_cast<i64>(m_gradient_line_colors.size()); |
114 | 0 | if (m_repeat_mode == RepeatMode::Repeat) { |
115 | 0 | return mod(current_loc, gradient_len); |
116 | 0 | } else if (m_repeat_mode == RepeatMode::Reflect) { |
117 | 0 | auto color_loc = AK::abs(current_loc % gradient_len); |
118 | 0 | auto repeats = current_loc / gradient_len; |
119 | 0 | return (repeats & 1) ? gradient_len - color_loc : color_loc; |
120 | 0 | } |
121 | 0 | } |
122 | 131M | return loc; |
123 | 131M | }; |
124 | 66.3M | auto int_loc = static_cast<i64>(floor(loc)); |
125 | 66.3M | auto blend = loc - int_loc; |
126 | 66.3M | auto color = get_color(repeat_wrap_if_required(int_loc)); |
127 | | // Blend between the two neighboring colors (this fixes some nasty aliasing issues at small angles) |
128 | 66.3M | if (blend >= 0.004f) |
129 | 65.1M | color = color_blend(color, get_color(repeat_wrap_if_required(int_loc + 1)), blend); |
130 | 66.3M | return color; |
131 | 66.3M | } |
132 | | |
133 | | void paint_into_physical_rect(Painter& painter, IntRect rect, auto location_transform) |
134 | 0 | { |
135 | 0 | auto clipped_rect = rect.intersected(painter.clip_rect() * painter.scale()); |
136 | 0 | auto start_offset = clipped_rect.location() - rect.location(); |
137 | 0 | for (int y = 0; y < clipped_rect.height(); y++) { |
138 | 0 | for (int x = 0; x < clipped_rect.width(); x++) { |
139 | 0 | auto pixel = sample_color(location_transform(x + start_offset.x(), y + start_offset.y())); |
140 | 0 | painter.set_physical_pixel(clipped_rect.location().translated(x, y), pixel, m_requires_blending); |
141 | 0 | } |
142 | 0 | } |
143 | 0 | } Unexecuted instantiation: GradientPainting.cpp:void Gfx::GradientLine::paint_into_physical_rect<Gfx::create_linear_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, float, AK::Optional<float>)::$_0>(Gfx::Painter&, Gfx::Rect<int>, Gfx::create_linear_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, float, AK::Optional<float>)::$_0) Unexecuted instantiation: GradientPainting.cpp:void Gfx::GradientLine::paint_into_physical_rect<Gfx::create_conic_gradient(AK::Span<Gfx::ColorStop const>, Gfx::Point<float>, float, AK::Optional<float>, Gfx::UsePremultipliedAlpha)::$_0>(Gfx::Painter&, Gfx::Rect<int>, Gfx::create_conic_gradient(AK::Span<Gfx::ColorStop const>, Gfx::Point<float>, float, AK::Optional<float>, Gfx::UsePremultipliedAlpha)::$_0) Unexecuted instantiation: GradientPainting.cpp:void Gfx::GradientLine::paint_into_physical_rect<Gfx::create_radial_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, Gfx::Point<int>, Gfx::Size<int>, AK::Optional<float>, AK::Optional<float>)::$_0>(Gfx::Painter&, Gfx::Rect<int>, Gfx::create_radial_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, Gfx::Point<int>, Gfx::Size<int>, AK::Optional<float>, AK::Optional<float>)::$_0) |
144 | | |
145 | | bool repeating() const |
146 | 9.20k | { |
147 | 9.20k | return m_repeat_mode != RepeatMode::None; |
148 | 9.20k | } |
149 | | |
150 | | enum class RepeatMode { |
151 | | None, |
152 | | Repeat, |
153 | | Reflect |
154 | | }; |
155 | | |
156 | | void set_repeat_mode(RepeatMode repeat_mode) |
157 | 9.20k | { |
158 | | // Note: A gradient can be set to repeating without a repeat length. |
159 | | // The repeat length is used for CSS gradients but not for SVG gradients. |
160 | 9.20k | m_repeat_mode = repeat_mode; |
161 | 9.20k | } |
162 | | |
163 | | private: |
164 | | RepeatMode m_repeat_mode { RepeatMode::None }; |
165 | | int m_start_offset { 0 }; |
166 | | float m_sample_scale { 1 }; |
167 | | ReadonlySpan<ColorStop> m_color_stops {}; |
168 | | UsePremultipliedAlpha m_use_premultiplied_alpha { UsePremultipliedAlpha::Yes }; |
169 | | |
170 | | Vector<Color, 1024> m_gradient_line_colors; |
171 | | bool m_requires_blending = false; |
172 | | }; |
173 | | |
174 | | template<typename TransformFunction> |
175 | | struct Gradient { |
176 | | Gradient(GradientLine gradient_line, TransformFunction transform_function) |
177 | 9.20k | : m_gradient_line(move(gradient_line)) |
178 | 9.20k | , m_transform_function(move(transform_function)) |
179 | 9.20k | { |
180 | 9.20k | } Unexecuted instantiation: GradientPainting.cpp:Gfx::Gradient<Gfx::create_linear_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, float, AK::Optional<float>)::$_0>::Gradient(Gfx::GradientLine, Gfx::create_linear_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, float, AK::Optional<float>)::$_0) Unexecuted instantiation: GradientPainting.cpp:Gfx::Gradient<Gfx::create_conic_gradient(AK::Span<Gfx::ColorStop const>, Gfx::Point<float>, float, AK::Optional<float>, Gfx::UsePremultipliedAlpha)::$_0>::Gradient(Gfx::GradientLine, Gfx::create_conic_gradient(AK::Span<Gfx::ColorStop const>, Gfx::Point<float>, float, AK::Optional<float>, Gfx::UsePremultipliedAlpha)::$_0) Unexecuted instantiation: GradientPainting.cpp:Gfx::Gradient<Gfx::create_radial_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, Gfx::Point<int>, Gfx::Size<int>, AK::Optional<float>, AK::Optional<float>)::$_0>::Gradient(Gfx::GradientLine, Gfx::create_radial_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, Gfx::Point<int>, Gfx::Size<int>, AK::Optional<float>, AK::Optional<float>)::$_0) GradientPainting.cpp:Gfx::Gradient<Gfx::make_linear_gradient_between_two_points(Gfx::Point<float>, Gfx::Point<float>, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_0>::Gradient(Gfx::GradientLine, Gfx::make_linear_gradient_between_two_points(Gfx::Point<float>, Gfx::Point<float>, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_0) Line | Count | Source | 177 | 5.00k | : m_gradient_line(move(gradient_line)) | 178 | 5.00k | , m_transform_function(move(transform_function)) | 179 | 5.00k | { | 180 | 5.00k | } |
GradientPainting.cpp:Gfx::Gradient<Gfx::create_radial_gradient_between_two_circles(Gfx::Point<float>, float, Gfx::Point<float>, float, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_2>::Gradient(Gfx::GradientLine, Gfx::create_radial_gradient_between_two_circles(Gfx::Point<float>, float, Gfx::Point<float>, float, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_2) Line | Count | Source | 177 | 4.20k | : m_gradient_line(move(gradient_line)) | 178 | 4.20k | , m_transform_function(move(transform_function)) | 179 | 4.20k | { | 180 | 4.20k | } |
|
181 | | |
182 | | void paint(Painter& painter, IntRect rect) |
183 | 0 | { |
184 | 0 | m_gradient_line.paint_into_physical_rect(painter, rect, m_transform_function); |
185 | 0 | } Unexecuted instantiation: GradientPainting.cpp:Gfx::Gradient<Gfx::create_linear_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, float, AK::Optional<float>)::$_0>::paint(Gfx::Painter&, Gfx::Rect<int>) Unexecuted instantiation: GradientPainting.cpp:Gfx::Gradient<Gfx::create_conic_gradient(AK::Span<Gfx::ColorStop const>, Gfx::Point<float>, float, AK::Optional<float>, Gfx::UsePremultipliedAlpha)::$_0>::paint(Gfx::Painter&, Gfx::Rect<int>) Unexecuted instantiation: GradientPainting.cpp:Gfx::Gradient<Gfx::create_radial_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, Gfx::Point<int>, Gfx::Size<int>, AK::Optional<float>, AK::Optional<float>)::$_0>::paint(Gfx::Painter&, Gfx::Rect<int>) |
186 | | |
187 | | template<typename CoordinateType = int> |
188 | | auto sample_function() |
189 | 9.20k | { |
190 | 66.3M | return [this](Point<CoordinateType> point) { |
191 | 66.3M | return m_gradient_line.sample_color(m_transform_function(point.x(), point.y())); |
192 | 66.3M | }; Unexecuted instantiation: GradientPainting.cpp:Gfx::Gradient<Gfx::create_linear_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, float, AK::Optional<float>)::$_0>::sample_function<int>()::{lambda(Gfx::Point<int>)#1}::operator()(Gfx::Point<int>) constUnexecuted instantiation: GradientPainting.cpp:Gfx::Gradient<Gfx::create_conic_gradient(AK::Span<Gfx::ColorStop const>, Gfx::Point<float>, float, AK::Optional<float>, Gfx::UsePremultipliedAlpha)::$_0>::sample_function<int>()::{lambda(Gfx::Point<int>)#1}::operator()(Gfx::Point<int>) constUnexecuted instantiation: GradientPainting.cpp:Gfx::Gradient<Gfx::create_radial_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, Gfx::Point<int>, Gfx::Size<int>, AK::Optional<float>, AK::Optional<float>)::$_0>::sample_function<int>()::{lambda(Gfx::Point<int>)#1}::operator()(Gfx::Point<int>) constUnexecuted instantiation: GradientPainting.cpp:Gfx::Gradient<Gfx::make_linear_gradient_between_two_points(Gfx::Point<float>, Gfx::Point<float>, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_0>::sample_function<int>()::{lambda(Gfx::Point<int>)#1}::operator()(Gfx::Point<int>) constGradientPainting.cpp:Gfx::Gradient<Gfx::make_linear_gradient_between_two_points(Gfx::Point<float>, Gfx::Point<float>, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_0>::sample_function<float>()::{lambda(Gfx::Point<float>)#1}::operator()(Gfx::Point<float>) constLine | Count | Source | 190 | 23.1M | return [this](Point<CoordinateType> point) { | 191 | 23.1M | return m_gradient_line.sample_color(m_transform_function(point.x(), point.y())); | 192 | 23.1M | }; |
Unexecuted instantiation: GradientPainting.cpp:Gfx::Gradient<Gfx::create_radial_gradient_between_two_circles(Gfx::Point<float>, float, Gfx::Point<float>, float, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_2>::sample_function<int>()::{lambda(Gfx::Point<int>)#1}::operator()(Gfx::Point<int>) constGradientPainting.cpp:Gfx::Gradient<Gfx::create_radial_gradient_between_two_circles(Gfx::Point<float>, float, Gfx::Point<float>, float, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_2>::sample_function<float>()::{lambda(Gfx::Point<float>)#1}::operator()(Gfx::Point<float>) constLine | Count | Source | 190 | 43.1M | return [this](Point<CoordinateType> point) { | 191 | 43.1M | return m_gradient_line.sample_color(m_transform_function(point.x(), point.y())); | 192 | 43.1M | }; |
|
193 | 9.20k | } Unexecuted instantiation: GradientPainting.cpp:auto Gfx::Gradient<Gfx::create_linear_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, float, AK::Optional<float>)::$_0>::sample_function<int>() Unexecuted instantiation: GradientPainting.cpp:auto Gfx::Gradient<Gfx::create_conic_gradient(AK::Span<Gfx::ColorStop const>, Gfx::Point<float>, float, AK::Optional<float>, Gfx::UsePremultipliedAlpha)::$_0>::sample_function<int>() Unexecuted instantiation: GradientPainting.cpp:auto Gfx::Gradient<Gfx::create_radial_gradient(Gfx::Rect<int> const&, AK::Span<Gfx::ColorStop const>, Gfx::Point<int>, Gfx::Size<int>, AK::Optional<float>, AK::Optional<float>)::$_0>::sample_function<int>() Unexecuted instantiation: GradientPainting.cpp:auto Gfx::Gradient<Gfx::make_linear_gradient_between_two_points(Gfx::Point<float>, Gfx::Point<float>, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_0>::sample_function<int>() GradientPainting.cpp:auto Gfx::Gradient<Gfx::make_linear_gradient_between_two_points(Gfx::Point<float>, Gfx::Point<float>, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_0>::sample_function<float>() Line | Count | Source | 189 | 5.00k | { | 190 | 5.00k | return [this](Point<CoordinateType> point) { | 191 | 5.00k | return m_gradient_line.sample_color(m_transform_function(point.x(), point.y())); | 192 | 5.00k | }; | 193 | 5.00k | } |
Unexecuted instantiation: GradientPainting.cpp:auto Gfx::Gradient<Gfx::create_radial_gradient_between_two_circles(Gfx::Point<float>, float, Gfx::Point<float>, float, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_2>::sample_function<int>() GradientPainting.cpp:auto Gfx::Gradient<Gfx::create_radial_gradient_between_two_circles(Gfx::Point<float>, float, Gfx::Point<float>, float, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_2>::sample_function<float>() Line | Count | Source | 189 | 4.20k | { | 190 | 4.20k | return [this](Point<CoordinateType> point) { | 191 | 4.20k | return m_gradient_line.sample_color(m_transform_function(point.x(), point.y())); | 192 | 4.20k | }; | 193 | 4.20k | } |
|
194 | | |
195 | | GradientLine& gradient_line() |
196 | 9.20k | { |
197 | 9.20k | return m_gradient_line; |
198 | 9.20k | } GradientPainting.cpp:Gfx::Gradient<Gfx::make_linear_gradient_between_two_points(Gfx::Point<float>, Gfx::Point<float>, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_0>::gradient_line() Line | Count | Source | 196 | 5.00k | { | 197 | 5.00k | return m_gradient_line; | 198 | 5.00k | } |
GradientPainting.cpp:Gfx::Gradient<Gfx::create_radial_gradient_between_two_circles(Gfx::Point<float>, float, Gfx::Point<float>, float, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_2>::gradient_line() Line | Count | Source | 196 | 4.20k | { | 197 | 4.20k | return m_gradient_line; | 198 | 4.20k | } |
|
199 | | |
200 | | private: |
201 | | GradientLine m_gradient_line; |
202 | | TransformFunction m_transform_function; |
203 | | }; |
204 | | |
205 | | static auto create_linear_gradient(IntRect const& physical_rect, ReadonlySpan<ColorStop> color_stops, float angle, Optional<float> repeat_length) |
206 | 0 | { |
207 | 0 | float normalized_angle = normalized_gradient_angle_radians(angle); |
208 | 0 | float sin_angle, cos_angle; |
209 | 0 | AK::sincos(normalized_angle, sin_angle, cos_angle); |
210 | | |
211 | | // Full length of the gradient |
212 | 0 | auto gradient_length = calculate_gradient_length(physical_rect.size(), sin_angle, cos_angle); |
213 | 0 | IntPoint offset { cos_angle * (gradient_length / 2), sin_angle * (gradient_length / 2) }; |
214 | 0 | auto center = physical_rect.translated(-physical_rect.location()).center(); |
215 | 0 | auto start_point = center - offset; |
216 | | // Rotate gradient line to be horizontal |
217 | 0 | auto rotated_start_point_x = start_point.x() * cos_angle - start_point.y() * -sin_angle; |
218 | |
|
219 | 0 | GradientLine gradient_line(gradient_length, color_stops, repeat_length); |
220 | 0 | return Gradient { |
221 | 0 | move(gradient_line), |
222 | 0 | [=](int x, int y) { |
223 | 0 | return (x * cos_angle - (physical_rect.height() - y) * -sin_angle) - rotated_start_point_x; |
224 | 0 | } |
225 | 0 | }; |
226 | 0 | } |
227 | | |
228 | | static auto create_conic_gradient(ReadonlySpan<ColorStop> color_stops, FloatPoint center_point, float start_angle, Optional<float> repeat_length, UsePremultipliedAlpha use_premultiplied_alpha = UsePremultipliedAlpha::Yes) |
229 | 0 | { |
230 | | // FIXME: Do we need/want sub-degree accuracy for the gradient line? |
231 | 0 | GradientLine gradient_line(360, color_stops, repeat_length, use_premultiplied_alpha); |
232 | 0 | float normalized_start_angle = (360.0f - start_angle) + 90.0f; |
233 | | // The flooring can make gradients that want soft edges look worse, so only floor if we have hard edges. |
234 | | // Which makes sure the hard edge stay hard edges :^) |
235 | 0 | bool should_floor_angles = false; |
236 | 0 | for (size_t i = 0; i < color_stops.size() - 1; i++) { |
237 | 0 | if (color_stops[i + 1].position - color_stops[i].position <= 0.01f) { |
238 | 0 | should_floor_angles = true; |
239 | 0 | break; |
240 | 0 | } |
241 | 0 | } |
242 | 0 | return Gradient { |
243 | 0 | move(gradient_line), |
244 | 0 | [=](int x, int y) { |
245 | 0 | auto point = FloatPoint { x, y } - center_point; |
246 | | // FIXME: We could probably get away with some approximation here: |
247 | 0 | auto loc = fmod((AK::to_degrees(AK::atan2(point.y(), point.x())) + 360.0f + normalized_start_angle), 360.0f); |
248 | 0 | return should_floor_angles ? floor(loc) : loc; |
249 | 0 | } |
250 | 0 | }; |
251 | 0 | } |
252 | | |
253 | | static auto create_radial_gradient(IntRect const& physical_rect, ReadonlySpan<ColorStop> color_stops, IntPoint center, IntSize size, Optional<float> repeat_length, Optional<float> rotation_angle = {}) |
254 | 0 | { |
255 | | // A conservative guesstimate on how many colors we need to generate: |
256 | 0 | auto max_dimension = max(physical_rect.width(), physical_rect.height()); |
257 | 0 | auto max_visible_gradient = max(max_dimension / 2, min(size.width(), max_dimension)); |
258 | 0 | GradientLine gradient_line(max_visible_gradient, color_stops, repeat_length); |
259 | 0 | auto center_point = FloatPoint { center }.translated(0.5, 0.5); |
260 | 0 | AffineTransform rotation_transform; |
261 | 0 | if (rotation_angle.has_value()) { |
262 | 0 | auto angle_as_radians = AK::to_radians(rotation_angle.value()); |
263 | 0 | rotation_transform.rotate_radians(angle_as_radians); |
264 | 0 | } |
265 | |
|
266 | 0 | return Gradient { |
267 | 0 | move(gradient_line), |
268 | 0 | [=](int x, int y) { |
269 | | // FIXME: See if there's a more efficient calculation we do there :^) |
270 | 0 | auto point = FloatPoint(x, y) - center_point; |
271 | |
|
272 | 0 | if (rotation_angle.has_value()) |
273 | 0 | point.transform_by(rotation_transform); |
274 | |
|
275 | 0 | auto gradient_x = point.x() / size.width(); |
276 | 0 | auto gradient_y = point.y() / size.height(); |
277 | 0 | return AK::sqrt(gradient_x * gradient_x + gradient_y * gradient_y) * max_visible_gradient; |
278 | 0 | } |
279 | 0 | }; |
280 | 0 | } |
281 | | |
282 | | void Painter::fill_rect_with_linear_gradient(IntRect const& rect, ReadonlySpan<ColorStop> color_stops, float angle, Optional<float> repeat_length) |
283 | 0 | { |
284 | 0 | auto a_rect = to_physical(rect); |
285 | 0 | if (a_rect.intersected(clip_rect() * scale()).is_empty()) |
286 | 0 | return; |
287 | 0 | auto linear_gradient = create_linear_gradient(a_rect, color_stops, angle, repeat_length); |
288 | 0 | linear_gradient.paint(*this, a_rect); |
289 | 0 | } |
290 | | |
291 | | static FloatPoint pixel_center(IntPoint point) |
292 | 0 | { |
293 | 0 | return point.to_type<float>().translated(0.5f, 0.5f); |
294 | 0 | } |
295 | | |
296 | | void Painter::fill_rect_with_conic_gradient(IntRect const& rect, ReadonlySpan<ColorStop> color_stops, IntPoint center, float start_angle, Optional<float> repeat_length) |
297 | 0 | { |
298 | 0 | auto a_rect = to_physical(rect); |
299 | 0 | if (a_rect.intersected(clip_rect() * scale()).is_empty()) |
300 | 0 | return; |
301 | | // Translate position/center to the center of the pixel (avoids some funky painting) |
302 | 0 | auto center_point = pixel_center(center * scale()); |
303 | 0 | auto conic_gradient = create_conic_gradient(color_stops, center_point, start_angle, repeat_length); |
304 | 0 | conic_gradient.paint(*this, a_rect); |
305 | 0 | } |
306 | | |
307 | | void Painter::fill_rect_with_radial_gradient(IntRect const& rect, ReadonlySpan<ColorStop> color_stops, IntPoint center, IntSize size, Optional<float> repeat_length, Optional<float> rotation_angle) |
308 | 0 | { |
309 | 0 | auto a_rect = to_physical(rect); |
310 | 0 | if (a_rect.intersected(clip_rect() * scale()).is_empty()) |
311 | 0 | return; |
312 | | |
313 | 0 | auto radial_gradient = create_radial_gradient(a_rect, color_stops, center * scale(), size * scale(), repeat_length, rotation_angle); |
314 | 0 | radial_gradient.paint(*this, a_rect); |
315 | 0 | } |
316 | | |
317 | | // TODO: Figure out how to handle scale() here... Not important while not supported by fill_path() |
318 | | |
319 | | void LinearGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunction paint) const |
320 | 0 | { |
321 | 0 | VERIFY(color_stops().size() > 2); |
322 | 0 | auto linear_gradient = create_linear_gradient(physical_bounding_box, color_stops(), m_angle, repeat_length()); |
323 | 0 | paint(linear_gradient.sample_function()); |
324 | 0 | } |
325 | | |
326 | | void ConicGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunction paint) const |
327 | 0 | { |
328 | 0 | VERIFY(color_stops().size() > 2); |
329 | 0 | (void)physical_bounding_box; |
330 | 0 | auto conic_gradient = create_conic_gradient(color_stops(), pixel_center(m_center), m_start_angle, repeat_length()); |
331 | 0 | paint(conic_gradient.sample_function()); |
332 | 0 | } |
333 | | |
334 | | void RadialGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunction paint) const |
335 | 0 | { |
336 | 0 | VERIFY(color_stops().size() > 2); |
337 | 0 | auto radial_gradient = create_radial_gradient(physical_bounding_box, color_stops(), m_center, m_size, repeat_length()); |
338 | 0 | paint(radial_gradient.sample_function()); |
339 | 0 | } |
340 | | |
341 | | // The following implements the gradient fill/stoke styles for the HTML canvas: https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles |
342 | | |
343 | | static auto make_sample_non_relative(IntPoint draw_location, auto sample) |
344 | 0 | { |
345 | 0 | return [=, sample = move(sample)](IntPoint point) { return sample(point.translated(draw_location)); };Unexecuted instantiation: GradientPainting.cpp:Gfx::make_sample_non_relative<Gfx::Gradient<Gfx::make_linear_gradient_between_two_points(Gfx::Point<float>, Gfx::Point<float>, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_0>::sample_function<int>()::{lambda(Gfx::Point<int>)#1}>(Gfx::Point<int>, Gfx::Gradient<Gfx::make_linear_gradient_between_two_points(Gfx::Point<float>, Gfx::Point<float>, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_0>::sample_function<int>()::{lambda(Gfx::Point<int>)#1})::{lambda(Gfx::Point<int>)#1}::operator()(Gfx::Point<int>) constUnexecuted instantiation: GradientPainting.cpp:Gfx::make_sample_non_relative<Gfx::Gradient<Gfx::create_conic_gradient(AK::Span<Gfx::ColorStop const>, Gfx::Point<float>, float, AK::Optional<float>, Gfx::UsePremultipliedAlpha)::$_0>::sample_function<int>()::{lambda(Gfx::Point<int>)#1}>(Gfx::Point<int>, Gfx::Gradient<Gfx::create_conic_gradient(AK::Span<Gfx::ColorStop const>, Gfx::Point<float>, float, AK::Optional<float>, Gfx::UsePremultipliedAlpha)::$_0>::sample_function<int>()::{lambda(Gfx::Point<int>)#1})::{lambda(Gfx::Point<int>)#1}::operator()(Gfx::Point<int>) constUnexecuted instantiation: GradientPainting.cpp:Gfx::make_sample_non_relative<Gfx::Gradient<Gfx::create_radial_gradient_between_two_circles(Gfx::Point<float>, float, Gfx::Point<float>, float, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_2>::sample_function<int>()::{lambda(Gfx::Point<int>)#1}>(Gfx::Point<int>, Gfx::Gradient<Gfx::create_radial_gradient_between_two_circles(Gfx::Point<float>, float, Gfx::Point<float>, float, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_2>::sample_function<int>()::{lambda(Gfx::Point<int>)#1})::{lambda(Gfx::Point<int>)#1}::operator()(Gfx::Point<int>) const |
346 | 0 | } Unexecuted instantiation: GradientPainting.cpp:auto Gfx::make_sample_non_relative<Gfx::Gradient<Gfx::make_linear_gradient_between_two_points(Gfx::Point<float>, Gfx::Point<float>, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_0>::sample_function<int>()::{lambda(Gfx::Point<int>)#1}>(Gfx::Point<int>, Gfx::Gradient<Gfx::make_linear_gradient_between_two_points(Gfx::Point<float>, Gfx::Point<float>, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_0>::sample_function<int>()::{lambda(Gfx::Point<int>)#1})Unexecuted instantiation: GradientPainting.cpp:auto Gfx::make_sample_non_relative<Gfx::Gradient<Gfx::create_conic_gradient(AK::Span<Gfx::ColorStop const>, Gfx::Point<float>, float, AK::Optional<float>, Gfx::UsePremultipliedAlpha)::$_0>::sample_function<int>()::{lambda(Gfx::Point<int>)#1}>(Gfx::Point<int>, Gfx::Gradient<Gfx::create_conic_gradient(AK::Span<Gfx::ColorStop const>, Gfx::Point<float>, float, AK::Optional<float>, Gfx::UsePremultipliedAlpha)::$_0>::sample_function<int>()::{lambda(Gfx::Point<int>)#1})Unexecuted instantiation: GradientPainting.cpp:auto Gfx::make_sample_non_relative<Gfx::Gradient<Gfx::create_radial_gradient_between_two_circles(Gfx::Point<float>, float, Gfx::Point<float>, float, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_2>::sample_function<int>()::{lambda(Gfx::Point<int>)#1}>(Gfx::Point<int>, Gfx::Gradient<Gfx::create_radial_gradient_between_two_circles(Gfx::Point<float>, float, Gfx::Point<float>, float, AK::Span<Gfx::ColorStop const>, AK::Optional<float>)::$_2>::sample_function<int>()::{lambda(Gfx::Point<int>)#1}) |
347 | | |
348 | | static auto make_linear_gradient_between_two_points(FloatPoint p0, FloatPoint p1, ReadonlySpan<ColorStop> color_stops, Optional<float> repeat_length) |
349 | 5.00k | { |
350 | 5.00k | auto delta = p1 - p0; |
351 | 5.00k | auto angle = AK::atan2(delta.y(), delta.x()); |
352 | 5.00k | float sin_angle, cos_angle; |
353 | 5.00k | AK::sincos(angle, sin_angle, cos_angle); |
354 | 5.00k | int gradient_length = ceilf(p1.distance_from(p0)); |
355 | 5.00k | auto rotated_start_point_x = p0.x() * cos_angle - p0.y() * -sin_angle; |
356 | | |
357 | 5.00k | return Gradient { |
358 | 5.00k | GradientLine(gradient_length, color_stops, repeat_length, UsePremultipliedAlpha::No), |
359 | 23.1M | [=](int x, int y) { |
360 | 23.1M | return (x * cos_angle - y * -sin_angle) - rotated_start_point_x; |
361 | 23.1M | } |
362 | 5.00k | }; |
363 | 5.00k | } |
364 | | |
365 | | void CanvasLinearGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunction paint) const |
366 | 0 | { |
367 | | // If x0 = x1 and y0 = y1, then the linear gradient must paint nothing. |
368 | 0 | if (m_p0 == m_p1) |
369 | 0 | return; |
370 | 0 | if (color_stops().is_empty()) |
371 | 0 | return; |
372 | 0 | if (color_stops().size() < 2) |
373 | 0 | return paint([this](IntPoint) { return color_stops().first().color; }); |
374 | | |
375 | 0 | auto linear_gradient = make_linear_gradient_between_two_points(m_p0, m_p1, color_stops(), repeat_length()); |
376 | 0 | paint(make_sample_non_relative(physical_bounding_box.location(), linear_gradient.sample_function())); |
377 | 0 | } |
378 | | |
379 | | static GradientLine::RepeatMode svg_spread_method_to_repeat_mode(SVGGradientPaintStyle::SpreadMethod spread_method) |
380 | 9.20k | { |
381 | 9.20k | switch (spread_method) { |
382 | 9.20k | case SVGGradientPaintStyle::SpreadMethod::Pad: |
383 | 9.20k | return GradientLine::RepeatMode::None; |
384 | 0 | case SVGGradientPaintStyle::SpreadMethod::Reflect: |
385 | 0 | return GradientLine::RepeatMode::Reflect; |
386 | 0 | case SVGGradientPaintStyle::SpreadMethod::Repeat: |
387 | 0 | return GradientLine::RepeatMode::Repeat; |
388 | 0 | default: |
389 | 0 | VERIFY_NOT_REACHED(); |
390 | 9.20k | } |
391 | 9.20k | } |
392 | | |
393 | | void SVGGradientPaintStyle::set_gradient_transform(AffineTransform transform) |
394 | 883k | { |
395 | | // Note: The scaling is removed so enough points on the gradient line are generated. |
396 | | // Otherwise, if you scale a tiny path the gradient looks pixelated. |
397 | 883k | m_scale = 1.0f; |
398 | 883k | if (auto inverse = transform.inverse(); inverse.has_value()) { |
399 | 883k | auto transform_scale = transform.scale(); |
400 | 883k | m_scale = max(transform_scale.x(), transform_scale.y()); |
401 | 883k | m_inverse_transform = AffineTransform {}.scale(m_scale, m_scale).multiply(*inverse); |
402 | 883k | } else { |
403 | 0 | m_inverse_transform = OptionalNone {}; |
404 | 0 | } |
405 | 883k | } |
406 | | |
407 | | void SVGLinearGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunction paint) const |
408 | 877k | { |
409 | 877k | if (color_stops().is_empty()) |
410 | 0 | return; |
411 | | // If ‘x1’ = ‘x2’ and ‘y1’ = ‘y2’, then the area to be painted will be painted as |
412 | | // a single color using the color and opacity of the last gradient stop. |
413 | 877k | if (m_p0 == m_p1) |
414 | 23.5M | return paint([this](IntPoint) { return color_stops().last().color; }); |
415 | 5.00k | if (color_stops().size() < 2) |
416 | 0 | return paint([this](IntPoint) { return color_stops().first().color; }); |
417 | | |
418 | 5.00k | float scale = gradient_transform_scale(); |
419 | 5.00k | auto linear_gradient = make_linear_gradient_between_two_points( |
420 | 5.00k | m_p0.scaled(scale, scale), m_p1.scaled(scale, scale), |
421 | 5.00k | color_stops(), repeat_length()); |
422 | 5.00k | linear_gradient.gradient_line().set_repeat_mode( |
423 | 5.00k | svg_spread_method_to_repeat_mode(spread_method())); |
424 | | |
425 | 23.1M | paint([&, sampler = linear_gradient.sample_function<float>()](IntPoint target_point) { |
426 | 23.1M | auto point = target_point.translated(physical_bounding_box.location()).to_type<float>(); |
427 | 23.1M | if (auto inverse_transform = scale_adjusted_inverse_gradient_transform(); inverse_transform.has_value()) |
428 | 23.1M | point = inverse_transform->map(point); |
429 | | |
430 | 23.1M | return sampler(point); |
431 | 23.1M | }); |
432 | 5.00k | } |
433 | | |
434 | | void CanvasConicGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunction paint) const |
435 | 0 | { |
436 | 0 | if (color_stops().is_empty()) |
437 | 0 | return; |
438 | 0 | if (color_stops().size() < 2) |
439 | 0 | return paint([this](IntPoint) { return color_stops().first().color; }); |
440 | | |
441 | | // Follows the same rendering rule as CSS 'conic-gradient' and it is equivalent to CSS |
442 | | // 'conic-gradient(from adjustedStartAnglerad at xpx ypx, angularColorStopList)'. |
443 | | // Here: |
444 | | // adjustedStartAngle is given by startAngle + π/2; |
445 | 0 | auto conic_gradient = create_conic_gradient(color_stops(), m_center, m_start_angle + 90.0f, repeat_length(), UsePremultipliedAlpha::No); |
446 | 0 | paint(make_sample_non_relative(physical_bounding_box.location(), conic_gradient.sample_function())); |
447 | 0 | } |
448 | | |
449 | | static auto create_radial_gradient_between_two_circles(Gfx::FloatPoint start_center, float start_radius, Gfx::FloatPoint end_center, float end_radius, ReadonlySpan<ColorStop> color_stops, Optional<float> repeat_length) |
450 | 4.20k | { |
451 | 4.20k | bool reverse_gradient = end_radius < start_radius; |
452 | 4.20k | if (reverse_gradient) { |
453 | 0 | swap(end_radius, start_radius); |
454 | 0 | swap(end_center, start_center); |
455 | 0 | } |
456 | | |
457 | | // FIXME: Handle the start_radius == end_radius special case separately. |
458 | | // This hack is not quite correct. |
459 | 4.20k | if (end_radius - start_radius < 1) |
460 | 1.52k | end_radius += 1; |
461 | | |
462 | | // Spec steps: Useless for writing an actual implementation (give it a go :P): |
463 | | // |
464 | | // 2. Let x(ω) = (x1-x0)ω + x0 |
465 | | // Let y(ω) = (y1-y0)ω + y0 |
466 | | // Let r(ω) = (r1-r0)ω + r0 |
467 | | // Let the color at ω be the color at that position on the gradient |
468 | | // (with the colors coming from the interpolation and extrapolation described above). |
469 | | // |
470 | | // 3. For all values of ω where r(ω) > 0, starting with the value of ω nearest to positive infinity and |
471 | | // ending with the value of ω nearest to negative infinity, draw the circumference of the circle with |
472 | | // radius r(ω) at position (x(ω), y(ω)), with the color at ω, but only painting on the parts of the |
473 | | // bitmap that have not yet been painted on by earlier circles in this step for this rendering of the gradient. |
474 | | |
475 | 4.20k | auto center_dist = end_center.distance_from(start_center); |
476 | 4.20k | bool inner_contained = ((center_dist + start_radius) < end_radius); |
477 | | |
478 | 4.20k | auto start_point = start_center; |
479 | 4.20k | if (start_radius != 0) { |
480 | | // Set the start point to the focal point. |
481 | 0 | auto f = end_radius / (end_radius - start_radius); |
482 | 0 | auto one_minus_f = 1 - f; |
483 | 0 | start_point = start_center.scaled(f) + end_center.scaled(one_minus_f); |
484 | 0 | } |
485 | | |
486 | | // This is just an approximate upperbound (the gradient line class will shorten this if necessary). |
487 | 4.20k | int gradient_length = AK::ceil(center_dist + end_radius + start_radius); |
488 | 4.20k | GradientLine gradient_line(gradient_length, color_stops, repeat_length, UsePremultipliedAlpha::No); |
489 | | |
490 | | // If you can simplify this please do, this is "best guess" implementation due to lack of specification. |
491 | | // It was implemented to visually match chrome/firefox in all cases: |
492 | | // - Start circle inside end circle |
493 | | // - Start circle outside end circle |
494 | | // - Start circle radius == end circle radius |
495 | | // - Start circle larger than end circle (inside end circle) |
496 | | // - Start circle larger than end circle (outside end circle) |
497 | | // - Start circle or end circle radius == 0 |
498 | | |
499 | 8.40k | auto circle_distance_finder = [=](auto radius, auto center) { |
500 | 8.40k | auto radius2 = radius * radius; |
501 | 8.40k | auto delta = center - start_point; |
502 | 8.40k | auto delta_xy = delta.x() * delta.y(); |
503 | 8.40k | auto dx2_factor = radius2 - delta.y() * delta.y(); |
504 | 8.40k | auto dy2_factor = radius2 - delta.x() * delta.x(); |
505 | 43.1M | return [=](bool positive_root, auto vec) { |
506 | | // This works out the distance to the nearest point on the circle |
507 | | // in the direction of the "vec" vector. |
508 | 43.1M | auto dx2 = vec.x() * vec.x(); |
509 | 43.1M | auto dy2 = vec.y() * vec.y(); |
510 | 43.1M | auto root = sqrtf(dx2 * dx2_factor + dy2 * dy2_factor |
511 | 43.1M | + 2 * vec.x() * vec.y() * delta_xy); |
512 | 43.1M | auto dot = vec.x() * delta.x() + vec.y() * delta.y(); |
513 | 43.1M | return ((positive_root ? root : -root) + dot) / (dx2 + dy2); |
514 | 43.1M | }; |
515 | 8.40k | }; |
516 | | |
517 | 4.20k | auto end_circle_dist = circle_distance_finder(end_radius, end_center); |
518 | 43.1M | auto start_circle_dist = [=, dist = circle_distance_finder(start_radius, start_center)](bool positive_root, auto vec) { |
519 | 43.1M | if (start_center == start_point) |
520 | 43.1M | return start_radius; |
521 | 0 | return dist(positive_root, vec); |
522 | 43.1M | }; |
523 | | |
524 | 4.20k | return Gradient { |
525 | 4.20k | move(gradient_line), |
526 | 43.1M | [=](float x, float y) { |
527 | 43.1M | auto loc = [&] { |
528 | 43.1M | FloatPoint point { x, y }; |
529 | | // Add a little to avoid division by zero at the focal point. |
530 | 43.1M | if (point == start_point) |
531 | 390 | point += FloatPoint { 0.001f, 0.001f }; |
532 | | // The "vec" (unit) vector points from the focal point to the current point. |
533 | 43.1M | auto dist = point.distance_from(start_point); |
534 | 43.1M | auto vec = (point - start_point) / dist; |
535 | 43.1M | bool use_positive_root = inner_contained || reverse_gradient; |
536 | 43.1M | auto dist_end = end_circle_dist(use_positive_root, vec); |
537 | 43.1M | auto dist_start = start_circle_dist(use_positive_root, vec); |
538 | | // FIXME: Returning nan is a hack for "Don't paint me!" |
539 | 43.1M | if (dist_end < 0) |
540 | 0 | return AK::NaN<float>; |
541 | 43.1M | if (dist_end - dist_start < 0) |
542 | 0 | return float(gradient_length); |
543 | 43.1M | return (dist - dist_start) / (dist_end - dist_start); |
544 | 43.1M | }(); |
545 | 43.1M | if (reverse_gradient) |
546 | 0 | loc = 1.0f - loc; |
547 | 43.1M | return loc * gradient_length; |
548 | 43.1M | } |
549 | 4.20k | }; |
550 | 4.20k | } |
551 | | |
552 | | void CanvasRadialGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunction paint) const |
553 | 0 | { |
554 | | // 1. If x0 = x1 and y0 = y1 and r0 = r1, then the radial gradient must paint nothing. Return. |
555 | 0 | if (m_start_center == m_end_center && m_start_radius == m_end_radius) |
556 | 0 | return; |
557 | 0 | if (color_stops().is_empty()) |
558 | 0 | return; |
559 | 0 | if (color_stops().size() < 2) |
560 | 0 | return paint([this](IntPoint) { return color_stops().first().color; }); |
561 | 0 | if (m_end_radius == 0 && m_start_radius == 0) |
562 | 0 | return; |
563 | 0 | auto radial_gradient = create_radial_gradient_between_two_circles(m_start_center, m_start_radius, m_end_center, m_end_radius, color_stops(), repeat_length()); |
564 | 0 | paint(make_sample_non_relative(physical_bounding_box.location(), radial_gradient.sample_function())); |
565 | 0 | } |
566 | | |
567 | | void SVGRadialGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunction paint) const |
568 | 6.04k | { |
569 | | // FIXME: Ensure this handles all the edge cases of SVG gradients. |
570 | 6.04k | if (color_stops().is_empty()) |
571 | 0 | return; |
572 | 6.04k | if (color_stops().size() < 2 || (m_end_radius == 0 && m_start_radius == 0)) |
573 | 52.8k | return paint([this](IntPoint) { return color_stops().last().color; }); |
574 | | |
575 | 4.20k | float scale = gradient_transform_scale(); |
576 | 4.20k | auto radial_gradient = create_radial_gradient_between_two_circles( |
577 | 4.20k | m_start_center.scaled(scale, scale), m_start_radius * scale, m_end_center.scaled(scale, scale), m_end_radius * scale, |
578 | 4.20k | color_stops(), repeat_length()); |
579 | 4.20k | radial_gradient.gradient_line().set_repeat_mode( |
580 | 4.20k | svg_spread_method_to_repeat_mode(spread_method())); |
581 | | |
582 | 43.1M | paint([&, sampler = radial_gradient.sample_function<float>()](IntPoint target_point) { |
583 | 43.1M | auto point = target_point.translated(physical_bounding_box.location()).to_type<float>(); |
584 | 43.1M | if (auto inverse_transform = scale_adjusted_inverse_gradient_transform(); inverse_transform.has_value()) |
585 | 43.1M | point = inverse_transform->map(point); |
586 | 43.1M | return sampler(point); |
587 | 43.1M | }); |
588 | 4.20k | } |
589 | | |
590 | | } |