FillPathImplementation.h 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /*
  2. * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #pragma once
  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. namespace Gfx::Detail {
  13. [[maybe_unused]] inline void approximately_place_on_int_grid(FloatPoint ffrom, FloatPoint fto, IntPoint& from, IntPoint& to, Optional<IntPoint> previous_to)
  14. {
  15. auto diffs = fto - ffrom;
  16. // Truncate all first (round down).
  17. from = ffrom.to_type<int>();
  18. to = fto.to_type<int>();
  19. // There are 16 possible configurations, by deciding to round each
  20. // coord up or down (and there are four coords, from.x from.y to.x to.y)
  21. // we will simply choose one which most closely matches the correct slope
  22. // with the following heuristic:
  23. // - if the x diff is positive or zero (that is, a right-to-left slant), round 'from.x' up and 'to.x' down.
  24. // - if the x diff is negative (that is, a left-to-right slant), round 'from.x' down and 'to.x' up.
  25. // Note that we do not need to touch the 'y' attribute, as that is our scanline.
  26. if (diffs.x() >= 0) {
  27. from.set_x(from.x() + 1);
  28. } else {
  29. to.set_x(to.x() + 1);
  30. }
  31. 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.
  32. from.set_x(previous_to.value().x());
  33. }
  34. enum class FillPathMode {
  35. PlaceOnIntGrid,
  36. AllowFloatingPoints,
  37. };
  38. template<FillPathMode fill_path_mode, typename ColorFunction>
  39. void fill_path(Painter& painter, Path const& path, ColorFunction color_function, Gfx::Painter::WindingRule winding_rule, Optional<FloatPoint> offset = {})
  40. {
  41. using GridCoordinateType = Conditional<fill_path_mode == FillPathMode::PlaceOnIntGrid, int, float>;
  42. using PointType = Point<GridCoordinateType>;
  43. auto draw_scanline = [&](int y, float x1, float x2) {
  44. const auto draw_offset = offset.value_or({ 0, 0 });
  45. const auto draw_origin = (path.bounding_box().top_left() + draw_offset).to_type<int>();
  46. // FIMXE: Offset is added here to handle floating point translations in the AA painter,
  47. // really this should be done there but this function is a bit too specialised.
  48. y = floorf(y + draw_offset.y());
  49. x1 += draw_offset.x();
  50. x2 += draw_offset.x();
  51. if (x1 > x2)
  52. swap(x1, x2);
  53. auto set_pixel = [&](int x, int y, Color color) {
  54. painter.set_pixel(x, y, color, true);
  55. };
  56. if constexpr (fill_path_mode == FillPathMode::AllowFloatingPoints) {
  57. int int_x1 = ceilf(x1);
  58. int int_x2 = floorf(x2);
  59. float left_subpixel = int_x1 - x1;
  60. float right_subpixel = x2 - int_x2;
  61. auto left_color = color_function(IntPoint(int_x1 - 1, y) - draw_origin);
  62. auto right_color = color_function(IntPoint(int_x2, y) - draw_origin);
  63. set_pixel(int_x1 - 1, y, left_color.with_alpha(left_color.alpha() * left_subpixel));
  64. set_pixel(int_x2, y, right_color.with_alpha(right_color.alpha() * right_subpixel));
  65. for (int x = int_x1; x < int_x2; x++)
  66. set_pixel(x, y, color_function(IntPoint(x, y) - draw_origin));
  67. } else {
  68. for (int x = x1; x < int(x2); x++)
  69. set_pixel(x, y, color_function(IntPoint(x, y) - draw_origin));
  70. }
  71. };
  72. auto const& segments = path.split_lines();
  73. if (segments.size() == 0)
  74. return;
  75. Vector<Path::SplitLineSegment> active_list;
  76. active_list.ensure_capacity(segments.size());
  77. // first, grab the segments for the very first scanline
  78. GridCoordinateType first_y = path.bounding_box().bottom_right().y() + 1;
  79. GridCoordinateType last_y = path.bounding_box().top_left().y() - 1;
  80. float scanline = first_y;
  81. size_t last_active_segment { 0 };
  82. for (auto& segment : segments) {
  83. if (segment.maximum_y != scanline)
  84. break;
  85. active_list.append(segment);
  86. ++last_active_segment;
  87. }
  88. auto is_inside_shape = [winding_rule](int winding_number) {
  89. if (winding_rule == Gfx::Painter::WindingRule::Nonzero)
  90. return winding_number != 0;
  91. if (winding_rule == Gfx::Painter::WindingRule::EvenOdd)
  92. return winding_number % 2 == 0;
  93. VERIFY_NOT_REACHED();
  94. };
  95. auto increment_winding = [winding_rule](int& winding_number, PointType const& from, PointType const& to) {
  96. if (winding_rule == Gfx::Painter::WindingRule::EvenOdd) {
  97. ++winding_number;
  98. return;
  99. }
  100. if (winding_rule == Gfx::Painter::WindingRule::Nonzero) {
  101. if (from.dy_relative_to(to) < 0)
  102. ++winding_number;
  103. else
  104. --winding_number;
  105. return;
  106. }
  107. VERIFY_NOT_REACHED();
  108. };
  109. while (scanline >= last_y) {
  110. Optional<PointType> previous_to;
  111. if (active_list.size()) {
  112. // sort the active list by 'x' from right to left
  113. quick_sort(active_list, [](auto const& line0, auto const& line1) {
  114. return line1.x < line0.x;
  115. });
  116. if constexpr (fill_path_mode == FillPathMode::PlaceOnIntGrid && FILL_PATH_DEBUG) {
  117. if ((int)scanline % 10 == 0) {
  118. painter.draw_text(Gfx::Rect<GridCoordinateType>(active_list.last().x - 20, scanline, 20, 10), DeprecatedString::number((int)scanline));
  119. }
  120. }
  121. if (active_list.size() > 1) {
  122. auto winding_number { winding_rule == Gfx::Painter::WindingRule::Nonzero ? 1 : 0 };
  123. for (size_t i = 1; i < active_list.size(); ++i) {
  124. auto& previous = active_list[i - 1];
  125. auto& current = active_list[i];
  126. PointType from, to;
  127. PointType truncated_from { previous.x, scanline };
  128. PointType truncated_to { current.x, scanline };
  129. if constexpr (fill_path_mode == FillPathMode::PlaceOnIntGrid) {
  130. approximately_place_on_int_grid({ previous.x, scanline }, { current.x, scanline }, from, to, previous_to);
  131. } else {
  132. from = truncated_from;
  133. to = truncated_to;
  134. }
  135. if (is_inside_shape(winding_number)) {
  136. // The points between this segment and the previous are
  137. // inside the shape
  138. dbgln_if(FILL_PATH_DEBUG, "y={}: {} at {}: {} -- {}", scanline, winding_number, i, from, to);
  139. draw_scanline(floorf(scanline), from.x(), to.x());
  140. }
  141. auto is_passing_through_maxima = scanline == previous.maximum_y
  142. || scanline == previous.minimum_y
  143. || scanline == current.maximum_y
  144. || scanline == current.minimum_y;
  145. auto is_passing_through_vertex = false;
  146. if (is_passing_through_maxima) {
  147. is_passing_through_vertex = previous.x == current.x;
  148. }
  149. if (!is_passing_through_vertex || previous.inverse_slope * current.inverse_slope < 0)
  150. increment_winding(winding_number, truncated_from, truncated_to);
  151. // update the x coord
  152. active_list[i - 1].x -= active_list[i - 1].inverse_slope;
  153. }
  154. active_list.last().x -= active_list.last().inverse_slope;
  155. } else {
  156. auto point = PointType(active_list[0].x, scanline);
  157. draw_scanline(floorf(scanline), point.x(), point.x());
  158. // update the x coord
  159. active_list.first().x -= active_list.first().inverse_slope;
  160. }
  161. }
  162. --scanline;
  163. // remove any edge that goes out of bound from the active list
  164. for (size_t i = 0, count = active_list.size(); i < count; ++i) {
  165. if (scanline <= active_list[i].minimum_y) {
  166. active_list.remove(i);
  167. --count;
  168. --i;
  169. }
  170. }
  171. for (size_t j = last_active_segment; j < segments.size(); ++j, ++last_active_segment) {
  172. auto& segment = segments[j];
  173. if (segment.maximum_y < scanline)
  174. break;
  175. if (segment.minimum_y >= scanline)
  176. continue;
  177. active_list.append(segment);
  178. }
  179. }
  180. }
  181. }