AntiAliasingPainter.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. /*
  2. * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
  3. * Copyright (c) 2022, Ben Maxwell <macdue@dueutil.tech>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #if defined(__GNUC__) && !defined(__clang__)
  8. # pragma GCC optimize("O3")
  9. #endif
  10. #include "FillPathImplementation.h"
  11. #include <AK/Function.h>
  12. #include <AK/NumericLimits.h>
  13. #include <LibGfx/AntiAliasingPainter.h>
  14. #include <LibGfx/Path.h>
  15. namespace Gfx {
  16. // Base algorithm from https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm,
  17. // because there seems to be no other known method for drawing AA'd lines (?)
  18. template<AntiAliasingPainter::AntiAliasPolicy policy>
  19. void AntiAliasingPainter::draw_anti_aliased_line(FloatPoint const& actual_from, FloatPoint const& actual_to, Color color, float thickness, Painter::LineStyle style, Color)
  20. {
  21. // FIXME: Implement this :P
  22. VERIFY(style == Painter::LineStyle::Solid);
  23. auto corrected_thickness = thickness > 1 ? thickness - 1 : thickness;
  24. auto size = IntSize(corrected_thickness, corrected_thickness);
  25. auto plot = [&](int x, int y, float c) {
  26. m_underlying_painter.fill_rect(IntRect::centered_on({ x, y }, size), color.with_alpha(color.alpha() * c));
  27. };
  28. auto integer_part = [](float x) { return floorf(x); };
  29. auto round = [&](float x) { return integer_part(x + 0.5f); };
  30. auto fractional_part = [&](float x) { return x - floorf(x); };
  31. auto one_minus_fractional_part = [&](float x) { return 1.0f - fractional_part(x); };
  32. auto draw_line = [&](float x0, float y0, float x1, float y1) {
  33. bool steep = fabsf(y1 - y0) > fabsf(x1 - x0);
  34. if (steep) {
  35. swap(x0, y0);
  36. swap(x1, y1);
  37. }
  38. if (x0 > x1) {
  39. swap(x0, x1);
  40. swap(y0, y1);
  41. }
  42. float dx = x1 - x0;
  43. float dy = y1 - y0;
  44. float gradient;
  45. if (dx == 0.0f)
  46. gradient = 1.0f;
  47. else
  48. gradient = dy / dx;
  49. // Handle first endpoint.
  50. int x_end = round(x0);
  51. int y_end = y0 + gradient * (x_end - x0);
  52. float x_gap = one_minus_fractional_part(x0 + 0.5f);
  53. int xpxl1 = x_end; // This will be used in the main loop.
  54. int ypxl1 = integer_part(y_end);
  55. if (steep) {
  56. plot(ypxl1, xpxl1, one_minus_fractional_part(y_end) * x_gap);
  57. plot(ypxl1 + 1, xpxl1, fractional_part(y_end) * x_gap);
  58. } else {
  59. plot(xpxl1, ypxl1, one_minus_fractional_part(y_end) * x_gap);
  60. plot(xpxl1, ypxl1 + 1, fractional_part(y_end) * x_gap);
  61. }
  62. float intery = y_end + gradient; // First y-intersection for the main loop.
  63. // Handle second endpoint.
  64. x_end = round(x1);
  65. y_end = y1 + gradient * (x_end - x1);
  66. x_gap = fractional_part(x1 + 0.5f);
  67. int xpxl2 = x_end; // This will be used in the main loop
  68. int ypxl2 = integer_part(y_end);
  69. if (steep) {
  70. plot(ypxl2, xpxl2, one_minus_fractional_part(y_end) * x_gap);
  71. plot(ypxl2 + 1, xpxl2, fractional_part(y_end) * x_gap);
  72. } else {
  73. plot(xpxl2, ypxl2, one_minus_fractional_part(y_end) * x_gap);
  74. plot(xpxl2, ypxl2 + 1, fractional_part(y_end) * x_gap);
  75. }
  76. // Main loop.
  77. if (steep) {
  78. for (int x = xpxl1 + 1; x <= xpxl2 - 1; ++x) {
  79. if constexpr (policy == AntiAliasPolicy::OnlyEnds) {
  80. plot(integer_part(intery), x, 1);
  81. } else {
  82. plot(integer_part(intery), x, one_minus_fractional_part(intery));
  83. }
  84. plot(integer_part(intery) + 1, x, fractional_part(intery));
  85. intery += gradient;
  86. }
  87. } else {
  88. for (int x = xpxl1 + 1; x <= xpxl2 - 1; ++x) {
  89. if constexpr (policy == AntiAliasPolicy::OnlyEnds) {
  90. plot(x, integer_part(intery), 1);
  91. } else {
  92. plot(x, integer_part(intery), one_minus_fractional_part(intery));
  93. }
  94. plot(x, integer_part(intery) + 1, fractional_part(intery));
  95. intery += gradient;
  96. }
  97. }
  98. };
  99. auto mapped_from = m_transform.map(actual_from);
  100. auto mapped_to = m_transform.map(actual_to);
  101. draw_line(mapped_from.x(), mapped_from.y(), mapped_to.x(), mapped_to.y());
  102. }
  103. void AntiAliasingPainter::draw_aliased_line(FloatPoint const& actual_from, FloatPoint const& actual_to, Color color, float thickness, Painter::LineStyle style, Color alternate_color)
  104. {
  105. draw_anti_aliased_line<AntiAliasPolicy::OnlyEnds>(actual_from, actual_to, color, thickness, style, alternate_color);
  106. }
  107. void AntiAliasingPainter::draw_dotted_line(IntPoint point1, IntPoint point2, Color color, int thickness)
  108. {
  109. // AA circles don't really work below a radius of 2px.
  110. if (thickness < 4)
  111. return m_underlying_painter.draw_line(point1, point2, color, thickness, Painter::LineStyle::Dotted);
  112. auto draw_spaced_dots = [&](int start, int end, auto to_point) {
  113. int step = thickness * 2;
  114. if (start > end)
  115. swap(start, end);
  116. int delta = end - start;
  117. int dots = delta / step;
  118. if (dots == 0)
  119. return;
  120. int fudge_per_dot = 0;
  121. int extra_fudge = 0;
  122. if (dots > 3) {
  123. // Fudge the numbers so the last dot is drawn at the `end' point (otherwise you can get lines cuts short).
  124. // You need at least a handful of dots to do this.
  125. int fudge = delta % step;
  126. fudge_per_dot = fudge / dots;
  127. extra_fudge = fudge % dots;
  128. }
  129. for (int dot = start; dot <= end; dot += (step + fudge_per_dot + (extra_fudge > 0))) {
  130. fill_circle(to_point(dot), thickness / 2, color);
  131. --extra_fudge;
  132. }
  133. };
  134. if (point1.y() == point2.y()) {
  135. draw_spaced_dots(point1.x(), point2.x(), [&](int dot_x) {
  136. return IntPoint { dot_x, point1.y() };
  137. });
  138. } else if (point1.x() == point2.x()) {
  139. draw_spaced_dots(point1.y(), point2.y(), [&](int dot_y) {
  140. return IntPoint { point1.x(), dot_y };
  141. });
  142. } else {
  143. TODO();
  144. }
  145. }
  146. void AntiAliasingPainter::draw_line(FloatPoint const& actual_from, FloatPoint const& actual_to, Color color, float thickness, Painter::LineStyle style, Color alternate_color)
  147. {
  148. if (style == Painter::LineStyle::Dotted)
  149. return draw_dotted_line(actual_from.to_rounded<int>(), actual_to.to_rounded<int>(), color, static_cast<int>(round(thickness)));
  150. draw_anti_aliased_line<AntiAliasPolicy::Full>(actual_from, actual_to, color, thickness, style, alternate_color);
  151. }
  152. void AntiAliasingPainter::fill_path(Path& path, Color color, Painter::WindingRule rule)
  153. {
  154. Detail::fill_path<Detail::FillPathMode::AllowFloatingPoints>(*this, path, color, rule);
  155. }
  156. void AntiAliasingPainter::stroke_path(Path const& path, Color color, float thickness)
  157. {
  158. FloatPoint cursor;
  159. for (auto& segment : path.segments()) {
  160. switch (segment.type()) {
  161. case Segment::Type::Invalid:
  162. VERIFY_NOT_REACHED();
  163. case Segment::Type::MoveTo:
  164. cursor = segment.point();
  165. break;
  166. case Segment::Type::LineTo:
  167. draw_line(cursor, segment.point(), color, thickness);
  168. cursor = segment.point();
  169. break;
  170. case Segment::Type::QuadraticBezierCurveTo: {
  171. auto& through = static_cast<QuadraticBezierCurveSegment const&>(segment).through();
  172. draw_quadratic_bezier_curve(through, cursor, segment.point(), color, thickness);
  173. cursor = segment.point();
  174. break;
  175. }
  176. case Segment::Type::CubicBezierCurveTo: {
  177. auto& curve = static_cast<CubicBezierCurveSegment const&>(segment);
  178. auto& through_0 = curve.through_0();
  179. auto& through_1 = curve.through_1();
  180. draw_cubic_bezier_curve(through_0, through_1, cursor, segment.point(), color, thickness);
  181. cursor = segment.point();
  182. break;
  183. }
  184. case Segment::Type::EllipticalArcTo:
  185. auto& arc = static_cast<EllipticalArcSegment const&>(segment);
  186. draw_elliptical_arc(cursor, segment.point(), arc.center(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), color, thickness);
  187. cursor = segment.point();
  188. break;
  189. }
  190. }
  191. }
  192. void AntiAliasingPainter::draw_elliptical_arc(FloatPoint const& p1, FloatPoint const& p2, FloatPoint const& center, FloatPoint const& radii, float x_axis_rotation, float theta_1, float theta_delta, Color color, float thickness, Painter::LineStyle style)
  193. {
  194. Painter::for_each_line_segment_on_elliptical_arc(p1, p2, center, radii, x_axis_rotation, theta_1, theta_delta, [&](FloatPoint const& fp1, FloatPoint const& fp2) {
  195. draw_line(fp1, fp2, color, thickness, style);
  196. });
  197. }
  198. void AntiAliasingPainter::draw_quadratic_bezier_curve(FloatPoint const& control_point, FloatPoint const& p1, FloatPoint const& p2, Color color, float thickness, Painter::LineStyle style)
  199. {
  200. Painter::for_each_line_segment_on_bezier_curve(control_point, p1, p2, [&](FloatPoint const& fp1, FloatPoint const& fp2) {
  201. draw_line(fp1, fp2, color, thickness, style);
  202. });
  203. }
  204. void AntiAliasingPainter::draw_cubic_bezier_curve(FloatPoint const& control_point_0, FloatPoint const& control_point_1, FloatPoint const& p1, FloatPoint const& p2, Color color, float thickness, Painter::LineStyle style)
  205. {
  206. Painter::for_each_line_segment_on_cubic_bezier_curve(control_point_0, control_point_1, p1, p2, [&](FloatPoint const& fp1, FloatPoint const& fp2) {
  207. draw_line(fp1, fp2, color, thickness, style);
  208. });
  209. }
  210. void AntiAliasingPainter::fill_rect(FloatRect const& float_rect, Color color)
  211. {
  212. // Draw the integer part of the rectangle:
  213. float right_x = float_rect.x() + float_rect.width();
  214. float bottom_y = float_rect.y() + float_rect.height();
  215. int x1 = ceilf(float_rect.x());
  216. int y1 = ceilf(float_rect.y());
  217. int x2 = floorf(right_x);
  218. int y2 = floorf(bottom_y);
  219. auto solid_rect = Gfx::IntRect::from_two_points({ x1, y1 }, { x2, y2 });
  220. m_underlying_painter.fill_rect(solid_rect, color);
  221. if (float_rect == solid_rect)
  222. return;
  223. // Draw the rest:
  224. float left_subpixel = x1 - float_rect.x();
  225. float top_subpixel = y1 - float_rect.y();
  226. float right_subpixel = right_x - x2;
  227. float bottom_subpixel = bottom_y - y2;
  228. float top_left_subpixel = top_subpixel * left_subpixel;
  229. float top_right_subpixel = top_subpixel * right_subpixel;
  230. float bottom_left_subpixel = bottom_subpixel * left_subpixel;
  231. float bottom_right_subpixel = bottom_subpixel * right_subpixel;
  232. auto subpixel = [&](float alpha) {
  233. return color.with_alpha(color.alpha() * alpha);
  234. };
  235. auto set_pixel = [&](int x, int y, float alpha) {
  236. m_underlying_painter.set_pixel(x, y, subpixel(alpha), true);
  237. };
  238. auto line_to_rect = [&](int x1, int y1, int x2, int y2) {
  239. return IntRect::from_two_points({ x1, y1 }, { x2 + 1, y2 + 1 });
  240. };
  241. set_pixel(x1 - 1, y1 - 1, top_left_subpixel);
  242. set_pixel(x2, y1 - 1, top_right_subpixel);
  243. set_pixel(x2, y2, bottom_right_subpixel);
  244. set_pixel(x1 - 1, y2, bottom_left_subpixel);
  245. m_underlying_painter.fill_rect(line_to_rect(x1, y1 - 1, x2 - 1, y1 - 1), subpixel(top_subpixel));
  246. m_underlying_painter.fill_rect(line_to_rect(x1, y2, x2 - 1, y2), subpixel(bottom_subpixel));
  247. m_underlying_painter.fill_rect(line_to_rect(x1 - 1, y1, x1 - 1, y2 - 1), subpixel(left_subpixel));
  248. m_underlying_painter.fill_rect(line_to_rect(x2, y1, x2, y2 - 1), subpixel(right_subpixel));
  249. }
  250. void AntiAliasingPainter::draw_ellipse(IntRect const& a_rect, Color color, int thickness)
  251. {
  252. // FIXME: Come up with an allocation-free version of this!
  253. // Using draw_line() for segments of an ellipse was attempted but gave really poor results :^(
  254. // There probably is a way to adjust the fill of draw_ellipse_part() to do this, but getting it rendering correctly is tricky.
  255. // The outline of the steps required to paint it efficiently is:
  256. // - Paint the outer ellipse without the fill (from the fill() lambda in draw_ellipse_part())
  257. // - Paint the inner ellipse, but in the set_pixel() invert the alpha values
  258. // - Somehow fill in the gap between the two ellipses (the tricky part to get right)
  259. // - Have to avoid overlapping pixels and accidentally painting over some of the edge pixels
  260. auto color_no_alpha = color;
  261. color_no_alpha.set_alpha(255);
  262. auto outline_ellipse_bitmap = ({
  263. auto bitmap = Bitmap::try_create(BitmapFormat::BGRA8888, a_rect.size());
  264. if (bitmap.is_error())
  265. return warnln("Failed to allocate temporary bitmap for antialiased outline ellipse!");
  266. bitmap.release_value();
  267. });
  268. auto outer_rect = a_rect;
  269. outer_rect.set_location({ 0, 0 });
  270. auto inner_rect = outer_rect.shrunken(thickness * 2, thickness * 2);
  271. Painter painter { outline_ellipse_bitmap };
  272. AntiAliasingPainter aa_painter { painter };
  273. aa_painter.fill_ellipse(outer_rect, color_no_alpha);
  274. aa_painter.fill_ellipse(inner_rect, color_no_alpha, BlendMode::AlphaSubtract);
  275. m_underlying_painter.blit(a_rect.location(), outline_ellipse_bitmap, outline_ellipse_bitmap->rect(), color.alpha() / 255.);
  276. }
  277. void AntiAliasingPainter::fill_circle(IntPoint const& center, int radius, Color color, BlendMode blend_mode)
  278. {
  279. if (radius <= 0)
  280. return;
  281. draw_ellipse_part(center, radius, radius, color, false, {}, blend_mode);
  282. }
  283. void AntiAliasingPainter::fill_ellipse(IntRect const& a_rect, Color color, BlendMode blend_mode)
  284. {
  285. auto center = a_rect.center();
  286. auto radius_a = a_rect.width() / 2;
  287. auto radius_b = a_rect.height() / 2;
  288. if (radius_a <= 0 || radius_b <= 0)
  289. return;
  290. if (radius_a == radius_b)
  291. return fill_circle(center, radius_a, color, blend_mode);
  292. auto x_paint_range = draw_ellipse_part(center, radius_a, radius_b, color, false, {}, blend_mode);
  293. // FIXME: This paints some extra fill pixels that are clipped
  294. draw_ellipse_part(center, radius_b, radius_a, color, true, x_paint_range, blend_mode);
  295. }
  296. FLATTEN AntiAliasingPainter::Range AntiAliasingPainter::draw_ellipse_part(
  297. IntPoint center, int radius_a, int radius_b, Color color, bool flip_x_and_y, Optional<Range> x_clip, BlendMode blend_mode)
  298. {
  299. /*
  300. Algorithm from: https://cs.uwaterloo.ca/research/tr/1984/CS-84-38.pdf
  301. This method can draw a whole circle with a whole circle in one call using
  302. 8-way symmetry, or an ellipse in two calls using 4-way symmetry.
  303. */
  304. center *= m_underlying_painter.scale();
  305. radius_a *= m_underlying_painter.scale();
  306. radius_b *= m_underlying_painter.scale();
  307. // If this is a ellipse everything can be drawn in one pass with 8 way symmetry
  308. bool const is_circle = radius_a == radius_b;
  309. // These happen to be the same here, but are treated separately in the paper:
  310. // intensity is the fill alpha
  311. int const intensity = 255;
  312. // 0 to subpixel_resolution is the range of alpha values for the circle edges
  313. int const subpixel_resolution = intensity;
  314. // Current pixel address
  315. int i = 0;
  316. int q = radius_b;
  317. // 1st and 2nd order differences of y
  318. int delta_y = 0;
  319. int delta2_y = 0;
  320. int const a_squared = radius_a * radius_a;
  321. int const b_squared = radius_b * radius_b;
  322. // Exact and predicted values of f(i) -- the ellipse equation scaled by subpixel_resolution
  323. int y = subpixel_resolution * radius_b;
  324. int y_hat = 0;
  325. // The value of f(i)*f(i)
  326. int f_squared = y * y;
  327. // 1st and 2nd order differences of f(i)*f(i)
  328. int delta_f_squared = -(static_cast<int64_t>(b_squared) * subpixel_resolution * subpixel_resolution) / a_squared;
  329. int delta2_f_squared = 2 * delta_f_squared;
  330. // edge_intersection_area/subpixel_resolution = percentage of pixel intersected by circle
  331. // (aka the alpha for the pixel)
  332. int edge_intersection_area = 0;
  333. int old_area = edge_intersection_area;
  334. auto predict = [&] {
  335. delta_y += delta2_y;
  336. // y_hat is the predicted value of f(i)
  337. y_hat = y + delta_y;
  338. };
  339. auto minimize = [&] {
  340. // Initialize the minimization
  341. delta_f_squared += delta2_f_squared;
  342. f_squared += delta_f_squared;
  343. int min_squared_error = y_hat * y_hat - f_squared;
  344. int prediction_overshot = 1;
  345. y = y_hat;
  346. // Force error negative
  347. if (min_squared_error > 0) {
  348. min_squared_error = -min_squared_error;
  349. prediction_overshot = -1;
  350. }
  351. // Minimize
  352. int previous_error = min_squared_error;
  353. while (min_squared_error < 0) {
  354. y += prediction_overshot;
  355. previous_error = min_squared_error;
  356. min_squared_error += y + y - prediction_overshot;
  357. }
  358. if (min_squared_error + previous_error > 0)
  359. y -= prediction_overshot;
  360. };
  361. auto correct = [&] {
  362. int error = y - y_hat;
  363. // FIXME: The alpha values seem too low, which makes things look
  364. // overly pointy. This fixes that, though there's probably a better
  365. // solution to be found. (This issue seems to exist in the base algorithm)
  366. error /= 4;
  367. delta2_y += error;
  368. delta_y += error;
  369. };
  370. int min_paint_x = NumericLimits<int>::max();
  371. int max_paint_x = NumericLimits<int>::min();
  372. auto pixel = [&](int x, int y, int alpha) {
  373. if (alpha <= 0 || alpha > 255)
  374. return;
  375. if (flip_x_and_y)
  376. swap(x, y);
  377. if (x_clip.has_value() && x_clip->contains_inclusive(x))
  378. return;
  379. min_paint_x = min(x, min_paint_x);
  380. max_paint_x = max(x, max_paint_x);
  381. alpha = (alpha * color.alpha()) / 255;
  382. if (blend_mode == BlendMode::AlphaSubtract)
  383. alpha = ~alpha;
  384. auto pixel_color = color;
  385. pixel_color.set_alpha(alpha);
  386. m_underlying_painter.set_pixel(center + IntPoint { x, y }, pixel_color, blend_mode == BlendMode::Normal);
  387. };
  388. auto fill = [&](int x, int ymax, int ymin, int alpha) {
  389. while (ymin <= ymax) {
  390. pixel(x, ymin, alpha);
  391. ymin += 1;
  392. }
  393. };
  394. auto symmetric_pixel = [&](int x, int y, int alpha) {
  395. pixel(x, y, alpha);
  396. pixel(x, -y - 1, alpha);
  397. pixel(-x - 1, -y - 1, alpha);
  398. pixel(-x - 1, y, alpha);
  399. if (is_circle) {
  400. pixel(y, x, alpha);
  401. pixel(y, -x - 1, alpha);
  402. pixel(-y - 1, -x - 1, alpha);
  403. pixel(-y - 1, x, alpha);
  404. }
  405. };
  406. // These are calculated incrementally (as it is possibly a tiny bit faster)
  407. int ib_squared = 0;
  408. int qa_squared = q * a_squared;
  409. auto in_symmetric_region = [&] {
  410. // Main fix two stop cond here
  411. return is_circle ? i < q : ib_squared < qa_squared;
  412. };
  413. // Draws a 8 octants for a circle or 4 quadrants for a (partial) ellipse
  414. while (in_symmetric_region()) {
  415. predict();
  416. minimize();
  417. correct();
  418. old_area = edge_intersection_area;
  419. edge_intersection_area += delta_y;
  420. if (edge_intersection_area >= 0) {
  421. // Single pixel on perimeter
  422. symmetric_pixel(i, q, (edge_intersection_area + old_area) / 2);
  423. fill(i, q - 1, -q, intensity);
  424. fill(-i - 1, q - 1, -q, intensity);
  425. } else {
  426. // Two pixels on perimeter
  427. edge_intersection_area += subpixel_resolution;
  428. symmetric_pixel(i, q, old_area / 2);
  429. q -= 1;
  430. qa_squared -= a_squared;
  431. fill(i, q - 1, -q, intensity);
  432. fill(-i - 1, q - 1, -q, intensity);
  433. if (!is_circle || in_symmetric_region()) {
  434. symmetric_pixel(i, q, (edge_intersection_area + subpixel_resolution) / 2);
  435. if (is_circle) {
  436. fill(q, i - 1, -i, intensity);
  437. fill(-q - 1, i - 1, -i, intensity);
  438. }
  439. } else {
  440. edge_intersection_area += subpixel_resolution;
  441. }
  442. }
  443. i += 1;
  444. ib_squared += b_squared;
  445. }
  446. if (is_circle) {
  447. int alpha = edge_intersection_area / 2;
  448. pixel(q, q, alpha);
  449. pixel(-q - 1, q, alpha);
  450. pixel(-q - 1, -q - 1, alpha);
  451. pixel(q, -q - 1, alpha);
  452. }
  453. return Range { min_paint_x, max_paint_x };
  454. }
  455. void AntiAliasingPainter::fill_rect_with_rounded_corners(IntRect const& a_rect, Color color, int radius)
  456. {
  457. fill_rect_with_rounded_corners(a_rect, color, radius, radius, radius, radius);
  458. }
  459. 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)
  460. {
  461. fill_rect_with_rounded_corners(a_rect, color,
  462. { top_left_radius, top_left_radius },
  463. { top_right_radius, top_right_radius },
  464. { bottom_right_radius, bottom_right_radius },
  465. { bottom_left_radius, bottom_left_radius });
  466. }
  467. 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)
  468. {
  469. if (!top_left && !top_right && !bottom_right && !bottom_left) {
  470. if (blend_mode == BlendMode::Normal)
  471. return m_underlying_painter.fill_rect(a_rect, color);
  472. else if (blend_mode == BlendMode::AlphaSubtract)
  473. return m_underlying_painter.clear_rect(a_rect, Color());
  474. }
  475. if (color.alpha() == 0)
  476. return;
  477. IntPoint top_left_corner {
  478. a_rect.x() + top_left.horizontal_radius,
  479. a_rect.y() + top_left.vertical_radius,
  480. };
  481. IntPoint top_right_corner {
  482. a_rect.x() + a_rect.width() - top_right.horizontal_radius,
  483. a_rect.y() + top_right.vertical_radius,
  484. };
  485. IntPoint bottom_left_corner {
  486. a_rect.x() + bottom_left.horizontal_radius,
  487. a_rect.y() + a_rect.height() - bottom_left.vertical_radius
  488. };
  489. IntPoint bottom_right_corner {
  490. a_rect.x() + a_rect.width() - bottom_right.horizontal_radius,
  491. a_rect.y() + a_rect.height() - bottom_right.vertical_radius
  492. };
  493. // All corners are centered at the same point, so this can be painted as a single ellipse.
  494. if (top_left_corner == top_right_corner && top_right_corner == bottom_left_corner && bottom_left_corner == bottom_right_corner)
  495. return fill_ellipse(a_rect, color, blend_mode);
  496. IntRect top_rect {
  497. a_rect.x() + top_left.horizontal_radius,
  498. a_rect.y(),
  499. a_rect.width() - top_left.horizontal_radius - top_right.horizontal_radius,
  500. top_left.vertical_radius
  501. };
  502. IntRect right_rect {
  503. a_rect.x() + a_rect.width() - top_right.horizontal_radius,
  504. a_rect.y() + top_right.vertical_radius,
  505. top_right.horizontal_radius,
  506. a_rect.height() - top_right.vertical_radius - bottom_right.vertical_radius
  507. };
  508. IntRect bottom_rect {
  509. a_rect.x() + bottom_left.horizontal_radius,
  510. a_rect.y() + a_rect.height() - bottom_right.vertical_radius,
  511. a_rect.width() - bottom_left.horizontal_radius - bottom_right.horizontal_radius,
  512. bottom_right.vertical_radius
  513. };
  514. IntRect left_rect {
  515. a_rect.x(),
  516. a_rect.y() + top_left.vertical_radius,
  517. bottom_left.horizontal_radius,
  518. a_rect.height() - top_left.vertical_radius - bottom_left.vertical_radius
  519. };
  520. IntRect inner = {
  521. left_rect.x() + left_rect.width(),
  522. left_rect.y(),
  523. a_rect.width() - left_rect.width() - right_rect.width(),
  524. a_rect.height() - top_rect.height() - bottom_rect.height()
  525. };
  526. if (blend_mode == BlendMode::Normal) {
  527. m_underlying_painter.fill_rect(top_rect, color);
  528. m_underlying_painter.fill_rect(right_rect, color);
  529. m_underlying_painter.fill_rect(bottom_rect, color);
  530. m_underlying_painter.fill_rect(left_rect, color);
  531. m_underlying_painter.fill_rect(inner, color);
  532. } else if (blend_mode == BlendMode::AlphaSubtract) {
  533. m_underlying_painter.clear_rect(top_rect, Color());
  534. m_underlying_painter.clear_rect(right_rect, Color());
  535. m_underlying_painter.clear_rect(bottom_rect, Color());
  536. m_underlying_painter.clear_rect(left_rect, Color());
  537. m_underlying_painter.clear_rect(inner, Color());
  538. }
  539. auto fill_corner = [&](auto const& ellipse_center, auto const& corner_point, CornerRadius const& corner) {
  540. PainterStateSaver save { m_underlying_painter };
  541. m_underlying_painter.add_clip_rect(IntRect::from_two_points(ellipse_center, corner_point));
  542. fill_ellipse(IntRect::centered_at(ellipse_center, { corner.horizontal_radius * 2, corner.vertical_radius * 2 }), color, blend_mode);
  543. };
  544. auto bounding_rect = a_rect.inflated(0, 1, 1, 0);
  545. if (top_left)
  546. fill_corner(top_left_corner, bounding_rect.top_left(), top_left);
  547. if (top_right)
  548. fill_corner(top_right_corner, bounding_rect.top_right(), top_right);
  549. if (bottom_left)
  550. fill_corner(bottom_left_corner, bounding_rect.bottom_left(), bottom_left);
  551. if (bottom_right)
  552. fill_corner(bottom_right_corner, bounding_rect.bottom_right(), bottom_right);
  553. }
  554. }