RadialGradientStyleValue.cpp 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
  3. * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
  4. * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
  5. * Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
  6. *
  7. * SPDX-License-Identifier: BSD-2-Clause
  8. */
  9. #include "RadialGradientStyleValue.h"
  10. #include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
  11. #include <LibWeb/Layout/Node.h>
  12. namespace Web::CSS {
  13. String RadialGradientStyleValue::to_string() const
  14. {
  15. StringBuilder builder;
  16. if (is_repeating())
  17. builder.append("repeating-"sv);
  18. builder.appendff("radial-gradient({} "sv,
  19. m_properties.ending_shape == EndingShape::Circle ? "circle"sv : "ellipse"sv);
  20. m_properties.size.visit(
  21. [&](Extent extent) {
  22. builder.append([&] {
  23. switch (extent) {
  24. case Extent::ClosestCorner:
  25. return "closest-corner"sv;
  26. case Extent::ClosestSide:
  27. return "closest-side"sv;
  28. case Extent::FarthestCorner:
  29. return "farthest-corner"sv;
  30. case Extent::FarthestSide:
  31. return "farthest-side"sv;
  32. default:
  33. VERIFY_NOT_REACHED();
  34. }
  35. }());
  36. },
  37. [&](CircleSize const& circle_size) {
  38. builder.append(circle_size.radius.to_string());
  39. },
  40. [&](EllipseSize const& ellipse_size) {
  41. builder.appendff("{} {}", ellipse_size.radius_a.to_string(), ellipse_size.radius_b.to_string());
  42. });
  43. if (!m_properties.position->is_center())
  44. builder.appendff(" at {}"sv, m_properties.position->to_string());
  45. builder.append(", "sv);
  46. serialize_color_stop_list(builder, m_properties.color_stop_list);
  47. builder.append(')');
  48. return MUST(builder.to_string());
  49. }
  50. CSSPixelSize RadialGradientStyleValue::resolve_size(Layout::Node const& node, CSSPixelPoint center, CSSPixelRect const& size) const
  51. {
  52. auto const side_shape = [&](auto distance_function) {
  53. auto const distance_from = [&](CSSPixels v, CSSPixels a, CSSPixels b, auto distance_function) {
  54. return distance_function(abs(a - v), abs(b - v));
  55. };
  56. auto x_dist = distance_from(center.x(), size.left(), size.right(), distance_function);
  57. auto y_dist = distance_from(center.y(), size.top(), size.bottom(), distance_function);
  58. if (m_properties.ending_shape == EndingShape::Circle) {
  59. auto dist = distance_function(x_dist, y_dist);
  60. return CSSPixelSize { dist, dist };
  61. } else {
  62. return CSSPixelSize { x_dist, y_dist };
  63. }
  64. };
  65. auto const closest_side_shape = [&] {
  66. return side_shape(AK::min<CSSPixels>);
  67. };
  68. auto const farthest_side_shape = [&] {
  69. return side_shape(AK::max<CSSPixels>);
  70. };
  71. auto const corner_distance = [&](auto distance_compare, CSSPixelPoint& corner) {
  72. auto top_left_distance_squared = square_distance_between(size.top_left(), center);
  73. auto top_right_distance_squared = square_distance_between(size.top_right(), center);
  74. auto bottom_right_distance_squared = square_distance_between(size.bottom_right(), center);
  75. auto bottom_left_distance_squared = square_distance_between(size.bottom_left(), center);
  76. auto distance_squared = top_left_distance_squared;
  77. corner = size.top_left();
  78. if (distance_compare(top_right_distance_squared, distance_squared)) {
  79. corner = size.top_right();
  80. distance_squared = top_right_distance_squared;
  81. }
  82. if (distance_compare(bottom_right_distance_squared, distance_squared)) {
  83. corner = size.bottom_right();
  84. distance_squared = bottom_right_distance_squared;
  85. }
  86. if (distance_compare(bottom_left_distance_squared, distance_squared)) {
  87. corner = size.bottom_left();
  88. distance_squared = bottom_left_distance_squared;
  89. }
  90. return sqrt(distance_squared);
  91. };
  92. auto const closest_corner_distance = [&](CSSPixelPoint& corner) {
  93. return corner_distance([](CSSPixels a, CSSPixels b) { return a < b; }, corner);
  94. };
  95. auto const farthest_corner_distance = [&](CSSPixelPoint& corner) {
  96. return corner_distance([](CSSPixels a, CSSPixels b) { return a > b; }, corner);
  97. };
  98. auto const corner_shape = [&](auto corner_distance, auto get_shape) {
  99. CSSPixelPoint corner {};
  100. auto distance = corner_distance(corner);
  101. if (m_properties.ending_shape == EndingShape::Ellipse) {
  102. auto shape = get_shape();
  103. auto aspect_ratio = shape.width() / shape.height();
  104. auto p = corner - center;
  105. auto radius_a = sqrt(p.y() * p.y() * aspect_ratio * aspect_ratio + p.x() * p.x());
  106. auto radius_b = radius_a / aspect_ratio;
  107. return CSSPixelSize { radius_a, radius_b };
  108. }
  109. return CSSPixelSize { distance, distance };
  110. };
  111. // https://w3c.github.io/csswg-drafts/css-images/#radial-gradient-syntax
  112. auto resolved_size = m_properties.size.visit(
  113. [&](Extent extent) {
  114. switch (extent) {
  115. case Extent::ClosestSide:
  116. // The ending shape is sized so that it exactly meets the side of the gradient box closest to the gradient’s center.
  117. // If the shape is an ellipse, it exactly meets the closest side in each dimension.
  118. return closest_side_shape();
  119. case Extent::ClosestCorner:
  120. // The ending shape is sized so that it passes through the corner of the gradient box closest to the gradient’s center.
  121. // If the shape is an ellipse, the ending shape is given the same aspect-ratio it would have if closest-side were specified
  122. return corner_shape(closest_corner_distance, closest_side_shape);
  123. case Extent::FarthestCorner:
  124. // Same as closest-corner, except the ending shape is sized based on the farthest corner.
  125. // If the shape is an ellipse, the ending shape is given the same aspect ratio it would have if farthest-side were specified.
  126. return corner_shape(farthest_corner_distance, farthest_side_shape);
  127. case Extent::FarthestSide:
  128. // Same as closest-side, except the ending shape is sized based on the farthest side(s).
  129. return farthest_side_shape();
  130. default:
  131. VERIFY_NOT_REACHED();
  132. }
  133. },
  134. [&](CircleSize const& circle_size) {
  135. auto radius = circle_size.radius.to_px(node);
  136. return CSSPixelSize { radius, radius };
  137. },
  138. [&](EllipseSize const& ellipse_size) {
  139. auto radius_a = ellipse_size.radius_a.resolved(node, size.width()).to_px(node);
  140. auto radius_b = ellipse_size.radius_b.resolved(node, size.height()).to_px(node);
  141. return CSSPixelSize { radius_a, radius_b };
  142. });
  143. // Handle degenerate cases
  144. // https://w3c.github.io/csswg-drafts/css-images/#degenerate-radials
  145. constexpr auto arbitrary_small_number = CSSPixels::smallest_positive_value();
  146. constexpr auto arbitrary_large_number = CSSPixels::max();
  147. // If the ending shape is a circle with zero radius:
  148. if (m_properties.ending_shape == EndingShape::Circle && resolved_size.is_empty()) {
  149. // Render as if the ending shape was a circle whose radius was an arbitrary very small number greater than zero.
  150. // This will make the gradient continue to look like a circle.
  151. return CSSPixelSize { arbitrary_small_number, arbitrary_small_number };
  152. }
  153. // If the ending shape has zero width (regardless of the height):
  154. if (resolved_size.width() <= 0) {
  155. // Render as if the ending shape was an ellipse whose height was an arbitrary very large number
  156. // and whose width was an arbitrary very small number greater than zero.
  157. // This will make the gradient look similar to a horizontal linear gradient that is mirrored across the center of the ellipse.
  158. // It also means that all color-stop positions specified with a percentage resolve to 0px.
  159. return CSSPixelSize { arbitrary_small_number, arbitrary_large_number };
  160. }
  161. // Otherwise, if the ending shape has zero height:
  162. if (resolved_size.height() <= 0) {
  163. // Render as if the ending shape was an ellipse whose width was an arbitrary very large number and whose height
  164. // was an arbitrary very small number greater than zero. This will make the gradient look like a solid-color image equal
  165. // to the color of the last color-stop, or equal to the average color of the gradient if it’s repeating.
  166. return CSSPixelSize { arbitrary_large_number, arbitrary_small_number };
  167. }
  168. return resolved_size;
  169. }
  170. void RadialGradientStyleValue::resolve_for_size(Layout::NodeWithStyleAndBoxModelMetrics const& node, CSSPixelSize paint_size) const
  171. {
  172. CSSPixelRect gradient_box { { 0, 0 }, paint_size };
  173. auto center = m_properties.position->resolved(node, gradient_box);
  174. auto gradient_size = resolve_size(node, center, gradient_box);
  175. if (m_resolved.has_value() && m_resolved->gradient_size == gradient_size)
  176. return;
  177. m_resolved = ResolvedData {
  178. Painting::resolve_radial_gradient_data(node, gradient_size, *this),
  179. gradient_size,
  180. center,
  181. };
  182. }
  183. bool RadialGradientStyleValue::equals(CSSStyleValue const& other) const
  184. {
  185. if (type() != other.type())
  186. return false;
  187. auto& other_gradient = other.as_radial_gradient();
  188. return m_properties == other_gradient.m_properties;
  189. }
  190. void RadialGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering) const
  191. {
  192. VERIFY(m_resolved.has_value());
  193. auto center = context.rounded_device_point(m_resolved->center).to_type<int>();
  194. auto size = context.rounded_device_size(m_resolved->gradient_size).to_type<int>();
  195. context.display_list_recorder().fill_rect_with_radial_gradient(dest_rect.to_type<int>(), m_resolved->data, center, size);
  196. }
  197. }