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;