BackgroundPainting.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
  4. * Copyright (c) 2022, MacDue <macdue@dueutil.tech>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include <LibGfx/AntiAliasingPainter.h>
  9. #include <LibWeb/Layout/Node.h>
  10. #include <LibWeb/Layout/Viewport.h>
  11. #include <LibWeb/Painting/BackgroundPainting.h>
  12. #include <LibWeb/Painting/BorderRadiusCornerClipper.h>
  13. #include <LibWeb/Painting/GradientPainting.h>
  14. #include <LibWeb/Painting/PaintContext.h>
  15. #include <LibWeb/Painting/PaintableBox.h>
  16. #include <LibWeb/Painting/RecordingPainter.h>
  17. namespace Web::Painting {
  18. // https://drafts.csswg.org/css-images/#default-sizing
  19. static CSSPixelSize run_default_sizing_algorithm(
  20. Optional<CSSPixels> specified_width, Optional<CSSPixels> specified_height,
  21. Optional<CSSPixels> natural_width, Optional<CSSPixels> natural_height,
  22. Optional<CSSPixelFraction> natural_aspect_ratio,
  23. CSSPixelSize default_size)
  24. {
  25. // If the specified size is a definite width and height, the concrete object size is given that width and height.
  26. if (specified_width.has_value() && specified_height.has_value())
  27. return CSSPixelSize { specified_width.value(), specified_height.value() };
  28. // If the specified size is only a width or height (but not both) then the concrete object size is given that specified width or height.
  29. // The other dimension is calculated as follows:
  30. if (specified_width.has_value() || specified_height.has_value()) {
  31. // 1. If the object has a natural aspect ratio,
  32. // the missing dimension of the concrete object size is calculated using that aspect ratio and the present dimension.
  33. if (natural_aspect_ratio.has_value() && !natural_aspect_ratio->might_be_saturated()) {
  34. if (specified_width.has_value())
  35. return CSSPixelSize { specified_width.value(), (CSSPixels(1) / natural_aspect_ratio.value()) * specified_width.value() };
  36. if (specified_height.has_value())
  37. return CSSPixelSize { specified_height.value() * natural_aspect_ratio.value(), specified_height.value() };
  38. }
  39. // 2. Otherwise, if the missing dimension is present in the object’s natural dimensions,
  40. // the missing dimension is taken from the object’s natural dimensions.
  41. if (specified_height.has_value() && natural_width.has_value())
  42. return CSSPixelSize { natural_width.value(), specified_height.value() };
  43. if (specified_width.has_value() && natural_height.has_value())
  44. return CSSPixelSize { specified_width.value(), natural_height.value() };
  45. // 3. Otherwise, the missing dimension of the concrete object size is taken from the default object size.
  46. if (specified_height.has_value())
  47. return CSSPixelSize { default_size.width(), specified_height.value() };
  48. if (specified_width.has_value())
  49. return CSSPixelSize { specified_width.value(), default_size.height() };
  50. VERIFY_NOT_REACHED();
  51. }
  52. // If the specified size has no constraints:
  53. // 1. If the object has a natural height or width, its size is resolved as if its natural dimensions were given as the specified size.
  54. if (natural_width.has_value() || natural_height.has_value())
  55. return run_default_sizing_algorithm(natural_width, natural_height, natural_width, natural_height, natural_aspect_ratio, default_size);
  56. // FIXME: 2. Otherwise, its size is resolved as a contain constraint against the default object size.
  57. return default_size;
  58. }
  59. // https://www.w3.org/TR/css-backgrounds-3/#backgrounds
  60. void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMetrics const& layout_node, CSSPixelRect const& border_rect, Color background_color, CSS::ImageRendering image_rendering, Vector<CSS::BackgroundLayerData> const* background_layers, BorderRadiiData const& border_radii)
  61. {
  62. auto& painter = context.recording_painter();
  63. struct BackgroundBox {
  64. CSSPixelRect rect;
  65. BorderRadiiData radii;
  66. inline void shrink(CSSPixels top, CSSPixels right, CSSPixels bottom, CSSPixels left)
  67. {
  68. rect.shrink(top, right, bottom, left);
  69. radii.shrink(top, right, bottom, left);
  70. }
  71. };
  72. BackgroundBox border_box {
  73. border_rect,
  74. border_radii
  75. };
  76. auto get_box = [&](CSS::BackgroundBox box_clip) {
  77. auto box = border_box;
  78. switch (box_clip) {
  79. case CSS::BackgroundBox::ContentBox: {
  80. auto& padding = layout_node.box_model().padding;
  81. box.shrink(padding.top, padding.right, padding.bottom, padding.left);
  82. [[fallthrough]];
  83. }
  84. case CSS::BackgroundBox::PaddingBox: {
  85. auto& border = layout_node.box_model().border;
  86. box.shrink(border.top, border.right, border.bottom, border.left);
  87. [[fallthrough]];
  88. }
  89. case CSS::BackgroundBox::BorderBox:
  90. default:
  91. return box;
  92. }
  93. };
  94. auto color_box = border_box;
  95. if (background_layers && !background_layers->is_empty())
  96. color_box = get_box(background_layers->last().clip);
  97. auto layer_is_paintable = [&](auto& layer) {
  98. return layer.background_image && layer.background_image->is_paintable();
  99. };
  100. bool has_paintable_layers = false;
  101. if (background_layers) {
  102. for (auto& layer : *background_layers) {
  103. if (layer_is_paintable(layer)) {
  104. has_paintable_layers = true;
  105. break;
  106. }
  107. }
  108. }
  109. context.recording_painter().fill_rect_with_rounded_corners(
  110. context.rounded_device_rect(color_box.rect).to_type<int>(),
  111. background_color,
  112. color_box.radii.top_left.as_corner(context),
  113. color_box.radii.top_right.as_corner(context),
  114. color_box.radii.bottom_right.as_corner(context),
  115. color_box.radii.bottom_left.as_corner(context));
  116. if (!has_paintable_layers)
  117. return;
  118. struct {
  119. DevicePixels top { 0 };
  120. DevicePixels bottom { 0 };
  121. DevicePixels left { 0 };
  122. DevicePixels right { 0 };
  123. } clip_shrink;
  124. auto border_top = layout_node.computed_values().border_top();
  125. auto border_bottom = layout_node.computed_values().border_bottom();
  126. auto border_left = layout_node.computed_values().border_left();
  127. auto border_right = layout_node.computed_values().border_right();
  128. if (border_top.color.alpha() == 255 && border_bottom.color.alpha() == 255
  129. && border_left.color.alpha() == 255 && border_right.color.alpha() == 255) {
  130. clip_shrink.top = context.rounded_device_pixels(border_top.width);
  131. clip_shrink.bottom = context.rounded_device_pixels(border_bottom.width);
  132. clip_shrink.left = context.rounded_device_pixels(border_left.width);
  133. clip_shrink.right = context.rounded_device_pixels(border_right.width);
  134. }
  135. // Note: Background layers are ordered front-to-back, so we paint them in reverse
  136. for (auto& layer : background_layers->in_reverse()) {
  137. if (!layer_is_paintable(layer))
  138. continue;
  139. RecordingPainterStateSaver state { painter };
  140. // Clip
  141. auto clip_box = get_box(layer.clip);
  142. CSSPixelRect const& css_clip_rect = clip_box.rect;
  143. auto clip_rect = context.rounded_device_rect(css_clip_rect);
  144. painter.add_clip_rect(clip_rect.to_type<int>());
  145. ScopedCornerRadiusClip corner_clip { context, clip_rect, clip_box.radii };
  146. if (layer.clip == CSS::BackgroundBox::BorderBox) {
  147. // Shrink the effective clip rect if to account for the bits the borders will definitely paint over
  148. // (if they all have alpha == 255).
  149. clip_rect.shrink(clip_shrink.top, clip_shrink.right, clip_shrink.bottom, clip_shrink.left);
  150. }
  151. auto& image = *layer.background_image;
  152. CSSPixelRect background_positioning_area;
  153. // Attachment and Origin
  154. switch (layer.attachment) {
  155. case CSS::BackgroundAttachment::Fixed:
  156. background_positioning_area = layout_node.root().navigable()->viewport_rect();
  157. break;
  158. case CSS::BackgroundAttachment::Local:
  159. background_positioning_area = get_box(layer.origin).rect;
  160. if (is<Layout::Box>(layout_node)) {
  161. auto* paintable_box = static_cast<Layout::Box const&>(layout_node).paintable_box();
  162. if (paintable_box) {
  163. auto scroll_offset = paintable_box->scroll_offset();
  164. background_positioning_area.translate_by(-scroll_offset.x(), -scroll_offset.y());
  165. }
  166. }
  167. break;
  168. case CSS::BackgroundAttachment::Scroll:
  169. background_positioning_area = get_box(layer.origin).rect;
  170. break;
  171. }
  172. Optional<CSSPixels> specified_width {};
  173. Optional<CSSPixels> specified_height {};
  174. if (layer.size_type == CSS::BackgroundSize::LengthPercentage) {
  175. if (!layer.size_x.is_auto())
  176. specified_width = layer.size_x.to_px(layout_node, background_positioning_area.width());
  177. if (!layer.size_y.is_auto())
  178. specified_height = layer.size_y.to_px(layout_node, background_positioning_area.height());
  179. }
  180. auto concrete_image_size = run_default_sizing_algorithm(
  181. specified_width, specified_height,
  182. image.natural_width(), image.natural_height(), image.natural_aspect_ratio(),
  183. background_positioning_area.size());
  184. // If any of these are zero, the NaNs will pop up in the painting code.
  185. if (background_positioning_area.is_empty() || concrete_image_size.is_empty())
  186. continue;
  187. // Size
  188. CSSPixelRect image_rect;
  189. switch (layer.size_type) {
  190. case CSS::BackgroundSize::Contain: {
  191. double max_width_ratio = (background_positioning_area.width() / concrete_image_size.width()).to_double();
  192. double max_height_ratio = (background_positioning_area.height() / concrete_image_size.height()).to_double();
  193. double ratio = min(max_width_ratio, max_height_ratio);
  194. image_rect.set_size(concrete_image_size.width().scaled(ratio), concrete_image_size.height().scaled(ratio));
  195. break;
  196. }
  197. case CSS::BackgroundSize::Cover: {
  198. double max_width_ratio = (background_positioning_area.width() / concrete_image_size.width()).to_double();
  199. double max_height_ratio = (background_positioning_area.height() / concrete_image_size.height()).to_double();
  200. double ratio = max(max_width_ratio, max_height_ratio);
  201. image_rect.set_size(concrete_image_size.width().scaled(ratio), concrete_image_size.height().scaled(ratio));
  202. break;
  203. }
  204. case CSS::BackgroundSize::LengthPercentage:
  205. image_rect.set_size(concrete_image_size);
  206. break;
  207. }
  208. // If after sizing we have a 0px image, we're done. Attempting to paint this would be an infinite loop.
  209. if (image_rect.is_empty())
  210. continue;
  211. // If background-repeat is round for one (or both) dimensions, there is a second step.
  212. // The UA must scale the image in that dimension (or both dimensions) so that it fits a
  213. // whole number of times in the background positioning area.
  214. if (layer.repeat_x == CSS::Repeat::Round || layer.repeat_y == CSS::Repeat::Round) {
  215. // If X ≠ 0 is the width of the image after step one and W is the width of the
  216. // background positioning area, then the rounded width X' = W / round(W / X)
  217. // where round() is a function that returns the nearest natural number
  218. // (integer greater than zero).
  219. if (layer.repeat_x == CSS::Repeat::Round) {
  220. image_rect.set_width(background_positioning_area.width() / round(background_positioning_area.width() / image_rect.width()));
  221. }
  222. if (layer.repeat_y == CSS::Repeat::Round) {
  223. image_rect.set_height(background_positioning_area.height() / round(background_positioning_area.height() / image_rect.height()));
  224. }
  225. // If background-repeat is round for one dimension only and if background-size is auto
  226. // for the other dimension, then there is a third step: that other dimension is scaled
  227. // so that the original aspect ratio is restored.
  228. if (layer.repeat_x != layer.repeat_y) {
  229. if (layer.size_x.is_auto()) {
  230. image_rect.set_width(image_rect.height() * (concrete_image_size.width() / concrete_image_size.height()));
  231. }
  232. if (layer.size_y.is_auto()) {
  233. image_rect.set_height(image_rect.width() * (concrete_image_size.height() / concrete_image_size.width()));
  234. }
  235. }
  236. }
  237. CSSPixels space_x = background_positioning_area.width() - image_rect.width();
  238. CSSPixels space_y = background_positioning_area.height() - image_rect.height();
  239. // Position
  240. CSSPixels offset_x = layer.position_offset_x.to_px(layout_node, space_x);
  241. if (layer.position_edge_x == CSS::PositionEdge::Right) {
  242. image_rect.set_right_without_resize(background_positioning_area.right() - offset_x);
  243. } else {
  244. image_rect.set_left(background_positioning_area.left() + offset_x);
  245. }
  246. CSSPixels offset_y = layer.position_offset_y.to_px(layout_node, space_y);
  247. if (layer.position_edge_y == CSS::PositionEdge::Bottom) {
  248. image_rect.set_bottom_without_resize(background_positioning_area.bottom() - offset_y);
  249. } else {
  250. image_rect.set_top(background_positioning_area.top() + offset_y);
  251. }
  252. // Repetition
  253. bool repeat_x = false;
  254. bool repeat_y = false;
  255. CSSPixels x_step = 0;
  256. CSSPixels y_step = 0;
  257. switch (layer.repeat_x) {
  258. case CSS::Repeat::Round:
  259. x_step = image_rect.width();
  260. repeat_x = true;
  261. break;
  262. case CSS::Repeat::Space: {
  263. int whole_images = (background_positioning_area.width() / image_rect.width()).to_int();
  264. if (whole_images <= 1) {
  265. x_step = image_rect.width();
  266. repeat_x = false;
  267. } else {
  268. auto space = fmod(background_positioning_area.width().to_double(), image_rect.width().to_double());
  269. x_step = image_rect.width() + CSSPixels::nearest_value_for(space / static_cast<double>(whole_images - 1));
  270. repeat_x = true;
  271. }
  272. break;
  273. }
  274. case CSS::Repeat::Repeat:
  275. x_step = image_rect.width();
  276. repeat_x = true;
  277. break;
  278. case CSS::Repeat::NoRepeat:
  279. repeat_x = false;
  280. break;
  281. }
  282. // Move image_rect to the left-most tile position that is still visible
  283. if (repeat_x && image_rect.x() > css_clip_rect.x()) {
  284. auto x_delta = floor(x_step * ceil((image_rect.x() - css_clip_rect.x()) / x_step));
  285. image_rect.set_x(image_rect.x() - x_delta);
  286. }
  287. switch (layer.repeat_y) {
  288. case CSS::Repeat::Round:
  289. y_step = image_rect.height();
  290. repeat_y = true;
  291. break;
  292. case CSS::Repeat::Space: {
  293. int whole_images = (background_positioning_area.height() / image_rect.height()).to_int();
  294. if (whole_images <= 1) {
  295. y_step = image_rect.height();
  296. repeat_y = false;
  297. } else {
  298. auto space = fmod(background_positioning_area.height().to_float(), image_rect.height().to_float());
  299. y_step = image_rect.height() + CSSPixels::nearest_value_for(static_cast<double>(space) / static_cast<double>(whole_images - 1));
  300. repeat_y = true;
  301. }
  302. break;
  303. }
  304. case CSS::Repeat::Repeat:
  305. y_step = image_rect.height();
  306. repeat_y = true;
  307. break;
  308. case CSS::Repeat::NoRepeat:
  309. repeat_y = false;
  310. break;
  311. }
  312. // Move image_rect to the top-most tile position that is still visible
  313. if (repeat_y && image_rect.y() > css_clip_rect.y()) {
  314. auto y_delta = floor(y_step * ceil((image_rect.y() - css_clip_rect.y()) / y_step));
  315. image_rect.set_y(image_rect.y() - y_delta);
  316. }
  317. CSSPixels initial_image_x = image_rect.x();
  318. CSSPixels image_y = image_rect.y();
  319. Optional<DevicePixelRect> last_image_device_rect;
  320. image.resolve_for_size(layout_node, image_rect.size());
  321. auto for_each_image_device_rect = [&](auto callback) {
  322. while (image_y < css_clip_rect.bottom()) {
  323. image_rect.set_y(image_y);
  324. auto image_x = initial_image_x;
  325. while (image_x < css_clip_rect.right()) {
  326. image_rect.set_x(image_x);
  327. auto image_device_rect = context.rounded_device_rect(image_rect);
  328. callback(image_device_rect);
  329. if (!repeat_x)
  330. break;
  331. image_x += x_step;
  332. }
  333. if (!repeat_y)
  334. break;
  335. image_y += y_step;
  336. }
  337. };
  338. if (auto color = image.color_if_single_pixel_bitmap(); color.has_value()) {
  339. // OPTIMIZATION: If the image is a single pixel, we can just fill the whole area with it.
  340. // However, we must first figure out the real coverage area, taking repeat etc into account.
  341. // FIXME: This could be written in a far more efficient way.
  342. auto fill_rect = Optional<DevicePixelRect> {};
  343. for_each_image_device_rect([&](auto const& image_device_rect) {
  344. if (!fill_rect.has_value()) {
  345. fill_rect = image_device_rect;
  346. } else {
  347. fill_rect = fill_rect->united(image_device_rect);
  348. }
  349. });
  350. painter.fill_rect(fill_rect->to_type<int>(), color.value());
  351. } else {
  352. for_each_image_device_rect([&](auto const& image_device_rect) {
  353. image.paint(context, image_device_rect, image_rendering);
  354. });
  355. }
  356. }
  357. }
  358. }