/src/serenity/Userland/Libraries/LibGfx/AntiAliasingPainter.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> |
3 | | * Copyright (c) 2022, Ben Maxwell <macdue@dueutil.tech> |
4 | | * Copyright (c) 2022, Torsten Engelmann <engelTorsten@gmx.de> |
5 | | * |
6 | | * SPDX-License-Identifier: BSD-2-Clause |
7 | | */ |
8 | | |
9 | | #if defined(AK_COMPILER_GCC) |
10 | | # pragma GCC optimize("O3") |
11 | | #endif |
12 | | |
13 | | #include <AK/Function.h> |
14 | | #include <AK/NumericLimits.h> |
15 | | #include <LibGfx/AntiAliasingPainter.h> |
16 | | #include <LibGfx/Line.h> |
17 | | #include <LibGfx/Painter.h> |
18 | | |
19 | | namespace Gfx { |
20 | | |
21 | | void AntiAliasingPainter::draw_line(IntPoint actual_from, IntPoint actual_to, Color color, float thickness, LineStyle style, Color alternate_color) |
22 | 0 | { |
23 | 0 | draw_line(actual_from.to_type<float>(), actual_to.to_type<float>(), color, thickness, style, alternate_color); |
24 | 0 | } |
25 | | |
26 | | static Path::StrokeStyle line_style_to_stroke_style(LineStyle style, float thickness) |
27 | 0 | { |
28 | 0 | switch (style) { |
29 | 0 | case LineStyle::Solid: |
30 | 0 | return Path::StrokeStyle { .thickness = thickness, .cap_style = Path::CapStyle::Butt, .join_style = Path::JoinStyle::Miter }; |
31 | 0 | case LineStyle::Dotted: |
32 | 0 | return Path::StrokeStyle { .thickness = thickness, .cap_style = Path::CapStyle::Round, .join_style = Path::JoinStyle::Round, .dash_pattern = { 0, ceil(thickness / 2) * 4 } }; |
33 | 0 | case LineStyle::Dashed: |
34 | 0 | return Path::StrokeStyle { .thickness = thickness, .cap_style = Path::CapStyle::Butt, .join_style = Path::JoinStyle::Miter, .dash_pattern = { thickness, thickness } }; |
35 | 0 | } |
36 | 0 | VERIFY_NOT_REACHED(); |
37 | 0 | } |
38 | | |
39 | | void AntiAliasingPainter::draw_line(FloatPoint actual_from, FloatPoint actual_to, Color color, float thickness, LineStyle style, Color) |
40 | 0 | { |
41 | 0 | if (color.alpha() == 0) |
42 | 0 | return; |
43 | | |
44 | 0 | Path line; |
45 | 0 | line.move_to(actual_from); |
46 | 0 | line.line_to(actual_to); |
47 | 0 | stroke_path(line, color, line_style_to_stroke_style(style, thickness)); |
48 | 0 | } |
49 | | |
50 | | void AntiAliasingPainter::stroke_path(Path const& path, Color color, Path::StrokeStyle const& stroke_style) |
51 | 38.1k | { |
52 | 38.1k | if (stroke_style.thickness <= 0 || color.alpha() == 0) |
53 | 853 | return; |
54 | | // FIXME: Cache this? Probably at a higher level such as in LibWeb? |
55 | 37.2k | fill_path(path.stroke_to_fill(stroke_style), color); |
56 | 37.2k | } |
57 | | |
58 | | void AntiAliasingPainter::stroke_path(Path const& path, Gfx::PaintStyle const& paint_style, Path::StrokeStyle const& stroke_style, float opacity) |
59 | 6.46k | { |
60 | 6.46k | if (stroke_style.thickness <= 0 || opacity <= 0) |
61 | 418 | return; |
62 | | // FIXME: Cache this? Probably at a higher level such as in LibWeb? |
63 | 6.04k | fill_path(path.stroke_to_fill(stroke_style), paint_style, opacity); |
64 | 6.04k | } |
65 | | |
66 | | void AntiAliasingPainter::fill_rect(FloatRect const& float_rect, Color color) |
67 | 0 | { |
68 | | // Draw the integer part of the rectangle: |
69 | 0 | float right_x = float_rect.x() + float_rect.width(); |
70 | 0 | float bottom_y = float_rect.y() + float_rect.height(); |
71 | 0 | int x1 = ceilf(float_rect.x()); |
72 | 0 | int y1 = ceilf(float_rect.y()); |
73 | 0 | int x2 = floorf(right_x); |
74 | 0 | int y2 = floorf(bottom_y); |
75 | 0 | auto solid_rect = Gfx::IntRect::from_two_points({ x1, y1 }, { x2, y2 }); |
76 | 0 | m_underlying_painter.fill_rect(solid_rect, color); |
77 | |
|
78 | 0 | if (float_rect == solid_rect) |
79 | 0 | return; |
80 | | |
81 | | // Draw the rest: |
82 | 0 | float left_subpixel = x1 - float_rect.x(); |
83 | 0 | float top_subpixel = y1 - float_rect.y(); |
84 | 0 | float right_subpixel = right_x - x2; |
85 | 0 | float bottom_subpixel = bottom_y - y2; |
86 | 0 | float top_left_subpixel = top_subpixel * left_subpixel; |
87 | 0 | float top_right_subpixel = top_subpixel * right_subpixel; |
88 | 0 | float bottom_left_subpixel = bottom_subpixel * left_subpixel; |
89 | 0 | float bottom_right_subpixel = bottom_subpixel * right_subpixel; |
90 | |
|
91 | 0 | auto subpixel = [&](float alpha) { |
92 | 0 | return color.with_alpha(color.alpha() * alpha); |
93 | 0 | }; |
94 | |
|
95 | 0 | auto set_pixel = [&](int x, int y, float alpha) { |
96 | 0 | m_underlying_painter.set_pixel(x, y, subpixel(alpha), true); |
97 | 0 | }; |
98 | |
|
99 | 0 | auto line_to_rect = [&](int x1, int y1, int x2, int y2) { |
100 | 0 | return IntRect::from_two_points({ x1, y1 }, { x2 + 1, y2 + 1 }); |
101 | 0 | }; |
102 | |
|
103 | 0 | set_pixel(x1 - 1, y1 - 1, top_left_subpixel); |
104 | 0 | set_pixel(x2, y1 - 1, top_right_subpixel); |
105 | 0 | set_pixel(x2, y2, bottom_right_subpixel); |
106 | 0 | set_pixel(x1 - 1, y2, bottom_left_subpixel); |
107 | 0 | m_underlying_painter.fill_rect(line_to_rect(x1, y1 - 1, x2 - 1, y1 - 1), subpixel(top_subpixel)); |
108 | 0 | m_underlying_painter.fill_rect(line_to_rect(x1, y2, x2 - 1, y2), subpixel(bottom_subpixel)); |
109 | 0 | m_underlying_painter.fill_rect(line_to_rect(x1 - 1, y1, x1 - 1, y2 - 1), subpixel(left_subpixel)); |
110 | 0 | m_underlying_painter.fill_rect(line_to_rect(x2, y1, x2, y2 - 1), subpixel(right_subpixel)); |
111 | 0 | } |
112 | | |
113 | | void AntiAliasingPainter::draw_ellipse(IntRect const& a_rect, Color color, int thickness) |
114 | 0 | { |
115 | | // FIXME: Come up with an allocation-free version of this! |
116 | | // Using draw_line() for segments of an ellipse was attempted but gave really poor results :^( |
117 | | // There probably is a way to adjust the fill of draw_ellipse_part() to do this, but getting it rendering correctly is tricky. |
118 | | // The outline of the steps required to paint it efficiently is: |
119 | | // - Paint the outer ellipse without the fill (from the fill() lambda in draw_ellipse_part()) |
120 | | // - Paint the inner ellipse, but in the set_pixel() invert the alpha values |
121 | | // - Somehow fill in the gap between the two ellipses (the tricky part to get right) |
122 | | // - Have to avoid overlapping pixels and accidentally painting over some of the edge pixels |
123 | |
|
124 | 0 | auto color_no_alpha = color; |
125 | 0 | color_no_alpha.set_alpha(255); |
126 | 0 | auto outline_ellipse_bitmap = ({ |
127 | 0 | auto bitmap = Bitmap::create(BitmapFormat::BGRA8888, a_rect.size()); |
128 | 0 | if (bitmap.is_error()) |
129 | 0 | return warnln("Failed to allocate temporary bitmap for antialiased outline ellipse!"); |
130 | 0 | bitmap.release_value(); |
131 | 0 | }); |
132 | |
|
133 | 0 | auto outer_rect = a_rect; |
134 | 0 | outer_rect.set_location({ 0, 0 }); |
135 | 0 | auto inner_rect = outer_rect.shrunken(thickness * 2, thickness * 2); |
136 | 0 | Painter painter { outline_ellipse_bitmap }; |
137 | 0 | AntiAliasingPainter aa_painter { painter }; |
138 | 0 | aa_painter.fill_ellipse(outer_rect, color_no_alpha); |
139 | 0 | aa_painter.fill_ellipse(inner_rect, color_no_alpha, BlendMode::AlphaSubtract); |
140 | 0 | m_underlying_painter.blit(a_rect.location(), outline_ellipse_bitmap, outline_ellipse_bitmap->rect(), color.alpha() / 255.); |
141 | 0 | } |
142 | | |
143 | | void AntiAliasingPainter::fill_circle(IntPoint center, int radius, Color color, BlendMode blend_mode) |
144 | 0 | { |
145 | 0 | if (radius <= 0) |
146 | 0 | return; |
147 | 0 | draw_ellipse_part(center, radius, radius, color, false, {}, blend_mode); |
148 | 0 | } |
149 | | |
150 | | void AntiAliasingPainter::fill_ellipse(IntRect const& a_rect, Color color, BlendMode blend_mode) |
151 | 0 | { |
152 | 0 | auto center = a_rect.center(); |
153 | 0 | auto radius_a = a_rect.width() / 2; |
154 | 0 | auto radius_b = a_rect.height() / 2; |
155 | 0 | if (radius_a <= 0 || radius_b <= 0) |
156 | 0 | return; |
157 | 0 | if (radius_a == radius_b) |
158 | 0 | return fill_circle(center, radius_a, color, blend_mode); |
159 | 0 | auto x_paint_range = draw_ellipse_part(center, radius_a, radius_b, color, false, {}, blend_mode); |
160 | | // FIXME: This paints some extra fill pixels that are clipped |
161 | 0 | draw_ellipse_part(center, radius_b, radius_a, color, true, x_paint_range, blend_mode); |
162 | 0 | } |
163 | | |
164 | | FLATTEN AntiAliasingPainter::Range AntiAliasingPainter::draw_ellipse_part( |
165 | | IntPoint center, int radius_a, int radius_b, Color color, bool flip_x_and_y, Optional<Range> x_clip, BlendMode blend_mode) |
166 | 0 | { |
167 | | /* |
168 | | Algorithm from: https://cs.uwaterloo.ca/research/tr/1984/CS-84-38.pdf |
169 | | |
170 | | This method can draw a whole circle with a whole circle in one call using |
171 | | 8-way symmetry, or an ellipse in two calls using 4-way symmetry. |
172 | | */ |
173 | |
|
174 | 0 | center *= m_underlying_painter.scale(); |
175 | 0 | radius_a *= m_underlying_painter.scale(); |
176 | 0 | radius_b *= m_underlying_painter.scale(); |
177 | | |
178 | | // If this is a ellipse everything can be drawn in one pass with 8 way symmetry |
179 | 0 | bool const is_circle = radius_a == radius_b; |
180 | | |
181 | | // These happen to be the same here, but are treated separately in the paper: |
182 | | // intensity is the fill alpha |
183 | 0 | int const intensity = 255; |
184 | | // 0 to subpixel_resolution is the range of alpha values for the circle edges |
185 | 0 | int const subpixel_resolution = intensity; |
186 | | |
187 | | // Current pixel address |
188 | 0 | int i = 0; |
189 | 0 | int q = radius_b; |
190 | | |
191 | | // 1st and 2nd order differences of y |
192 | 0 | int delta_y = 0; |
193 | 0 | int delta2_y = 0; |
194 | |
|
195 | 0 | int const a_squared = radius_a * radius_a; |
196 | 0 | int const b_squared = radius_b * radius_b; |
197 | | |
198 | | // Exact and predicted values of f(i) -- the ellipse equation scaled by subpixel_resolution |
199 | 0 | int y = subpixel_resolution * radius_b; |
200 | 0 | int y_hat = 0; |
201 | | |
202 | | // The value of f(i)*f(i) |
203 | 0 | int f_squared = y * y; |
204 | | |
205 | | // 1st and 2nd order differences of f(i)*f(i) |
206 | 0 | int delta_f_squared = (static_cast<int64_t>(b_squared) * subpixel_resolution * subpixel_resolution) / a_squared; |
207 | 0 | int delta2_f_squared = -delta_f_squared - delta_f_squared; |
208 | | |
209 | | // edge_intersection_area/subpixel_resolution = percentage of pixel intersected by circle |
210 | | // (aka the alpha for the pixel) |
211 | 0 | int edge_intersection_area = 0; |
212 | 0 | int old_area = edge_intersection_area; |
213 | |
|
214 | 0 | auto predict = [&] { |
215 | 0 | delta_y += delta2_y; |
216 | | // y_hat is the predicted value of f(i) |
217 | 0 | y_hat = y + delta_y; |
218 | 0 | }; |
219 | |
|
220 | 0 | auto minimize = [&] { |
221 | | // Initialize the minimization |
222 | 0 | delta_f_squared += delta2_f_squared; |
223 | 0 | f_squared += delta_f_squared; |
224 | |
|
225 | 0 | int min_squared_error = y_hat * y_hat - f_squared; |
226 | 0 | int prediction_overshot = 1; |
227 | 0 | y = y_hat; |
228 | | |
229 | | // Force error negative |
230 | 0 | if (min_squared_error > 0) { |
231 | 0 | min_squared_error = -min_squared_error; |
232 | 0 | prediction_overshot = -1; |
233 | 0 | } |
234 | | |
235 | | // Minimize |
236 | 0 | int previous_error = min_squared_error; |
237 | 0 | while (min_squared_error < 0) { |
238 | 0 | y += prediction_overshot; |
239 | 0 | previous_error = min_squared_error; |
240 | 0 | min_squared_error += y + y - prediction_overshot; |
241 | 0 | } |
242 | |
|
243 | 0 | if (min_squared_error + previous_error > 0) |
244 | 0 | y -= prediction_overshot; |
245 | 0 | }; |
246 | |
|
247 | 0 | auto correct = [&] { |
248 | 0 | int error = y - y_hat; |
249 | |
|
250 | 0 | delta2_y += error; |
251 | 0 | delta_y += error; |
252 | 0 | }; |
253 | |
|
254 | 0 | int min_paint_x = NumericLimits<int>::max(); |
255 | 0 | int max_paint_x = NumericLimits<int>::min(); |
256 | 0 | auto pixel = [&](int x, int y, int alpha) { |
257 | 0 | if (alpha <= 0 || alpha > 255) |
258 | 0 | return; |
259 | 0 | if (flip_x_and_y) |
260 | 0 | swap(x, y); |
261 | 0 | if (x_clip.has_value() && x_clip->contains_inclusive(x)) |
262 | 0 | return; |
263 | 0 | min_paint_x = min(x, min_paint_x); |
264 | 0 | max_paint_x = max(x, max_paint_x); |
265 | 0 | alpha = (alpha * color.alpha()) / 255; |
266 | 0 | if (blend_mode == BlendMode::AlphaSubtract) |
267 | 0 | alpha = ~alpha; |
268 | 0 | auto pixel_color = color; |
269 | 0 | pixel_color.set_alpha(alpha); |
270 | 0 | m_underlying_painter.set_pixel(center + IntPoint { x, y }, pixel_color, blend_mode == BlendMode::Normal); |
271 | 0 | }; |
272 | |
|
273 | 0 | auto fill = [&](int x, int ymax, int ymin, int alpha) { |
274 | 0 | while (ymin <= ymax) { |
275 | 0 | pixel(x, ymin, alpha); |
276 | 0 | ymin += 1; |
277 | 0 | } |
278 | 0 | }; |
279 | |
|
280 | 0 | auto symmetric_pixel = [&](int x, int y, int alpha) { |
281 | 0 | pixel(x, y, alpha); |
282 | 0 | pixel(x, -y - 1, alpha); |
283 | 0 | pixel(-x - 1, -y - 1, alpha); |
284 | 0 | pixel(-x - 1, y, alpha); |
285 | 0 | if (is_circle) { |
286 | 0 | pixel(y, x, alpha); |
287 | 0 | pixel(y, -x - 1, alpha); |
288 | 0 | pixel(-y - 1, -x - 1, alpha); |
289 | 0 | pixel(-y - 1, x, alpha); |
290 | 0 | } |
291 | 0 | }; |
292 | | |
293 | | // These are calculated incrementally (as it is possibly a tiny bit faster) |
294 | 0 | int ib_squared = 0; |
295 | 0 | int qa_squared = q * a_squared; |
296 | |
|
297 | 0 | auto in_symmetric_region = [&] { |
298 | | // Main fix two stop cond here |
299 | 0 | return is_circle ? i < q : ib_squared < qa_squared; |
300 | 0 | }; |
301 | | |
302 | | // Draws a 8 octants for a circle or 4 quadrants for a (partial) ellipse |
303 | 0 | while (in_symmetric_region()) { |
304 | 0 | predict(); |
305 | 0 | minimize(); |
306 | 0 | correct(); |
307 | 0 | old_area = edge_intersection_area; |
308 | 0 | edge_intersection_area += delta_y; |
309 | 0 | if (edge_intersection_area >= 0) { |
310 | | // Single pixel on perimeter |
311 | 0 | symmetric_pixel(i, q, (edge_intersection_area + old_area) / 2); |
312 | 0 | fill(i, q - 1, -q, intensity); |
313 | 0 | fill(-i - 1, q - 1, -q, intensity); |
314 | 0 | } else { |
315 | | // Two pixels on perimeter |
316 | 0 | edge_intersection_area += subpixel_resolution; |
317 | 0 | symmetric_pixel(i, q, old_area / 2); |
318 | 0 | q -= 1; |
319 | 0 | qa_squared -= a_squared; |
320 | 0 | fill(i, q - 1, -q, intensity); |
321 | 0 | fill(-i - 1, q - 1, -q, intensity); |
322 | 0 | if (!is_circle || in_symmetric_region()) { |
323 | 0 | symmetric_pixel(i, q, (edge_intersection_area + subpixel_resolution) / 2); |
324 | 0 | if (is_circle) { |
325 | 0 | fill(q, i - 1, -i, intensity); |
326 | 0 | fill(-q - 1, i - 1, -i, intensity); |
327 | 0 | } |
328 | 0 | } else { |
329 | 0 | edge_intersection_area += subpixel_resolution; |
330 | 0 | } |
331 | 0 | } |
332 | 0 | i += 1; |
333 | 0 | ib_squared += b_squared; |
334 | 0 | } |
335 | |
|
336 | 0 | if (is_circle) { |
337 | 0 | int alpha = edge_intersection_area / 2; |
338 | 0 | pixel(q, q, alpha); |
339 | 0 | pixel(-q - 1, q, alpha); |
340 | 0 | pixel(-q - 1, -q - 1, alpha); |
341 | 0 | pixel(q, -q - 1, alpha); |
342 | 0 | } |
343 | |
|
344 | 0 | return Range { min_paint_x, max_paint_x }; |
345 | 0 | } |
346 | | |
347 | | void AntiAliasingPainter::fill_rect_with_rounded_corners(IntRect const& a_rect, Color color, int radius) |
348 | 0 | { |
349 | 0 | fill_rect_with_rounded_corners(a_rect, color, radius, radius, radius, radius); |
350 | 0 | } |
351 | | |
352 | | void AntiAliasingPainter::fill_rect_with_rounded_corners(IntRect const& a_rect, Color color, int top_left_radius, int top_right_radius, int bottom_right_radius, int bottom_left_radius) |
353 | 0 | { |
354 | 0 | fill_rect_with_rounded_corners(a_rect, color, |
355 | 0 | { top_left_radius, top_left_radius }, |
356 | 0 | { top_right_radius, top_right_radius }, |
357 | 0 | { bottom_right_radius, bottom_right_radius }, |
358 | 0 | { bottom_left_radius, bottom_left_radius }); |
359 | 0 | } |
360 | | |
361 | | void AntiAliasingPainter::fill_rect_with_rounded_corners(IntRect const& a_rect, Color color, CornerRadius top_left, CornerRadius top_right, CornerRadius bottom_right, CornerRadius bottom_left, BlendMode blend_mode) |
362 | 0 | { |
363 | 0 | if (!top_left && !top_right && !bottom_right && !bottom_left) { |
364 | 0 | if (blend_mode == BlendMode::Normal) |
365 | 0 | return m_underlying_painter.fill_rect(a_rect, color); |
366 | 0 | else if (blend_mode == BlendMode::AlphaSubtract) |
367 | 0 | return m_underlying_painter.clear_rect(a_rect, Color()); |
368 | 0 | } |
369 | | |
370 | 0 | if (color.alpha() == 0) |
371 | 0 | return; |
372 | | |
373 | 0 | IntPoint top_left_corner { |
374 | 0 | a_rect.x() + top_left.horizontal_radius, |
375 | 0 | a_rect.y() + top_left.vertical_radius, |
376 | 0 | }; |
377 | 0 | IntPoint top_right_corner { |
378 | 0 | a_rect.x() + a_rect.width() - top_right.horizontal_radius, |
379 | 0 | a_rect.y() + top_right.vertical_radius, |
380 | 0 | }; |
381 | 0 | IntPoint bottom_left_corner { |
382 | 0 | a_rect.x() + bottom_left.horizontal_radius, |
383 | 0 | a_rect.y() + a_rect.height() - bottom_left.vertical_radius |
384 | 0 | }; |
385 | 0 | IntPoint bottom_right_corner { |
386 | 0 | a_rect.x() + a_rect.width() - bottom_right.horizontal_radius, |
387 | 0 | a_rect.y() + a_rect.height() - bottom_right.vertical_radius |
388 | 0 | }; |
389 | | |
390 | | // All corners are centered at the same point, so this can be painted as a single ellipse. |
391 | 0 | if (top_left_corner == top_right_corner && top_right_corner == bottom_left_corner && bottom_left_corner == bottom_right_corner) |
392 | 0 | return fill_ellipse(a_rect, color, blend_mode); |
393 | | |
394 | 0 | IntRect top_rect { |
395 | 0 | a_rect.x() + top_left.horizontal_radius, |
396 | 0 | a_rect.y(), |
397 | 0 | a_rect.width() - top_left.horizontal_radius - top_right.horizontal_radius, |
398 | 0 | top_left.vertical_radius |
399 | 0 | }; |
400 | 0 | IntRect right_rect { |
401 | 0 | a_rect.x() + a_rect.width() - top_right.horizontal_radius, |
402 | 0 | a_rect.y() + top_right.vertical_radius, |
403 | 0 | top_right.horizontal_radius, |
404 | 0 | a_rect.height() - top_right.vertical_radius - bottom_right.vertical_radius |
405 | 0 | }; |
406 | 0 | IntRect bottom_rect { |
407 | 0 | a_rect.x() + bottom_left.horizontal_radius, |
408 | 0 | a_rect.y() + a_rect.height() - bottom_right.vertical_radius, |
409 | 0 | a_rect.width() - bottom_left.horizontal_radius - bottom_right.horizontal_radius, |
410 | 0 | bottom_right.vertical_radius |
411 | 0 | }; |
412 | 0 | IntRect left_rect { |
413 | 0 | a_rect.x(), |
414 | 0 | a_rect.y() + top_left.vertical_radius, |
415 | 0 | bottom_left.horizontal_radius, |
416 | 0 | a_rect.height() - top_left.vertical_radius - bottom_left.vertical_radius |
417 | 0 | }; |
418 | |
|
419 | 0 | IntRect inner = { |
420 | 0 | left_rect.x() + left_rect.width(), |
421 | 0 | left_rect.y(), |
422 | 0 | a_rect.width() - left_rect.width() - right_rect.width(), |
423 | 0 | a_rect.height() - top_rect.height() - bottom_rect.height() |
424 | 0 | }; |
425 | |
|
426 | 0 | if (blend_mode == BlendMode::Normal) { |
427 | 0 | m_underlying_painter.fill_rect(top_rect, color); |
428 | 0 | m_underlying_painter.fill_rect(right_rect, color); |
429 | 0 | m_underlying_painter.fill_rect(bottom_rect, color); |
430 | 0 | m_underlying_painter.fill_rect(left_rect, color); |
431 | 0 | m_underlying_painter.fill_rect(inner, color); |
432 | 0 | } else if (blend_mode == BlendMode::AlphaSubtract) { |
433 | 0 | m_underlying_painter.clear_rect(top_rect, Color()); |
434 | 0 | m_underlying_painter.clear_rect(right_rect, Color()); |
435 | 0 | m_underlying_painter.clear_rect(bottom_rect, Color()); |
436 | 0 | m_underlying_painter.clear_rect(left_rect, Color()); |
437 | 0 | m_underlying_painter.clear_rect(inner, Color()); |
438 | 0 | } |
439 | |
|
440 | 0 | auto fill_corner = [&](auto const& ellipse_center, auto const& corner_point, CornerRadius const& corner) { |
441 | 0 | PainterStateSaver save { m_underlying_painter }; |
442 | 0 | m_underlying_painter.add_clip_rect(IntRect::from_two_points(ellipse_center, corner_point)); |
443 | 0 | fill_ellipse(IntRect::centered_on(ellipse_center, { corner.horizontal_radius * 2, corner.vertical_radius * 2 }), color, blend_mode); |
444 | 0 | }; |
445 | |
|
446 | 0 | auto bounding_rect = a_rect.inflated(0, 1, 1, 0); |
447 | 0 | if (top_left) |
448 | 0 | fill_corner(top_left_corner, bounding_rect.top_left(), top_left); |
449 | 0 | if (top_right) |
450 | 0 | fill_corner(top_right_corner, bounding_rect.top_right().moved_left(1), top_right); |
451 | 0 | if (bottom_left) |
452 | 0 | fill_corner(bottom_left_corner, bounding_rect.bottom_left().moved_up(1), bottom_left); |
453 | 0 | if (bottom_right) |
454 | 0 | fill_corner(bottom_right_corner, bounding_rect.bottom_right().translated(-1), bottom_right); |
455 | 0 | } |
456 | | |
457 | | } |