AntiAliasingPainter.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  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. #if defined(AK_COMPILER_GCC)
  9. # pragma GCC optimize("O3")
  10. #endif
  11. #include <AK/Function.h>
  12. #include <AK/NumericLimits.h>
  13. #include <LibGfx/AntiAliasingPainter.h>
  14. #include <LibGfx/DeprecatedPainter.h>
  15. #include <LibGfx/Line.h>
  16. namespace Gfx {
  17. void AntiAliasingPainter::draw_anti_aliased_line(FloatPoint actual_from, FloatPoint actual_to, Color color, float thickness, LineStyle style, Color, LineLengthMode line_length_mode)
  18. {
  19. // FIXME: Implement this :P
  20. VERIFY(style == LineStyle::Solid);
  21. if (color.alpha() == 0)
  22. return;
  23. // FIMXE:
  24. // This is not a proper line drawing algorithm.
  25. // It's hack-ish AA rotated rectangle painting.
  26. // There's probably more optimal ways to achieve this
  27. // (though this still runs faster than the previous AA-line code)
  28. //
  29. // If you, reading this comment, know a better way that:
  30. // 1. Does not overpaint (i.e. painting a line with transparency looks correct)
  31. // 2. Has square end points (i.e. the line is a rectangle)
  32. // 3. Has good anti-aliasing
  33. // 4. Is less hacky than this
  34. //
  35. // Please delete this code and implement it!
  36. int int_thickness = AK::ceil(thickness);
  37. auto mapped_from = m_transform.map(actual_from);
  38. auto mapped_to = m_transform.map(actual_to);
  39. auto distance = mapped_to.distance_from(mapped_from);
  40. auto length = distance + (line_length_mode == LineLengthMode::PointToPoint);
  41. // Axis-aligned lines:
  42. if (mapped_from.y() == mapped_to.y()) {
  43. auto start_point = (mapped_from.x() < mapped_to.x() ? mapped_from : mapped_to).translated(0, -int_thickness / 2);
  44. return fill_rect(Gfx::FloatRect(start_point, { length, thickness }), color);
  45. }
  46. if (mapped_from.x() == mapped_to.x()) {
  47. auto start_point = (mapped_from.y() < mapped_to.y() ? mapped_from : mapped_to).translated(-int_thickness / 2, 0);
  48. return fill_rect(Gfx::FloatRect(start_point, { thickness, length }), color);
  49. }
  50. // The painting only works for the positive XY quadrant (because that is easier).
  51. // So flip things around until we're there:
  52. bool flip_x = false;
  53. bool flip_y = false;
  54. if (mapped_to.x() < mapped_from.x() && mapped_to.y() < mapped_from.y())
  55. swap(mapped_to, mapped_from);
  56. if ((flip_x = mapped_to.x() < mapped_from.x()))
  57. mapped_to.set_x(2 * mapped_from.x() - mapped_to.x());
  58. if ((flip_y = mapped_to.y() < mapped_from.y()))
  59. mapped_to.set_y(2 * mapped_from.y() - mapped_to.y());
  60. auto delta = mapped_to - mapped_from;
  61. auto line_angle_radians = AK::atan2(delta.y(), delta.x()) - 0.5f * AK::Pi<float>;
  62. float sin_inverse_angle;
  63. float cos_inverse_angle;
  64. AK::sincos(-line_angle_radians, sin_inverse_angle, cos_inverse_angle);
  65. auto inverse_rotate_point = [=](FloatPoint point) {
  66. return Gfx::FloatPoint(
  67. point.x() * cos_inverse_angle - point.y() * sin_inverse_angle,
  68. point.y() * cos_inverse_angle + point.x() * sin_inverse_angle);
  69. };
  70. Gfx::FloatRect line_rect({ -(thickness * 255) / 2.0f, 0 }, Gfx::FloatSize(thickness * 255, length * 255));
  71. auto gradient = delta.y() / delta.x();
  72. // Work out how long we need to scan along the X-axis to reach the other side of the line.
  73. // E.g. for a vertical line this would be `thickness', in general it is this:
  74. int scan_line_length = AK::ceil(AK::sqrt((gradient * gradient + 1) * thickness * thickness) / gradient);
  75. auto x_gradient = 1 / gradient;
  76. int x_step = floorf(x_gradient);
  77. float x_error = 0;
  78. float x_error_per_y = x_gradient - x_step;
  79. auto y_offset = int_thickness + 1;
  80. auto x_offset = int(x_gradient * y_offset);
  81. int const line_start_x = mapped_from.x();
  82. int const line_start_y = mapped_from.y();
  83. int const line_end_x = mapped_to.x();
  84. int const line_end_y = mapped_to.y();
  85. auto set_pixel = [=, this](int x, int y, Gfx::Color color) {
  86. // FIXME: The lines seem slightly off (<= 1px) when flipped.
  87. if (flip_x)
  88. x = 2 * line_start_x - x;
  89. if (flip_y)
  90. y = 2 * line_start_y - y;
  91. m_underlying_painter.set_pixel(x, y, color, true);
  92. };
  93. // Scan a bit extra to avoid issues from the x_error:
  94. int const overscan = max(x_step, 1) * 2 + 1;
  95. int x = line_start_x - x_offset;
  96. int const center_offset = (scan_line_length + 1) / 2;
  97. for (int y = line_start_y - y_offset; y < line_end_y + y_offset; y += 1) {
  98. for (int i = -overscan; i < scan_line_length + overscan; i++) {
  99. int scan_x_pos = x + i - center_offset;
  100. // Avoid scanning over pixels definitely outside the line:
  101. int dx = (line_start_x - int_thickness) - (scan_x_pos + 1);
  102. if (dx > 0) {
  103. i += dx;
  104. continue;
  105. }
  106. if (line_end_x + int_thickness <= scan_x_pos - 1)
  107. break;
  108. auto sample = inverse_rotate_point(Gfx::FloatPoint(scan_x_pos - line_start_x, y - line_start_y));
  109. Gfx::FloatRect sample_px(sample * 255, Gfx::FloatSize(255, 255));
  110. sample_px.intersect(line_rect);
  111. auto alpha = (sample_px.width() * sample_px.height()) / 255.0f;
  112. alpha = (alpha * color.alpha()) / 255;
  113. set_pixel(scan_x_pos, y, color.with_alpha(alpha));
  114. }
  115. x += x_step;
  116. x_error += x_error_per_y;
  117. if (x_error > 1.0f) {
  118. x_error -= 1.0f;
  119. x += 1;
  120. }
  121. }
  122. }
  123. void AntiAliasingPainter::draw_dotted_line(IntPoint point1, IntPoint point2, Color color, int thickness)
  124. {
  125. // AA circles don't really work below a radius of 2px.
  126. if (thickness < 4)
  127. return m_underlying_painter.draw_line(point1, point2, color, thickness, LineStyle::Dotted);
  128. auto draw_spaced_dots = [&](int start, int end, auto to_point) {
  129. int step = thickness * 2;
  130. if (start > end)
  131. swap(start, end);
  132. int delta = end - start;
  133. int dots = delta / step;
  134. if (dots == 0)
  135. return;
  136. int fudge_per_dot = 0;
  137. int extra_fudge = 0;
  138. if (dots > 3) {
  139. // Fudge the numbers so the last dot is drawn at the `end' point (otherwise you can get lines cuts short).
  140. // You need at least a handful of dots to do this.
  141. int fudge = delta % step;
  142. fudge_per_dot = fudge / dots;
  143. extra_fudge = fudge % dots;
  144. }
  145. for (int dot = start; dot <= end; dot += (step + fudge_per_dot + (extra_fudge > 0))) {
  146. fill_circle(to_point(dot), thickness / 2, color);
  147. --extra_fudge;
  148. }
  149. };
  150. if (point1.y() == point2.y()) {
  151. draw_spaced_dots(point1.x(), point2.x(), [&](int dot_x) {
  152. return IntPoint { dot_x, point1.y() };
  153. });
  154. } else if (point1.x() == point2.x()) {
  155. draw_spaced_dots(point1.y(), point2.y(), [&](int dot_y) {
  156. return IntPoint { point1.x(), dot_y };
  157. });
  158. } else {
  159. TODO();
  160. }
  161. }
  162. void AntiAliasingPainter::draw_line(IntPoint actual_from, IntPoint actual_to, Color color, float thickness, LineStyle style, Color alternate_color, LineLengthMode line_length_mode)
  163. {
  164. draw_line(actual_from.to_type<float>(), actual_to.to_type<float>(), color, thickness, style, alternate_color, line_length_mode);
  165. }
  166. void AntiAliasingPainter::draw_line(FloatPoint actual_from, FloatPoint actual_to, Color color, float thickness, LineStyle style, Color alternate_color, LineLengthMode line_length_mode)
  167. {
  168. if (style == LineStyle::Dotted)
  169. return draw_dotted_line(actual_from.to_rounded<int>(), actual_to.to_rounded<int>(), color, static_cast<int>(round(thickness)));
  170. draw_anti_aliased_line(actual_from, actual_to, color, thickness, style, alternate_color, line_length_mode);
  171. }
  172. void AntiAliasingPainter::stroke_path(DeprecatedPath const& path, Color color, float thickness)
  173. {
  174. if (thickness <= 0)
  175. return;
  176. // FIXME: Cache this? Probably at a higher level such as in LibWeb?
  177. fill_path(path.stroke_to_fill(thickness), color);
  178. }
  179. void AntiAliasingPainter::stroke_path(DeprecatedPath const& path, Gfx::PaintStyle const& paint_style, float thickness, float opacity)
  180. {
  181. if (thickness <= 0)
  182. return;
  183. // FIXME: Cache this? Probably at a higher level such as in LibWeb?
  184. fill_path(path.stroke_to_fill(thickness), paint_style, opacity);
  185. }
  186. void AntiAliasingPainter::fill_rect(FloatRect const& float_rect, Color color)
  187. {
  188. // Draw the integer part of the rectangle:
  189. float right_x = float_rect.x() + float_rect.width();
  190. float bottom_y = float_rect.y() + float_rect.height();
  191. int x1 = ceilf(float_rect.x());
  192. int y1 = ceilf(float_rect.y());
  193. int x2 = floorf(right_x);
  194. int y2 = floorf(bottom_y);
  195. auto solid_rect = Gfx::IntRect::from_two_points({ x1, y1 }, { x2, y2 });
  196. m_underlying_painter.fill_rect(solid_rect, color);
  197. if (float_rect == solid_rect)
  198. return;
  199. // Draw the rest:
  200. float left_subpixel = x1 - float_rect.x();
  201. float top_subpixel = y1 - float_rect.y();
  202. float right_subpixel = right_x - x2;
  203. float bottom_subpixel = bottom_y - y2;
  204. float top_left_subpixel = top_subpixel * left_subpixel;
  205. float top_right_subpixel = top_subpixel * right_subpixel;
  206. float bottom_left_subpixel = bottom_subpixel * left_subpixel;
  207. float bottom_right_subpixel = bottom_subpixel * right_subpixel;
  208. auto subpixel = [&](float alpha) {
  209. return color.with_alpha(color.alpha() * alpha);
  210. };
  211. auto set_pixel = [&](int x, int y, float alpha) {
  212. m_underlying_painter.set_pixel(x, y, subpixel(alpha), true);
  213. };
  214. auto line_to_rect = [&](int x1, int y1, int x2, int y2) {
  215. return IntRect::from_two_points({ x1, y1 }, { x2 + 1, y2 + 1 });
  216. };
  217. set_pixel(x1 - 1, y1 - 1, top_left_subpixel);
  218. set_pixel(x2, y1 - 1, top_right_subpixel);
  219. set_pixel(x2, y2, bottom_right_subpixel);
  220. set_pixel(x1 - 1, y2, bottom_left_subpixel);
  221. m_underlying_painter.fill_rect(line_to_rect(x1, y1 - 1, x2 - 1, y1 - 1), subpixel(top_subpixel));
  222. m_underlying_painter.fill_rect(line_to_rect(x1, y2, x2 - 1, y2), subpixel(bottom_subpixel));
  223. m_underlying_painter.fill_rect(line_to_rect(x1 - 1, y1, x1 - 1, y2 - 1), subpixel(left_subpixel));
  224. m_underlying_painter.fill_rect(line_to_rect(x2, y1, x2, y2 - 1), subpixel(right_subpixel));
  225. }
  226. void AntiAliasingPainter::draw_ellipse(IntRect const& a_rect, Color color, int thickness)
  227. {
  228. // FIXME: Come up with an allocation-free version of this!
  229. // Using draw_line() for segments of an ellipse was attempted but gave really poor results :^(
  230. // There probably is a way to adjust the fill of draw_ellipse_part() to do this, but getting it rendering correctly is tricky.
  231. // The outline of the steps required to paint it efficiently is:
  232. // - Paint the outer ellipse without the fill (from the fill() lambda in draw_ellipse_part())
  233. // - Paint the inner ellipse, but in the set_pixel() invert the alpha values
  234. // - Somehow fill in the gap between the two ellipses (the tricky part to get right)
  235. // - Have to avoid overlapping pixels and accidentally painting over some of the edge pixels
  236. auto color_no_alpha = color;
  237. color_no_alpha.set_alpha(255);
  238. auto outline_ellipse_bitmap = ({
  239. auto bitmap = Bitmap::create(BitmapFormat::BGRA8888, a_rect.size());
  240. if (bitmap.is_error())
  241. return warnln("Failed to allocate temporary bitmap for antialiased outline ellipse!");
  242. bitmap.release_value();
  243. });
  244. auto outer_rect = a_rect;
  245. outer_rect.set_location({ 0, 0 });
  246. auto inner_rect = outer_rect.shrunken(thickness * 2, thickness * 2);
  247. DeprecatedPainter painter { outline_ellipse_bitmap };
  248. AntiAliasingPainter aa_painter { painter };
  249. aa_painter.fill_ellipse(outer_rect, color_no_alpha);
  250. aa_painter.fill_ellipse(inner_rect, color_no_alpha, BlendMode::AlphaSubtract);
  251. m_underlying_painter.blit(a_rect.location(), outline_ellipse_bitmap, outline_ellipse_bitmap->rect(), color.alpha() / 255.);
  252. }
  253. void AntiAliasingPainter::fill_circle(IntPoint center, int radius, Color color, BlendMode blend_mode)
  254. {
  255. if (radius <= 0)
  256. return;
  257. draw_ellipse_part(center, radius, radius, color, false, {}, blend_mode);
  258. }
  259. void AntiAliasingPainter::fill_ellipse(IntRect const& a_rect, Color color, BlendMode blend_mode)
  260. {
  261. auto center = a_rect.center();
  262. auto radius_a = a_rect.width() / 2;
  263. auto radius_b = a_rect.height() / 2;
  264. if (radius_a <= 0 || radius_b <= 0)
  265. return;
  266. if (radius_a == radius_b)
  267. return fill_circle(center, radius_a, color, blend_mode);
  268. auto x_paint_range = draw_ellipse_part(center, radius_a, radius_b, color, false, {}, blend_mode);
  269. // FIXME: This paints some extra fill pixels that are clipped
  270. draw_ellipse_part(center, radius_b, radius_a, color, true, x_paint_range, blend_mode);
  271. }
  272. FLATTEN AntiAliasingPainter::Range AntiAliasingPainter::draw_ellipse_part(
  273. IntPoint center, int radius_a, int radius_b, Color color, bool flip_x_and_y, Optional<Range> x_clip, BlendMode blend_mode)
  274. {
  275. /*
  276. Algorithm from: https://cs.uwaterloo.ca/research/tr/1984/CS-84-38.pdf
  277. This method can draw a whole circle with a whole circle in one call using
  278. 8-way symmetry, or an ellipse in two calls using 4-way symmetry.
  279. */
  280. // If this is a ellipse everything can be drawn in one pass with 8 way symmetry
  281. bool const is_circle = radius_a == radius_b;
  282. // These happen to be the same here, but are treated separately in the paper:
  283. // intensity is the fill alpha
  284. int const intensity = 255;
  285. // 0 to subpixel_resolution is the range of alpha values for the circle edges
  286. int const subpixel_resolution = intensity;
  287. // Current pixel address
  288. int i = 0;
  289. int q = radius_b;
  290. // 1st and 2nd order differences of y
  291. int delta_y = 0;
  292. int delta2_y = 0;
  293. int const a_squared = radius_a * radius_a;
  294. int const b_squared = radius_b * radius_b;
  295. // Exact and predicted values of f(i) -- the ellipse equation scaled by subpixel_resolution
  296. int y = subpixel_resolution * radius_b;
  297. int y_hat = 0;
  298. // The value of f(i)*f(i)
  299. int f_squared = y * y;
  300. // 1st and 2nd order differences of f(i)*f(i)
  301. int delta_f_squared = (static_cast<int64_t>(b_squared) * subpixel_resolution * subpixel_resolution) / a_squared;
  302. int delta2_f_squared = -delta_f_squared - delta_f_squared;
  303. // edge_intersection_area/subpixel_resolution = percentage of pixel intersected by circle
  304. // (aka the alpha for the pixel)
  305. int edge_intersection_area = 0;
  306. int old_area = edge_intersection_area;
  307. auto predict = [&] {
  308. delta_y += delta2_y;
  309. // y_hat is the predicted value of f(i)
  310. y_hat = y + delta_y;
  311. };
  312. auto minimize = [&] {
  313. // Initialize the minimization
  314. delta_f_squared += delta2_f_squared;
  315. f_squared += delta_f_squared;
  316. int min_squared_error = y_hat * y_hat - f_squared;
  317. int prediction_overshot = 1;
  318. y = y_hat;
  319. // Force error negative
  320. if (min_squared_error > 0) {
  321. min_squared_error = -min_squared_error;
  322. prediction_overshot = -1;
  323. }
  324. // Minimize
  325. int previous_error = min_squared_error;
  326. while (min_squared_error < 0) {
  327. y += prediction_overshot;
  328. previous_error = min_squared_error;
  329. min_squared_error += y + y - prediction_overshot;
  330. }
  331. if (min_squared_error + previous_error > 0)
  332. y -= prediction_overshot;
  333. };
  334. auto correct = [&] {
  335. int error = y - y_hat;
  336. delta2_y += error;
  337. delta_y += error;
  338. };
  339. int min_paint_x = NumericLimits<int>::max();
  340. int max_paint_x = NumericLimits<int>::min();
  341. auto pixel = [&](int x, int y, int alpha) {
  342. if (alpha <= 0 || alpha > 255)
  343. return;
  344. if (flip_x_and_y)
  345. swap(x, y);
  346. if (x_clip.has_value() && x_clip->contains_inclusive(x))
  347. return;
  348. min_paint_x = min(x, min_paint_x);
  349. max_paint_x = max(x, max_paint_x);
  350. alpha = (alpha * color.alpha()) / 255;
  351. if (blend_mode == BlendMode::AlphaSubtract)
  352. alpha = ~alpha;
  353. auto pixel_color = color;
  354. pixel_color.set_alpha(alpha);
  355. m_underlying_painter.set_pixel(center + IntPoint { x, y }, pixel_color, blend_mode == BlendMode::Normal);
  356. };
  357. auto fill = [&](int x, int ymax, int ymin, int alpha) {
  358. while (ymin <= ymax) {
  359. pixel(x, ymin, alpha);
  360. ymin += 1;
  361. }
  362. };
  363. auto symmetric_pixel = [&](int x, int y, int alpha) {
  364. pixel(x, y, alpha);
  365. pixel(x, -y - 1, alpha);
  366. pixel(-x - 1, -y - 1, alpha);
  367. pixel(-x - 1, y, alpha);
  368. if (is_circle) {
  369. pixel(y, x, alpha);
  370. pixel(y, -x - 1, alpha);
  371. pixel(-y - 1, -x - 1, alpha);
  372. pixel(-y - 1, x, alpha);
  373. }
  374. };
  375. // These are calculated incrementally (as it is possibly a tiny bit faster)
  376. int ib_squared = 0;
  377. int qa_squared = q * a_squared;
  378. auto in_symmetric_region = [&] {
  379. // Main fix two stop cond here
  380. return is_circle ? i < q : ib_squared < qa_squared;
  381. };
  382. // Draws a 8 octants for a circle or 4 quadrants for a (partial) ellipse
  383. while (in_symmetric_region()) {
  384. predict();
  385. minimize();
  386. correct();
  387. old_area = edge_intersection_area;
  388. edge_intersection_area += delta_y;
  389. if (edge_intersection_area >= 0) {
  390. // Single pixel on perimeter
  391. symmetric_pixel(i, q, (edge_intersection_area + old_area) / 2);
  392. fill(i, q - 1, -q, intensity);
  393. fill(-i - 1, q - 1, -q, intensity);
  394. } else {
  395. // Two pixels on perimeter
  396. edge_intersection_area += subpixel_resolution;
  397. symmetric_pixel(i, q, old_area / 2);
  398. q -= 1;
  399. qa_squared -= a_squared;
  400. fill(i, q - 1, -q, intensity);
  401. fill(-i - 1, q - 1, -q, intensity);
  402. if (!is_circle || in_symmetric_region()) {
  403. symmetric_pixel(i, q, (edge_intersection_area + subpixel_resolution) / 2);
  404. if (is_circle) {
  405. fill(q, i - 1, -i, intensity);
  406. fill(-q - 1, i - 1, -i, intensity);
  407. }
  408. } else {
  409. edge_intersection_area += subpixel_resolution;
  410. }
  411. }
  412. i += 1;
  413. ib_squared += b_squared;
  414. }
  415. if (is_circle) {
  416. int alpha = edge_intersection_area / 2;
  417. pixel(q, q, alpha);
  418. pixel(-q - 1, q, alpha);
  419. pixel(-q - 1, -q - 1, alpha);
  420. pixel(q, -q - 1, alpha);
  421. }
  422. return Range { min_paint_x, max_paint_x };
  423. }
  424. void AntiAliasingPainter::fill_rect_with_rounded_corners(IntRect const& a_rect, Color color, int radius)
  425. {
  426. fill_rect_with_rounded_corners(a_rect, color, radius, radius, radius, radius);
  427. }
  428. 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)
  429. {
  430. fill_rect_with_rounded_corners(a_rect, color,
  431. { top_left_radius, top_left_radius },
  432. { top_right_radius, top_right_radius },
  433. { bottom_right_radius, bottom_right_radius },
  434. { bottom_left_radius, bottom_left_radius });
  435. }
  436. 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)
  437. {
  438. if (!top_left && !top_right && !bottom_right && !bottom_left) {
  439. if (blend_mode == BlendMode::Normal)
  440. return m_underlying_painter.fill_rect(a_rect, color);
  441. else if (blend_mode == BlendMode::AlphaSubtract)
  442. return m_underlying_painter.clear_rect(a_rect, Color());
  443. }
  444. if (color.alpha() == 0)
  445. return;
  446. IntPoint top_left_corner {
  447. a_rect.x() + top_left.horizontal_radius,
  448. a_rect.y() + top_left.vertical_radius,
  449. };
  450. IntPoint top_right_corner {
  451. a_rect.x() + a_rect.width() - top_right.horizontal_radius,
  452. a_rect.y() + top_right.vertical_radius,
  453. };
  454. IntPoint bottom_left_corner {
  455. a_rect.x() + bottom_left.horizontal_radius,
  456. a_rect.y() + a_rect.height() - bottom_left.vertical_radius
  457. };
  458. IntPoint bottom_right_corner {
  459. a_rect.x() + a_rect.width() - bottom_right.horizontal_radius,
  460. a_rect.y() + a_rect.height() - bottom_right.vertical_radius
  461. };
  462. // All corners are centered at the same point, so this can be painted as a single ellipse.
  463. if (top_left_corner == top_right_corner && top_right_corner == bottom_left_corner && bottom_left_corner == bottom_right_corner)
  464. return fill_ellipse(a_rect, color, blend_mode);
  465. IntRect top_rect {
  466. a_rect.x() + top_left.horizontal_radius,
  467. a_rect.y(),
  468. a_rect.width() - top_left.horizontal_radius - top_right.horizontal_radius,
  469. top_left.vertical_radius
  470. };
  471. IntRect right_rect {
  472. a_rect.x() + a_rect.width() - top_right.horizontal_radius,
  473. a_rect.y() + top_right.vertical_radius,
  474. top_right.horizontal_radius,
  475. a_rect.height() - top_right.vertical_radius - bottom_right.vertical_radius
  476. };
  477. IntRect bottom_rect {
  478. a_rect.x() + bottom_left.horizontal_radius,
  479. a_rect.y() + a_rect.height() - bottom_right.vertical_radius,
  480. a_rect.width() - bottom_left.horizontal_radius - bottom_right.horizontal_radius,
  481. bottom_right.vertical_radius
  482. };
  483. IntRect left_rect {
  484. a_rect.x(),
  485. a_rect.y() + top_left.vertical_radius,
  486. bottom_left.horizontal_radius,
  487. a_rect.height() - top_left.vertical_radius - bottom_left.vertical_radius
  488. };
  489. IntRect inner = {
  490. left_rect.x() + left_rect.width(),
  491. left_rect.y(),
  492. a_rect.width() - left_rect.width() - right_rect.width(),
  493. a_rect.height() - top_rect.height() - bottom_rect.height()
  494. };
  495. if (blend_mode == BlendMode::Normal) {
  496. m_underlying_painter.fill_rect(top_rect, color);
  497. m_underlying_painter.fill_rect(right_rect, color);
  498. m_underlying_painter.fill_rect(bottom_rect, color);
  499. m_underlying_painter.fill_rect(left_rect, color);
  500. m_underlying_painter.fill_rect(inner, color);
  501. } else if (blend_mode == BlendMode::AlphaSubtract) {
  502. m_underlying_painter.clear_rect(top_rect, Color());
  503. m_underlying_painter.clear_rect(right_rect, Color());
  504. m_underlying_painter.clear_rect(bottom_rect, Color());
  505. m_underlying_painter.clear_rect(left_rect, Color());
  506. m_underlying_painter.clear_rect(inner, Color());
  507. }
  508. auto fill_corner = [&](auto const& ellipse_center, auto const& corner_point, CornerRadius const& corner) {
  509. DeprecatedPainterStateSaver save { m_underlying_painter };
  510. m_underlying_painter.add_clip_rect(IntRect::from_two_points(ellipse_center, corner_point));
  511. fill_ellipse(IntRect::centered_at(ellipse_center, { corner.horizontal_radius * 2, corner.vertical_radius * 2 }), color, blend_mode);
  512. };
  513. auto bounding_rect = a_rect.inflated(0, 1, 1, 0);
  514. if (top_left)
  515. fill_corner(top_left_corner, bounding_rect.top_left(), top_left);
  516. if (top_right)
  517. fill_corner(top_right_corner, bounding_rect.top_right().moved_left(1), top_right);
  518. if (bottom_left)
  519. fill_corner(bottom_left_corner, bounding_rect.bottom_left().moved_up(1), bottom_left);
  520. if (bottom_right)
  521. fill_corner(bottom_right_corner, bounding_rect.bottom_right().translated(-1), bottom_right);
  522. }
  523. }