ShadowPainting.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibGfx/DisjointRectSet.h>
  8. #include <LibGfx/Filters/FastBoxBlurFilter.h>
  9. #include <LibGfx/Painter.h>
  10. #include <LibWeb/Layout/LineBoxFragment.h>
  11. #include <LibWeb/Painting/PaintContext.h>
  12. #include <LibWeb/Painting/ShadowPainting.h>
  13. namespace Web::Painting {
  14. void paint_box_shadow(PaintContext& context, Gfx::IntRect const& content_rect, Vector<ShadowData> const& box_shadow_layers)
  15. {
  16. if (box_shadow_layers.is_empty())
  17. return;
  18. auto& painter = context.painter();
  19. // Note: Box-shadow layers are ordered front-to-back, so we paint them in reverse
  20. for (int layer_index = box_shadow_layers.size() - 1; layer_index >= 0; layer_index--) {
  21. auto& box_shadow_data = box_shadow_layers[layer_index];
  22. // FIXME: Paint inset shadows.
  23. if (box_shadow_data.placement != ShadowPlacement::Outer)
  24. continue;
  25. // FIXME: Account for rounded corners.
  26. auto fill_rect_masked = [](auto& painter, auto fill_rect, auto mask_rect, auto color) {
  27. Gfx::DisjointRectSet rect_set;
  28. rect_set.add(fill_rect);
  29. auto shattered = rect_set.shatter(mask_rect);
  30. for (auto& rect : shattered.rects())
  31. painter.fill_rect(rect, color);
  32. };
  33. // If there's no blurring, we can save a lot of effort.
  34. if (box_shadow_data.blur_radius == 0) {
  35. fill_rect_masked(painter, content_rect.inflated(box_shadow_data.spread_distance, box_shadow_data.spread_distance, box_shadow_data.spread_distance, box_shadow_data.spread_distance).translated(box_shadow_data.offset_x, box_shadow_data.offset_y), content_rect, box_shadow_data.color);
  36. continue;
  37. }
  38. auto expansion = box_shadow_data.spread_distance - (box_shadow_data.blur_radius * 2);
  39. Gfx::IntRect solid_rect = {
  40. content_rect.x() + box_shadow_data.offset_x - expansion,
  41. content_rect.y() + box_shadow_data.offset_y - expansion,
  42. content_rect.width() + 2 * expansion,
  43. content_rect.height() + 2 * expansion
  44. };
  45. fill_rect_masked(painter, solid_rect, content_rect, box_shadow_data.color);
  46. // Calculating and blurring the box-shadow full size is expensive, and wasteful - aside from the corners,
  47. // all vertical strips of the shadow are identical, and the same goes for horizontal ones.
  48. // So instead, we generate a shadow bitmap that is just large enough to include the corners and 1px of
  49. // non-corner, and then we repeatedly blit sections of it. This is similar to a NinePatch on Android.
  50. auto corner_size = box_shadow_data.blur_radius * 4;
  51. Gfx::IntRect corner_rect { 0, 0, corner_size, corner_size };
  52. Gfx::IntRect all_corners_rect { 0, 0, corner_size * 2 + 1, corner_size * 2 + 1 };
  53. Gfx::IntRect left_edge_rect { 0, corner_size, corner_size, 1 };
  54. Gfx::IntRect right_edge_rect { all_corners_rect.width() - corner_size, corner_size, corner_size, 1 };
  55. Gfx::IntRect top_edge_rect { corner_size, 0, 1, corner_size };
  56. Gfx::IntRect bottom_edge_rect { corner_size, all_corners_rect.height() - corner_size, 1, corner_size };
  57. auto shadows_bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, all_corners_rect.size());
  58. if (shadows_bitmap.is_error()) {
  59. dbgln("Unable to allocate temporary bitmap for box-shadow rendering: {}", shadows_bitmap.error());
  60. return;
  61. }
  62. auto shadow_bitmap = shadows_bitmap.release_value();
  63. Gfx::Painter corner_painter { *shadow_bitmap };
  64. auto double_radius = box_shadow_data.blur_radius * 2;
  65. corner_painter.fill_rect(all_corners_rect.shrunken(double_radius, double_radius, double_radius, double_radius), box_shadow_data.color);
  66. Gfx::FastBoxBlurFilter filter(*shadow_bitmap);
  67. filter.apply_three_passes(box_shadow_data.blur_radius);
  68. auto left_start = solid_rect.left() - corner_size;
  69. auto right_start = solid_rect.left() + solid_rect.width();
  70. auto top_start = solid_rect.top() - corner_size;
  71. auto bottom_start = solid_rect.top() + solid_rect.height();
  72. // FIXME: Painter only lets us define a clip-rect which discards drawing outside of it, whereas here we want
  73. // a rect which discards drawing inside it. So, we run the draw operations 4 times with clip-rects
  74. // covering each side of the content_rect exactly once.
  75. auto paint_shadow = [&](Gfx::IntRect clip_rect) {
  76. painter.save();
  77. painter.add_clip_rect(clip_rect);
  78. // Paint corners
  79. painter.blit({ left_start, top_start }, shadow_bitmap, corner_rect);
  80. painter.blit({ right_start, top_start }, shadow_bitmap, corner_rect.translated(corner_rect.width() + 1, 0));
  81. painter.blit({ left_start, bottom_start }, shadow_bitmap, corner_rect.translated(0, corner_rect.height() + 1));
  82. painter.blit({ right_start, bottom_start }, shadow_bitmap, corner_rect.translated(corner_rect.width() + 1, corner_rect.height() + 1));
  83. // Horizontal edges
  84. for (auto y = solid_rect.top(); y <= solid_rect.bottom(); ++y) {
  85. painter.blit({ left_start, y }, shadow_bitmap, left_edge_rect);
  86. painter.blit({ right_start, y }, shadow_bitmap, right_edge_rect);
  87. }
  88. // Vertical edges
  89. for (auto x = solid_rect.left(); x <= solid_rect.right(); ++x) {
  90. painter.blit({ x, top_start }, shadow_bitmap, top_edge_rect);
  91. painter.blit({ x, bottom_start }, shadow_bitmap, bottom_edge_rect);
  92. }
  93. painter.restore();
  94. };
  95. // Everything above content_rect, including sides
  96. paint_shadow({ 0, 0, painter.target()->width(), content_rect.top() });
  97. // Everything below content_rect, including sides
  98. paint_shadow({ 0, content_rect.bottom() + 1, painter.target()->width(), painter.target()->height() });
  99. // Everything directly to the left of content_rect
  100. paint_shadow({ 0, content_rect.top(), content_rect.left(), content_rect.height() });
  101. // Everything directly to the right of content_rect
  102. paint_shadow({ content_rect.right() + 1, content_rect.top(), painter.target()->width(), content_rect.height() });
  103. }
  104. }
  105. void paint_text_shadow(PaintContext& context, Layout::LineBoxFragment const& fragment, Vector<ShadowData> const& shadow_layers)
  106. {
  107. if (shadow_layers.is_empty())
  108. return;
  109. auto& painter = context.painter();
  110. // Note: Box-shadow layers are ordered front-to-back, so we paint them in reverse
  111. for (auto& layer : shadow_layers.in_reverse()) {
  112. // Space around the painted text to allow it to blur.
  113. // FIXME: Include spread in this once we use that.
  114. auto margin = layer.blur_radius * 2;
  115. Gfx::IntRect text_rect {
  116. margin, margin,
  117. static_cast<int>(ceilf(fragment.width())),
  118. static_cast<int>(ceilf(fragment.height()))
  119. };
  120. Gfx::IntRect bounding_rect {
  121. 0, 0,
  122. text_rect.width() + margin + margin,
  123. text_rect.height() + margin + margin
  124. };
  125. // FIXME: Figure out the maximum bitmap size for all shadows and then allocate it once and reuse it?
  126. auto maybe_shadow_bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, bounding_rect.size());
  127. if (maybe_shadow_bitmap.is_error()) {
  128. dbgln("Unable to allocate temporary bitmap for box-shadow rendering: {}", maybe_shadow_bitmap.error());
  129. return;
  130. }
  131. auto shadow_bitmap = maybe_shadow_bitmap.release_value();
  132. Gfx::Painter shadow_painter { *shadow_bitmap };
  133. shadow_painter.set_font(context.painter().font());
  134. // FIXME: "Spread" the shadow somehow.
  135. shadow_painter.draw_text(text_rect, fragment.text(), Gfx::TextAlignment::TopLeft, layer.color);
  136. // Blur
  137. Gfx::FastBoxBlurFilter filter(*shadow_bitmap);
  138. filter.apply_three_passes(layer.blur_radius);
  139. auto draw_rect = Gfx::enclosing_int_rect(fragment.absolute_rect());
  140. Gfx::IntPoint draw_location {
  141. draw_rect.x() + layer.offset_x - margin,
  142. draw_rect.y() + layer.offset_y - margin
  143. };
  144. painter.blit(draw_location, *shadow_bitmap, bounding_rect);
  145. }
  146. }
  147. }