LibWeb: Add initial implementation of CRC2D.globalAlpha

Works for fills and strokes (using colors, gradients, or patterns),
along with images.

fill_rect() has been updated to use fill_path(), which allows it to
easily transform the rect, and already supports opacity.

Co-authored-by: MacDue <macdue@dueutil.tech>
This commit is contained in:
Simon Danner 2023-05-19 22:42:47 +02:00 committed by Andreas Kling
parent ff5d530aa3
commit 45f86466bb
8 changed files with 150 additions and 39 deletions

View file

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>Canvas 2D global alpha test</title>
<style type="text/css">
body {
color: #fff;
}
canvas {
border-width: 1px;
border-style: solid;
border-color: #fff;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function() {
ctx = document.getElementById("foo").getContext("2d");
image = new Image(100, 100);
image.onload = drawImage;
image.src = "car.png";
function drawImage() {
ctx.drawImage(image, 0, 0, 400, 400);
}
var width = 200;
var height = 100;
ctx.globalAlpha = 0.4;
ctx.font = "100px serif";
ctx.fillText("hello friends", 50, 90);
for (var i = 0; i < 2; i++)
{
ctx.globalAlpha = 0.5;
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, width, height);
ctx.globalAlpha = 0.6;
ctx.strokeStyle = 'blue';
ctx.strokeRect(10, 10, width, height);
ctx.scale(0.5, 0.5);
ctx.translate(10 + width * 2, 10 + height * 2);
}
});
</script>
</head>
<body>
<canvas id="foo" width="1000" height="1000"></canvas>
</body>
</html>

View file

@ -201,6 +201,7 @@
<li><a href="canvas-path.html">canvas path house!</a></li>
<li><a href="canvas-clip-path.html">canvas clip paths</a></li>
<li><a href="trigonometry.html">canvas + trigonometry functions</a></li>
<li><a href="canvas-global-alpha.html">canvas globalAlpha</a></li>
<li><a href="canvas-path2d.html">Path2D</a></li>
<li><a href="webgl-clear-color-and-multiple-contexts.html">WebGL Demo - Multiple Contexts and glClear(Color)</a></li>
<li><h3>Wasm</h3></li>

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/HTML/ImageData.h>
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;
};
}

View file

@ -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")
};

View file

@ -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<CanvasClip> clip;
};
DrawingState& drawing_state() { return m_drawing_state; }

View file

@ -69,19 +69,28 @@ JS::NonnullGCPtr<HTMLCanvasElement> 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<int>(), *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<void> CanvasRenderingContext2D::draw_image_internal(CanvasIm
scaling_mode = Gfx::Painter::ScalingMode::BilinearBlend;
}
painter.underlying_painter().draw_scaled_bitmap_with_transform(destination_rect.to_rounded<int>(), *bitmap, source_rect, drawing_state().transform, 1.0f, scaling_mode);
painter.underlying_painter().draw_scaled_bitmap_with_transform(destination_rect.to_rounded<int>(), *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<float>(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;
}
}

View file

@ -16,6 +16,7 @@
#include <LibGfx/Painter.h>
#include <LibGfx/Path.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/HTML/Canvas/CanvasCompositing.h>
#include <LibWeb/HTML/Canvas/CanvasDrawImage.h>
#include <LibWeb/HTML/Canvas/CanvasDrawPath.h>
#include <LibWeb/HTML/Canvas/CanvasFillStrokeStyles.h>
@ -51,6 +52,7 @@ class CanvasRenderingContext2D
, public CanvasDrawImage
, public CanvasImageData
, public CanvasImageSmoothing
, public CanvasCompositing
, public CanvasPathDrawingStyles<CanvasRenderingContext2D> {
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<HTMLCanvasElement> m_element;
OwnPtr<Gfx::Painter> m_painter;

View file

@ -1,4 +1,5 @@
#import <HTML/HTMLCanvasElement.idl>
#import <HTML/Canvas/CanvasCompositing.idl>
#import <HTML/Canvas/CanvasDrawImage.idl>
#import <HTML/Canvas/CanvasDrawPath.idl>
#import <HTML/Canvas/CanvasFillStrokeStyles.idl>
@ -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;