diff --git a/Base/res/html/misc/canvas-global-alpha.html b/Base/res/html/misc/canvas-global-alpha.html new file mode 100644 index 0000000000..bd25fd9fc7 --- /dev/null +++ b/Base/res/html/misc/canvas-global-alpha.html @@ -0,0 +1,57 @@ + + + +Canvas 2D global alpha test + + + + + + + diff --git a/Base/res/html/misc/welcome.html b/Base/res/html/misc/welcome.html index 20adf8486f..38382d98f3 100644 --- a/Base/res/html/misc/welcome.html +++ b/Base/res/html/misc/welcome.html @@ -201,6 +201,7 @@
  • canvas path house!
  • canvas clip paths
  • canvas + trigonometry functions
  • +
  • canvas globalAlpha
  • Path2D
  • WebGL Demo - Multiple Contexts and glClear(Color)
  • Wasm

  • diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.h b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.h new file mode 100644 index 0000000000..1861e6e4c4 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::HTML { + +// https://html.spec.whatwg.org/multipage/canvas.html#canvascompositing +class CanvasCompositing { +public: + virtual ~CanvasCompositing() = default; + + virtual float global_alpha() const = 0; + virtual void set_global_alpha(float) = 0; + +protected: + CanvasCompositing() = default; +}; + +} diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.idl b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.idl new file mode 100644 index 0000000000..db080c6309 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.idl @@ -0,0 +1,6 @@ +// https://html.spec.whatwg.org/multipage/canvas.html#canvascompositing +interface mixin CanvasCompositing { + // compositing + attribute unrestricted double globalAlpha; // (default 1.0) +// FIXME: attribute DOMString globalCompositeOperation; // (default "source-over") +}; diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.h b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.h index 6727c29f19..34a70f7608 100644 --- a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.h +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.h @@ -78,6 +78,7 @@ public: float line_width { 1 }; bool image_smoothing_enabled { true }; Bindings::ImageSmoothingQuality image_smoothing_quality { Bindings::ImageSmoothingQuality::Low }; + float global_alpha = { 1 }; Optional clip; }; DrawingState& drawing_state() { return m_drawing_state; } diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index 812894816d..ad89b90e02 100644 --- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -69,19 +69,28 @@ JS::NonnullGCPtr CanvasRenderingContext2D::canvas_for_binding return *m_element; } +Gfx::Path CanvasRenderingContext2D::rect_path(float x, float y, float width, float height) +{ + auto& drawing_state = this->drawing_state(); + + auto top_left = drawing_state.transform.map(Gfx::FloatPoint(x, y)); + auto top_right = drawing_state.transform.map(Gfx::FloatPoint(x + width, y)); + auto bottom_left = drawing_state.transform.map(Gfx::FloatPoint(x, y + height)); + auto bottom_right = drawing_state.transform.map(Gfx::FloatPoint(x + width, y + height)); + + Gfx::Path path; + path.move_to(top_left); + path.line_to(top_right); + path.line_to(bottom_right); + path.line_to(bottom_left); + path.line_to(top_left); + + return path; +} + void CanvasRenderingContext2D::fill_rect(float x, float y, float width, float height) { - draw_clipped([&](auto& painter) { - auto& drawing_state = this->drawing_state(); - auto rect = drawing_state.transform.map(Gfx::FloatRect(x, y, width, height)); - if (auto color = drawing_state.fill_style.as_color(); color.has_value()) { - painter.fill_rect(rect, *color); - } else { - // FIXME: This should use AntiAliasingPainter::fill_rect() too but that does not support PaintStyle yet. - painter.underlying_painter().fill_rect(rect.to_rounded(), *drawing_state.fill_style.to_gfx_paint_style()); - } - return rect; - }); + return fill_internal(rect_path(x, y, width, height), Gfx::Painter::WindingRule::EvenOdd); } void CanvasRenderingContext2D::clear_rect(float x, float y, float width, float height) @@ -95,21 +104,7 @@ void CanvasRenderingContext2D::clear_rect(float x, float y, float width, float h void CanvasRenderingContext2D::stroke_rect(float x, float y, float width, float height) { - auto& drawing_state = this->drawing_state(); - - auto top_left = drawing_state.transform.map(Gfx::FloatPoint(x, y)); - auto top_right = drawing_state.transform.map(Gfx::FloatPoint(x + width - 1, y)); - auto bottom_left = drawing_state.transform.map(Gfx::FloatPoint(x, y + height - 1)); - auto bottom_right = drawing_state.transform.map(Gfx::FloatPoint(x + width - 1, y + height - 1)); - - Gfx::Path path; - path.move_to(top_left); - path.line_to(top_right); - path.line_to(bottom_right); - path.line_to(bottom_left); - path.line_to(top_left); - - stroke_internal(path); + stroke_internal(rect_path(x, y, width, height)); } // 4.12.5.1.14 Drawing images, https://html.spec.whatwg.org/multipage/canvas.html#drawing-images @@ -164,7 +159,7 @@ WebIDL::ExceptionOr CanvasRenderingContext2D::draw_image_internal(CanvasIm scaling_mode = Gfx::Painter::ScalingMode::BilinearBlend; } - painter.underlying_painter().draw_scaled_bitmap_with_transform(destination_rect.to_rounded(), *bitmap, source_rect, drawing_state().transform, 1.0f, scaling_mode); + painter.underlying_painter().draw_scaled_bitmap_with_transform(destination_rect.to_rounded(), *bitmap, source_rect, drawing_state().transform, drawing_state().global_alpha, scaling_mode); // 7. If image is not origin-clean, then set the CanvasRenderingContext2D's origin-clean flag to false. if (image_is_not_origin_clean(image)) @@ -212,7 +207,8 @@ void CanvasRenderingContext2D::fill_text(DeprecatedString const& text, float x, auto& base_painter = painter.underlying_painter(); auto text_rect = Gfx::FloatRect(x, y, max_width.has_value() ? static_cast(max_width.value()) : base_painter.font().width(text), base_painter.font().pixel_size()); auto transformed_rect = drawing_state.transform.map(text_rect); - base_painter.draw_text(transformed_rect, text, Gfx::TextAlignment::TopLeft, drawing_state.fill_style.to_color_but_fixme_should_accept_any_paint_style()); + auto color = drawing_state.fill_style.to_color_but_fixme_should_accept_any_paint_style(); + base_painter.draw_text(transformed_rect, text, Gfx::TextAlignment::TopLeft, color.with_opacity(drawing_state.global_alpha)); return transformed_rect; }); } @@ -233,9 +229,9 @@ void CanvasRenderingContext2D::stroke_internal(Gfx::Path const& path) draw_clipped([&](auto& painter) { auto& drawing_state = this->drawing_state(); if (auto color = drawing_state.stroke_style.as_color(); color.has_value()) { - painter.stroke_path(path, *color, drawing_state.line_width); + painter.stroke_path(path, color->with_opacity(drawing_state.global_alpha), drawing_state.line_width); } else { - painter.stroke_path(path, drawing_state.stroke_style.to_gfx_paint_style(), drawing_state.line_width); + painter.stroke_path(path, drawing_state.stroke_style.to_gfx_paint_style(), drawing_state.line_width, drawing_state.global_alpha); } return path.bounding_box(); }); @@ -262,17 +258,18 @@ static Gfx::Painter::WindingRule parse_fill_rule(StringView fill_rule) return Gfx::Painter::WindingRule::Nonzero; } -void CanvasRenderingContext2D::fill_internal(Gfx::Path& path, Gfx::Painter::WindingRule winding_rule) +void CanvasRenderingContext2D::fill_internal(Gfx::Path const& path, Gfx::Painter::WindingRule winding_rule) { - draw_clipped([=, this](auto& painter) mutable { - path.close_all_subpaths(); + draw_clipped([&, this](auto& painter) mutable { + auto path_to_fill = path; + path_to_fill.close_all_subpaths(); auto& drawing_state = this->drawing_state(); if (auto color = drawing_state.fill_style.as_color(); color.has_value()) { - painter.fill_path(path, *color, winding_rule); + painter.fill_path(path_to_fill, color->with_opacity(drawing_state.global_alpha), winding_rule); } else { - painter.fill_path(path, drawing_state.fill_style.to_gfx_paint_style(), 1.0f, winding_rule); + painter.fill_path(path_to_fill, drawing_state.fill_style.to_gfx_paint_style(), drawing_state.global_alpha, winding_rule); } - return path.bounding_box(); + return path_to_fill.bounding_box(); }); } @@ -591,4 +588,20 @@ void CanvasRenderingContext2D::set_image_smoothing_quality(Bindings::ImageSmooth drawing_state().image_smoothing_quality = quality; } +float CanvasRenderingContext2D::global_alpha() const +{ + return drawing_state().global_alpha; +} + +// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-globalalpha +void CanvasRenderingContext2D::set_global_alpha(float alpha) +{ + // 1. If the given value is either infinite, NaN, or not in the range 0.0 to 1.0, then return. + if (!isfinite(alpha) || alpha < 0.0f || alpha > 1.0f) { + return; + } + // 2. Otherwise, set this's global alpha to the given value. + drawing_state().global_alpha = alpha; +} + } diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h index 0f646f9725..ea7f1e7a6d 100644 --- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h +++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,7 @@ class CanvasRenderingContext2D , public CanvasDrawImage , public CanvasImageData , public CanvasImageSmoothing + , public CanvasCompositing , public CanvasPathDrawingStyles { WEB_PLATFORM_OBJECT(CanvasRenderingContext2D, Bindings::PlatformObject); @@ -93,6 +95,9 @@ public: virtual Bindings::ImageSmoothingQuality image_smoothing_quality() const override; virtual void set_image_smoothing_quality(Bindings::ImageSmoothingQuality) override; + virtual float global_alpha() const override; + virtual void set_global_alpha(float) override; + private: explicit CanvasRenderingContext2D(JS::Realm&, HTMLCanvasElement&); @@ -133,9 +138,11 @@ private: HTMLCanvasElement& canvas_element(); HTMLCanvasElement const& canvas_element() const; + Gfx::Path rect_path(float x, float y, float width, float height); + void stroke_internal(Gfx::Path const&); - void fill_internal(Gfx::Path&, Gfx::Painter::WindingRule winding_rule); - void clip_internal(Gfx::Path&, Gfx::Painter::WindingRule winding_rule); + void fill_internal(Gfx::Path const&, Gfx::Painter::WindingRule); + void clip_internal(Gfx::Path&, Gfx::Painter::WindingRule); JS::NonnullGCPtr m_element; OwnPtr m_painter; diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl index 2bcf9e06bc..1f2aa5baf1 100644 --- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl +++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl @@ -1,4 +1,5 @@ #import +#import #import #import #import @@ -21,7 +22,7 @@ interface CanvasRenderingContext2D { CanvasRenderingContext2D includes CanvasState; CanvasRenderingContext2D includes CanvasTransform; -// FIXME: CanvasRenderingContext2D includes CanvasCompositing; +CanvasRenderingContext2D includes CanvasCompositing; CanvasRenderingContext2D includes CanvasImageSmoothing; CanvasRenderingContext2D includes CanvasFillStrokeStyles; // FIXME: CanvasRenderingContext2D includes CanvasShadowStyles;