AntiAliasingPainter.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /*
  2. * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "FillPathImplementation.h"
  7. #include <AK/Function.h>
  8. #include <LibGfx/AntiAliasingPainter.h>
  9. #include <LibGfx/Path.h>
  10. static float fractional_part(float x)
  11. {
  12. return x - floorf(x);
  13. }
  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>
  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)
  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 draw_point = [&](FloatPoint const& point, Color color) {
  24. auto center = m_transform.map(point).to_type<int>();
  25. m_underlying_painter.fill_rect(Gfx::IntRect::centered_on(center, size), color);
  26. };
  27. auto color_with_alpha = [&color](float new_alpha) {
  28. return color.with_alpha(color.alpha() * new_alpha);
  29. };
  30. auto actual_distance = actual_to - actual_from;
  31. auto from = actual_from;
  32. auto to = actual_to;
  33. auto is_steep = fabsf(actual_distance.y()) > fabsf(actual_distance.x());
  34. if (is_steep) {
  35. from = { from.y(), from.x() };
  36. to = { to.y(), to.x() };
  37. }
  38. if (from.x() > to.x())
  39. swap(from, to);
  40. auto distance = to - from;
  41. auto gradient = fabsf(distance.x()) < 1e-10f ? 1.0f : distance.y() / distance.x();
  42. auto draw_one_end = [&](auto& point) {
  43. auto end_x = roundf(point.x());
  44. auto end_point = FloatPoint { end_x, point.y() + gradient * (end_x - point.x()) };
  45. auto x_gap = 1 - fractional_part(point.x() + 0.5f);
  46. auto current_point = FloatPoint { end_point.x(), floorf(end_point.y()) };
  47. if (is_steep) {
  48. draw_point({ current_point.y(), current_point.x() }, color_with_alpha(x_gap * (1 - fractional_part(end_point.y()))));
  49. draw_point({ current_point.y() + 1, current_point.x() }, color_with_alpha(x_gap * fractional_part(end_point.y())));
  50. } else {
  51. draw_point(current_point, color_with_alpha(x_gap * (1 - fractional_part(end_point.y())) * 255));
  52. draw_point({ current_point.x(), current_point.y() + 1 }, color_with_alpha(x_gap * fractional_part(end_point.y())));
  53. }
  54. return end_point;
  55. };
  56. auto first_end_point = draw_one_end(from);
  57. auto last_end_point = draw_one_end(to);
  58. auto next_intersection = first_end_point.y() + gradient;
  59. auto delta_x = 0.7f; // Should be max(fabsf(sin_x), fabsf(cos_x)) with fewer samples needed if the line is axis-aligned.
  60. // but there's no point in doing expensive calculations when the delta range is so small (0.7-1.0)
  61. // so instead, just pick the smallest delta.
  62. auto delta_y = gradient * delta_x;
  63. auto x = first_end_point.x();
  64. while (x < last_end_point.x()) {
  65. if (is_steep) {
  66. if constexpr (policy == AntiAliasPolicy::OnlyEnds) {
  67. draw_point({ floorf(next_intersection), x }, color);
  68. } else {
  69. draw_point({ floorf(next_intersection), x }, color_with_alpha(1 - fractional_part(next_intersection)));
  70. }
  71. draw_point({ floorf(next_intersection) + 1, x }, color_with_alpha(fractional_part(next_intersection)));
  72. } else {
  73. if constexpr (policy == AntiAliasPolicy::OnlyEnds) {
  74. draw_point({ x, floorf(next_intersection) }, color);
  75. } else {
  76. draw_point({ x, floorf(next_intersection) }, color_with_alpha(1 - fractional_part(next_intersection)));
  77. }
  78. draw_point({ x, floorf(next_intersection) + 1 }, color_with_alpha(fractional_part(next_intersection)));
  79. }
  80. next_intersection += delta_y;
  81. x += delta_x;
  82. }
  83. }
  84. 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)
  85. {
  86. draw_anti_aliased_line<AntiAliasPolicy::OnlyEnds>(actual_from, actual_to, color, thickness, style, alternate_color);
  87. }
  88. void Gfx::AntiAliasingPainter::draw_line(FloatPoint const& actual_from, FloatPoint const& actual_to, Color color, float thickness, Gfx::Painter::LineStyle style, Color alternate_color)
  89. {
  90. draw_anti_aliased_line<AntiAliasPolicy::Full>(actual_from, actual_to, color, thickness, style, alternate_color);
  91. }
  92. void Gfx::AntiAliasingPainter::fill_path(Path& path, Color color, Painter::WindingRule rule)
  93. {
  94. Detail::fill_path<Detail::FillPathMode::AllowFloatingPoints>(*this, path, color, rule);
  95. }
  96. void Gfx::AntiAliasingPainter::stroke_path(Path const& path, Color color, float thickness)
  97. {
  98. FloatPoint cursor;
  99. for (auto& segment : path.segments()) {
  100. switch (segment.type()) {
  101. case Segment::Type::Invalid:
  102. VERIFY_NOT_REACHED();
  103. case Segment::Type::MoveTo:
  104. cursor = segment.point();
  105. break;
  106. case Segment::Type::LineTo:
  107. draw_line(cursor, segment.point(), color, thickness);
  108. cursor = segment.point();
  109. break;
  110. case Segment::Type::QuadraticBezierCurveTo: {
  111. auto& through = static_cast<QuadraticBezierCurveSegment const&>(segment).through();
  112. draw_quadratic_bezier_curve(through, cursor, segment.point(), color, thickness);
  113. cursor = segment.point();
  114. break;
  115. }
  116. case Segment::Type::CubicBezierCurveTo: {
  117. auto& curve = static_cast<CubicBezierCurveSegment const&>(segment);
  118. auto& through_0 = curve.through_0();
  119. auto& through_1 = curve.through_1();
  120. draw_cubic_bezier_curve(through_0, through_1, cursor, segment.point(), color, thickness);
  121. cursor = segment.point();
  122. break;
  123. }
  124. case Segment::Type::EllipticalArcTo:
  125. auto& arc = static_cast<EllipticalArcSegment const&>(segment);
  126. draw_elliptical_arc(cursor, segment.point(), arc.center(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), color, thickness);
  127. cursor = segment.point();
  128. break;
  129. }
  130. }
  131. }
  132. 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)
  133. {
  134. 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) {
  135. draw_line(fp1, fp2, color, thickness, style);
  136. });
  137. }
  138. void Gfx::AntiAliasingPainter::draw_quadratic_bezier_curve(FloatPoint const& control_point, FloatPoint const& p1, FloatPoint const& p2, Color color, float thickness, Painter::LineStyle style)
  139. {
  140. Gfx::Painter::for_each_line_segment_on_bezier_curve(control_point, p1, p2, [&](FloatPoint const& fp1, FloatPoint const& fp2) {
  141. draw_line(fp1, fp2, color, thickness, style);
  142. });
  143. }
  144. void Gfx::AntiAliasingPainter::draw_cubic_bezier_curve(const FloatPoint& control_point_0, const FloatPoint& control_point_1, const FloatPoint& p1, const FloatPoint& p2, Color color, float thickness, Painter::LineStyle style)
  145. {
  146. Gfx::Painter::for_each_line_segment_on_cubic_bezier_curve(control_point_0, control_point_1, p1, p2, [&](FloatPoint const& fp1, FloatPoint const& fp2) {
  147. draw_line(fp1, fp2, color, thickness, style);
  148. });
  149. }