CanvasPattern.cpp 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. /*
  2. * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
  3. * Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibGfx/Bitmap.h>
  8. #include <LibWeb/Bindings/CanvasPatternPrototype.h>
  9. #include <LibWeb/Bindings/Intrinsics.h>
  10. #include <LibWeb/HTML/CanvasPattern.h>
  11. #include <LibWeb/HTML/CanvasRenderingContext2D.h>
  12. #include <LibWeb/HTML/ImageBitmap.h>
  13. #include <LibWeb/SVG/SVGImageElement.h>
  14. namespace Web::HTML {
  15. GC_DEFINE_ALLOCATOR(CanvasPattern);
  16. void CanvasPatternPaintStyle::paint(Gfx::IntRect physical_bounding_box, PaintFunction paint) const
  17. {
  18. // 1. Create an infinite transparent black bitmap.
  19. // *waves magic wand 🪄*
  20. // Done!
  21. // 2. Place a copy of the image on the bitmap, anchored such that its top left corner
  22. // is at the origin of the coordinate space, with one coordinate space unit per CSS pixel of the image,
  23. // then place repeated copies of this image horizontally to the left and right, if the repetition behavior
  24. // is "repeat-x", or vertically up and down, if the repetition behavior is "repeat-y", or in all four directions
  25. // all over the bitmap, if the repetition behavior is "repeat".
  26. // FIMXE: If the original image data is a bitmap image, then the value painted at a point in the area of
  27. // the repetitions is computed by filtering the original image data. When scaling up, if the imageSmoothingEnabled
  28. // attribute is set to false, then the image must be rendered using nearest-neighbor interpolation.
  29. // Otherwise, the user agent may use any filtering algorithm (for example bilinear interpolation or nearest-neighbor).
  30. // User agents which support multiple filtering algorithms may use the value of the imageSmoothingQuality attribute
  31. // to guide the choice of filtering algorithm. When such a filtering algorithm requires a pixel value from outside
  32. // the original image data, it must instead use the value from wrapping the pixel's coordinates to the original
  33. // image's dimensions. (That is, the filter uses 'repeat' behavior, regardless of the value of the pattern's repetition behavior.)
  34. // FIXME: 3. Transform the resulting bitmap according to the pattern's transformation matrix.
  35. // FIXME: 4. Transform the resulting bitmap again, this time according to the current transformation matrix.
  36. // 5. Replace any part of the image outside the area in which the pattern is to be rendered with transparent black.
  37. // 6. The resulting bitmap is what is to be rendered, with the same origin and same scale.
  38. auto const bitmap_width = m_immutable_bitmap->width();
  39. auto const bitmap_height = m_immutable_bitmap->height();
  40. paint([=, this](auto point) {
  41. point.translate_by(physical_bounding_box.location());
  42. point = [&]() -> Gfx::IntPoint {
  43. switch (m_repetition) {
  44. case Repetition::NoRepeat: {
  45. return point;
  46. }
  47. case Repetition::Repeat: {
  48. return {
  49. point.x() % bitmap_width,
  50. point.y() % bitmap_height
  51. };
  52. }
  53. case Repetition::RepeatX: {
  54. return {
  55. point.x() % bitmap_width,
  56. point.y()
  57. };
  58. }
  59. case Repetition::RepeatY: {
  60. return {
  61. point.x(),
  62. point.y() % bitmap_height
  63. };
  64. }
  65. default:
  66. VERIFY_NOT_REACHED();
  67. }
  68. }();
  69. if (m_immutable_bitmap->rect().contains(point))
  70. return m_immutable_bitmap->get_pixel(point.x(), point.y());
  71. return Gfx::Color();
  72. });
  73. }
  74. CanvasPattern::CanvasPattern(JS::Realm& realm, CanvasPatternPaintStyle& pattern)
  75. : PlatformObject(realm)
  76. , m_pattern(pattern)
  77. {
  78. }
  79. CanvasPattern::~CanvasPattern() = default;
  80. // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createpattern
  81. WebIDL::ExceptionOr<GC::Ptr<CanvasPattern>> CanvasPattern::create(JS::Realm& realm, CanvasImageSource const& image, StringView repetition)
  82. {
  83. auto parse_repetition = [&](auto repetition) -> Optional<CanvasPatternPaintStyle::Repetition> {
  84. if (repetition == "repeat"sv)
  85. return CanvasPatternPaintStyle::Repetition::Repeat;
  86. if (repetition == "repeat-x"sv)
  87. return CanvasPatternPaintStyle::Repetition::RepeatX;
  88. if (repetition == "repeat-y"sv)
  89. return CanvasPatternPaintStyle::Repetition::RepeatY;
  90. if (repetition == "no-repeat"sv)
  91. return CanvasPatternPaintStyle::Repetition::NoRepeat;
  92. return {};
  93. };
  94. // 1. Let usability be the result of checking the usability of image.
  95. auto usability = TRY(check_usability_of_image(image));
  96. // 2. If usability is bad, then return null.
  97. if (usability == CanvasImageSourceUsability::Bad)
  98. return GC::Ptr<CanvasPattern> {};
  99. // 3. Assert: usability is good.
  100. VERIFY(usability == CanvasImageSourceUsability::Good);
  101. // 4. If repetition is the empty string, then set it to "repeat".
  102. if (repetition.is_empty())
  103. repetition = "repeat"sv;
  104. // 5. If repetition is not identical to one of "repeat", "repeat-x", "repeat-y", or "no-repeat",
  105. // then throw a "SyntaxError" DOMException.
  106. auto repetition_value = parse_repetition(repetition);
  107. if (!repetition_value.has_value())
  108. return WebIDL::SyntaxError::create(realm, "Repetition value is not valid"_string);
  109. // Note: Bitmap won't be null here, as if it were it would have "bad" usability.
  110. auto bitmap = image.visit(
  111. [](GC::Root<HTMLImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return source->immutable_bitmap(); },
  112. [](GC::Root<SVG::SVGImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return source->current_image_bitmap(); },
  113. [](GC::Root<HTMLCanvasElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*source->surface()); },
  114. [](GC::Root<HTMLVideoElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); },
  115. [](GC::Root<ImageBitmap> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); });
  116. // 6. Let pattern be a new CanvasPattern object with the image image and the repetition behavior given by repetition.
  117. auto pattern = TRY_OR_THROW_OOM(realm.vm(), CanvasPatternPaintStyle::create(*bitmap, *repetition_value));
  118. // FIXME: 7. If image is not origin-clean, then mark pattern as not origin-clean.
  119. // 8. Return pattern.
  120. return realm.create<CanvasPattern>(realm, *pattern);
  121. }
  122. void CanvasPattern::initialize(JS::Realm& realm)
  123. {
  124. Base::initialize(realm);
  125. WEB_SET_PROTOTYPE_FOR_INTERFACE(CanvasPattern);
  126. }
  127. }