AntiAliasingPainter.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  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 <LibGfx/AntiAliasingPainter.h>
  13. #include <LibGfx/Path.h>
  14. // Base algorithm from https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm,
  15. // because there seems to be no other known method for drawing AA'd lines (?)
  16. template<Gfx::AntiAliasingPainter::AntiAliasPolicy policy, typename TransformPoint>
  17. void Gfx::AntiAliasingPainter::draw_anti_aliased_line(FloatPoint const& actual_from, FloatPoint const& actual_to, Color color, float thickness, Gfx::Painter::LineStyle style, Color, TransformPoint transform_point)
  18. {
  19. // FIXME: Implement this :P
  20. VERIFY(style == Painter::LineStyle::Solid);
  21. auto corrected_thickness = thickness > 1 ? thickness - 1 : thickness;
  22. auto size = IntSize(corrected_thickness, corrected_thickness);
  23. auto plot = [&](int x, int y, float c) {
  24. transform_point(x, y, m_transform);
  25. m_underlying_painter.fill_rect(IntRect::centered_on({ x, y }, size), color.with_alpha(color.alpha() * c));
  26. };
  27. auto integer_part = [](float x) { return floorf(x); };
  28. auto round = [&](float x) { return integer_part(x + 0.5f); };
  29. auto fractional_part = [&](float x) { return x - floorf(x); };
  30. auto one_minus_fractional_part = [&](float x) { return 1.0f - fractional_part(x); };
  31. auto draw_line = [&](float x0, float y0, float x1, float y1) {
  32. bool steep = fabsf(y1 - y0) > fabsf(x1 - x0);
  33. if (steep) {
  34. swap(x0, y0);
  35. swap(x1, y1);
  36. }
  37. if (x0 > x1) {
  38. swap(x0, x1);
  39. swap(y0, y1);
  40. }
  41. float dx = x1 - x0;
  42. float dy = y1 - y0;
  43. float gradient;
  44. if (dx == 0.0f)
  45. gradient = 1.0f;
  46. else
  47. gradient = dy / dx;
  48. // Handle first endpoint.
  49. int x_end = round(x0);
  50. int y_end = y0 + gradient * (x_end - x0);
  51. float x_gap = one_minus_fractional_part(x0 + 0.5f);
  52. int xpxl1 = x_end; // This will be used in the main loop.
  53. int ypxl1 = integer_part(y_end);
  54. if (steep) {
  55. plot(ypxl1, xpxl1, one_minus_fractional_part(y_end) * x_gap);
  56. plot(ypxl1 + 1, xpxl1, fractional_part(y_end) * x_gap);
  57. } else {
  58. plot(xpxl1, ypxl1, one_minus_fractional_part(y_end) * x_gap);
  59. plot(xpxl1, ypxl1 + 1, fractional_part(y_end) * x_gap);
  60. }
  61. float intery = y_end + gradient; // First y-intersection for the main loop.
  62. // Handle second endpoint.
  63. x_end = round(x1);
  64. y_end = y1 + gradient * (x_end - x1);
  65. x_gap = fractional_part(x1 + 0.5f);
  66. int xpxl2 = x_end; // This will be used in the main loop
  67. int ypxl2 = integer_part(y_end);
  68. if (steep) {
  69. plot(ypxl2, xpxl2, one_minus_fractional_part(y_end) * x_gap);
  70. plot(ypxl2 + 1, xpxl2, fractional_part(y_end) * x_gap);
  71. } else {
  72. plot(xpxl2, ypxl2, one_minus_fractional_part(y_end) * x_gap);
  73. plot(xpxl2, ypxl2 + 1, fractional_part(y_end) * x_gap);
  74. }
  75. // Main loop.
  76. if (steep) {
  77. for (int x = xpxl1 + 1; x <= xpxl2 - 1; ++x) {
  78. if constexpr (policy == AntiAliasPolicy::OnlyEnds) {
  79. plot(integer_part(intery), x, 1);
  80. } else {
  81. plot(integer_part(intery), x, one_minus_fractional_part(intery));
  82. }
  83. plot(integer_part(intery) + 1, x, fractional_part(intery));
  84. intery += gradient;
  85. }
  86. } else {
  87. for (int x = xpxl1 + 1; x <= xpxl2 - 1; ++x) {
  88. if constexpr (policy == AntiAliasPolicy::OnlyEnds) {
  89. plot(x, integer_part(intery), 1);
  90. } else {
  91. plot(x, integer_part(intery), one_minus_fractional_part(intery));
  92. }
  93. plot(x, integer_part(intery) + 1, fractional_part(intery));
  94. intery += gradient;
  95. }
  96. }
  97. };
  98. draw_line(actual_from.x(), actual_from.y(), actual_to.x(), actual_to.y());
  99. }
  100. static ALWAYS_INLINE void no_transform(int&, int&, Gfx::AffineTransform const&)
  101. {
  102. }
  103. static ALWAYS_INLINE void full_transform(int& x, int& y, Gfx::AffineTransform const& transform)
  104. {
  105. auto mapped = transform.map(Gfx::IntPoint { x, y });
  106. x = mapped.x();
  107. y = mapped.y();
  108. }
  109. void Gfx::AntiAliasingPainter::draw_aliased_line(FloatPoint const& actual_from, FloatPoint const& actual_to, Color color, float thickness, Gfx::Painter::LineStyle style, Color alternate_color)
  110. {
  111. if (m_transform.is_identity_or_translation()) {
  112. m_underlying_painter.translate(m_transform.e(), m_transform.f());
  113. draw_anti_aliased_line<AntiAliasPolicy::OnlyEnds>(actual_from, actual_to, color, thickness, style, alternate_color, no_transform);
  114. m_underlying_painter.translate(-m_transform.e(), -m_transform.f());
  115. } else {
  116. draw_anti_aliased_line<AntiAliasPolicy::OnlyEnds>(actual_from, actual_to, color, thickness, style, alternate_color, full_transform);
  117. }
  118. }
  119. void Gfx::AntiAliasingPainter::draw_line(FloatPoint const& actual_from, FloatPoint const& actual_to, Color color, float thickness, Gfx::Painter::LineStyle style, Color alternate_color)
  120. {
  121. if (m_transform.is_identity_or_translation()) {
  122. m_underlying_painter.translate(m_transform.e(), m_transform.f());
  123. draw_anti_aliased_line<AntiAliasPolicy::Full>(actual_from, actual_to, color, thickness, style, alternate_color, no_transform);
  124. m_underlying_painter.translate(-m_transform.e(), -m_transform.f());
  125. } else {
  126. draw_anti_aliased_line<AntiAliasPolicy::Full>(actual_from, actual_to, color, thickness, style, alternate_color, full_transform);
  127. }
  128. }
  129. void Gfx::AntiAliasingPainter::fill_path(Path& path, Color color, Painter::WindingRule rule)
  130. {
  131. Detail::fill_path<Detail::FillPathMode::AllowFloatingPoints>(*this, path, color, rule);
  132. }
  133. void Gfx::AntiAliasingPainter::stroke_path(Path const& path, Color color, float thickness)
  134. {
  135. FloatPoint cursor;
  136. for (auto& segment : path.segments()) {
  137. switch (segment.type()) {
  138. case Segment::Type::Invalid:
  139. VERIFY_NOT_REACHED();
  140. case Segment::Type::MoveTo:
  141. cursor = segment.point();
  142. break;
  143. case Segment::Type::LineTo:
  144. draw_line(cursor, segment.point(), color, thickness);
  145. cursor = segment.point();
  146. break;
  147. case Segment::Type::QuadraticBezierCurveTo: {
  148. auto& through = static_cast<QuadraticBezierCurveSegment const&>(segment).through();
  149. draw_quadratic_bezier_curve(through, cursor, segment.point(), color, thickness);
  150. cursor = segment.point();
  151. break;
  152. }
  153. case Segment::Type::CubicBezierCurveTo: {
  154. auto& curve = static_cast<CubicBezierCurveSegment const&>(segment);
  155. auto& through_0 = curve.through_0();
  156. auto& through_1 = curve.through_1();
  157. draw_cubic_bezier_curve(through_0, through_1, cursor, segment.point(), color, thickness);
  158. cursor = segment.point();
  159. break;
  160. }
  161. case Segment::Type::EllipticalArcTo:
  162. auto& arc = static_cast<EllipticalArcSegment const&>(segment);
  163. draw_elliptical_arc(cursor, segment.point(), arc.center(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), color, thickness);
  164. cursor = segment.point();
  165. break;
  166. }
  167. }
  168. }
  169. void Gfx::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)
  170. {
  171. Gfx::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) {
  172. draw_line(fp1, fp2, color, thickness, style);
  173. });
  174. }
  175. void Gfx::AntiAliasingPainter::draw_quadratic_bezier_curve(FloatPoint const& control_point, FloatPoint const& p1, FloatPoint const& p2, Color color, float thickness, Painter::LineStyle style)
  176. {
  177. Gfx::Painter::for_each_line_segment_on_bezier_curve(control_point, p1, p2, [&](FloatPoint const& fp1, FloatPoint const& fp2) {
  178. draw_line(fp1, fp2, color, thickness, style);
  179. });
  180. }
  181. void Gfx::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)
  182. {
  183. Gfx::Painter::for_each_line_segment_on_cubic_bezier_curve(control_point_0, control_point_1, p1, p2, [&](FloatPoint const& fp1, FloatPoint const& fp2) {
  184. draw_line(fp1, fp2, color, thickness, style);
  185. });
  186. }
  187. void Gfx::AntiAliasingPainter::draw_circle(IntPoint center, int radius, Color color)
  188. {
  189. /*
  190. Algorithm from: https://cs.uwaterloo.ca/research/tr/1984/CS-84-38.pdf
  191. Inline comments are from the paper.
  192. */
  193. center *= m_underlying_painter.scale();
  194. radius *= m_underlying_painter.scale();
  195. // TODO: Generalize to ellipses (see paper)
  196. // These happen to be the same here, but are treated separately in the paper:
  197. // intensity is the fill alpha
  198. int const intensity = color.alpha();
  199. // 0 to subpixel_resolution is the range of alpha values for the circle edges
  200. int const subpixel_resolution = intensity;
  201. // Note: Variable names below are based off the paper
  202. // Current pixel address
  203. int i = 0;
  204. int q = radius;
  205. // 1st and 2nd order differences of y
  206. int delta_y = 0;
  207. int delta2_y = 0;
  208. // Exact and predicted values of f(i) -- the circle equation scaled by subpixel_resolution
  209. int y = subpixel_resolution * radius;
  210. int y_hat = 0;
  211. // The value of f(i)*f(i)
  212. int f_squared = y * y;
  213. // 1st and 2nd order differences of f(i)*f(i)
  214. int delta_f_squared = subpixel_resolution * subpixel_resolution;
  215. int delta2_f_squared = -delta_f_squared - delta_f_squared;
  216. // edge_intersection_area/subpixel_resolution = percentage of pixel intersected by circle
  217. // (aka the alpha for the pixel)
  218. int edge_intersection_area = 0;
  219. int old_area = edge_intersection_area;
  220. auto predict = [&] {
  221. delta_y += delta2_y;
  222. // y_hat is the predicted value of f(i)
  223. y_hat = y + delta_y;
  224. };
  225. auto minimize = [&] {
  226. // Initialize the minimization
  227. delta_f_squared += delta2_f_squared;
  228. f_squared += delta_f_squared;
  229. int min_squared_error = y_hat * y_hat - f_squared;
  230. int prediction_overshot = 1;
  231. y = y_hat;
  232. // Force error negative
  233. if (min_squared_error > 0) {
  234. min_squared_error = -min_squared_error;
  235. prediction_overshot = -1;
  236. }
  237. // Minimize
  238. int previous_error = min_squared_error;
  239. while (min_squared_error < 0) {
  240. y += prediction_overshot;
  241. previous_error = min_squared_error;
  242. min_squared_error += y + y - prediction_overshot;
  243. }
  244. if (min_squared_error + previous_error > 0)
  245. y -= prediction_overshot;
  246. };
  247. auto correct = [&] {
  248. int error = y - y_hat;
  249. delta2_y += error;
  250. delta_y += error;
  251. };
  252. auto pixel = [&](int x, int y, int alpha) {
  253. if (alpha <= 0 || alpha > 255)
  254. return;
  255. auto pixel_colour = color;
  256. pixel_colour.set_alpha(alpha);
  257. m_underlying_painter.set_pixel(center + IntPoint { x, y }, pixel_colour, true);
  258. };
  259. auto fill = [&](int x, int ymax, int ymin, int alpha) {
  260. while (ymin <= ymax) {
  261. pixel(x, ymin, alpha);
  262. ymin += 1;
  263. }
  264. };
  265. auto eight_pixel = [&](int x, int y, int alpha) {
  266. pixel(x, y, alpha);
  267. pixel(x, -y - 1, alpha);
  268. pixel(-x - 1, -y - 1, alpha);
  269. pixel(-x - 1, y, alpha);
  270. pixel(y, x, alpha);
  271. pixel(y, -x - 1, alpha);
  272. pixel(-y - 1, -x - 1, alpha);
  273. pixel(-y - 1, x, alpha);
  274. };
  275. while (i < q) {
  276. predict();
  277. minimize();
  278. correct();
  279. old_area = edge_intersection_area;
  280. edge_intersection_area += delta_y;
  281. if (edge_intersection_area >= 0) {
  282. // Single pixel on perimeter
  283. eight_pixel(i, q, (edge_intersection_area + old_area) / 2);
  284. fill(i, q - 1, -q, intensity);
  285. fill(-i - 1, q - 1, -q, intensity);
  286. } else {
  287. // Two pixels on perimeter
  288. edge_intersection_area += subpixel_resolution;
  289. eight_pixel(i, q, old_area / 2);
  290. q -= 1;
  291. fill(i, q - 1, -q, intensity);
  292. fill(-i - 1, q - 1, -q, intensity);
  293. if (i < q) {
  294. // Haven't gone below the diagonal
  295. eight_pixel(i, q, (edge_intersection_area + subpixel_resolution) / 2);
  296. fill(q, i - 1, -i, intensity);
  297. fill(-q - 1, i - 1, -i, intensity);
  298. } else {
  299. // Went below the diagonal, fix edge_intersection_area for final pixels
  300. edge_intersection_area += subpixel_resolution;
  301. }
  302. }
  303. i += 1;
  304. }
  305. // Fill in 4 remaning pixels
  306. int alpha = edge_intersection_area / 2;
  307. pixel(q, q, alpha);
  308. pixel(-q - 1, q, alpha);
  309. pixel(-q - 1, -q - 1, alpha);
  310. pixel(q, -q - 1, alpha);
  311. }
  312. void Gfx::AntiAliasingPainter::fill_rect_with_rounded_corners(IntRect const& a_rect, Color color, int radius)
  313. {
  314. fill_rect_with_rounded_corners(a_rect, color, radius, radius, radius, radius);
  315. }
  316. void Gfx::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)
  317. {
  318. if (!top_left_radius && !top_right_radius && !bottom_right_radius && !bottom_left_radius)
  319. return m_underlying_painter.fill_rect(a_rect, color);
  320. if (color.alpha() == 0)
  321. return;
  322. IntPoint top_left_corner {
  323. a_rect.x() + top_left_radius,
  324. a_rect.y() + top_left_radius,
  325. };
  326. IntPoint top_right_corner {
  327. a_rect.x() + a_rect.width() - top_right_radius,
  328. a_rect.y() + top_right_radius,
  329. };
  330. IntPoint bottom_left_corner {
  331. a_rect.x() + bottom_left_radius,
  332. a_rect.y() + a_rect.height() - bottom_right_radius
  333. };
  334. IntPoint bottom_right_corner {
  335. a_rect.x() + a_rect.width() - bottom_left_radius,
  336. a_rect.y() + a_rect.height() - bottom_left_radius
  337. };
  338. IntRect top_rect {
  339. a_rect.x() + top_left_radius,
  340. a_rect.y(),
  341. a_rect.width() - top_left_radius - top_right_radius,
  342. top_left_radius
  343. };
  344. IntRect right_rect {
  345. a_rect.x() + a_rect.width() - top_right_radius,
  346. a_rect.y() + top_right_radius,
  347. top_right_radius,
  348. a_rect.height() - top_right_radius - bottom_right_radius
  349. };
  350. IntRect bottom_rect {
  351. a_rect.x() + bottom_left_radius,
  352. a_rect.y() + a_rect.height() - bottom_right_radius,
  353. a_rect.width() - bottom_left_radius - bottom_right_radius,
  354. bottom_right_radius
  355. };
  356. IntRect left_rect {
  357. a_rect.x(),
  358. a_rect.y() + top_left_radius,
  359. bottom_left_radius,
  360. a_rect.height() - top_left_radius - bottom_left_radius
  361. };
  362. IntRect inner = {
  363. left_rect.x() + left_rect.width(),
  364. left_rect.y(),
  365. a_rect.width() - left_rect.width() - right_rect.width(),
  366. a_rect.height() - top_rect.height() - bottom_rect.height()
  367. };
  368. m_underlying_painter.fill_rect(top_rect, color);
  369. m_underlying_painter.fill_rect(right_rect, color);
  370. m_underlying_painter.fill_rect(bottom_rect, color);
  371. m_underlying_painter.fill_rect(left_rect, color);
  372. m_underlying_painter.fill_rect(inner, color);
  373. // FIXME: Don't draw a whole circle each time
  374. if (top_left_radius)
  375. draw_circle(top_left_corner, top_left_radius, color);
  376. if (top_right_radius)
  377. draw_circle(top_right_corner, top_right_radius, color);
  378. if (bottom_left_radius)
  379. draw_circle(bottom_left_corner, bottom_left_radius, color);
  380. if (bottom_right_radius)
  381. draw_circle(bottom_right_corner, bottom_right_radius, color);
  382. }