mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 00:42:44 +00:00 
			
		
		
		
	LibWeb: Implement CanvasRenderingContext2D.createPattern()
This is a first pass at implementing CRC2D.createPattern() and the associated CanvasPattern object. This implementation only works for a few of the required image sources [like CRC2D.drawImage()], and does not yet support transforms. Other than that it supports everything else (which is mainly the various repeat modes).
This commit is contained in:
		
							parent
							
								
									0c313c586b
								
							
						
					
					
						commit
						f74e2da875
					
				
					 8 changed files with 229 additions and 8 deletions
				
			
		|  | @ -170,6 +170,7 @@ set(SOURCES | ||||||
|     HTML/Canvas/CanvasPath.cpp |     HTML/Canvas/CanvasPath.cpp | ||||||
|     HTML/Canvas/CanvasState.cpp |     HTML/Canvas/CanvasState.cpp | ||||||
|     HTML/CanvasGradient.cpp |     HTML/CanvasGradient.cpp | ||||||
|  |     HTML/CanvasPattern.cpp | ||||||
|     HTML/CanvasRenderingContext2D.cpp |     HTML/CanvasRenderingContext2D.cpp | ||||||
|     HTML/CloseEvent.cpp |     HTML/CloseEvent.cpp | ||||||
|     HTML/CrossOrigin/AbstractOperations.cpp |     HTML/CrossOrigin/AbstractOperations.cpp | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ | ||||||
| #include <AK/DeprecatedString.h> | #include <AK/DeprecatedString.h> | ||||||
| #include <LibWeb/HTML/Canvas/CanvasState.h> | #include <LibWeb/HTML/Canvas/CanvasState.h> | ||||||
| #include <LibWeb/HTML/CanvasGradient.h> | #include <LibWeb/HTML/CanvasGradient.h> | ||||||
|  | #include <LibWeb/HTML/CanvasPattern.h> | ||||||
| 
 | 
 | ||||||
| namespace Web::HTML { | namespace Web::HTML { | ||||||
| 
 | 
 | ||||||
|  | @ -20,7 +21,7 @@ template<typename IncludingClass> | ||||||
| class CanvasFillStrokeStyles { | class CanvasFillStrokeStyles { | ||||||
| public: | public: | ||||||
|     ~CanvasFillStrokeStyles() = default; |     ~CanvasFillStrokeStyles() = default; | ||||||
|     using FillOrStrokeStyleVariant = Variant<DeprecatedString, JS::Handle<CanvasGradient>>; |     using FillOrStrokeStyleVariant = Variant<DeprecatedString, JS::Handle<CanvasGradient>, JS::Handle<CanvasPattern>>; | ||||||
| 
 | 
 | ||||||
|     static CanvasState::FillOrStrokeStyle to_canvas_state_fill_or_stroke_style(auto const& style) |     static CanvasState::FillOrStrokeStyle to_canvas_state_fill_or_stroke_style(auto const& style) | ||||||
|     { |     { | ||||||
|  | @ -73,6 +74,12 @@ public: | ||||||
|         return CanvasGradient::create_conic(realm, start_angle, x, y); |         return CanvasGradient::create_conic(realm, start_angle, x, y); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> create_pattern(CanvasImageSource const& image, StringView repetition) | ||||||
|  |     { | ||||||
|  |         auto& realm = static_cast<IncludingClass&>(*this).realm(); | ||||||
|  |         return CanvasPattern::create(realm, image, repetition); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| protected: | protected: | ||||||
|     CanvasFillStrokeStyles() = default; |     CanvasFillStrokeStyles() = default; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,13 +1,15 @@ | ||||||
| #import <HTML/CanvasGradient.idl> | #import <HTML/CanvasGradient.idl> | ||||||
|  | #import <HTML/CanvasPattern.idl> | ||||||
|  | #import <HTML/HTMLCanvasElement.idl> | ||||||
|  | #import <HTML/HTMLImageElement.idl> | ||||||
| 
 | 
 | ||||||
| // https://html.spec.whatwg.org/multipage/canvas.html#canvasfillstrokestyles | // https://html.spec.whatwg.org/multipage/canvas.html#canvasfillstrokestyles | ||||||
| interface mixin CanvasFillStrokeStyles { | interface mixin CanvasFillStrokeStyles { | ||||||
|     // FIXME: Should be `(DOMString or CanvasGradient or CanvasPattern)` |     attribute (DOMString or CanvasGradient or CanvasPattern) strokeStyle; | ||||||
|     attribute (DOMString or CanvasGradient) strokeStyle; |     attribute (DOMString or CanvasGradient or CanvasPattern) fillStyle; | ||||||
|     // FIXME: Should be `(DOMString or CanvasGradient or CanvasPattern)` |  | ||||||
|     attribute (DOMString or CanvasGradient) fillStyle; |  | ||||||
|     CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1); |     CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1); | ||||||
|     CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1); |     CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1); | ||||||
|     CanvasGradient createConicGradient(double startAngle, double x, double y); |     CanvasGradient createConicGradient(double startAngle, double x, double y); | ||||||
|     // FIXME: CanvasPattern? createPattern(CanvasImageSource image, [LegacyNullToEmptyString] DOMString repetition); |     // FIXME: 'image' should be a CanvasImageSource | ||||||
|  |     CanvasPattern? createPattern((HTMLImageElement or HTMLCanvasElement) image, [LegacyNullToEmptyString] DOMString repetition); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ | ||||||
| #include <LibGfx/Color.h> | #include <LibGfx/Color.h> | ||||||
| #include <LibGfx/PaintStyle.h> | #include <LibGfx/PaintStyle.h> | ||||||
| #include <LibWeb/HTML/CanvasGradient.h> | #include <LibWeb/HTML/CanvasGradient.h> | ||||||
|  | #include <LibWeb/HTML/CanvasPattern.h> | ||||||
| 
 | 
 | ||||||
| namespace Web::HTML { | namespace Web::HTML { | ||||||
| 
 | 
 | ||||||
|  | @ -26,7 +27,7 @@ public: | ||||||
|     void reset(); |     void reset(); | ||||||
|     bool is_context_lost(); |     bool is_context_lost(); | ||||||
| 
 | 
 | ||||||
|     using FillOrStrokeVariant = Variant<Gfx::Color, JS::Handle<CanvasGradient>>; |     using FillOrStrokeVariant = Variant<Gfx::Color, JS::Handle<CanvasGradient>, JS::Handle<CanvasPattern>>; | ||||||
| 
 | 
 | ||||||
|     struct FillOrStrokeStyle { |     struct FillOrStrokeStyle { | ||||||
|         FillOrStrokeStyle(Gfx::Color color) |         FillOrStrokeStyle(Gfx::Color color) | ||||||
|  | @ -49,7 +50,7 @@ public: | ||||||
|         Optional<Gfx::Color> as_color() const; |         Optional<Gfx::Color> as_color() const; | ||||||
|         Gfx::Color to_color_but_fixme_should_accept_any_paint_style() const; |         Gfx::Color to_color_but_fixme_should_accept_any_paint_style() const; | ||||||
| 
 | 
 | ||||||
|         using JsFillOrStrokeStyle = Variant<DeprecatedString, JS::Handle<CanvasGradient>>; |         using JsFillOrStrokeStyle = Variant<DeprecatedString, JS::Handle<CanvasGradient>, JS::Handle<CanvasPattern>>; | ||||||
| 
 | 
 | ||||||
|         JsFillOrStrokeStyle to_js_fill_or_stroke_style() const |         JsFillOrStrokeStyle to_js_fill_or_stroke_style() const | ||||||
|         { |         { | ||||||
|  |  | ||||||
							
								
								
									
										144
									
								
								Userland/Libraries/LibWeb/HTML/CanvasPattern.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								Userland/Libraries/LibWeb/HTML/CanvasPattern.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,144 @@ | ||||||
|  | /*
 | ||||||
|  |  * Copyright (c) 2023, MacDue <macdue@dueutil.tech> | ||||||
|  |  * | ||||||
|  |  * SPDX-License-Identifier: BSD-2-Clause | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <LibGfx/Bitmap.h> | ||||||
|  | #include <LibWeb/Bindings/Intrinsics.h> | ||||||
|  | #include <LibWeb/HTML/CanvasPattern.h> | ||||||
|  | #include <LibWeb/HTML/CanvasRenderingContext2D.h> | ||||||
|  | 
 | ||||||
|  | namespace Web::HTML { | ||||||
|  | 
 | ||||||
|  | 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".
 | ||||||
|  | 
 | ||||||
|  |     // FIMXE: If the original image data is a bitmap image, then the value painted at a point in the area of
 | ||||||
|  |     // 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.
 | ||||||
|  | 
 | ||||||
|  |     auto const bitmap_width = m_bitmap->width(); | ||||||
|  |     auto const bitmap_height = m_bitmap->height(); | ||||||
|  | 
 | ||||||
|  |     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(); | ||||||
|  |             } | ||||||
|  |         }(); | ||||||
|  |         if (m_bitmap->rect().contains(point)) | ||||||
|  |             return m_bitmap->get_pixel(point); | ||||||
|  |         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
 | ||||||
|  | WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> CanvasPattern::create(JS::Realm& realm, CanvasImageSource const& image, StringView repetition) | ||||||
|  | { | ||||||
|  |     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) | ||||||
|  |         return JS::GCPtr<CanvasPattern> {}; | ||||||
|  | 
 | ||||||
|  |     // 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()) | ||||||
|  |         return WebIDL::SyntaxError::create(realm, "Repetition value is not valid"); | ||||||
|  | 
 | ||||||
|  |     // Note: Bitmap won't be null here, as if it were it would have "bad" usability.
 | ||||||
|  |     auto const& bitmap = *image.visit([](auto const& source) -> Gfx::Bitmap const* { return source->bitmap(); }); | ||||||
|  | 
 | ||||||
|  |     // 6. Let pattern be a new CanvasPattern object with the image image and the repetition behavior given by repetition.
 | ||||||
|  |     auto pattern = CanvasPatternPaintStyle::create(bitmap, *repetition_value); | ||||||
|  | 
 | ||||||
|  |     // FIXME: 7. If image is not origin-clean, then mark pattern as not origin-clean.
 | ||||||
|  | 
 | ||||||
|  |     // 8. Return pattern.
 | ||||||
|  |     return MUST_OR_THROW_OOM(realm.heap().allocate<CanvasPattern>(realm, realm, *pattern)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | JS::ThrowCompletionOr<void> CanvasPattern::initialize(JS::Realm& realm) | ||||||
|  | { | ||||||
|  |     MUST_OR_THROW_OOM(Base::initialize(realm)); | ||||||
|  |     set_prototype(&Bindings::ensure_web_prototype<Bindings::CanvasPatternPrototype>(realm, "CanvasPattern")); | ||||||
|  | 
 | ||||||
|  |     return {}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								Userland/Libraries/LibWeb/HTML/CanvasPattern.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								Userland/Libraries/LibWeb/HTML/CanvasPattern.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | ||||||
|  | /*
 | ||||||
|  |  * Copyright (c) 2023, MacDue <macdue@dueutil.tech> | ||||||
|  |  * | ||||||
|  |  * SPDX-License-Identifier: BSD-2-Clause | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <LibGfx/PaintStyle.h> | ||||||
|  | #include <LibWeb/Bindings/PlatformObject.h> | ||||||
|  | #include <LibWeb/HTML/Canvas/CanvasDrawImage.h> | ||||||
|  | 
 | ||||||
|  | namespace Web::HTML { | ||||||
|  | 
 | ||||||
|  | class CanvasPatternPaintStyle final : public Gfx::PaintStyle { | ||||||
|  | public: | ||||||
|  |     enum class Repetition { | ||||||
|  |         Repeat, | ||||||
|  |         RepeatX, | ||||||
|  |         RepeatY, | ||||||
|  |         NoRepeat | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     static NonnullRefPtr<CanvasPatternPaintStyle> create(Gfx::Bitmap const& bitmap, Repetition repetition) | ||||||
|  |     { | ||||||
|  |         return adopt_ref(*new CanvasPatternPaintStyle(bitmap, repetition)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     virtual void paint(Gfx::IntRect physical_bounding_box, PaintFunction paint) const override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     CanvasPatternPaintStyle(Gfx::Bitmap const& bitmap, Repetition repetition) | ||||||
|  |         : m_bitmap(bitmap) | ||||||
|  |         , m_repetition(repetition) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     NonnullRefPtr<Gfx::Bitmap const> m_bitmap; | ||||||
|  |     Repetition m_repetition { Repetition::Repeat }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class CanvasPattern final : public Bindings::PlatformObject { | ||||||
|  |     WEB_PLATFORM_OBJECT(CanvasPattern, Bindings::PlatformObject); | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |     static WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> create(JS::Realm&, CanvasImageSource const& image, StringView repetition); | ||||||
|  | 
 | ||||||
|  |     ~CanvasPattern(); | ||||||
|  | 
 | ||||||
|  |     NonnullRefPtr<Gfx::PaintStyle> to_gfx_paint_style() { return m_pattern; } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     CanvasPattern(JS::Realm&, CanvasPatternPaintStyle&); | ||||||
|  | 
 | ||||||
|  |     virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override; | ||||||
|  | 
 | ||||||
|  |     NonnullRefPtr<CanvasPatternPaintStyle> m_pattern; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								Userland/Libraries/LibWeb/HTML/CanvasPattern.idl
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								Userland/Libraries/LibWeb/HTML/CanvasPattern.idl
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | [Exposed=(Window,Worker)] | ||||||
|  | interface CanvasPattern { | ||||||
|  |   // opaque object | ||||||
|  |   // FIXME: undefined setTransform(optional DOMMatrix2DInit transform = {}); | ||||||
|  | }; | ||||||
|  | @ -65,6 +65,7 @@ libweb_js_bindings(Geometry/DOMRect) | ||||||
| libweb_js_bindings(Geometry/DOMRectList) | libweb_js_bindings(Geometry/DOMRectList) | ||||||
| libweb_js_bindings(Geometry/DOMRectReadOnly) | libweb_js_bindings(Geometry/DOMRectReadOnly) | ||||||
| libweb_js_bindings(HTML/CanvasGradient) | libweb_js_bindings(HTML/CanvasGradient) | ||||||
|  | libweb_js_bindings(HTML/CanvasPattern) | ||||||
| libweb_js_bindings(HTML/CanvasRenderingContext2D) | libweb_js_bindings(HTML/CanvasRenderingContext2D) | ||||||
| libweb_js_bindings(HTML/CloseEvent) | libweb_js_bindings(HTML/CloseEvent) | ||||||
| libweb_js_bindings(HTML/DOMParser) | libweb_js_bindings(HTML/DOMParser) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 MacDue
						MacDue