FillPathImplementation.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. /*
  2. * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
  3. * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/Debug.h>
  8. #include <AK/QuickSort.h>
  9. #include <LibGfx/Color.h>
  10. #include <LibGfx/Painter.h>
  11. #include <LibGfx/Path.h>
  12. #if defined(AK_COMPILER_GCC)
  13. # pragma GCC optimize("O3")
  14. #endif
  15. namespace Gfx {
  16. template<typename T, typename TColorOrFunction>
  17. ALWAYS_INLINE void Painter::draw_scanline_for_fill_path(int y, T x_start, T x_end, TColorOrFunction color)
  18. {
  19. // Fill path should scale the scanlines before calling this.
  20. VERIFY(scale() == 1);
  21. constexpr bool is_floating_point = IsSameIgnoringCV<T, float>;
  22. constexpr bool has_constant_color = IsSameIgnoringCV<TColorOrFunction, Color>;
  23. int x1 = 0;
  24. int x2 = 0;
  25. u8 left_subpixel_alpha = 0;
  26. u8 right_subpixel_alpha = 0;
  27. if constexpr (is_floating_point) {
  28. x1 = ceilf(x_start);
  29. x2 = floorf(x_end);
  30. left_subpixel_alpha = (x1 - x_start) * 255;
  31. right_subpixel_alpha = (x_end - x2) * 255;
  32. x1 -= left_subpixel_alpha > 0;
  33. x2 += right_subpixel_alpha > 0;
  34. } else {
  35. x1 = x_start;
  36. x2 = x_end;
  37. }
  38. IntRect scanline(x1, y, x2 - x1, 1);
  39. scanline = scanline.translated(translation());
  40. auto clipped = scanline.intersected(clip_rect());
  41. if (clipped.is_empty())
  42. return;
  43. auto get_color = [&](int offset) {
  44. if constexpr (has_constant_color) {
  45. return color;
  46. } else {
  47. return color(offset);
  48. }
  49. };
  50. if constexpr (is_floating_point) {
  51. // Paint left and right subpixels (then remove them from the scanline).
  52. auto get_color_with_alpha = [&](int offset, u8 alpha) {
  53. auto color_at_offset = get_color(offset);
  54. u8 color_alpha = (alpha * color_at_offset.alpha()) / 255;
  55. return color_at_offset.with_alpha(color_alpha);
  56. };
  57. bool paint_left_subpixel = clipped.left() == scanline.left() && left_subpixel_alpha;
  58. bool paint_right_subpixel = clipped.right() == scanline.right() && right_subpixel_alpha;
  59. if (paint_left_subpixel)
  60. set_physical_pixel(clipped.top_left(), get_color_with_alpha(0, left_subpixel_alpha), true);
  61. if (paint_right_subpixel)
  62. set_physical_pixel(clipped.top_right().moved_left(1), get_color_with_alpha(scanline.width(), right_subpixel_alpha), true);
  63. clipped.shrink(0, paint_right_subpixel, 0, paint_left_subpixel);
  64. if (clipped.is_empty())
  65. return;
  66. }
  67. if constexpr (has_constant_color) {
  68. if (color.alpha() == 255) {
  69. // Speedy path: Constant color and no alpha blending.
  70. fast_u32_fill(m_target->scanline(clipped.y()) + clipped.x(), color.value(), clipped.width());
  71. return;
  72. }
  73. }
  74. for (int x = clipped.x(); x < clipped.right(); x++)
  75. set_physical_pixel({ x, clipped.y() }, get_color(x - scanline.x()), true);
  76. }
  77. [[maybe_unused]] inline void approximately_place_on_int_grid(FloatPoint ffrom, FloatPoint fto, IntPoint& from, IntPoint& to, Optional<IntPoint> previous_to)
  78. {
  79. auto diffs = fto - ffrom;
  80. // Truncate all first (round down).
  81. from = ffrom.to_type<int>();
  82. to = fto.to_type<int>();
  83. // There are 16 possible configurations, by deciding to round each
  84. // coord up or down (and there are four coords, from.x from.y to.x to.y)
  85. // we will simply choose one which most closely matches the correct slope
  86. // with the following heuristic:
  87. // - if the x diff is positive or zero (that is, a right-to-left slant), round 'from.x' up and 'to.x' down.
  88. // - if the x diff is negative (that is, a left-to-right slant), round 'from.x' down and 'to.x' up.
  89. // Note that we do not need to touch the 'y' attribute, as that is our scanline.
  90. if (diffs.x() >= 0) {
  91. from.set_x(from.x() + 1);
  92. } else {
  93. to.set_x(to.x() + 1);
  94. }
  95. if (previous_to.has_value() && from.x() != previous_to.value().x()) // The points have to line up, since we're using these lines to fill a shape.
  96. from.set_x(previous_to.value().x());
  97. }
  98. template<Painter::FillPathMode fill_path_mode, typename ColorOrFunction>
  99. void Painter::fill_path_impl(Path const& path, ColorOrFunction color, Gfx::Painter::WindingRule winding_rule, Optional<FloatPoint> offset)
  100. {
  101. using GridCoordinateType = Conditional<fill_path_mode == FillPathMode::PlaceOnIntGrid, int, float>;
  102. using PointType = Point<GridCoordinateType>;
  103. auto draw_scanline = [&](int y, GridCoordinateType x1, GridCoordinateType x2) {
  104. const auto draw_offset = offset.value_or({ 0, 0 });
  105. // Note: .to_floored() is used here to be consistent with enclosing_int_rect()
  106. const auto draw_origin = (path.bounding_box().top_left() + draw_offset).to_floored<int>();
  107. // FIMXE: Offset is added here to handle floating point translations in the AA painter,
  108. // really this should be done there but this function is a bit too specialised.
  109. y = floorf(y + draw_offset.y());
  110. x1 += draw_offset.x();
  111. x2 += draw_offset.x();
  112. if (x1 > x2)
  113. swap(x1, x2);
  114. if constexpr (IsSameIgnoringCV<ColorOrFunction, Color>) {
  115. draw_scanline_for_fill_path(y, x1, x2, color);
  116. } else {
  117. draw_scanline_for_fill_path(y, x1, x2, [&](int offset) {
  118. return color(IntPoint(x1 + offset, y) - draw_origin);
  119. });
  120. }
  121. };
  122. auto const& segments = path.split_lines();
  123. if (segments.size() == 0)
  124. return;
  125. Vector<Path::SplitLineSegment> active_list;
  126. active_list.ensure_capacity(segments.size());
  127. // first, grab the segments for the very first scanline
  128. GridCoordinateType first_y = path.bounding_box().bottom();
  129. GridCoordinateType last_y = path.bounding_box().top() - 1;
  130. float scanline = first_y;
  131. size_t last_active_segment { 0 };
  132. for (auto& segment : segments) {
  133. if (segment.maximum_y != scanline)
  134. break;
  135. active_list.append(segment);
  136. ++last_active_segment;
  137. }
  138. auto is_inside_shape = [winding_rule](int winding_number) {
  139. if (winding_rule == Gfx::Painter::WindingRule::Nonzero)
  140. return winding_number != 0;
  141. if (winding_rule == Gfx::Painter::WindingRule::EvenOdd)
  142. return winding_number % 2 == 0;
  143. VERIFY_NOT_REACHED();
  144. };
  145. auto increment_winding = [winding_rule](int& winding_number, PointType const& from, PointType const& to) {
  146. if (winding_rule == Gfx::Painter::WindingRule::EvenOdd) {
  147. ++winding_number;
  148. return;
  149. }
  150. if (winding_rule == Gfx::Painter::WindingRule::Nonzero) {
  151. if (from.dy_relative_to(to) < 0)
  152. ++winding_number;
  153. else
  154. --winding_number;
  155. return;
  156. }
  157. VERIFY_NOT_REACHED();
  158. };
  159. while (scanline >= last_y) {
  160. Optional<PointType> previous_to;
  161. if (active_list.size()) {
  162. // sort the active list by 'x' from right to left
  163. quick_sort(active_list, [](auto const& line0, auto const& line1) {
  164. return line1.x < line0.x;
  165. });
  166. if constexpr (fill_path_mode == FillPathMode::PlaceOnIntGrid && FILL_PATH_DEBUG) {
  167. if ((int)scanline % 10 == 0) {
  168. draw_text(Gfx::Rect<GridCoordinateType>(active_list.last().x - 20, scanline, 20, 10), DeprecatedString::number((int)scanline));
  169. }
  170. }
  171. if (active_list.size() > 1) {
  172. auto winding_number { winding_rule == Gfx::Painter::WindingRule::Nonzero ? 1 : 0 };
  173. for (size_t i = 1; i < active_list.size(); ++i) {
  174. auto& previous = active_list[i - 1];
  175. auto& current = active_list[i];
  176. PointType from, to;
  177. PointType truncated_from { previous.x, scanline };
  178. PointType truncated_to { current.x, scanline };
  179. if constexpr (fill_path_mode == FillPathMode::PlaceOnIntGrid) {
  180. approximately_place_on_int_grid({ previous.x, scanline }, { current.x, scanline }, from, to, previous_to);
  181. } else {
  182. from = truncated_from;
  183. to = truncated_to;
  184. }
  185. if (is_inside_shape(winding_number)) {
  186. // The points between this segment and the previous are
  187. // inside the shape
  188. dbgln_if(FILL_PATH_DEBUG, "y={}: {} at {}: {} -- {}", scanline, winding_number, i, from, to);
  189. draw_scanline(floorf(scanline), from.x(), to.x());
  190. }
  191. auto is_passing_through_maxima = scanline == previous.maximum_y
  192. || scanline == previous.minimum_y
  193. || scanline == current.maximum_y
  194. || scanline == current.minimum_y;
  195. auto is_passing_through_vertex = false;
  196. if (is_passing_through_maxima) {
  197. is_passing_through_vertex = previous.x == current.x;
  198. }
  199. if (!is_passing_through_vertex || previous.inverse_slope * current.inverse_slope < 0)
  200. increment_winding(winding_number, truncated_from, truncated_to);
  201. // update the x coord
  202. active_list[i - 1].x -= active_list[i - 1].inverse_slope;
  203. }
  204. active_list.last().x -= active_list.last().inverse_slope;
  205. } else {
  206. auto point = PointType(active_list[0].x, scanline);
  207. draw_scanline(floorf(scanline), point.x(), point.x());
  208. // update the x coord
  209. active_list.first().x -= active_list.first().inverse_slope;
  210. }
  211. }
  212. --scanline;
  213. // remove any edge that goes out of bound from the active list
  214. for (size_t i = 0, count = active_list.size(); i < count; ++i) {
  215. if (scanline <= active_list[i].minimum_y) {
  216. active_list.remove(i);
  217. --count;
  218. --i;
  219. }
  220. }
  221. for (size_t j = last_active_segment; j < segments.size(); ++j, ++last_active_segment) {
  222. auto& segment = segments[j];
  223. if (segment.maximum_y < scanline)
  224. break;
  225. if (segment.minimum_y >= scanline)
  226. continue;
  227. active_list.append(segment);
  228. }
  229. }
  230. }
  231. void Painter::fill_path(Path const& path, Color color, WindingRule winding_rule)
  232. {
  233. VERIFY(scale() == 1); // FIXME: Add scaling support.
  234. fill_path_impl<FillPathMode::PlaceOnIntGrid>(path, color, winding_rule);
  235. }
  236. void Painter::fill_path(Path const& path, PaintStyle const& paint_style, Painter::WindingRule rule)
  237. {
  238. VERIFY(scale() == 1); // FIXME: Add scaling support.
  239. paint_style.paint(enclosing_int_rect(path.bounding_box()), [&](PaintStyle::SamplerFunction sampler) {
  240. fill_path_impl<FillPathMode::PlaceOnIntGrid>(path, move(sampler), rule);
  241. });
  242. }
  243. void Painter::antialiased_fill_path(Path const& path, Color color, WindingRule rule, FloatPoint translation)
  244. {
  245. VERIFY(scale() == 1); // FIXME: Add scaling support.
  246. fill_path_impl<FillPathMode::AllowFloatingPoints>(path, color, rule, translation);
  247. }
  248. void Painter::antialiased_fill_path(Path const& path, PaintStyle const& paint_style, WindingRule rule, FloatPoint translation)
  249. {
  250. VERIFY(scale() == 1); // FIXME: Add scaling support.
  251. paint_style.paint(enclosing_int_rect(path.bounding_box()), [&](PaintStyle::SamplerFunction sampler) {
  252. fill_path_impl<FillPathMode::AllowFloatingPoints>(path, move(sampler), rule, translation);
  253. });
  254. }
  255. }