Serenity Operating System
at master 144 lines 6.2 kB view raw
1/* 2 * Copyright (c) 2023, MacDue <macdue@dueutil.tech> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <LibGfx/Bitmap.h> 8#include <LibWeb/Bindings/Intrinsics.h> 9#include <LibWeb/HTML/CanvasPattern.h> 10#include <LibWeb/HTML/CanvasRenderingContext2D.h> 11 12namespace Web::HTML { 13 14void CanvasPatternPaintStyle::paint(Gfx::IntRect physical_bounding_box, PaintFunction paint) const 15{ 16 // 1. Create an infinite transparent black bitmap. 17 // *waves magic wand 🪄* 18 // Done! 19 20 // 2. Place a copy of the image on the bitmap, anchored such that its top left corner 21 // is at the origin of the coordinate space, with one coordinate space unit per CSS pixel of the image, 22 // then place repeated copies of this image horizontally to the left and right, if the repetition behavior 23 // is "repeat-x", or vertically up and down, if the repetition behavior is "repeat-y", or in all four directions 24 // all over the bitmap, if the repetition behavior is "repeat". 25 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 35 // FIXME: 3. Transform the resulting bitmap according to the pattern's transformation matrix. 36 37 // FIXME: 4. Transform the resulting bitmap again, this time according to the current transformation matrix. 38 39 // 5. Replace any part of the image outside the area in which the pattern is to be rendered with transparent black. 40 41 // 6. The resulting bitmap is what is to be rendered, with the same origin and same scale. 42 43 auto const bitmap_width = m_bitmap->width(); 44 auto const bitmap_height = m_bitmap->height(); 45 46 paint([=, this](auto point) { 47 point.translate_by(physical_bounding_box.location()); 48 point = [&]() -> Gfx::IntPoint { 49 switch (m_repetition) { 50 case Repetition::NoRepeat: { 51 return point; 52 } 53 case Repetition::Repeat: { 54 return { 55 point.x() % bitmap_width, 56 point.y() % bitmap_height 57 }; 58 } 59 case Repetition::RepeatX: { 60 return { 61 point.x() % bitmap_width, 62 point.y() 63 }; 64 } 65 case Repetition::RepeatY: { 66 return { 67 point.x(), 68 point.y() % bitmap_height 69 }; 70 } 71 default: 72 VERIFY_NOT_REACHED(); 73 } 74 }(); 75 if (m_bitmap->rect().contains(point)) 76 return m_bitmap->get_pixel(point); 77 return Gfx::Color(); 78 }); 79} 80 81CanvasPattern::CanvasPattern(JS::Realm& realm, CanvasPatternPaintStyle& pattern) 82 : PlatformObject(realm) 83 , m_pattern(pattern) 84{ 85} 86 87CanvasPattern::~CanvasPattern() = default; 88 89// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createpattern 90WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> CanvasPattern::create(JS::Realm& realm, CanvasImageSource const& image, StringView repetition) 91{ 92 auto parse_repetition = [&](auto repetition) -> Optional<CanvasPatternPaintStyle::Repetition> { 93 if (repetition == "repeat"sv) 94 return CanvasPatternPaintStyle::Repetition::Repeat; 95 if (repetition == "repeat-x"sv) 96 return CanvasPatternPaintStyle::Repetition::RepeatX; 97 if (repetition == "repeat-y"sv) 98 return CanvasPatternPaintStyle::Repetition::RepeatY; 99 if (repetition == "no-repeat"sv) 100 return CanvasPatternPaintStyle::Repetition::NoRepeat; 101 return {}; 102 }; 103 104 // 1. Let usability be the result of checking the usability of image. 105 auto usability = TRY(check_usability_of_image(image)); 106 107 // 2. If usability is bad, then return null. 108 if (usability == CanvasImageSourceUsability::Bad) 109 return JS::GCPtr<CanvasPattern> {}; 110 111 // 3. Assert: usability is good. 112 VERIFY(usability == CanvasImageSourceUsability::Good); 113 114 // 4. If repetition is the empty string, then set it to "repeat". 115 if (repetition.is_empty()) 116 repetition = "repeat"sv; 117 118 // 5. If repetition is not identical to one of "repeat", "repeat-x", "repeat-y", or "no-repeat", 119 // then throw a "SyntaxError" DOMException. 120 auto repetition_value = parse_repetition(repetition); 121 if (!repetition_value.has_value()) 122 return WebIDL::SyntaxError::create(realm, "Repetition value is not valid"); 123 124 // Note: Bitmap won't be null here, as if it were it would have "bad" usability. 125 auto const& bitmap = *image.visit([](auto const& source) -> Gfx::Bitmap const* { return source->bitmap(); }); 126 127 // 6. Let pattern be a new CanvasPattern object with the image image and the repetition behavior given by repetition. 128 auto pattern = TRY_OR_THROW_OOM(realm.vm(), CanvasPatternPaintStyle::create(bitmap, *repetition_value)); 129 130 // FIXME: 7. If image is not origin-clean, then mark pattern as not origin-clean. 131 132 // 8. Return pattern. 133 return MUST_OR_THROW_OOM(realm.heap().allocate<CanvasPattern>(realm, realm, *pattern)); 134} 135 136JS::ThrowCompletionOr<void> CanvasPattern::initialize(JS::Realm& realm) 137{ 138 MUST_OR_THROW_OOM(Base::initialize(realm)); 139 set_prototype(&Bindings::ensure_web_prototype<Bindings::CanvasPatternPrototype>(realm, "CanvasPattern")); 140 141 return {}; 142} 143 144}