mirror of
https://github.com/SerenityOS/serenity
synced 2024-07-21 18:15:58 +00:00
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:
parent
ff5d530aa3
commit
45f86466bb
57
Base/res/html/misc/canvas-global-alpha.html
Normal file
57
Base/res/html/misc/canvas-global-alpha.html
Normal 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>
|
|
@ -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>
|
||||
|
|
25
Userland/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.h
Normal file
25
Userland/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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")
|
||||
};
|
|
@ -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; }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue