AntiAliasingPainter.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  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 "FillPathImplementation.h"
  12. #include <AK/Function.h>
  13. #include <AK/NumericLimits.h>
  14. #include <LibGfx/AntiAliasingPainter.h>
  15. #include <LibGfx/Line.h>
  16. namespace Gfx {
  17. template<AntiAliasingPainter::FixmeEnableHacksForBetterPathPainting path_hacks>
  18. void AntiAliasingPainter::draw_anti_aliased_line(FloatPoint actual_from, FloatPoint actual_to, Color color, float thickness, Painter::LineStyle style, Color)
  19. {
  20. // FIXME: Implement this :P
  21. VERIFY(style == Painter::LineStyle::Solid);
  22. if (color.alpha() == 0)
  23. return;
  24. // FIMXE:
  25. // This is not a proper line drawing algorithm.
  26. // It's hack-ish AA rotated rectangle painting.
  27. // There's probably more optimal ways to achieve this
  28. // (though this still runs faster than the previous AA-line code)
  29. //
  30. // If you, reading this comment, know a better way that:
  31. // 1. Does not overpaint (i.e. painting a line with transparency looks correct)
  32. // 2. Has square end points (i.e. the line is a rectangle)
  33. // 3. Has good anti-aliasing
  34. // 4. Is less hacky than this
  35. //
  36. // Please delete this code and implement it!
  37. int int_thickness = AK::ceil(thickness);
  38. auto mapped_from = m_transform.map(actual_from);
  39. auto mapped_to = m_transform.map(actual_to);
  40. auto distance = mapped_to.distance_from(mapped_from);
  41. auto length = distance + 1;
  42. // Axis-aligned lines:
  43. if (mapped_from.y() == mapped_to.y()) {
  44. auto start_point = (mapped_from.x() < mapped_to.x() ? mapped_from : mapped_to).translated(0, -int_thickness / 2);
  45. if constexpr (path_hacks == FixmeEnableHacksForBetterPathPainting::Yes) {
  46. // FIXME: SVG fill_path() hack:
  47. // SVG asks for 1px scanlines at floating point y values, if they're not snapped to a pixel they look faint.
  48. start_point.set_y(floorf(start_point.y()));
  49. }
  50. return fill_rect(Gfx::FloatRect(start_point, { length, thickness }), color);
  51. }
  52. if (mapped_from.x() == mapped_to.x()) {
  53. auto start_point = (mapped_from.y() < mapped_to.y() ? mapped_from : mapped_to).translated(-int_thickness / 2, 0);
  54. return fill_rect(Gfx::FloatRect(start_point, { thickness, length }), color);
  55. }
  56. if constexpr (path_hacks == FixmeEnableHacksForBetterPathPainting::Yes) {
  57. // FIXME: SVG stoke_path() hack:
  58. // When painting stokes SVG asks for many very short lines...
  59. // These look better just painted as dots/AA rectangles
  60. // (Technically this should be rotated or a circle, but that currently gives worse results)
  61. if (distance < 1.0f)
  62. return fill_rect(Gfx::FloatRect::centered_at(mapped_from, { thickness, thickness }), color);
  63. }
  64. // The painting only works for the positive XY quadrant (because that is easier).
  65. // So flip things around until we're there:
  66. bool flip_x = false;
  67. bool flip_y = false;
  68. if (mapped_to.x() < mapped_from.x() && mapped_to.y() < mapped_from.y())
  69. swap(mapped_to, mapped_from);
  70. if ((flip_x = mapped_to.x() < mapped_from.x()))
  71. mapped_to.set_x(2 * mapped_from.x() - mapped_to.x());
  72. if ((flip_y = mapped_to.y() < mapped_from.y()))
  73. mapped_to.set_y(2 * mapped_from.y() - mapped_to.y());
  74. auto delta = mapped_to - mapped_from;
  75. auto line_angle_radians = AK::atan2(delta.y(), delta.x()) - 0.5f * AK::Pi<float>;
  76. float sin_inverse_angle;
  77. float cos_inverse_angle;
  78. AK::sincos(-line_angle_radians, sin_inverse_angle, cos_inverse_angle);
  79. auto inverse_rotate_point = [=](FloatPoint point) {
  80. return Gfx::FloatPoint(
  81. point.x() * cos_inverse_angle - point.y() * sin_inverse_angle,
  82. point.y() * cos_inverse_angle + point.x() * sin_inverse_angle);
  83. };
  84. Gfx::FloatRect line_rect({ -(thickness * 255) / 2.0f, 0 }, Gfx::FloatSize(thickness * 255, length * 255));
  85. auto gradient = delta.y() / delta.x();
  86. // Work out how long we need to scan along the X-axis to reach the other side of the line.
  87. // E.g. for a vertical line this would be `thickness', in general it is this:
  88. int scan_line_length = AK::ceil(AK::sqrt((gradient * gradient + 1) * thickness * thickness) / gradient);
  89. auto x_gradient = 1 / gradient;
  90. int x_step = floorf(x_gradient);
  91. float x_error = 0;
  92. float x_error_per_y = x_gradient - x_step;
  93. auto y_offset = int_thickness;
  94. auto x_offset = int(x_gradient * y_offset);
  95. int const line_start_x = mapped_from.x();
  96. int const line_start_y = mapped_from.y();
  97. int const line_end_x = mapped_to.x();
  98. int const line_end_y = mapped_to.y();
  99. auto set_pixel = [=, this](int x, int y, Gfx::Color color) {
  100. // FIXME: The lines seem slightly off (<= 1px) when flipped.
  101. if (flip_x)
  102. x = 2 * line_start_x - x;
  103. if (flip_y)
  104. y = 2 * line_start_y - y;
  105. m_underlying_painter.set_pixel(x, y, color, true);
  106. };
  107. // Scan a bit extra to avoid issues from the x_error:
  108. int const overscan = max(x_step, 1) * 2 + 1;
  109. int x = line_start_x - x_offset;
  110. int const center_offset = (scan_line_length + 1) / 2;
  111. for (int y = line_start_y - y_offset; y < line_end_y + y_offset; y += 1) {
  112. for (int i = -overscan; i < scan_line_length + overscan; i++) {
  113. int scan_x_pos = x + i - center_offset;
  114. // Avoid scanning over pixels definitely outside the line:
  115. int dx = (line_start_x - int_thickness) - (scan_x_pos + 1);
  116. if (dx > 0) {
  117. i += dx;
  118. continue;
  119. }
  120. if (line_end_x + int_thickness <= scan_x_pos - 1)
  121. break;
  122. auto sample = inverse_rotate_point(Gfx::FloatPoint(scan_x_pos - line_start_x, y - line_start_y));
  123. Gfx::FloatRect sample_px(sample * 255, Gfx::FloatSize(255, 255));
  124. sample_px.intersect(line_rect);
  125. auto alpha = (sample_px.width() * sample_px.height()) / 255.0f;
  126. alpha = (alpha * color.alpha()) / 255;
  127. set_pixel(scan_x_pos, y, color.with_alpha(alpha));
  128. }
  129. x += x_step;
  130. x_error += x_error_per_y;
  131. if (x_error > 1.0f) {
  132. x_error -= 1.0f;
  133. x += 1;
  134. }
  135. }
  136. }
  137. void AntiAliasingPainter::draw_line_for_path(FloatPoint const& actual_from, FloatPoint const& actual_to, Color color, float thickness, Painter::LineStyle style, Color alternate_color)
  138. {
  139. draw_anti_aliased_line<FixmeEnableHacksForBetterPathPainting::Yes>(actual_from, actual_to, color, thickness, style, alternate_color);
  140. }
  141. void AntiAliasingPainter::draw_dotted_line(IntPoint point1, IntPoint point2, Color color, int thickness)
  142. {
  143. // AA circles don't really work below a radius of 2px.
  144. if (thickness < 4)
  145. return m_underlying_painter.draw_line(point1, point2, color, thickness, Painter::LineStyle::Dotted);
  146. auto draw_spaced_dots = [&](int start, int end, auto to_point) {
  147. int step = thickness * 2;
  148. if (start > end)
  149. swap(start, end);
  150. int delta = end - start;
  151. int dots = delta / step;
  152. if (dots == 0)
  153. return;
  154. int fudge_per_dot = 0;
  155. int extra_fudge = 0;
  156. if (dots > 3) {
  157. // Fudge the numbers so the last dot is drawn at the `end' point (otherwise you can get lines cuts short).
  158. // You need at least a handful of dots to do this.
  159. int fudge = delta % step;
  160. fudge_per_dot = fudge / dots;
  161. extra_fudge = fudge % dots;
  162. }
  163. for (int dot = start; dot <= end; dot += (step + fudge_per_dot + (extra_fudge > 0))) {
  164. fill_circle(to_point(dot), thickness / 2, color);
  165. --extra_fudge;
  166. }
  167. };
  168. if (point1.y() == point2.y()) {
  169. draw_spaced_dots(point1.x(), point2.x(), [&](int dot_x) {
  170. return IntPoint { dot_x, point1.y() };
  171. });
  172. } else if (point1.x() == point2.x()) {
  173. draw_spaced_dots(point1.y(), point2.y(), [&](int dot_y) {
  174. return IntPoint { point1.x(), dot_y };
  175. });
  176. } else {
  177. TODO();
  178. }
  179. }
  180. void AntiAliasingPainter::draw_line(FloatPoint const& actual_from, FloatPoint const& actual_to, Color color, float thickness, Painter::LineStyle style, Color alternate_color)
  181. {
  182. if (style == Painter::LineStyle::Dotted)
  183. return draw_dotted_line(actual_from.to_rounded<int>(), actual_to.to_rounded<int>(), color, static_cast<int>(round(thickness)));
  184. draw_anti_aliased_line<FixmeEnableHacksForBetterPathPainting::No>(actual_from, actual_to, color, thickness, style, alternate_color);
  185. }
  186. void AntiAliasingPainter::fill_path(Path& path, Color color, Painter::WindingRule rule)
  187. {
  188. Detail::fill_path<Detail::FillPathMode::AllowFloatingPoints>(*this, path, color, rule);
  189. }
  190. void AntiAliasingPainter::stroke_path(Path const& path, Color color, float thickness)
  191. {
  192. FloatPoint cursor;
  193. bool previous_was_line = false;
  194. FloatLine last_line;
  195. Optional<FloatLine> first_line;
  196. for (auto& segment : path.segments()) {
  197. switch (segment.type()) {
  198. case Segment::Type::Invalid:
  199. VERIFY_NOT_REACHED();
  200. case Segment::Type::MoveTo:
  201. cursor = segment.point();
  202. break;
  203. case Segment::Type::LineTo:
  204. draw_line(cursor, segment.point(), color, thickness);
  205. if (thickness > 1) {
  206. if (!first_line.has_value())
  207. first_line = FloatLine(cursor, segment.point());
  208. if (previous_was_line)
  209. stroke_segment_intersection(cursor, segment.point(), last_line, color, thickness);
  210. last_line.set_a(cursor);
  211. last_line.set_b(segment.point());
  212. }
  213. cursor = segment.point();
  214. break;
  215. case Segment::Type::QuadraticBezierCurveTo: {
  216. auto& through = static_cast<QuadraticBezierCurveSegment const&>(segment).through();
  217. draw_quadratic_bezier_curve(through, cursor, segment.point(), color, thickness);
  218. cursor = segment.point();
  219. break;
  220. }
  221. case Segment::Type::CubicBezierCurveTo: {
  222. auto& curve = static_cast<CubicBezierCurveSegment const&>(segment);
  223. auto& through_0 = curve.through_0();
  224. auto& through_1 = curve.through_1();
  225. draw_cubic_bezier_curve(through_0, through_1, cursor, segment.point(), color, thickness);
  226. cursor = segment.point();
  227. break;
  228. }
  229. case Segment::Type::EllipticalArcTo:
  230. auto& arc = static_cast<EllipticalArcSegment const&>(segment);
  231. draw_elliptical_arc(cursor, segment.point(), arc.center(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), color, thickness);
  232. cursor = segment.point();
  233. break;
  234. }
  235. previous_was_line = segment.type() == Segment::Type::LineTo;
  236. }
  237. // Check if the figure was started and closed as line at the same position.
  238. if (thickness > 1 && previous_was_line && path.segments().size() >= 2 && path.segments().first().point() == cursor && (path.segments().first().type() == Segment::Type::LineTo || (path.segments().first().type() == Segment::Type::MoveTo && path.segments()[1].type() == Segment::Type::LineTo)))
  239. stroke_segment_intersection(first_line.value().a(), first_line.value().b(), last_line, color, thickness);
  240. }
  241. 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)
  242. {
  243. 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) {
  244. draw_line_for_path(fp1, fp2, color, thickness, style);
  245. });
  246. }
  247. void AntiAliasingPainter::draw_quadratic_bezier_curve(FloatPoint const& control_point, FloatPoint const& p1, FloatPoint const& p2, Color color, float thickness, Painter::LineStyle style)
  248. {
  249. Painter::for_each_line_segment_on_bezier_curve(control_point, p1, p2, [&](FloatPoint const& fp1, FloatPoint const& fp2) {
  250. draw_line_for_path(fp1, fp2, color, thickness, style);
  251. });
  252. }
  253. 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)
  254. {
  255. Painter::for_each_line_segment_on_cubic_bezier_curve(control_point_0, control_point_1, p1, p2, [&](FloatPoint const& fp1, FloatPoint const& fp2) {
  256. draw_line_for_path(fp1, fp2, color, thickness, style);
  257. });
  258. }
  259. void AntiAliasingPainter::fill_rect(FloatRect const& float_rect, Color color)
  260. {
  261. // Draw the integer part of the rectangle:
  262. float right_x = float_rect.x() + float_rect.width();
  263. float bottom_y = float_rect.y() + float_rect.height();
  264. int x1 = ceilf(float_rect.x());
  265. int y1 = ceilf(float_rect.y());
  266. int x2 = floorf(right_x);
  267. int y2 = floorf(bottom_y);
  268. auto solid_rect = Gfx::IntRect::from_two_points({ x1, y1 }, { x2, y2 });
  269. m_underlying_painter.fill_rect(solid_rect, color);
  270. if (float_rect == solid_rect)
  271. return;
  272. // Draw the rest:
  273. float left_subpixel = x1 - float_rect.x();
  274. float top_subpixel = y1 - float_rect.y();
  275. float right_subpixel = right_x - x2;
  276. float bottom_subpixel = bottom_y - y2;
  277. float top_left_subpixel = top_subpixel * left_subpixel;
  278. float top_right_subpixel = top_subpixel * right_subpixel;
  279. float bottom_left_subpixel = bottom_subpixel * left_subpixel;
  280. float bottom_right_subpixel = bottom_subpixel * right_subpixel;
  281. auto subpixel = [&](float alpha) {
  282. return color.with_alpha(color.alpha() * alpha);
  283. };
  284. auto set_pixel = [&](int x, int y, float alpha) {
  285. m_underlying_painter.set_pixel(x, y, subpixel(alpha), true);
  286. };
  287. auto line_to_rect = [&](int x1, int y1, int x2, int y2) {
  288. return IntRect::from_two_points({ x1, y1 }, { x2 + 1, y2 + 1 });
  289. };
  290. set_pixel(x1 - 1, y1 - 1, top_left_subpixel);
  291. set_pixel(x2, y1 - 1, top_right_subpixel);
  292. set_pixel(x2, y2, bottom_right_subpixel);
  293. set_pixel(x1 - 1, y2, bottom_left_subpixel);
  294. m_underlying_painter.fill_rect(line_to_rect(x1, y1 - 1, x2 - 1, y1 - 1), subpixel(top_subpixel));
  295. m_underlying_painter.fill_rect(line_to_rect(x1, y2, x2 - 1, y2), subpixel(bottom_subpixel));
  296. m_underlying_painter.fill_rect(line_to_rect(x1 - 1, y1, x1 - 1, y2 - 1), subpixel(left_subpixel));
  297. m_underlying_painter.fill_rect(line_to_rect(x2, y1, x2, y2 - 1), subpixel(right_subpixel));
  298. }
  299. void AntiAliasingPainter::draw_ellipse(IntRect const& a_rect, Color color, int thickness)
  300. {
  301. // FIXME: Come up with an allocation-free version of this!
  302. // Using draw_line() for segments of an ellipse was attempted but gave really poor results :^(
  303. // There probably is a way to adjust the fill of draw_ellipse_part() to do this, but getting it rendering correctly is tricky.
  304. // The outline of the steps required to paint it efficiently is:
  305. // - Paint the outer ellipse without the fill (from the fill() lambda in draw_ellipse_part())
  306. // - Paint the inner ellipse, but in the set_pixel() invert the alpha values
  307. // - Somehow fill in the gap between the two ellipses (the tricky part to get right)
  308. // - Have to avoid overlapping pixels and accidentally painting over some of the edge pixels
  309. auto color_no_alpha = color;
  310. color_no_alpha.set_alpha(255);
  311. auto outline_ellipse_bitmap = ({
  312. auto bitmap = Bitmap::try_create(BitmapFormat::BGRA8888, a_rect.size());
  313. if (bitmap.is_error())
  314. return warnln("Failed to allocate temporary bitmap for antialiased outline ellipse!");
  315. bitmap.release_value();
  316. });
  317. auto outer_rect = a_rect;
  318. outer_rect.set_location({ 0, 0 });
  319. auto inner_rect = outer_rect.shrunken(thickness * 2, thickness * 2);
  320. Painter painter { outline_ellipse_bitmap };
  321. AntiAliasingPainter aa_painter { painter };
  322. aa_painter.fill_ellipse(outer_rect, color_no_alpha);
  323. aa_painter.fill_ellipse(inner_rect, color_no_alpha, BlendMode::AlphaSubtract);
  324. m_underlying_painter.blit(a_rect.location(), outline_ellipse_bitmap, outline_ellipse_bitmap->rect(), color.alpha() / 255.);
  325. }
  326. void AntiAliasingPainter::fill_circle(IntPoint const& center, int radius, Color color, BlendMode blend_mode)
  327. {
  328. if (radius <= 0)
  329. return;
  330. draw_ellipse_part(center, radius, radius, color, false, {}, blend_mode);
  331. }
  332. void AntiAliasingPainter::fill_ellipse(IntRect const& a_rect, Color color, BlendMode blend_mode)
  333. {
  334. auto center = a_rect.center();
  335. auto radius_a = a_rect.width() / 2;
  336. auto radius_b = a_rect.height() / 2;
  337. if (radius_a <= 0 || radius_b <= 0)
  338. return;
  339. if (radius_a == radius_b)
  340. return fill_circle(center, radius_a, color, blend_mode);
  341. auto x_paint_range = draw_ellipse_part(center, radius_a, radius_b, color, false, {}, blend_mode);
  342. // FIXME: This paints some extra fill pixels that are clipped
  343. draw_ellipse_part(center, radius_b, radius_a, color, true, x_paint_range, blend_mode);
  344. }
  345. FLATTEN AntiAliasingPainter::Range AntiAliasingPainter::draw_ellipse_part(
  346. IntPoint center, int radius_a, int radius_b, Color color, bool flip_x_and_y, Optional<Range> x_clip, BlendMode blend_mode)
  347. {
  348. /*
  349. Algorithm from: https://cs.uwaterloo.ca/research/tr/1984/CS-84-38.pdf
  350. This method can draw a whole circle with a whole circle in one call using
  351. 8-way symmetry, or an ellipse in two calls using 4-way symmetry.
  352. */
  353. center *= m_underlying_painter.scale();
  354. radius_a *= m_underlying_painter.scale();
  355. radius_b *= m_underlying_painter.scale();
  356. // If this is a ellipse everything can be drawn in one pass with 8 way symmetry
  357. bool const is_circle = radius_a == radius_b;
  358. // These happen to be the same here, but are treated separately in the paper:
  359. // intensity is the fill alpha
  360. int const intensity = 255;
  361. // 0 to subpixel_resolution is the range of alpha values for the circle edges
  362. int const subpixel_resolution = intensity;
  363. // Current pixel address
  364. int i = 0;
  365. int q = radius_b;
  366. // 1st and 2nd order differences of y
  367. int delta_y = 0;
  368. int delta2_y = 0;
  369. int const a_squared = radius_a * radius_a;
  370. int const b_squared = radius_b * radius_b;
  371. // Exact and predicted values of f(i) -- the ellipse equation scaled by subpixel_resolution
  372. int y = subpixel_resolution * radius_b;
  373. int y_hat = 0;
  374. // The value of f(i)*f(i)
  375. int f_squared = y * y;
  376. // 1st and 2nd order differences of f(i)*f(i)
  377. int delta_f_squared = -(static_cast<int64_t>(b_squared) * subpixel_resolution * subpixel_resolution) / a_squared;
  378. int delta2_f_squared = 2 * delta_f_squared;
  379. // edge_intersection_area/subpixel_resolution = percentage of pixel intersected by circle
  380. // (aka the alpha for the pixel)
  381. int edge_intersection_area = 0;
  382. int old_area = edge_intersection_area;
  383. auto predict = [&] {
  384. delta_y += delta2_y;
  385. // y_hat is the predicted value of f(i)
  386. y_hat = y + delta_y;
  387. };
  388. auto minimize = [&] {
  389. // Initialize the minimization
  390. delta_f_squared += delta2_f_squared;
  391. f_squared += delta_f_squared;
  392. int min_squared_error = y_hat * y_hat - f_squared;
  393. int prediction_overshot = 1;
  394. y = y_hat;
  395. // Force error negative
  396. if (min_squared_error > 0) {
  397. min_squared_error = -min_squared_error;
  398. prediction_overshot = -1;
  399. }
  400. // Minimize
  401. int previous_error = min_squared_error;
  402. while (min_squared_error < 0) {
  403. y += prediction_overshot;
  404. previous_error = min_squared_error;
  405. min_squared_error += y + y - prediction_overshot;
  406. }
  407. if (min_squared_error + previous_error > 0)
  408. y -= prediction_overshot;
  409. };
  410. auto correct = [&] {
  411. int error = y - y_hat;
  412. // FIXME: The alpha values seem too low, which makes things look
  413. // overly pointy. This fixes that, though there's probably a better
  414. // solution to be found. (This issue seems to exist in the base algorithm)
  415. error /= 4;
  416. delta2_y += error;
  417. delta_y += error;
  418. };
  419. int min_paint_x = NumericLimits<int>::max();
  420. int max_paint_x = NumericLimits<int>::min();
  421. auto pixel = [&](int x, int y, int alpha) {
  422. if (alpha <= 0 || alpha > 255)
  423. return;
  424. if (flip_x_and_y)
  425. swap(x, y);
  426. if (x_clip.has_value() && x_clip->contains_inclusive(x))
  427. return;
  428. min_paint_x = min(x, min_paint_x);
  429. max_paint_x = max(x, max_paint_x);
  430. alpha = (alpha * color.alpha()) / 255;
  431. if (blend_mode == BlendMode::AlphaSubtract)
  432. alpha = ~alpha;
  433. auto pixel_color = color;
  434. pixel_color.set_alpha(alpha);
  435. m_underlying_painter.set_pixel(center + IntPoint { x, y }, pixel_color, blend_mode == BlendMode::Normal);
  436. };
  437. auto fill = [&](int x, int ymax, int ymin, int alpha) {
  438. while (ymin <= ymax) {
  439. pixel(x, ymin, alpha);
  440. ymin += 1;
  441. }
  442. };
  443. auto symmetric_pixel = [&](int x, int y, int alpha) {
  444. pixel(x, y, alpha);
  445. pixel(x, -y - 1, alpha);
  446. pixel(-x - 1, -y - 1, alpha);
  447. pixel(-x - 1, y, alpha);
  448. if (is_circle) {
  449. pixel(y, x, alpha);
  450. pixel(y, -x - 1, alpha);
  451. pixel(-y - 1, -x - 1, alpha);
  452. pixel(-y - 1, x, alpha);
  453. }
  454. };
  455. // These are calculated incrementally (as it is possibly a tiny bit faster)
  456. int ib_squared = 0;
  457. int qa_squared = q * a_squared;
  458. auto in_symmetric_region = [&] {
  459. // Main fix two stop cond here
  460. return is_circle ? i < q : ib_squared < qa_squared;
  461. };
  462. // Draws a 8 octants for a circle or 4 quadrants for a (partial) ellipse
  463. while (in_symmetric_region()) {
  464. predict();
  465. minimize();
  466. correct();
  467. old_area = edge_intersection_area;
  468. edge_intersection_area += delta_y;
  469. if (edge_intersection_area >= 0) {
  470. // Single pixel on perimeter
  471. symmetric_pixel(i, q, (edge_intersection_area + old_area) / 2);
  472. fill(i, q - 1, -q, intensity);
  473. fill(-i - 1, q - 1, -q, intensity);
  474. } else {
  475. // Two pixels on perimeter
  476. edge_intersection_area += subpixel_resolution;
  477. symmetric_pixel(i, q, old_area / 2);
  478. q -= 1;
  479. qa_squared -= a_squared;
  480. fill(i, q - 1, -q, intensity);
  481. fill(-i - 1, q - 1, -q, intensity);
  482. if (!is_circle || in_symmetric_region()) {
  483. symmetric_pixel(i, q, (edge_intersection_area + subpixel_resolution) / 2);
  484. if (is_circle) {
  485. fill(q, i - 1, -i, intensity);
  486. fill(-q - 1, i - 1, -i, intensity);
  487. }
  488. } else {
  489. edge_intersection_area += subpixel_resolution;
  490. }
  491. }
  492. i += 1;
  493. ib_squared += b_squared;
  494. }
  495. if (is_circle) {
  496. int alpha = edge_intersection_area / 2;
  497. pixel(q, q, alpha);
  498. pixel(-q - 1, q, alpha);
  499. pixel(-q - 1, -q - 1, alpha);
  500. pixel(q, -q - 1, alpha);
  501. }
  502. return Range { min_paint_x, max_paint_x };
  503. }
  504. void AntiAliasingPainter::fill_rect_with_rounded_corners(IntRect const& a_rect, Color color, int radius)
  505. {
  506. fill_rect_with_rounded_corners(a_rect, color, radius, radius, radius, radius);
  507. }
  508. 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)
  509. {
  510. fill_rect_with_rounded_corners(a_rect, color,
  511. { top_left_radius, top_left_radius },
  512. { top_right_radius, top_right_radius },
  513. { bottom_right_radius, bottom_right_radius },
  514. { bottom_left_radius, bottom_left_radius });
  515. }
  516. 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)
  517. {
  518. if (!top_left && !top_right && !bottom_right && !bottom_left) {
  519. if (blend_mode == BlendMode::Normal)
  520. return m_underlying_painter.fill_rect(a_rect, color);
  521. else if (blend_mode == BlendMode::AlphaSubtract)
  522. return m_underlying_painter.clear_rect(a_rect, Color());
  523. }
  524. if (color.alpha() == 0)
  525. return;
  526. IntPoint top_left_corner {
  527. a_rect.x() + top_left.horizontal_radius,
  528. a_rect.y() + top_left.vertical_radius,
  529. };
  530. IntPoint top_right_corner {
  531. a_rect.x() + a_rect.width() - top_right.horizontal_radius,
  532. a_rect.y() + top_right.vertical_radius,
  533. };
  534. IntPoint bottom_left_corner {
  535. a_rect.x() + bottom_left.horizontal_radius,
  536. a_rect.y() + a_rect.height() - bottom_left.vertical_radius
  537. };
  538. IntPoint bottom_right_corner {
  539. a_rect.x() + a_rect.width() - bottom_right.horizontal_radius,
  540. a_rect.y() + a_rect.height() - bottom_right.vertical_radius
  541. };
  542. // All corners are centered at the same point, so this can be painted as a single ellipse.
  543. if (top_left_corner == top_right_corner && top_right_corner == bottom_left_corner && bottom_left_corner == bottom_right_corner)
  544. return fill_ellipse(a_rect, color, blend_mode);
  545. IntRect top_rect {
  546. a_rect.x() + top_left.horizontal_radius,
  547. a_rect.y(),
  548. a_rect.width() - top_left.horizontal_radius - top_right.horizontal_radius,
  549. top_left.vertical_radius
  550. };
  551. IntRect right_rect {
  552. a_rect.x() + a_rect.width() - top_right.horizontal_radius,
  553. a_rect.y() + top_right.vertical_radius,
  554. top_right.horizontal_radius,
  555. a_rect.height() - top_right.vertical_radius - bottom_right.vertical_radius
  556. };
  557. IntRect bottom_rect {
  558. a_rect.x() + bottom_left.horizontal_radius,
  559. a_rect.y() + a_rect.height() - bottom_right.vertical_radius,
  560. a_rect.width() - bottom_left.horizontal_radius - bottom_right.horizontal_radius,
  561. bottom_right.vertical_radius
  562. };
  563. IntRect left_rect {
  564. a_rect.x(),
  565. a_rect.y() + top_left.vertical_radius,
  566. bottom_left.horizontal_radius,
  567. a_rect.height() - top_left.vertical_radius - bottom_left.vertical_radius
  568. };
  569. IntRect inner = {
  570. left_rect.x() + left_rect.width(),
  571. left_rect.y(),
  572. a_rect.width() - left_rect.width() - right_rect.width(),
  573. a_rect.height() - top_rect.height() - bottom_rect.height()
  574. };
  575. if (blend_mode == BlendMode::Normal) {
  576. m_underlying_painter.fill_rect(top_rect, color);
  577. m_underlying_painter.fill_rect(right_rect, color);
  578. m_underlying_painter.fill_rect(bottom_rect, color);
  579. m_underlying_painter.fill_rect(left_rect, color);
  580. m_underlying_painter.fill_rect(inner, color);
  581. } else if (blend_mode == BlendMode::AlphaSubtract) {
  582. m_underlying_painter.clear_rect(top_rect, Color());
  583. m_underlying_painter.clear_rect(right_rect, Color());
  584. m_underlying_painter.clear_rect(bottom_rect, Color());
  585. m_underlying_painter.clear_rect(left_rect, Color());
  586. m_underlying_painter.clear_rect(inner, Color());
  587. }
  588. auto fill_corner = [&](auto const& ellipse_center, auto const& corner_point, CornerRadius const& corner) {
  589. PainterStateSaver save { m_underlying_painter };
  590. m_underlying_painter.add_clip_rect(IntRect::from_two_points(ellipse_center, corner_point));
  591. fill_ellipse(IntRect::centered_at(ellipse_center, { corner.horizontal_radius * 2, corner.vertical_radius * 2 }), color, blend_mode);
  592. };
  593. auto bounding_rect = a_rect.inflated(0, 1, 1, 0);
  594. if (top_left)
  595. fill_corner(top_left_corner, bounding_rect.top_left(), top_left);
  596. if (top_right)
  597. fill_corner(top_right_corner, bounding_rect.top_right(), top_right);
  598. if (bottom_left)
  599. fill_corner(bottom_left_corner, bounding_rect.bottom_left(), bottom_left);
  600. if (bottom_right)
  601. fill_corner(bottom_right_corner, bounding_rect.bottom_right(), bottom_right);
  602. }
  603. void AntiAliasingPainter::stroke_segment_intersection(FloatPoint const& current_line_a, FloatPoint const& current_line_b, FloatLine const& previous_line, Color color, float thickness)
  604. {
  605. // FIXME: This is currently drawn in slightly the wrong place most of the time.
  606. // FIXME: This is sometimes drawn when the intersection would not be visible anyway.
  607. // Starting point of the current line is where the last line ended... this is an intersection.
  608. auto intersection = current_line_a;
  609. auto previous_line_b = (previous_line.a());
  610. // If both are straight lines we can simply draw a rectangle at the intersection.
  611. if ((current_line_a.x() == current_line_b.x() || current_line_a.y() == current_line_b.y()) && (previous_line.a().x() == previous_line.b().x() || previous_line.a().y() == previous_line.b().y())) {
  612. intersection = m_transform.map(current_line_a);
  613. // Adjust coordinates to handle rounding offsets.
  614. auto intersection_rect = IntSize(thickness, thickness);
  615. float drawing_edge_offset = fmodf(thickness, 2.0f) < 0.5f && thickness > 3 ? 1 : 0;
  616. auto integer_part = [](float x) { return floorf(x); };
  617. auto round = [&](float x) { return integer_part(x + 0.5f); };
  618. if (thickness == 1)
  619. drawing_edge_offset = -1;
  620. if (current_line_a.x() == current_line_b.x() && previous_line.a().x() == previous_line.b().x()) {
  621. intersection_rect.set_height(1);
  622. }
  623. if (current_line_a.y() == current_line_b.y() && previous_line.a().y() == previous_line.b().y()) {
  624. intersection_rect.set_width(1);
  625. drawing_edge_offset = thickness == 1 ? -1 : 0;
  626. intersection.set_x(intersection.x() - 1 + (thickness == 1 ? 1 : 0));
  627. intersection.set_y(intersection.y() + (thickness > 3 && fmodf(thickness, 2.0f) < 0.5f ? 1 : 0));
  628. }
  629. m_underlying_painter.fill_rect(IntRect::centered_on({ round(intersection.x()) + drawing_edge_offset, round(intersection.y()) + drawing_edge_offset }, intersection_rect), color);
  630. return;
  631. }
  632. float scale_to_move_current = (thickness / 2) / intersection.distance_from(current_line_b);
  633. float scale_to_move_previous = (thickness / 2) / intersection.distance_from(previous_line_b);
  634. // Move the point on the line by half of the thickness.
  635. double offset_current_edge_x = scale_to_move_current * (current_line_b.x() - intersection.x());
  636. double offset_current_edge_y = scale_to_move_current * (current_line_b.y() - intersection.y());
  637. double offset_prev_edge_x = scale_to_move_previous * (previous_line_b.x() - intersection.x());
  638. double offset_prev_edge_y = scale_to_move_previous * (previous_line_b.y() - intersection.y());
  639. // Rotate the point by 90 and 270 degrees to get the points for both edges.
  640. double rad_90deg = 0.5 * M_PI;
  641. FloatPoint current_rotated_90deg = { (offset_current_edge_x * cos(rad_90deg) - offset_current_edge_y * sin(rad_90deg)), (offset_current_edge_x * sin(rad_90deg) + offset_current_edge_y * cos(rad_90deg)) };
  642. FloatPoint current_rotated_270deg = intersection - current_rotated_90deg;
  643. FloatPoint previous_rotated_90deg = { (offset_prev_edge_x * cos(rad_90deg) - offset_prev_edge_y * sin(rad_90deg)), (offset_prev_edge_x * sin(rad_90deg) + offset_prev_edge_y * cos(rad_90deg)) };
  644. FloatPoint previous_rotated_270deg = intersection - previous_rotated_90deg;
  645. // Translate coordinates to the intersection point.
  646. current_rotated_90deg += intersection;
  647. previous_rotated_90deg += intersection;
  648. FloatLine outer_line_current_90 = FloatLine({ current_rotated_90deg, current_line_b - static_cast<FloatPoint>(intersection - current_rotated_90deg) });
  649. FloatLine outer_line_current_270 = FloatLine({ current_rotated_270deg, current_line_b - static_cast<FloatPoint>(intersection - current_rotated_270deg) });
  650. FloatLine outer_line_prev_270 = FloatLine({ previous_rotated_270deg, previous_line_b - static_cast<FloatPoint>(intersection - previous_rotated_270deg) });
  651. FloatLine outer_line_prev_90 = FloatLine({ previous_rotated_90deg, previous_line_b - static_cast<FloatPoint>(intersection - previous_rotated_90deg) });
  652. Optional<FloatPoint> edge_spike_90 = outer_line_current_90.intersected(outer_line_prev_270);
  653. Optional<FloatPoint> edge_spike_270;
  654. if (edge_spike_90.has_value()) {
  655. edge_spike_270 = intersection + (intersection - edge_spike_90.value());
  656. } else {
  657. edge_spike_270 = outer_line_current_270.intersected(outer_line_prev_90);
  658. if (edge_spike_270.has_value()) {
  659. edge_spike_90 = intersection + (intersection - edge_spike_270.value());
  660. }
  661. }
  662. Path intersection_edge_path;
  663. intersection_edge_path.move_to(current_rotated_90deg);
  664. if (edge_spike_90.has_value())
  665. intersection_edge_path.line_to(edge_spike_90.value());
  666. intersection_edge_path.line_to(previous_rotated_270deg);
  667. intersection_edge_path.line_to(current_rotated_270deg);
  668. if (edge_spike_270.has_value())
  669. intersection_edge_path.line_to(edge_spike_270.value());
  670. intersection_edge_path.line_to(previous_rotated_90deg);
  671. intersection_edge_path.close();
  672. fill_path(intersection_edge_path, color);
  673. }
  674. }