BackgroundPainting.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  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, Vector<Gfx::Path> const& clip_paths)
  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. 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. clip_paths);
  117. if (!has_paintable_layers)
  118. return;
  119. struct {
  120. DevicePixels top { 0 };
  121. DevicePixels bottom { 0 };
  122. DevicePixels left { 0 };
  123. DevicePixels right { 0 };
  124. } clip_shrink;
  125. auto border_top = layout_node.computed_values().border_top();
  126. auto border_bottom = layout_node.computed_values().border_bottom();
  127. auto border_left = layout_node.computed_values().border_left();
  128. auto border_right = layout_node.computed_values().border_right();
  129. if (border_top.color.alpha() == 255 && border_bottom.color.alpha() == 255
  130. && border_left.color.alpha() == 255 && border_right.color.alpha() == 255) {
  131. clip_shrink.top = context.rounded_device_pixels(border_top.width);
  132. clip_shrink.bottom = context.rounded_device_pixels(border_bottom.width);
  133. clip_shrink.left = context.rounded_device_pixels(border_left.width);
  134. clip_shrink.right = context.rounded_device_pixels(border_right.width);
  135. }
  136. // Note: Background layers are ordered front-to-back, so we paint them in reverse
  137. for (auto& layer : background_layers->in_reverse()) {
  138. if (!layer_is_paintable(layer))
  139. continue;
  140. RecordingPainterStateSaver state { painter };
  141. // Clip
  142. auto clip_box = get_box(layer.clip);
  143. CSSPixelRect const& css_clip_rect = clip_box.rect;
  144. auto clip_rect = context.rounded_device_rect(css_clip_rect);
  145. painter.add_clip_rect(clip_rect.to_type<int>());
  146. ScopedCornerRadiusClip corner_clip { context, clip_rect, clip_box.radii };
  147. if (layer.clip == CSS::BackgroundBox::BorderBox) {
  148. // Shrink the effective clip rect if to account for the bits the borders will definitely paint over
  149. // (if they all have alpha == 255).
  150. clip_rect.shrink(clip_shrink.top, clip_shrink.right, clip_shrink.bottom, clip_shrink.left);
  151. }
  152. auto& image = *layer.background_image;
  153. CSSPixelRect background_positioning_area;
  154. // Attachment and Origin
  155. switch (layer.attachment) {
  156. case CSS::BackgroundAttachment::Fixed:
  157. background_positioning_area = layout_node.root().navigable()->viewport_rect();
  158. break;
  159. case CSS::BackgroundAttachment::Local:
  160. background_positioning_area = get_box(layer.origin).rect;
  161. if (is<Layout::Box>(layout_node)) {
  162. auto* paintable_box = static_cast<Layout::Box const&>(layout_node).paintable_box();
  163. if (paintable_box) {
  164. auto scroll_offset = paintable_box->scroll_offset();
  165. background_positioning_area.translate_by(-scroll_offset.x(), -scroll_offset.y());
  166. }
  167. }
  168. break;
  169. case CSS::BackgroundAttachment::Scroll:
  170. background_positioning_area = get_box(layer.origin).rect;
  171. break;
  172. }
  173. Optional<CSSPixels> specified_width {};
  174. Optional<CSSPixels> specified_height {};
  175. if (layer.size_type == CSS::BackgroundSize::LengthPercentage) {
  176. if (!layer.size_x.is_auto())
  177. specified_width = layer.size_x.to_px(layout_node, background_positioning_area.width());
  178. if (!layer.size_y.is_auto())
  179. specified_height = layer.size_y.to_px(layout_node, background_positioning_area.height());
  180. }
  181. auto concrete_image_size = run_default_sizing_algorithm(
  182. specified_width, specified_height,
  183. image.natural_width(), image.natural_height(), image.natural_aspect_ratio(),
  184. background_positioning_area.size());
  185. // If any of these are zero, the NaNs will pop up in the painting code.
  186. if (background_positioning_area.is_empty() || concrete_image_size.is_empty())
  187. continue;
  188. // Size
  189. CSSPixelRect image_rect;
  190. switch (layer.size_type) {
  191. case CSS::BackgroundSize::Contain: {
  192. double max_width_ratio = (background_positioning_area.width() / concrete_image_size.width()).to_double();
  193. double max_height_ratio = (background_positioning_area.height() / concrete_image_size.height()).to_double();
  194. double ratio = min(max_width_ratio, max_height_ratio);
  195. image_rect.set_size(concrete_image_size.width().scaled(ratio), concrete_image_size.height().scaled(ratio));
  196. break;
  197. }
  198. case CSS::BackgroundSize::Cover: {
  199. double max_width_ratio = (background_positioning_area.width() / concrete_image_size.width()).to_double();
  200. double max_height_ratio = (background_positioning_area.height() / concrete_image_size.height()).to_double();
  201. double ratio = max(max_width_ratio, max_height_ratio);
  202. image_rect.set_size(concrete_image_size.width().scaled(ratio), concrete_image_size.height().scaled(ratio));
  203. break;
  204. }
  205. case CSS::BackgroundSize::LengthPercentage:
  206. image_rect.set_size(concrete_image_size);
  207. break;
  208. }
  209. // If after sizing we have a 0px image, we're done. Attempting to paint this would be an infinite loop.
  210. if (image_rect.is_empty())
  211. continue;
  212. // If background-repeat is round for one (or both) dimensions, there is a second step.
  213. // The UA must scale the image in that dimension (or both dimensions) so that it fits a
  214. // whole number of times in the background positioning area.
  215. if (layer.repeat_x == CSS::Repeat::Round || layer.repeat_y == CSS::Repeat::Round) {
  216. // If X ≠ 0 is the width of the image after step one and W is the width of the
  217. // background positioning area, then the rounded width X' = W / round(W / X)
  218. // where round() is a function that returns the nearest natural number
  219. // (integer greater than zero).
  220. if (layer.repeat_x == CSS::Repeat::Round) {
  221. image_rect.set_width(background_positioning_area.width() / round(background_positioning_area.width() / image_rect.width()));
  222. }
  223. if (layer.repeat_y == CSS::Repeat::Round) {
  224. image_rect.set_height(background_positioning_area.height() / round(background_positioning_area.height() / image_rect.height()));
  225. }
  226. // If background-repeat is round for one dimension only and if background-size is auto
  227. // for the other dimension, then there is a third step: that other dimension is scaled
  228. // so that the original aspect ratio is restored.
  229. if (layer.repeat_x != layer.repeat_y) {
  230. if (layer.size_x.is_auto()) {
  231. image_rect.set_width(image_rect.height() * (concrete_image_size.width() / concrete_image_size.height()));
  232. }
  233. if (layer.size_y.is_auto()) {
  234. image_rect.set_height(image_rect.width() * (concrete_image_size.height() / concrete_image_size.width()));
  235. }
  236. }
  237. }
  238. CSSPixels space_x = background_positioning_area.width() - image_rect.width();
  239. CSSPixels space_y = background_positioning_area.height() - image_rect.height();
  240. // Position
  241. CSSPixels offset_x = layer.position_offset_x.to_px(layout_node, space_x);
  242. if (layer.position_edge_x == CSS::PositionEdge::Right) {
  243. image_rect.set_right_without_resize(background_positioning_area.right() - offset_x);
  244. } else {
  245. image_rect.set_left(background_positioning_area.left() + offset_x);
  246. }
  247. CSSPixels offset_y = layer.position_offset_y.to_px(layout_node, space_y);
  248. if (layer.position_edge_y == CSS::PositionEdge::Bottom) {
  249. image_rect.set_bottom_without_resize(background_positioning_area.bottom() - offset_y);
  250. } else {
  251. image_rect.set_top(background_positioning_area.top() + offset_y);
  252. }
  253. // Repetition
  254. bool repeat_x = false;
  255. bool repeat_y = false;
  256. CSSPixels x_step = 0;
  257. CSSPixels y_step = 0;
  258. switch (layer.repeat_x) {
  259. case CSS::Repeat::Round:
  260. x_step = image_rect.width();
  261. repeat_x = true;
  262. break;
  263. case CSS::Repeat::Space: {
  264. int whole_images = (background_positioning_area.width() / image_rect.width()).to_int();
  265. if (whole_images <= 1) {
  266. x_step = image_rect.width();
  267. repeat_x = false;
  268. } else {
  269. auto space = fmod(background_positioning_area.width().to_double(), image_rect.width().to_double());
  270. x_step = image_rect.width() + CSSPixels::nearest_value_for(space / static_cast<double>(whole_images - 1));
  271. repeat_x = true;
  272. }
  273. break;
  274. }
  275. case CSS::Repeat::Repeat:
  276. x_step = image_rect.width();
  277. repeat_x = true;
  278. break;
  279. case CSS::Repeat::NoRepeat:
  280. repeat_x = false;
  281. break;
  282. }
  283. // Move image_rect to the left-most tile position that is still visible
  284. if (repeat_x && image_rect.x() > css_clip_rect.x()) {
  285. auto x_delta = floor(x_step * ceil((image_rect.x() - css_clip_rect.x()) / x_step));
  286. image_rect.set_x(image_rect.x() - x_delta);
  287. }
  288. switch (layer.repeat_y) {
  289. case CSS::Repeat::Round:
  290. y_step = image_rect.height();
  291. repeat_y = true;
  292. break;
  293. case CSS::Repeat::Space: {
  294. int whole_images = (background_positioning_area.height() / image_rect.height()).to_int();
  295. if (whole_images <= 1) {
  296. y_step = image_rect.height();
  297. repeat_y = false;
  298. } else {
  299. auto space = fmod(background_positioning_area.height().to_float(), image_rect.height().to_float());
  300. y_step = image_rect.height() + CSSPixels::nearest_value_for(static_cast<double>(space) / static_cast<double>(whole_images - 1));
  301. repeat_y = true;
  302. }
  303. break;
  304. }
  305. case CSS::Repeat::Repeat:
  306. y_step = image_rect.height();
  307. repeat_y = true;
  308. break;
  309. case CSS::Repeat::NoRepeat:
  310. repeat_y = false;
  311. break;
  312. }
  313. // Move image_rect to the top-most tile position that is still visible
  314. if (repeat_y && image_rect.y() > css_clip_rect.y()) {
  315. auto y_delta = floor(y_step * ceil((image_rect.y() - css_clip_rect.y()) / y_step));
  316. image_rect.set_y(image_rect.y() - y_delta);
  317. }
  318. CSSPixels initial_image_x = image_rect.x();
  319. CSSPixels image_y = image_rect.y();
  320. Optional<DevicePixelRect> last_image_device_rect;
  321. image.resolve_for_size(layout_node, image_rect.size());
  322. auto for_each_image_device_rect = [&](auto callback) {
  323. while (image_y < css_clip_rect.bottom()) {
  324. image_rect.set_y(image_y);
  325. auto image_x = initial_image_x;
  326. while (image_x < css_clip_rect.right()) {
  327. image_rect.set_x(image_x);
  328. auto image_device_rect = context.rounded_device_rect(image_rect);
  329. callback(image_device_rect);
  330. if (!repeat_x)
  331. break;
  332. image_x += x_step;
  333. }
  334. if (!repeat_y)
  335. break;
  336. image_y += y_step;
  337. }
  338. };
  339. if (auto color = image.color_if_single_pixel_bitmap(); color.has_value()) {
  340. // OPTIMIZATION: If the image is a single pixel, we can just fill the whole area with it.
  341. // However, we must first figure out the real coverage area, taking repeat etc into account.
  342. // FIXME: This could be written in a far more efficient way.
  343. auto fill_rect = Optional<DevicePixelRect> {};
  344. for_each_image_device_rect([&](auto const& image_device_rect) {
  345. if (!fill_rect.has_value()) {
  346. fill_rect = image_device_rect;
  347. } else {
  348. fill_rect = fill_rect->united(image_device_rect);
  349. }
  350. });
  351. painter.fill_rect(fill_rect->to_type<int>(), color.value(), clip_paths);
  352. } else {
  353. for_each_image_device_rect([&](auto const& image_device_rect) {
  354. image.paint(context, image_device_rect, image_rendering, clip_paths);
  355. });
  356. }
  357. }
  358. }
  359. }