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:
parent
fac0f82031
commit
35efd4d14b
Notes:
github-actions[bot]
2025-04-29 11:52:28 +00:00
Author: https://github.com/gmta
Commit: 35efd4d14b
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4506
Reviewed-by: https://github.com/shannonbooth
7 changed files with 68 additions and 12 deletions
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue