| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (c) 2023, MacDue <macdue@dueutil.tech> | 
					
						
							| 
									
										
										
										
											2024-11-09 02:36:31 +01:00
										 |  |  |  * Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com> | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  |  * | 
					
						
							|  |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <LibGfx/Bitmap.h>
 | 
					
						
							| 
									
										
										
										
											2024-04-27 12:09:58 +12:00
										 |  |  | #include <LibWeb/Bindings/CanvasPatternPrototype.h>
 | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  | #include <LibWeb/Bindings/Intrinsics.h>
 | 
					
						
							|  |  |  | #include <LibWeb/HTML/CanvasPattern.h>
 | 
					
						
							|  |  |  | #include <LibWeb/HTML/CanvasRenderingContext2D.h>
 | 
					
						
							| 
									
										
										
										
											2024-04-05 09:15:45 -04:00
										 |  |  | #include <LibWeb/HTML/ImageBitmap.h>
 | 
					
						
							| 
									
										
										
										
											2024-10-04 14:39:04 +02:00
										 |  |  | #include <LibWeb/SVG/SVGImageElement.h>
 | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | namespace Web::HTML { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  | GC_DEFINE_ALLOCATOR(CanvasPattern); | 
					
						
							| 
									
										
										
										
											2023-11-19 19:47:52 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-06 23:41:31 +01:00
										 |  |  | // https://html.spec.whatwg.org/multipage/canvas.html#dom-canvaspattern-settransform
 | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  | void CanvasPatternPaintStyle::paint(Gfx::IntRect physical_bounding_box, PaintFunction paint) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // 1. Create an infinite transparent black bitmap.
 | 
					
						
							|  |  |  |     // *waves magic wand 🪄*
 | 
					
						
							|  |  |  |     // Done!
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 2. Place a copy of the image on the bitmap, anchored such that its top left corner
 | 
					
						
							|  |  |  |     // is at the origin of the coordinate space, with one coordinate space unit per CSS pixel of the image,
 | 
					
						
							|  |  |  |     // then place repeated copies of this image horizontally to the left and right, if the repetition behavior
 | 
					
						
							|  |  |  |     // is "repeat-x", or vertically up and down, if the repetition behavior is "repeat-y", or in all four directions
 | 
					
						
							|  |  |  |     // all over the bitmap, if the repetition behavior is "repeat".
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-29 16:48:28 +01:00
										 |  |  |     // FIXME: If the original image data is a bitmap image, then the value painted at a point in the area of
 | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  |     // the repetitions is computed by filtering the original image data. When scaling up, if the imageSmoothingEnabled
 | 
					
						
							|  |  |  |     // attribute is set to false, then the image must be rendered using nearest-neighbor interpolation.
 | 
					
						
							|  |  |  |     // Otherwise, the user agent may use any filtering algorithm (for example bilinear interpolation or nearest-neighbor).
 | 
					
						
							|  |  |  |     // User agents which support multiple filtering algorithms may use the value of the imageSmoothingQuality attribute
 | 
					
						
							|  |  |  |     // to guide the choice of filtering algorithm. When such a filtering algorithm requires a pixel value from outside
 | 
					
						
							|  |  |  |     // the original image data, it must instead use the value from wrapping the pixel's coordinates to the original
 | 
					
						
							|  |  |  |     // image's dimensions. (That is, the filter uses 'repeat' behavior, regardless of the value of the pattern's repetition behavior.)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // FIXME: 3. Transform the resulting bitmap according to the pattern's transformation matrix.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // FIXME: 4. Transform the resulting bitmap again, this time according to the current transformation matrix.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 5. Replace any part of the image outside the area in which the pattern is to be rendered with transparent black.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 6. The resulting bitmap is what is to be rendered, with the same origin and same scale.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-11 15:56:30 +13:00
										 |  |  |     // FIXME: This doesn't handle a 'none' canvas context mode.
 | 
					
						
							|  |  |  |     auto bitmap = m_image.visit( | 
					
						
							|  |  |  |         [](GC::Root<HTMLImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return source->immutable_bitmap(); }, | 
					
						
							|  |  |  |         [](GC::Root<SVG::SVGImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return source->current_image_bitmap(); }, | 
					
						
							| 
									
										
										
										
											2025-03-06 23:41:31 +01:00
										 |  |  |         [](GC::Root<OffscreenCanvas> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); }, | 
					
						
							| 
									
										
										
										
											2025-01-11 15:56:30 +13:00
										 |  |  |         [](GC::Root<HTMLCanvasElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*source->surface()); }, | 
					
						
							|  |  |  |         [](GC::Root<HTMLVideoElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); }, | 
					
						
							|  |  |  |         [](GC::Root<ImageBitmap> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); }); | 
					
						
							|  |  |  |     VERIFY(bitmap); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto const bitmap_width = bitmap->width(); | 
					
						
							|  |  |  |     auto const bitmap_height = bitmap->height(); | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     paint([=, this](auto point) { | 
					
						
							|  |  |  |         point.translate_by(physical_bounding_box.location()); | 
					
						
							|  |  |  |         point = [&]() -> Gfx::IntPoint { | 
					
						
							|  |  |  |             switch (m_repetition) { | 
					
						
							|  |  |  |             case Repetition::NoRepeat: { | 
					
						
							|  |  |  |                 return point; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             case Repetition::Repeat: { | 
					
						
							|  |  |  |                 return { | 
					
						
							|  |  |  |                     point.x() % bitmap_width, | 
					
						
							|  |  |  |                     point.y() % bitmap_height | 
					
						
							|  |  |  |                 }; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             case Repetition::RepeatX: { | 
					
						
							|  |  |  |                 return { | 
					
						
							|  |  |  |                     point.x() % bitmap_width, | 
					
						
							|  |  |  |                     point.y() | 
					
						
							|  |  |  |                 }; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             case Repetition::RepeatY: { | 
					
						
							|  |  |  |                 return { | 
					
						
							|  |  |  |                     point.x(), | 
					
						
							|  |  |  |                     point.y() % bitmap_height | 
					
						
							|  |  |  |                 }; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             default: | 
					
						
							|  |  |  |                 VERIFY_NOT_REACHED(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }(); | 
					
						
							| 
									
										
										
										
											2025-01-11 15:56:30 +13:00
										 |  |  |         if (bitmap->rect().contains(point)) | 
					
						
							|  |  |  |             return bitmap->get_pixel(point.x(), point.y()); | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  |         return Gfx::Color(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | CanvasPattern::CanvasPattern(JS::Realm& realm, CanvasPatternPaintStyle& pattern) | 
					
						
							|  |  |  |     : PlatformObject(realm) | 
					
						
							|  |  |  |     , m_pattern(pattern) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | CanvasPattern::~CanvasPattern() = default; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createpattern
 | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  | WebIDL::ExceptionOr<GC::Ptr<CanvasPattern>> CanvasPattern::create(JS::Realm& realm, CanvasImageSource const& image, StringView repetition) | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  | { | 
					
						
							|  |  |  |     auto parse_repetition = [&](auto repetition) -> Optional<CanvasPatternPaintStyle::Repetition> { | 
					
						
							|  |  |  |         if (repetition == "repeat"sv) | 
					
						
							|  |  |  |             return CanvasPatternPaintStyle::Repetition::Repeat; | 
					
						
							|  |  |  |         if (repetition == "repeat-x"sv) | 
					
						
							|  |  |  |             return CanvasPatternPaintStyle::Repetition::RepeatX; | 
					
						
							|  |  |  |         if (repetition == "repeat-y"sv) | 
					
						
							|  |  |  |             return CanvasPatternPaintStyle::Repetition::RepeatY; | 
					
						
							|  |  |  |         if (repetition == "no-repeat"sv) | 
					
						
							|  |  |  |             return CanvasPatternPaintStyle::Repetition::NoRepeat; | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 1. Let usability be the result of checking the usability of image.
 | 
					
						
							|  |  |  |     auto usability = TRY(check_usability_of_image(image)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 2. If usability is bad, then return null.
 | 
					
						
							|  |  |  |     if (usability == CanvasImageSourceUsability::Bad) | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  |         return GC::Ptr<CanvasPattern> {}; | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // 3. Assert: usability is good.
 | 
					
						
							|  |  |  |     VERIFY(usability == CanvasImageSourceUsability::Good); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 4. If repetition is the empty string, then set it to "repeat".
 | 
					
						
							|  |  |  |     if (repetition.is_empty()) | 
					
						
							|  |  |  |         repetition = "repeat"sv; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 5. If repetition is not identical to one of "repeat", "repeat-x", "repeat-y", or "no-repeat",
 | 
					
						
							|  |  |  |     // then throw a "SyntaxError" DOMException.
 | 
					
						
							|  |  |  |     auto repetition_value = parse_repetition(repetition); | 
					
						
							|  |  |  |     if (!repetition_value.has_value()) | 
					
						
							| 
									
										
										
										
											2024-10-12 20:56:21 +02:00
										 |  |  |         return WebIDL::SyntaxError::create(realm, "Repetition value is not valid"_string); | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // 6. Let pattern be a new CanvasPattern object with the image image and the repetition behavior given by repetition.
 | 
					
						
							| 
									
										
										
										
											2025-01-11 15:56:30 +13:00
										 |  |  |     auto pattern = TRY_OR_THROW_OOM(realm.vm(), CanvasPatternPaintStyle::create(image, *repetition_value)); | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // FIXME: 7. If image is not origin-clean, then mark pattern as not origin-clean.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 8. Return pattern.
 | 
					
						
							| 
									
										
										
										
											2024-11-14 05:50:17 +13:00
										 |  |  |     return realm.create<CanvasPattern>(realm, *pattern); | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-07 08:41:28 +02:00
										 |  |  | void CanvasPattern::initialize(JS::Realm& realm) | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2024-03-16 13:13:08 +01:00
										 |  |  |     WEB_SET_PROTOTYPE_FOR_INTERFACE(CanvasPattern); | 
					
						
							| 
									
										
										
										
											2025-04-20 16:22:57 +02:00
										 |  |  |     Base::initialize(realm); | 
					
						
							| 
									
										
										
										
											2023-02-02 20:47:46 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |