BorderRadiusCornerClipper.cpp 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /*
  2. * Copyright (c) 2022, MacDue <macdue@dueutil.tech>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibGfx/Bitmap.h>
  7. #include <LibGfx/Painter.h>
  8. #include <LibWeb/Painting/BorderRadiusCornerClipper.h>
  9. #include <LibWeb/Painting/PaintContext.h>
  10. namespace Web::Painting {
  11. BorderRadiusSamplingConfig calculate_border_radius_sampling_config(CornerRadii const& corner_radii, Gfx::IntRect const& border_rect)
  12. {
  13. auto top_left = corner_radii.top_left;
  14. auto top_right = corner_radii.top_right;
  15. auto bottom_right = corner_radii.bottom_right;
  16. auto bottom_left = corner_radii.bottom_left;
  17. Gfx::IntSize corners_bitmap_size {
  18. max(
  19. max(
  20. top_left.horizontal_radius + top_right.horizontal_radius,
  21. top_left.horizontal_radius + bottom_right.horizontal_radius),
  22. max(
  23. bottom_left.horizontal_radius + top_right.horizontal_radius,
  24. bottom_left.horizontal_radius + bottom_right.horizontal_radius)),
  25. max(
  26. max(
  27. top_left.vertical_radius + bottom_left.vertical_radius,
  28. top_left.vertical_radius + bottom_right.vertical_radius),
  29. max(
  30. top_right.vertical_radius + bottom_left.vertical_radius,
  31. top_right.vertical_radius + bottom_right.vertical_radius))
  32. };
  33. BorderRadiusSamplingConfig corner_data {
  34. .corner_radii = corner_radii,
  35. .page_locations = { .top_left = border_rect.top_left(), .top_right = border_rect.top_right().translated(-top_right.horizontal_radius, 0), .bottom_right = border_rect.bottom_right().translated(-bottom_right.horizontal_radius, -bottom_right.vertical_radius), .bottom_left = border_rect.bottom_left().translated(0, -bottom_left.vertical_radius) },
  36. .bitmap_locations = { .top_left = { 0, 0 }, .top_right = { corners_bitmap_size.width() - top_right.horizontal_radius, 0 }, .bottom_right = { corners_bitmap_size.width() - bottom_right.horizontal_radius, corners_bitmap_size.height() - bottom_right.vertical_radius }, .bottom_left = { 0, corners_bitmap_size.height() - bottom_left.vertical_radius } },
  37. .corners_bitmap_size = corners_bitmap_size,
  38. };
  39. return corner_data;
  40. }
  41. ErrorOr<NonnullRefPtr<BorderRadiusCornerClipper>> BorderRadiusCornerClipper::create(CornerRadii const& corner_radii, DevicePixelRect const& border_rect, CornerClip corner_clip)
  42. {
  43. VERIFY(corner_radii.has_any_radius());
  44. auto corner_data = calculate_border_radius_sampling_config(corner_radii, border_rect.to_type<int>());
  45. RefPtr<Gfx::Bitmap> corner_bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, corner_data.corners_bitmap_size));
  46. return try_make_ref_counted<BorderRadiusCornerClipper>(corner_data, corner_bitmap.release_nonnull(), corner_clip, border_rect);
  47. }
  48. void BorderRadiusCornerClipper::sample_under_corners(Gfx::Painter& page_painter)
  49. {
  50. // Generate a mask for the corners:
  51. Gfx::Painter corner_painter { *m_corner_bitmap };
  52. Gfx::AntiAliasingPainter corner_aa_painter { corner_painter };
  53. corner_aa_painter.fill_rect_with_rounded_corners(m_corner_bitmap->rect(), Color::NamedColor::Black,
  54. m_data.corner_radii.top_left, m_data.corner_radii.top_right, m_data.corner_radii.bottom_right, m_data.corner_radii.bottom_left);
  55. auto clip_rect = page_painter.clip_rect();
  56. auto translation = page_painter.translation();
  57. auto copy_page_masked = [&](Gfx::IntRect const& mask_src, Gfx::IntPoint const& page_location) {
  58. for (int row = 0; row < mask_src.height(); ++row) {
  59. for (int col = 0; col < mask_src.width(); ++col) {
  60. auto corner_location = mask_src.location().translated(col, row);
  61. auto mask_pixel = m_corner_bitmap->get_pixel<Gfx::StorageFormat::BGRA8888>(corner_location.x(), corner_location.y());
  62. u8 mask_alpha = mask_pixel.alpha();
  63. if (m_corner_clip == CornerClip::Outside)
  64. mask_alpha = ~mask_pixel.alpha();
  65. auto final_pixel = Color();
  66. if (mask_alpha > 0) {
  67. auto position = page_location.translated(col, row);
  68. position.translate_by(translation);
  69. if (!clip_rect.contains(position))
  70. continue;
  71. auto page_pixel = page_painter.target().get_pixel<Gfx::StorageFormat::BGRA8888>(position.x(), position.y());
  72. final_pixel = page_pixel.with_alpha(mask_alpha);
  73. }
  74. m_corner_bitmap->set_pixel<Gfx::StorageFormat::BGRA8888>(corner_location.x(), corner_location.y(), final_pixel);
  75. }
  76. }
  77. };
  78. // Copy the pixels under the corner mask (using the alpha of the mask):
  79. if (m_data.corner_radii.top_left)
  80. copy_page_masked(m_data.corner_radii.top_left.as_rect().translated(m_data.bitmap_locations.top_left), m_data.page_locations.top_left);
  81. if (m_data.corner_radii.top_right)
  82. copy_page_masked(m_data.corner_radii.top_right.as_rect().translated(m_data.bitmap_locations.top_right), m_data.page_locations.top_right);
  83. if (m_data.corner_radii.bottom_right)
  84. copy_page_masked(m_data.corner_radii.bottom_right.as_rect().translated(m_data.bitmap_locations.bottom_right), m_data.page_locations.bottom_right);
  85. if (m_data.corner_radii.bottom_left)
  86. copy_page_masked(m_data.corner_radii.bottom_left.as_rect().translated(m_data.bitmap_locations.bottom_left), m_data.page_locations.bottom_left);
  87. m_has_sampled = true;
  88. }
  89. void BorderRadiusCornerClipper::blit_corner_clipping(Gfx::Painter& painter)
  90. {
  91. VERIFY(m_has_sampled);
  92. // Restore the corners:
  93. if (m_data.corner_radii.top_left)
  94. painter.blit(m_data.page_locations.top_left, *m_corner_bitmap, m_data.corner_radii.top_left.as_rect().translated(m_data.bitmap_locations.top_left));
  95. if (m_data.corner_radii.top_right)
  96. painter.blit(m_data.page_locations.top_right, *m_corner_bitmap, m_data.corner_radii.top_right.as_rect().translated(m_data.bitmap_locations.top_right));
  97. if (m_data.corner_radii.bottom_right)
  98. painter.blit(m_data.page_locations.bottom_right, *m_corner_bitmap, m_data.corner_radii.bottom_right.as_rect().translated(m_data.bitmap_locations.bottom_right));
  99. if (m_data.corner_radii.bottom_left)
  100. painter.blit(m_data.page_locations.bottom_left, *m_corner_bitmap, m_data.corner_radii.bottom_left.as_rect().translated(m_data.bitmap_locations.bottom_left));
  101. }
  102. ScopedCornerRadiusClip::ScopedCornerRadiusClip(PaintContext& context, DevicePixelRect const& border_rect, BorderRadiiData const& border_radii, CornerClip corner_clip)
  103. : m_context(context)
  104. , m_id(context.allocate_corner_clipper_id())
  105. , m_border_rect(border_rect)
  106. {
  107. CornerRadii const corner_radii {
  108. .top_left = border_radii.top_left.as_corner(context),
  109. .top_right = border_radii.top_right.as_corner(context),
  110. .bottom_right = border_radii.bottom_right.as_corner(context),
  111. .bottom_left = border_radii.bottom_left.as_corner(context)
  112. };
  113. m_has_radius = corner_radii.has_any_radius();
  114. if (!m_has_radius)
  115. return;
  116. m_context.display_list_recorder().sample_under_corners(m_id, corner_radii, border_rect.to_type<int>(), corner_clip);
  117. }
  118. ScopedCornerRadiusClip::~ScopedCornerRadiusClip()
  119. {
  120. if (!m_has_radius)
  121. return;
  122. m_context.display_list_recorder().blit_corner_clipping(m_id);
  123. }
  124. }