1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-08 05:27:14 +09:00

LibWeb+LibGfx: Support alpha in CanvasRenderingContext2D

This is implemented by these related changes:

  * The Skia alpha type 'Opaque' is selected for surfaces that were
    created with the intention of not having an alpha channel.
    Previously we were simply creating one with alpha.

  * Clearing now happens through Skia's `clear()` which always uses the
    source color's value for the result, instead of setting all values
    to 0.

  * CanvasRenderingContext2D selects a different clearing color based on
    the `alpha` context attribute's value.
This commit is contained in:
Jelle Raaijmakers 2025-04-28 16:12:30 +02:00
parent fac0f82031
commit 35efd4d14b
Notes: github-actions[bot] 2025-04-29 11:52:28 +00:00
7 changed files with 68 additions and 12 deletions

View file

@ -125,11 +125,11 @@ PainterSkia::~PainterSkia() = default;
void PainterSkia::clear_rect(Gfx::FloatRect const& rect, Gfx::Color color)
{
SkPaint paint;
paint.setColor(to_skia_color(color));
paint.setBlendMode(SkBlendMode::kClear);
impl().with_canvas([&](auto& canvas) {
canvas.drawRect(to_skia_rect(rect), paint);
canvas.save();
canvas.clipRect(to_skia_rect(rect));
canvas.clear(to_skia_color(color));
canvas.restore();
});
}

View file

@ -30,7 +30,7 @@ struct PaintingSurface::Impl {
NonnullRefPtr<PaintingSurface> PaintingSurface::create_with_size(RefPtr<SkiaBackendContext> context, IntSize size, BitmapFormat color_type, AlphaType alpha_type)
{
auto sk_color_type = to_skia_color_type(color_type);
auto sk_alpha_type = alpha_type == AlphaType::Premultiplied ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
auto sk_alpha_type = to_skia_alpha_type(color_type, alpha_type);
auto image_info = SkImageInfo::Make(size.width(), size.height(), sk_color_type, sk_alpha_type, SkColorSpace::MakeSRGB());
if (!context) {
@ -50,7 +50,7 @@ NonnullRefPtr<PaintingSurface> PaintingSurface::create_with_size(RefPtr<SkiaBack
NonnullRefPtr<PaintingSurface> PaintingSurface::wrap_bitmap(Bitmap& bitmap)
{
auto color_type = to_skia_color_type(bitmap.format());
auto alpha_type = bitmap.alpha_type() == AlphaType::Premultiplied ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
auto alpha_type = to_skia_alpha_type(bitmap.format(), bitmap.alpha_type());
auto size = bitmap.size();
auto image_info = SkImageInfo::Make(bitmap.width(), bitmap.height(), color_type, alpha_type, SkColorSpace::MakeSRGB());
auto surface = SkSurfaces::WrapPixels(image_info, bitmap.begin(), bitmap.pitch());
@ -102,7 +102,7 @@ PaintingSurface::~PaintingSurface()
void PaintingSurface::read_into_bitmap(Bitmap& bitmap)
{
auto color_type = to_skia_color_type(bitmap.format());
auto alpha_type = bitmap.alpha_type() == AlphaType::Premultiplied ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
auto alpha_type = to_skia_alpha_type(bitmap.format(), bitmap.alpha_type());
auto image_info = SkImageInfo::Make(bitmap.width(), bitmap.height(), color_type, alpha_type, SkColorSpace::MakeSRGB());
SkPixmap const pixmap(image_info, bitmap.begin(), bitmap.pitch());
m_impl->surface->readPixels(pixmap, 0, 0);
@ -111,7 +111,7 @@ void PaintingSurface::read_into_bitmap(Bitmap& bitmap)
void PaintingSurface::write_from_bitmap(Bitmap const& bitmap)
{
auto color_type = to_skia_color_type(bitmap.format());
auto alpha_type = bitmap.alpha_type() == AlphaType::Premultiplied ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
auto alpha_type = to_skia_alpha_type(bitmap.format(), bitmap.alpha_type());
auto image_info = SkImageInfo::Make(bitmap.width(), bitmap.height(), color_type, alpha_type, SkColorSpace::MakeSRGB());
SkPixmap const pixmap(image_info, bitmap.begin(), bitmap.pitch());
m_impl->surface->writePixels(pixmap, 0, 0);

View file

@ -40,6 +40,20 @@ constexpr SkColorType to_skia_color_type(Gfx::BitmapFormat format)
VERIFY_NOT_REACHED();
}
constexpr SkAlphaType to_skia_alpha_type(Gfx::BitmapFormat format, Gfx::AlphaType alpha_type)
{
if (format == BitmapFormat::BGRx8888 || format == BitmapFormat::RGBx8888)
return kOpaque_SkAlphaType;
switch (alpha_type) {
case AlphaType::Premultiplied:
return kPremul_SkAlphaType;
case AlphaType::Unpremultiplied:
return kUnpremul_SkAlphaType;
}
VERIFY_NOT_REACHED();
}
constexpr SkRect to_skia_rect(auto const& rect)
{
return SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());

View file

@ -149,7 +149,7 @@ void CanvasRenderingContext2D::clear_rect(float x, float y, float width, float h
{
if (auto* painter = this->painter()) {
auto rect = Gfx::FloatRect(x, y, width, height);
painter->clear_rect(rect, Color::Transparent);
painter->clear_rect(rect, clear_color());
did_draw(rect);
}
}
@ -270,14 +270,23 @@ void CanvasRenderingContext2D::allocate_painting_surface_if_needed()
if (m_surface || m_size.is_empty())
return;
// FIXME: implement context attribute .alpha
// FIXME: implement context attribute .color_space
// FIXME: implement context attribute .color_type
// FIXME: implement context attribute .desynchronized
// FIXME: implement context attribute .will_read_frequently
auto color_type = m_context_attributes.alpha ? Gfx::BitmapFormat::BGRA8888 : Gfx::BitmapFormat::BGRx8888;
auto skia_backend_context = canvas_element().navigable()->traversable_navigable()->skia_backend_context();
m_surface = Gfx::PaintingSurface::create_with_size(skia_backend_context, canvas_element().bitmap_size_for_canvas(), Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
m_surface = Gfx::PaintingSurface::create_with_size(skia_backend_context, canvas_element().bitmap_size_for_canvas(), color_type, Gfx::AlphaType::Premultiplied);
// https://html.spec.whatwg.org/multipage/canvas.html#the-canvas-settings:concept-canvas-alpha
// Thus, the bitmap of such a context starts off as opaque black instead of transparent black;
// AD-HOC: Skia provides us with a full transparent surface by default; only clear the surface if alpha is disabled.
if (!m_context_attributes.alpha) {
auto* painter = this->painter();
painter->clear_rect(m_surface->rect().to_type<float>(), clear_color());
}
}
Gfx::Path CanvasRenderingContext2D::text_path(StringView text, float x, float y, Optional<double> max_width)
@ -377,6 +386,12 @@ static Gfx::Path::JoinStyle to_gfx_join(Bindings::CanvasLineJoin const& join_sty
VERIFY_NOT_REACHED();
}
// https://html.spec.whatwg.org/multipage/canvas.html#the-canvas-settings:concept-canvas-alpha
Gfx::Color CanvasRenderingContext2D::clear_color() const
{
return m_context_attributes.alpha ? Gfx::Color::Transparent : Gfx::Color::Black;
}
void CanvasRenderingContext2D::stroke_internal(Gfx::Path const& path)
{
auto* painter = this->painter();
@ -561,7 +576,7 @@ void CanvasRenderingContext2D::reset_to_default_state()
// 1. Clear canvas's bitmap to transparent black.
if (surface) {
painter()->clear_rect(surface->rect().to_type<float>(), Color::Transparent);
painter()->clear_rect(surface->rect().to_type<float>(), clear_color());
}
// 2. Empty the list of subpaths in context's current default path.

View file

@ -160,6 +160,8 @@ private:
[[nodiscard]] Gfx::Path rect_path(float x, float y, float width, float height);
[[nodiscard]] Gfx::Path text_path(StringView text, float x, float y, Optional<double> max_width);
Gfx::Color clear_color() const;
void stroke_internal(Gfx::Path const&);
void fill_internal(Gfx::Path const&, Gfx::WindingRule);
void clip_internal(Gfx::Path&, Gfx::WindingRule);

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CanvasRenderingContext 2D with alpha=flase, fillRect with semi-transparent color.</title>
<p>Test passes if a 100x100 black square is displayed below.</p>
<canvas id="c" width=100 height=100></canvas>
<script>
const ctx = document.getElementById("c").getContext("2d");
ctx.fillStyle = 'black';
ctx.fillRect(-1, -1, 102, 102);
</script>

View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CanvasRenderingContext 2D with alpha=flase, clearRect</title>
<link rel="author" title="Justin Novosad" href="mailto:junov@chromium.org">
<link rel="help" href="https://html.spec.whatwg.org/#concept-canvas-alpha">
<link rel="match" href="../../../../../../../expected/wpt-import/html/canvas/element/manual/context-attributes/clearRect_alpha_false-ref.html">
<meta name="assert" content="Canvas pixels remain opaque black when drawing semi-transparent rectangle.">
<p>Test passes if a 100x100 black square is displayed below.</p>
<canvas id="c" width=100 height=100></canvas>
<script>
const ctx = document.getElementById("c").getContext("2d", {alpha: false});
ctx.fillColor = 'red';
ctx.fillRect(25, 25, 50, 25);
ctx.clearRect(24, 24, 52, 52);
</script>