ShadowPainting.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
  4. * Copyright (c) 2022, MacDue <macdue@dueutil.tech>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include <AK/NumericLimits.h>
  9. #include <LibGfx/DisjointRectSet.h>
  10. #include <LibGfx/Filters/StackBlurFilter.h>
  11. #include <LibGfx/Painter.h>
  12. #include <LibWeb/Layout/LineBoxFragment.h>
  13. #include <LibWeb/Layout/Node.h>
  14. #include <LibWeb/Painting/BorderPainting.h>
  15. #include <LibWeb/Painting/BorderRadiusCornerClipper.h>
  16. #include <LibWeb/Painting/PaintContext.h>
  17. #include <LibWeb/Painting/ShadowPainting.h>
  18. namespace Web::Painting {
  19. void paint_box_shadow(PaintContext& context, CSSPixelRect const& content_rect, BorderRadiiData const& border_radii, Vector<ShadowData> const& box_shadow_layers)
  20. {
  21. if (box_shadow_layers.is_empty())
  22. return;
  23. auto& painter = context.painter();
  24. auto device_content_rect = context.rounded_device_rect(content_rect);
  25. auto top_left_corner = border_radii.top_left.as_corner(context);
  26. auto top_right_corner = border_radii.top_right.as_corner(context);
  27. auto bottom_right_corner = border_radii.bottom_right.as_corner(context);
  28. auto bottom_left_corner = border_radii.bottom_left.as_corner(context);
  29. ScopedCornerRadiusClip corner_clipper { context, painter, device_content_rect, border_radii, CornerClip::Inside };
  30. // Note: Box-shadow layers are ordered front-to-back, so we paint them in reverse
  31. for (auto& box_shadow_data : box_shadow_layers.in_reverse()) {
  32. // FIXME: Paint inset shadows.
  33. if (box_shadow_data.placement != ShadowPlacement::Outer)
  34. continue;
  35. DevicePixels offset_x = context.rounded_device_pixels(box_shadow_data.offset_x);
  36. DevicePixels offset_y = context.rounded_device_pixels(box_shadow_data.offset_y);
  37. DevicePixels blur_radius = context.rounded_device_pixels(box_shadow_data.blur_radius);
  38. DevicePixels spread_distance = context.rounded_device_pixels(box_shadow_data.spread_distance);
  39. auto fill_rect_masked = [](auto& painter, auto fill_rect, auto mask_rect, auto color) {
  40. Gfx::DisjointRectSet<DevicePixels> rect_set;
  41. rect_set.add(fill_rect);
  42. auto shattered = rect_set.shatter(mask_rect);
  43. for (auto& rect : shattered.rects())
  44. painter.fill_rect(rect.template to_type<int>(), color);
  45. };
  46. // Our blur cannot handle radii over 255 so there's no point trying (255 is silly big anyway)
  47. blur_radius = clamp(blur_radius, 0, 255);
  48. // If there's no blurring, nor rounded corners, we can save a lot of effort.
  49. auto non_blurred_shadow_rect = device_content_rect.inflated(spread_distance, spread_distance, spread_distance, spread_distance);
  50. if (blur_radius == 0 && !border_radii.has_any_radius()) {
  51. fill_rect_masked(painter, non_blurred_shadow_rect.translated(offset_x, offset_y), device_content_rect, box_shadow_data.color);
  52. continue;
  53. }
  54. auto top_left_shadow_corner = top_left_corner;
  55. auto top_right_shadow_corner = top_right_corner;
  56. auto bottom_right_shadow_corner = bottom_right_corner;
  57. auto bottom_left_shadow_corner = bottom_left_corner;
  58. auto spread_corner = [&](auto& corner) {
  59. if (corner) {
  60. corner.horizontal_radius += spread_distance.value();
  61. corner.vertical_radius += spread_distance.value();
  62. }
  63. };
  64. spread_corner(top_left_shadow_corner);
  65. spread_corner(top_right_shadow_corner);
  66. spread_corner(bottom_right_shadow_corner);
  67. spread_corner(bottom_left_shadow_corner);
  68. auto expansion = spread_distance - (blur_radius * 2);
  69. DevicePixelRect inner_bounding_rect = {
  70. device_content_rect.x() + offset_x - expansion,
  71. device_content_rect.y() + offset_y - expansion,
  72. device_content_rect.width() + 2 * expansion,
  73. device_content_rect.height() + 2 * expansion
  74. };
  75. // Calculating and blurring the box-shadow full size is expensive, and wasteful - aside from the corners,
  76. // all vertical strips of the shadow are identical, and the same goes for horizontal ones.
  77. // So instead, we generate a shadow bitmap that is just large enough to include the corners and 1px of
  78. // non-corner, and then we repeatedly blit sections of it. This is similar to a NinePatch on Android.
  79. auto double_radius = blur_radius * 2;
  80. auto blurred_edge_thickness = blur_radius * 4;
  81. auto default_corner_size = Gfx::IntSize { double_radius, double_radius };
  82. auto top_left_corner_size = (top_left_shadow_corner ? top_left_shadow_corner.as_rect().size() : default_corner_size).to_type<DevicePixels>();
  83. auto top_right_corner_size = (top_right_shadow_corner ? top_right_shadow_corner.as_rect().size() : default_corner_size).to_type<DevicePixels>();
  84. auto bottom_left_corner_size = (bottom_left_shadow_corner ? bottom_left_shadow_corner.as_rect().size() : default_corner_size).to_type<DevicePixels>();
  85. auto bottom_right_corner_size = (bottom_right_shadow_corner ? bottom_right_shadow_corner.as_rect().size() : default_corner_size).to_type<DevicePixels>();
  86. auto max_edge_width = non_blurred_shadow_rect.width() / 2;
  87. auto max_edge_height = non_blurred_shadow_rect.height() / 2;
  88. auto extra_edge_width = non_blurred_shadow_rect.width() % 2;
  89. auto extra_edge_height = non_blurred_shadow_rect.height() % 2;
  90. auto clip_corner_size = [&](auto& size, auto const& corner, DevicePixels x_bonus = 0, DevicePixels y_bonus = 0) {
  91. auto max_x = (max_edge_width + x_bonus).value();
  92. auto max_y = (max_edge_height + y_bonus).value();
  93. auto min_x = max(corner.horizontal_radius, min(double_radius, max_x).value());
  94. auto min_y = max(corner.vertical_radius, min(double_radius, max_y).value());
  95. if (min_x <= max_x)
  96. size.set_width(clamp(size.width(), min_x, max_x));
  97. if (min_y <= max_y)
  98. size.set_height(clamp(size.height(), min_y, max_y));
  99. };
  100. clip_corner_size(top_left_corner_size, top_left_corner, extra_edge_width, extra_edge_height);
  101. clip_corner_size(top_right_corner_size, top_right_corner, 0, extra_edge_height);
  102. clip_corner_size(bottom_left_corner_size, bottom_left_corner, extra_edge_width);
  103. clip_corner_size(bottom_right_corner_size, bottom_right_corner);
  104. auto shadow_bitmap_rect = DevicePixelRect(
  105. 0, 0,
  106. max(
  107. top_left_corner_size.width() + top_right_corner_size.width(),
  108. bottom_left_corner_size.width() + bottom_right_corner_size.width())
  109. + 1 + blurred_edge_thickness,
  110. max(
  111. top_left_corner_size.height() + bottom_left_corner_size.height(),
  112. top_right_corner_size.height() + bottom_right_corner_size.height())
  113. + 1 + blurred_edge_thickness);
  114. auto top_left_corner_rect = DevicePixelRect {
  115. 0, 0,
  116. top_left_corner_size.width() + double_radius,
  117. top_left_corner_size.height() + double_radius
  118. };
  119. auto top_right_corner_rect = DevicePixelRect {
  120. shadow_bitmap_rect.width() - (top_right_corner_size.width() + double_radius), 0,
  121. top_right_corner_size.width() + double_radius,
  122. top_right_corner_size.height() + double_radius
  123. };
  124. auto bottom_right_corner_rect = DevicePixelRect {
  125. shadow_bitmap_rect.width() - (bottom_right_corner_size.width() + double_radius),
  126. shadow_bitmap_rect.height() - (bottom_right_corner_size.height() + double_radius),
  127. bottom_right_corner_size.width() + double_radius,
  128. bottom_right_corner_size.height() + double_radius
  129. };
  130. auto bottom_left_corner_rect = DevicePixelRect {
  131. 0, shadow_bitmap_rect.height() - (bottom_left_corner_size.height() + double_radius),
  132. bottom_left_corner_size.width() + double_radius,
  133. bottom_left_corner_size.height() + double_radius
  134. };
  135. auto horizontal_edge_width = min(max_edge_height, double_radius) + double_radius;
  136. auto vertical_edge_width = min(max_edge_width, double_radius) + double_radius;
  137. auto horizontal_top_edge_width = min(max_edge_height + extra_edge_height, double_radius) + double_radius;
  138. auto vertical_left_edge_width = min(max_edge_width + extra_edge_width, double_radius) + double_radius;
  139. DevicePixelRect left_edge_rect { 0, top_left_corner_rect.height(), vertical_left_edge_width, 1 };
  140. DevicePixelRect right_edge_rect { shadow_bitmap_rect.width() - vertical_edge_width, top_right_corner_rect.height(), vertical_edge_width, 1 };
  141. DevicePixelRect top_edge_rect { top_left_corner_rect.width(), 0, 1, horizontal_top_edge_width };
  142. DevicePixelRect bottom_edge_rect { bottom_left_corner_rect.width(), shadow_bitmap_rect.height() - horizontal_edge_width, 1, horizontal_edge_width };
  143. auto shadows_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, shadow_bitmap_rect.size().to_type<int>());
  144. if (shadows_bitmap.is_error()) {
  145. dbgln("Unable to allocate temporary bitmap {} for box-shadow rendering: {}", shadow_bitmap_rect, shadows_bitmap.error());
  146. return;
  147. }
  148. auto shadow_bitmap = shadows_bitmap.release_value();
  149. Gfx::Painter corner_painter { *shadow_bitmap };
  150. Gfx::AntiAliasingPainter aa_corner_painter { corner_painter };
  151. aa_corner_painter.fill_rect_with_rounded_corners(shadow_bitmap_rect.shrunken(double_radius, double_radius, double_radius, double_radius).to_type<int>(), box_shadow_data.color, top_left_shadow_corner, top_right_shadow_corner, bottom_right_shadow_corner, bottom_left_shadow_corner);
  152. Gfx::StackBlurFilter filter(*shadow_bitmap);
  153. filter.process_rgba(blur_radius.value(), box_shadow_data.color);
  154. auto paint_shadow_infill = [&] {
  155. if (!border_radii.has_any_radius())
  156. return painter.fill_rect(inner_bounding_rect.to_type<int>(), box_shadow_data.color);
  157. auto top_left_inner_width = top_left_corner_rect.width() - blurred_edge_thickness;
  158. auto top_left_inner_height = top_left_corner_rect.height() - blurred_edge_thickness;
  159. auto top_right_inner_width = top_right_corner_rect.width() - blurred_edge_thickness;
  160. auto top_right_inner_height = top_right_corner_rect.height() - blurred_edge_thickness;
  161. auto bottom_right_inner_width = bottom_right_corner_rect.width() - blurred_edge_thickness;
  162. auto bottom_right_inner_height = bottom_right_corner_rect.height() - blurred_edge_thickness;
  163. auto bottom_left_inner_width = bottom_left_corner_rect.width() - blurred_edge_thickness;
  164. auto bottom_left_inner_height = bottom_left_corner_rect.height() - blurred_edge_thickness;
  165. DevicePixelRect top_rect {
  166. inner_bounding_rect.x() + top_left_inner_width,
  167. inner_bounding_rect.y(),
  168. inner_bounding_rect.width() - top_left_inner_width - top_right_inner_width,
  169. top_left_inner_height
  170. };
  171. DevicePixelRect right_rect {
  172. inner_bounding_rect.x() + inner_bounding_rect.width() - top_right_inner_width,
  173. inner_bounding_rect.y() + top_right_inner_height,
  174. top_right_inner_width,
  175. inner_bounding_rect.height() - top_right_inner_height - bottom_right_inner_height
  176. };
  177. DevicePixelRect bottom_rect {
  178. inner_bounding_rect.x() + bottom_left_inner_width,
  179. inner_bounding_rect.y() + inner_bounding_rect.height() - bottom_right_inner_height,
  180. inner_bounding_rect.width() - bottom_left_inner_width - bottom_right_inner_width,
  181. bottom_right_inner_height
  182. };
  183. DevicePixelRect left_rect {
  184. inner_bounding_rect.x(),
  185. inner_bounding_rect.y() + top_left_inner_height,
  186. bottom_left_inner_width,
  187. inner_bounding_rect.height() - top_left_inner_height - bottom_left_inner_height
  188. };
  189. DevicePixelRect inner = {
  190. left_rect.x() + left_rect.width(),
  191. left_rect.y(),
  192. inner_bounding_rect.width() - left_rect.width() - right_rect.width(),
  193. inner_bounding_rect.height() - top_rect.height() - bottom_rect.height()
  194. };
  195. painter.fill_rect(top_rect.to_type<int>(), box_shadow_data.color);
  196. painter.fill_rect(right_rect.to_type<int>(), box_shadow_data.color);
  197. painter.fill_rect(bottom_rect.to_type<int>(), box_shadow_data.color);
  198. painter.fill_rect(left_rect.to_type<int>(), box_shadow_data.color);
  199. painter.fill_rect(inner.to_type<int>(), box_shadow_data.color);
  200. };
  201. auto left_start = inner_bounding_rect.left() - blurred_edge_thickness;
  202. auto right_start = inner_bounding_rect.left() + inner_bounding_rect.width() + (blurred_edge_thickness - vertical_edge_width);
  203. auto top_start = inner_bounding_rect.top() - blurred_edge_thickness;
  204. auto bottom_start = inner_bounding_rect.top() + inner_bounding_rect.height() + (blurred_edge_thickness - horizontal_edge_width);
  205. // Note: The +1s in a few of the following translations are due to the -1s Gfx::Rect::right() and Gfx::Rect::bottom().
  206. auto top_left_corner_blit_pos = inner_bounding_rect.top_left().translated(-blurred_edge_thickness, -blurred_edge_thickness);
  207. auto top_right_corner_blit_pos = inner_bounding_rect.top_right().translated(-top_right_corner_size.width() + 1 + double_radius, -blurred_edge_thickness);
  208. auto bottom_left_corner_blit_pos = inner_bounding_rect.bottom_left().translated(-blurred_edge_thickness, -bottom_left_corner_size.height() + 1 + double_radius);
  209. auto bottom_right_corner_blit_pos = inner_bounding_rect.bottom_right().translated(-bottom_right_corner_size.width() + 1 + double_radius, -bottom_right_corner_size.height() + 1 + double_radius);
  210. auto paint_shadow = [&](DevicePixelRect clip_rect) {
  211. Gfx::PainterStateSaver save { painter };
  212. painter.add_clip_rect(clip_rect.to_type<int>());
  213. paint_shadow_infill();
  214. // Corners
  215. painter.blit(top_left_corner_blit_pos.to_type<int>(), shadow_bitmap, top_left_corner_rect.to_type<int>());
  216. painter.blit(top_right_corner_blit_pos.to_type<int>(), shadow_bitmap, top_right_corner_rect.to_type<int>());
  217. painter.blit(bottom_left_corner_blit_pos.to_type<int>(), shadow_bitmap, bottom_left_corner_rect.to_type<int>());
  218. painter.blit(bottom_right_corner_blit_pos.to_type<int>(), shadow_bitmap, bottom_right_corner_rect.to_type<int>());
  219. // Horizontal edges
  220. for (auto x = inner_bounding_rect.left() + (bottom_left_corner_size.width() - double_radius); x <= inner_bounding_rect.right() - (bottom_right_corner_size.width() - double_radius); ++x)
  221. painter.blit({ x, bottom_start }, shadow_bitmap, bottom_edge_rect.to_type<int>());
  222. for (auto x = inner_bounding_rect.left() + (top_left_corner_size.width() - double_radius); x <= inner_bounding_rect.right() - (top_right_corner_size.width() - double_radius); ++x)
  223. painter.blit({ x, top_start }, shadow_bitmap, top_edge_rect.to_type<int>());
  224. // Vertical edges
  225. for (auto y = inner_bounding_rect.top() + (top_right_corner_size.height() - double_radius); y <= inner_bounding_rect.bottom() - (bottom_right_corner_size.height() - double_radius); ++y)
  226. painter.blit({ right_start, y }, shadow_bitmap, right_edge_rect.to_type<int>());
  227. for (auto y = inner_bounding_rect.top() + (top_left_corner_size.height() - double_radius); y <= inner_bounding_rect.bottom() - (bottom_left_corner_size.height() - double_radius); ++y)
  228. painter.blit({ left_start, y }, shadow_bitmap, left_edge_rect.to_type<int>());
  229. };
  230. // FIXME: Painter only lets us define a clip-rect which discards drawing outside of it, whereas here we want
  231. // a rect which discards drawing inside it. So, we run the draw operations 4 to 8 times with clip-rects
  232. // covering each side of the content_rect exactly once.
  233. // If we were painting a shadow without a border radius we'd want to clip everything inside the box below.
  234. // If painting a shadow with rounded corners (but still rectangular) we want to clip everything inside
  235. // the box except the corners. This gives us an upper bound of 8 shadow paints now :^(.
  236. // (However, this does not seem to be the costly part in profiling).
  237. //
  238. // ┌───┬────────┬───┐
  239. // │ │xxxxxxxx│ │
  240. // ├───┼────────┼───┤
  241. // │xxx│xxxxxxxx│xxx│
  242. // │xxx│xxxxxxxx│xxx│
  243. // │xxx│xxxxxxxx│xxx│
  244. // │xxx│xxxxxxxx│xxx│
  245. // │xxx│xxxxxxxx│xxx│
  246. // ├───┼────────┼───┤
  247. // │ │ xxxxxx │ │
  248. // └───┴────────┴───┘
  249. // How many times would you like to paint the shadow sir?
  250. // Yes.
  251. // FIXME: Could reduce the shadow paints from 8 to 4 for shadows with all corner radii 50%.
  252. // FIXME: We use this since we want the clip rect to include everything after a certain x or y.
  253. // Note: Using painter.target()->width() or height() does not work, when the painter is a small
  254. // translated bitmap rather than full screen, as the clip rect may not intersect.
  255. constexpr auto really_large_number = NumericLimits<int>::max() / 2;
  256. // Everything above content_rect, including sides
  257. paint_shadow({ 0, 0, really_large_number, device_content_rect.top() });
  258. // Everything below content_rect, including sides
  259. paint_shadow({ 0, device_content_rect.bottom() + 1, really_large_number, really_large_number });
  260. // Everything directly to the left of content_rect
  261. paint_shadow({ 0, device_content_rect.top(), device_content_rect.left(), device_content_rect.height() });
  262. // Everything directly to the right of content_rect
  263. paint_shadow({ device_content_rect.right() + 1, device_content_rect.top(), really_large_number, device_content_rect.height() });
  264. if (top_left_corner) {
  265. // Inside the top left corner (the part outside the border radius)
  266. auto top_left = top_left_corner.as_rect().to_type<DevicePixels>().translated(device_content_rect.top_left());
  267. paint_shadow(top_left);
  268. }
  269. if (top_right_corner) {
  270. // Inside the top right corner (the part outside the border radius)
  271. auto top_right = top_right_corner.as_rect().to_type<DevicePixels>().translated(device_content_rect.top_right().translated(-top_right_corner.horizontal_radius + 1, 0));
  272. paint_shadow(top_right);
  273. }
  274. if (bottom_right_corner) {
  275. // Inside the bottom right corner (the part outside the border radius)
  276. auto bottom_right = bottom_right_corner.as_rect().to_type<DevicePixels>().translated(device_content_rect.bottom_right().translated(-bottom_right_corner.horizontal_radius + 1, -bottom_right_corner.vertical_radius + 1));
  277. paint_shadow(bottom_right);
  278. }
  279. if (bottom_left_corner) {
  280. // Inside the bottom left corner (the part outside the border radius)
  281. auto bottom_left = bottom_left_corner.as_rect().to_type<DevicePixels>().translated(device_content_rect.bottom_left().translated(0, -bottom_left_corner.vertical_radius + 1));
  282. paint_shadow(bottom_left);
  283. }
  284. }
  285. }
  286. void paint_text_shadow(PaintContext& context, Layout::LineBoxFragment const& fragment, Vector<ShadowData> const& shadow_layers)
  287. {
  288. if (shadow_layers.is_empty() || fragment.text().is_empty())
  289. return;
  290. auto& painter = context.painter();
  291. // Note: Box-shadow layers are ordered front-to-back, so we paint them in reverse
  292. for (auto& layer : shadow_layers.in_reverse()) {
  293. DevicePixels offset_x = context.rounded_device_pixels(layer.offset_x);
  294. DevicePixels offset_y = context.rounded_device_pixels(layer.offset_y);
  295. DevicePixels blur_radius = context.rounded_device_pixels(layer.blur_radius);
  296. DevicePixels fragment_width = context.enclosing_device_pixels(fragment.width());
  297. DevicePixels fragment_height = context.enclosing_device_pixels(fragment.height());
  298. // Space around the painted text to allow it to blur.
  299. // FIXME: Include spread in this once we use that.
  300. DevicePixels margin = blur_radius * 2;
  301. DevicePixelRect text_rect {
  302. margin, margin,
  303. fragment_width, fragment_height
  304. };
  305. DevicePixelRect bounding_rect {
  306. 0, 0,
  307. text_rect.width() + margin + margin,
  308. text_rect.height() + margin + margin
  309. };
  310. // FIXME: Figure out the maximum bitmap size for all shadows and then allocate it once and reuse it?
  311. auto maybe_shadow_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, bounding_rect.size().to_type<int>());
  312. if (maybe_shadow_bitmap.is_error()) {
  313. dbgln("Unable to allocate temporary bitmap {} for text-shadow rendering: {}", bounding_rect.size(), maybe_shadow_bitmap.error());
  314. return;
  315. }
  316. auto shadow_bitmap = maybe_shadow_bitmap.release_value();
  317. Gfx::Painter shadow_painter { *shadow_bitmap };
  318. // FIXME: "Spread" the shadow somehow.
  319. DevicePixelPoint baseline_start(text_rect.x(), text_rect.y() + context.rounded_device_pixels(fragment.baseline()));
  320. shadow_painter.draw_text_run(baseline_start.to_type<int>(), Utf8View(fragment.text()), fragment.layout_node().scaled_font(context), layer.color);
  321. // Blur
  322. Gfx::StackBlurFilter filter(*shadow_bitmap);
  323. filter.process_rgba(blur_radius.value(), layer.color);
  324. auto draw_rect = context.enclosing_device_rect(fragment.absolute_rect());
  325. DevicePixelPoint draw_location {
  326. draw_rect.x() + offset_x - margin,
  327. draw_rect.y() + offset_y - margin
  328. };
  329. painter.blit(draw_location.to_type<int>(), *shadow_bitmap, bounding_rect.to_type<int>());
  330. }
  331. }
  332. }