GradientPainting.cpp 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /*
  2. * Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Math.h>
  7. #include <LibGfx/Gradients.h>
  8. #include <LibGfx/Painter.h>
  9. #if defined(AK_COMPILER_GCC)
  10. # pragma GCC optimize("O3")
  11. #endif
  12. namespace Gfx {
  13. // Note: This file implements the CSS gradients for LibWeb according to the spec.
  14. // Please do not make ad-hoc changes that may break spec compliance!
  15. static float color_stop_step(ColorStop const& previous_stop, ColorStop const& next_stop, float position)
  16. {
  17. if (position < previous_stop.position)
  18. return 0;
  19. if (position > next_stop.position)
  20. return 1;
  21. // For any given point between the two color stops,
  22. // determine the point’s location as a percentage of the distance between the two color stops.
  23. // Let this percentage be P.
  24. auto stop_length = next_stop.position - previous_stop.position;
  25. // FIXME: Avoids NaNs... Still not quite correct?
  26. if (stop_length <= 0)
  27. return 1;
  28. auto p = (position - previous_stop.position) / stop_length;
  29. if (!next_stop.transition_hint.has_value())
  30. return p;
  31. if (*next_stop.transition_hint >= 1)
  32. return 0;
  33. if (*next_stop.transition_hint <= 0)
  34. return 1;
  35. // Let C, the color weighting at that point, be equal to P^(logH(.5)).
  36. auto c = AK::pow(p, AK::log<float>(0.5) / AK::log(*next_stop.transition_hint));
  37. // The color at that point is then a linear blend between the colors of the two color stops,
  38. // blending (1 - C) of the first stop and C of the second stop.
  39. return c;
  40. }
  41. class GradientLine {
  42. public:
  43. GradientLine(int gradient_length, Span<ColorStop const> color_stops, Optional<float> repeat_length)
  44. : m_repeating { repeat_length.has_value() }
  45. , m_start_offset { round_to<int>((m_repeating ? color_stops.first().position : 0.0f) * gradient_length) }
  46. {
  47. // Avoid generating excessive amounts of colors when the not enough shades to fill that length.
  48. auto necessary_length = min<int>((color_stops.size() - 1) * 255, gradient_length);
  49. m_sample_scale = float(necessary_length) / gradient_length;
  50. // Note: color_count will be < gradient_length for repeating gradients.
  51. auto color_count = round_to<int>(repeat_length.value_or(1.0f) * necessary_length);
  52. m_gradient_line_colors.resize(color_count);
  53. // Note: color.mixed_with() performs premultiplied alpha mixing when necessary as defined in:
  54. // https://drafts.csswg.org/css-images/#coloring-gradient-line
  55. for (int loc = 0; loc < color_count; loc++) {
  56. auto relative_loc = float(loc + m_start_offset) / necessary_length;
  57. Color gradient_color = color_stops[0].color.mixed_with(
  58. color_stops[1].color,
  59. color_stop_step(color_stops[0], color_stops[1], relative_loc));
  60. for (size_t i = 1; i < color_stops.size() - 1; i++) {
  61. gradient_color = gradient_color.mixed_with(
  62. color_stops[i + 1].color,
  63. color_stop_step(color_stops[i], color_stops[i + 1], relative_loc));
  64. }
  65. m_gradient_line_colors[loc] = gradient_color;
  66. if (gradient_color.alpha() < 255)
  67. m_requires_blending = true;
  68. }
  69. }
  70. Color get_color(i64 index) const
  71. {
  72. return m_gradient_line_colors[clamp(index, 0, m_gradient_line_colors.size() - 1)];
  73. }
  74. Color sample_color(float loc) const
  75. {
  76. if (m_sample_scale != 1.0f)
  77. loc *= m_sample_scale;
  78. auto repeat_wrap_if_required = [&](i64 loc) {
  79. if (m_repeating)
  80. return (loc + m_start_offset) % static_cast<i64>(m_gradient_line_colors.size());
  81. return loc;
  82. };
  83. auto int_loc = static_cast<i64>(floor(loc));
  84. auto blend = loc - int_loc;
  85. auto color = get_color(repeat_wrap_if_required(int_loc));
  86. // Blend between the two neighbouring colors (this fixes some nasty aliasing issues at small angles)
  87. if (blend >= 0.004f)
  88. color = color.mixed_with(get_color(repeat_wrap_if_required(int_loc + 1)), blend);
  89. return color;
  90. }
  91. void paint_into_physical_rect(Painter& painter, IntRect rect, auto location_transform)
  92. {
  93. auto clipped_rect = rect.intersected(painter.clip_rect() * painter.scale());
  94. auto start_offset = clipped_rect.location() - rect.location();
  95. for (int y = 0; y < clipped_rect.height(); y++) {
  96. for (int x = 0; x < clipped_rect.width(); x++) {
  97. auto pixel = sample_color(location_transform(x + start_offset.x(), y + start_offset.y()));
  98. painter.set_physical_pixel(clipped_rect.location().translated(x, y), pixel, m_requires_blending);
  99. }
  100. }
  101. }
  102. private:
  103. bool m_repeating;
  104. int m_start_offset;
  105. float m_sample_scale { 1 };
  106. Vector<Color, 1024> m_gradient_line_colors;
  107. bool m_requires_blending = false;
  108. };
  109. void Painter::fill_rect_with_linear_gradient(IntRect const& rect, Span<ColorStop const> const& color_stops, float angle, Optional<float> repeat_length)
  110. {
  111. auto a_rect = to_physical(rect);
  112. if (a_rect.intersected(clip_rect() * scale()).is_empty())
  113. return;
  114. float normalized_angle = normalized_gradient_angle_radians(angle);
  115. float sin_angle, cos_angle;
  116. AK::sincos(normalized_angle, sin_angle, cos_angle);
  117. // Full length of the gradient
  118. auto gradient_length = calculate_gradient_length(a_rect.size(), sin_angle, cos_angle);
  119. IntPoint offset { cos_angle * (gradient_length / 2), sin_angle * (gradient_length / 2) };
  120. auto center = a_rect.translated(-a_rect.location()).center();
  121. auto start_point = center - offset;
  122. // Rotate gradient line to be horizontal
  123. auto rotated_start_point_x = start_point.x() * cos_angle - start_point.y() * -sin_angle;
  124. GradientLine gradient_line(gradient_length, color_stops, repeat_length);
  125. gradient_line.paint_into_physical_rect(*this, a_rect, [&](int x, int y) {
  126. return (x * cos_angle - (a_rect.height() - y) * -sin_angle) - rotated_start_point_x;
  127. });
  128. }
  129. void Painter::fill_rect_with_conic_gradient(IntRect const& rect, Span<ColorStop const> const& color_stops, IntPoint center, float start_angle, Optional<float> repeat_length)
  130. {
  131. auto a_rect = to_physical(rect);
  132. if (a_rect.intersected(clip_rect() * scale()).is_empty())
  133. return;
  134. // FIXME: Do we need/want sub-degree accuracy for the gradient line?
  135. GradientLine gradient_line(360, color_stops, repeat_length);
  136. float normalized_start_angle = (360.0f - start_angle) + 90.0f;
  137. // Translate position/center to the center of the pixel (avoids some funky painting)
  138. auto center_point = FloatPoint { center * scale() }.translated(0.5, 0.5);
  139. // The flooring can make gradients that want soft edges look worse, so only floor if we have hard edges.
  140. // Which makes sure the hard edge stay hard edges :^)
  141. bool should_floor_angles = false;
  142. for (size_t i = 0; i < color_stops.size() - 1; i++) {
  143. if (color_stops[i + 1].position - color_stops[i].position <= 0.01f) {
  144. should_floor_angles = true;
  145. break;
  146. }
  147. }
  148. gradient_line.paint_into_physical_rect(*this, a_rect, [&](int x, int y) {
  149. auto point = FloatPoint { x, y } - center_point;
  150. // FIXME: We could probably get away with some approximation here:
  151. auto loc = fmod((AK::atan2(point.y(), point.x()) * 180.0f / AK::Pi<float> + 360.0f + normalized_start_angle), 360.0f);
  152. return should_floor_angles ? floor(loc) : loc;
  153. });
  154. }
  155. void Painter::fill_rect_with_radial_gradient(IntRect const& rect, Span<ColorStop const> const& color_stops, IntPoint center, IntSize size, Optional<float> repeat_length)
  156. {
  157. auto a_rect = to_physical(rect);
  158. if (a_rect.intersected(clip_rect() * scale()).is_empty())
  159. return;
  160. // A conservative guesstimate on how many colors we need to generate:
  161. auto max_dimension = max(a_rect.width(), a_rect.height());
  162. auto max_visible_gradient = max(max_dimension / 2, min(size.width(), max_dimension));
  163. GradientLine gradient_line(max_visible_gradient, color_stops, repeat_length);
  164. auto center_point = FloatPoint { center * scale() }.translated(0.5, 0.5);
  165. gradient_line.paint_into_physical_rect(*this, a_rect, [&](int x, int y) {
  166. // FIXME: See if there's a more efficient calculation we do there :^)
  167. auto point = FloatPoint(x, y) - center_point;
  168. auto gradient_x = point.x() / size.width();
  169. auto gradient_y = point.y() / size.height();
  170. return AK::sqrt(gradient_x * gradient_x + gradient_y * gradient_y) * max_visible_gradient;
  171. });
  172. }
  173. }