LibGfx+LibWeb: Cache SkImage in ImmutableBitmap

By caching the SkImage that is reused across repaints, we allow Skia t
optimize GPU texture caching.

ImmutableBitmap is chosen to own the SkImage because it guarantees that
the underlying pixels cannot be modified. This is not the case for
Gfx::Bitmap, where invalidating the SkImage would be challenging since
it exposes pointers to underlying data through methods like scanline().
This commit is contained in:
Aliaksandr Kalenik 2024-11-09 05:48:17 +01:00 committed by Alexander Kalenik
commit 698bca686e
Notes: github-actions[bot] 2024-11-09 20:21:03 +00:00
20 changed files with 186 additions and 118 deletions

View file

@ -10,6 +10,7 @@
#include <LibGfx/ImageFormats/PNGLoader.h> #include <LibGfx/ImageFormats/PNGLoader.h>
#include <LibGfx/ImageFormats/TIFFLoader.h> #include <LibGfx/ImageFormats/TIFFLoader.h>
#include <LibGfx/ImageFormats/TIFFMetadata.h> #include <LibGfx/ImageFormats/TIFFMetadata.h>
#include <LibGfx/ImmutableBitmap.h>
#include <LibGfx/Painter.h> #include <LibGfx/Painter.h>
#include <png.h> #include <png.h>
@ -239,11 +240,11 @@ ErrorOr<size_t> PNGLoadingContext::read_frames(png_structp png_ptr, png_infop in
case PNG_BLEND_OP_SOURCE: case PNG_BLEND_OP_SOURCE:
// All color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region. // All color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region.
painter->clear_rect(frame_rect, Gfx::Color::Transparent); painter->clear_rect(frame_rect, Gfx::Color::Transparent);
painter->draw_bitmap(frame_rect, *decoded_frame_bitmap, decoded_frame_bitmap->rect(), Gfx::ScalingMode::NearestNeighbor, 1.0f); painter->draw_bitmap(frame_rect, Gfx::ImmutableBitmap::create(*decoded_frame_bitmap), decoded_frame_bitmap->rect(), Gfx::ScalingMode::NearestNeighbor, 1.0f);
break; break;
case PNG_BLEND_OP_OVER: case PNG_BLEND_OP_OVER:
// The frame should be composited onto the output buffer based on its alpha, using a simple OVER operation as described in the "Alpha Channel Processing" section of the PNG specification. // The frame should be composited onto the output buffer based on its alpha, using a simple OVER operation as described in the "Alpha Channel Processing" section of the PNG specification.
painter->draw_bitmap(frame_rect, *decoded_frame_bitmap, decoded_frame_bitmap->rect(), ScalingMode::NearestNeighbor, 1.0f); painter->draw_bitmap(frame_rect, Gfx::ImmutableBitmap::create(*decoded_frame_bitmap), decoded_frame_bitmap->rect(), ScalingMode::NearestNeighbor, 1.0f);
break; break;
default: default:
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
@ -262,7 +263,7 @@ ErrorOr<size_t> PNGLoadingContext::read_frames(png_structp png_ptr, png_infop in
case PNG_DISPOSE_OP_PREVIOUS: case PNG_DISPOSE_OP_PREVIOUS:
// The frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame. // The frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame.
painter->clear_rect(frame_rect, Gfx::Color::Transparent); painter->clear_rect(frame_rect, Gfx::Color::Transparent);
painter->draw_bitmap(frame_rect, *prev_output_buffer, IntRect { x, y, width, height }, Gfx::ScalingMode::NearestNeighbor, 1.0f); painter->draw_bitmap(frame_rect, Gfx::ImmutableBitmap::create(*prev_output_buffer), IntRect { x, y, width, height }, Gfx::ScalingMode::NearestNeighbor, 1.0f);
break; break;
default: default:
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();

View file

@ -6,16 +6,105 @@
#include <LibGfx/ImmutableBitmap.h> #include <LibGfx/ImmutableBitmap.h>
#include <core/SkBitmap.h>
#include <core/SkImage.h>
namespace Gfx { namespace Gfx {
struct ImmutableBitmapImpl {
sk_sp<SkImage> sk_image;
SkBitmap sk_bitmap;
RefPtr<Gfx::Bitmap> gfx_bitmap;
};
int ImmutableBitmap::width() const
{
return m_impl->sk_image->width();
}
int ImmutableBitmap::height() const
{
return m_impl->sk_image->height();
}
IntRect ImmutableBitmap::rect() const
{
return { {}, size() };
}
IntSize ImmutableBitmap::size() const
{
return { width(), height() };
}
Gfx::AlphaType ImmutableBitmap::alpha_type() const
{
return m_impl->sk_image->alphaType() == kPremul_SkAlphaType ? Gfx::AlphaType::Premultiplied : Gfx::AlphaType::Unpremultiplied;
}
SkImage const* ImmutableBitmap::sk_image() const
{
return m_impl->sk_image.get();
}
RefPtr<Gfx::Bitmap const> ImmutableBitmap::bitmap() const
{
return m_impl->gfx_bitmap;
}
Color ImmutableBitmap::get_pixel(int x, int y) const
{
if (m_impl->gfx_bitmap) {
return m_impl->gfx_bitmap->get_pixel(x, y);
}
VERIFY_NOT_REACHED();
}
static SkColorType to_skia_color_type(Gfx::BitmapFormat format)
{
switch (format) {
case Gfx::BitmapFormat::Invalid:
return kUnknown_SkColorType;
case Gfx::BitmapFormat::BGRA8888:
case Gfx::BitmapFormat::BGRx8888:
return kBGRA_8888_SkColorType;
case Gfx::BitmapFormat::RGBA8888:
return kRGBA_8888_SkColorType;
case Gfx::BitmapFormat::RGBx8888:
return kRGB_888x_SkColorType;
default:
return kUnknown_SkColorType;
}
}
static SkAlphaType to_skia_alpha_type(Gfx::AlphaType alpha_type)
{
switch (alpha_type) {
case AlphaType::Premultiplied:
return kPremul_SkAlphaType;
case AlphaType::Unpremultiplied:
return kUnpremul_SkAlphaType;
default:
VERIFY_NOT_REACHED();
}
}
NonnullRefPtr<ImmutableBitmap> ImmutableBitmap::create(NonnullRefPtr<Bitmap> bitmap) NonnullRefPtr<ImmutableBitmap> ImmutableBitmap::create(NonnullRefPtr<Bitmap> bitmap)
{ {
return adopt_ref(*new ImmutableBitmap(move(bitmap))); ImmutableBitmapImpl impl;
auto info = SkImageInfo::Make(bitmap->width(), bitmap->height(), to_skia_color_type(bitmap->format()), to_skia_alpha_type(bitmap->alpha_type()));
impl.sk_bitmap.installPixels(info, const_cast<void*>(static_cast<void const*>(bitmap->scanline(0))), bitmap->pitch());
impl.sk_bitmap.setImmutable();
impl.sk_image = impl.sk_bitmap.asImage();
impl.gfx_bitmap = bitmap;
return adopt_ref(*new ImmutableBitmap(make<ImmutableBitmapImpl>(impl)));
} }
ImmutableBitmap::ImmutableBitmap(NonnullRefPtr<Bitmap> bitmap) ImmutableBitmap::ImmutableBitmap(NonnullOwnPtr<ImmutableBitmapImpl> impl)
: m_bitmap(move(bitmap)) : m_impl(move(impl))
{ {
} }
ImmutableBitmap::~ImmutableBitmap() = default;
} }

View file

@ -7,31 +7,41 @@
#pragma once #pragma once
#include <AK/Forward.h> #include <AK/Forward.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/RefCounted.h> #include <AK/RefCounted.h>
#include <LibGfx/Bitmap.h> #include <LibGfx/Bitmap.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>
#include <LibGfx/Rect.h> #include <LibGfx/Rect.h>
class SkImage;
namespace Gfx { namespace Gfx {
struct ImmutableBitmapImpl;
class ImmutableBitmap final : public RefCounted<ImmutableBitmap> { class ImmutableBitmap final : public RefCounted<ImmutableBitmap> {
public: public:
static NonnullRefPtr<ImmutableBitmap> create(NonnullRefPtr<Bitmap> bitmap); static NonnullRefPtr<ImmutableBitmap> create(NonnullRefPtr<Bitmap> bitmap);
~ImmutableBitmap() = default; ~ImmutableBitmap();
Bitmap const& bitmap() const { return *m_bitmap; } int width() const;
int height() const;
IntRect rect() const;
IntSize size() const;
size_t width() const { return m_bitmap->width(); } Gfx::AlphaType alpha_type() const;
size_t height() const { return m_bitmap->height(); }
IntRect rect() const { return m_bitmap->rect(); } SkImage const* sk_image() const;
IntSize size() const { return m_bitmap->size(); }
Color get_pixel(int x, int y) const;
RefPtr<Bitmap const> bitmap() const;
private: private:
NonnullRefPtr<Bitmap> m_bitmap; NonnullOwnPtr<ImmutableBitmapImpl> m_impl;
explicit ImmutableBitmap(NonnullRefPtr<Bitmap> bitmap); explicit ImmutableBitmap(NonnullOwnPtr<ImmutableBitmapImpl> bitmap);
}; };
} }

View file

@ -23,7 +23,7 @@ public:
virtual void clear_rect(Gfx::FloatRect const&, Gfx::Color) = 0; virtual void clear_rect(Gfx::FloatRect const&, Gfx::Color) = 0;
virtual void fill_rect(Gfx::FloatRect const&, Gfx::Color) = 0; virtual void fill_rect(Gfx::FloatRect const&, Gfx::Color) = 0;
virtual void draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode, float global_alpha) = 0; virtual void draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode, float global_alpha) = 0;
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness) = 0; virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness) = 0;
virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, float thickness, float global_alpha) = 0; virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, float thickness, float global_alpha) = 0;

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -7,6 +8,7 @@
#define AK_DONT_REPLACE_STD #define AK_DONT_REPLACE_STD
#include <AK/OwnPtr.h> #include <AK/OwnPtr.h>
#include <LibGfx/ImmutableBitmap.h>
#include <LibGfx/PainterSkia.h> #include <LibGfx/PainterSkia.h>
#include <LibGfx/PathSkia.h> #include <LibGfx/PathSkia.h>
@ -28,35 +30,6 @@
namespace Gfx { namespace Gfx {
static SkColorType to_skia_color_type(Gfx::BitmapFormat format)
{
switch (format) {
case Gfx::BitmapFormat::Invalid:
return kUnknown_SkColorType;
case Gfx::BitmapFormat::BGRA8888:
case Gfx::BitmapFormat::BGRx8888:
return kBGRA_8888_SkColorType;
case Gfx::BitmapFormat::RGBA8888:
return kRGBA_8888_SkColorType;
case Gfx::BitmapFormat::RGBx8888:
return kRGB_888x_SkColorType;
default:
return kUnknown_SkColorType;
}
}
static SkAlphaType to_skia_alpha_type(Gfx::AlphaType alpha_type)
{
switch (alpha_type) {
case AlphaType::Premultiplied:
return kPremul_SkAlphaType;
case AlphaType::Unpremultiplied:
return kUnpremul_SkAlphaType;
default:
VERIFY_NOT_REACHED();
}
}
struct PainterSkia::Impl { struct PainterSkia::Impl {
RefPtr<Gfx::PaintingSurface> painting_surface; RefPtr<Gfx::PaintingSurface> painting_surface;
@ -134,17 +107,13 @@ static SkSamplingOptions to_skia_sampling_options(Gfx::ScalingMode scaling_mode)
} }
} }
void PainterSkia::draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode scaling_mode, float global_alpha) void PainterSkia::draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode scaling_mode, float global_alpha)
{ {
SkBitmap sk_bitmap;
SkImageInfo info = SkImageInfo::Make(src_bitmap.width(), src_bitmap.height(), to_skia_color_type(src_bitmap.format()), to_skia_alpha_type(src_bitmap.alpha_type()));
sk_bitmap.installPixels(info, const_cast<void*>(static_cast<void const*>(src_bitmap.scanline(0))), src_bitmap.pitch());
SkPaint paint; SkPaint paint;
paint.setAlpha(static_cast<u8>(global_alpha * 255)); paint.setAlpha(static_cast<u8>(global_alpha * 255));
impl().canvas()->drawImageRect( impl().canvas()->drawImageRect(
sk_bitmap.asImage(), src_bitmap.sk_image(),
to_skia_rect(src_rect), to_skia_rect(src_rect),
to_skia_rect(dst_rect), to_skia_rect(dst_rect),
to_skia_sampling_options(scaling_mode), to_skia_sampling_options(scaling_mode),

View file

@ -20,7 +20,7 @@ public:
virtual void clear_rect(Gfx::FloatRect const&, Color) override; virtual void clear_rect(Gfx::FloatRect const&, Color) override;
virtual void fill_rect(Gfx::FloatRect const&, Color) override; virtual void fill_rect(Gfx::FloatRect const&, Color) override;
virtual void draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode, float global_alpha) override; virtual void draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode, float global_alpha) override;
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness) override; virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness) override;
virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, float thickness, float global_alpha) override; virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, float thickness, float global_alpha) override;
virtual void fill_path(Gfx::Path const&, Gfx::Color, Gfx::WindingRule) override; virtual void fill_path(Gfx::Path const&, Gfx::Color, Gfx::WindingRule) override;

View file

@ -169,7 +169,7 @@ Optional<Gfx::Color> ImageStyleValue::color_if_single_pixel_bitmap() const
{ {
if (auto const* b = bitmap(m_current_frame_index)) { if (auto const* b = bitmap(m_current_frame_index)) {
if (b->width() == 1 && b->height() == 1) if (b->width() == 1 && b->height() == 1)
return b->bitmap().get_pixel(0, 0); return b->get_pixel(0, 0);
} }
return {}; return {};
} }

View file

@ -13,10 +13,20 @@ namespace Web::HTML {
static void default_source_size(CanvasImageSource const& image, float& source_width, float& source_height) static void default_source_size(CanvasImageSource const& image, float& source_width, float& source_height)
{ {
image.visit( image.visit(
[&source_width, &source_height](JS::Handle<HTMLImageElement> const& source) {
if (source->immutable_bitmap()) {
source_width = source->immutable_bitmap()->width();
source_height = source->immutable_bitmap()->height();
} else {
// FIXME: This is very janky and not correct.
source_width = source->width();
source_height = source->height();
}
},
[&source_width, &source_height](JS::Handle<SVG::SVGImageElement> const& source) { [&source_width, &source_height](JS::Handle<SVG::SVGImageElement> const& source) {
if (source->bitmap()) { if (source->current_image_bitmap()) {
source_width = source->bitmap()->width(); source_width = source->current_image_bitmap()->width();
source_height = source->bitmap()->height(); source_height = source->current_image_bitmap()->height();
} else { } else {
// FIXME: This is very janky and not correct. // FIXME: This is very janky and not correct.
source_width = source->width()->anim_val()->value(); source_width = source->width()->anim_val()->value();

View file

@ -46,8 +46,8 @@ void CanvasPatternPaintStyle::paint(Gfx::IntRect physical_bounding_box, PaintFun
// 6. The resulting bitmap is what is to be rendered, with the same origin and same scale. // 6. The resulting bitmap is what is to be rendered, with the same origin and same scale.
auto const bitmap_width = m_bitmap->width(); auto const bitmap_width = m_immutable_bitmap->width();
auto const bitmap_height = m_bitmap->height(); auto const bitmap_height = m_immutable_bitmap->height();
paint([=, this](auto point) { paint([=, this](auto point) {
point.translate_by(physical_bounding_box.location()); point.translate_by(physical_bounding_box.location());
@ -78,8 +78,8 @@ void CanvasPatternPaintStyle::paint(Gfx::IntRect physical_bounding_box, PaintFun
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
}(); }();
if (m_bitmap->rect().contains(point)) if (m_immutable_bitmap->rect().contains(point))
return m_bitmap->get_pixel(point); return m_immutable_bitmap->get_pixel(point.x(), point.y());
return Gfx::Color(); return Gfx::Color();
}); });
} }
@ -129,14 +129,11 @@ WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> CanvasPattern::create(JS::Realm& r
// Note: Bitmap won't be null here, as if it were it would have "bad" usability. // Note: Bitmap won't be null here, as if it were it would have "bad" usability.
auto bitmap = image.visit( auto bitmap = image.visit(
[](JS::Handle<HTMLImageElement> const& source) -> RefPtr<Gfx::Bitmap> { return *source->bitmap(); }, [](JS::Handle<HTMLImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return source->immutable_bitmap(); },
[](JS::Handle<SVG::SVGImageElement> const& source) -> RefPtr<Gfx::Bitmap> { return *source->bitmap(); }, [](JS::Handle<SVG::SVGImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return source->current_image_bitmap(); },
[](JS::Handle<HTMLCanvasElement> const& source) -> RefPtr<Gfx::Bitmap> { [](JS::Handle<HTMLCanvasElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return source->surface()->create_snapshot(); },
auto snapshot = source->surface()->create_snapshot(); [](JS::Handle<HTMLVideoElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); },
return snapshot->bitmap(); [](JS::Handle<ImageBitmap> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); });
},
[](JS::Handle<HTMLVideoElement> const& source) -> RefPtr<Gfx::Bitmap> { return *source->bitmap(); },
[](JS::Handle<ImageBitmap> const& source) -> RefPtr<Gfx::Bitmap> { return *source->bitmap(); });
// 6. Let pattern be a new CanvasPattern object with the image image and the repetition behavior given by repetition. // 6. Let pattern be a new CanvasPattern object with the image image and the repetition behavior given by repetition.
auto pattern = TRY_OR_THROW_OOM(realm.vm(), CanvasPatternPaintStyle::create(*bitmap, *repetition_value)); auto pattern = TRY_OR_THROW_OOM(realm.vm(), CanvasPatternPaintStyle::create(*bitmap, *repetition_value));

View file

@ -21,7 +21,7 @@ public:
NoRepeat NoRepeat
}; };
static ErrorOr<NonnullRefPtr<CanvasPatternPaintStyle>> create(Gfx::Bitmap const& bitmap, Repetition repetition) static ErrorOr<NonnullRefPtr<CanvasPatternPaintStyle>> create(Gfx::ImmutableBitmap const& bitmap, Repetition repetition)
{ {
return adopt_nonnull_ref_or_enomem(new (nothrow) CanvasPatternPaintStyle(bitmap, repetition)); return adopt_nonnull_ref_or_enomem(new (nothrow) CanvasPatternPaintStyle(bitmap, repetition));
} }
@ -29,13 +29,13 @@ public:
virtual void paint(Gfx::IntRect physical_bounding_box, PaintFunction paint) const override; virtual void paint(Gfx::IntRect physical_bounding_box, PaintFunction paint) const override;
private: private:
CanvasPatternPaintStyle(Gfx::Bitmap const& bitmap, Repetition repetition) CanvasPatternPaintStyle(Gfx::ImmutableBitmap const& immutable_bitmap, Repetition repetition)
: m_bitmap(bitmap) : m_immutable_bitmap(immutable_bitmap)
, m_repetition(repetition) , m_repetition(repetition)
{ {
} }
NonnullRefPtr<Gfx::Bitmap const> m_bitmap; NonnullRefPtr<Gfx::ImmutableBitmap> m_immutable_bitmap;
Repetition m_repetition { Repetition::Repeat }; Repetition m_repetition { Repetition::Repeat };
}; };

View file

@ -124,14 +124,22 @@ WebIDL::ExceptionOr<void> CanvasRenderingContext2D::draw_image_internal(CanvasIm
return {}; return {};
auto bitmap = image.visit( auto bitmap = image.visit(
[](JS::Handle<HTMLImageElement> const& source) -> RefPtr<Gfx::Bitmap> { return *source->bitmap(); }, [](JS::Handle<HTMLImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> {
[](JS::Handle<SVG::SVGImageElement> const& source) -> RefPtr<Gfx::Bitmap> { return *source->bitmap(); }, return source->immutable_bitmap();
[](JS::Handle<HTMLCanvasElement> const& source) -> RefPtr<Gfx::Bitmap> {
auto snapshot = source->surface()->create_snapshot();
return snapshot->bitmap();
}, },
[](JS::Handle<HTMLVideoElement> const& source) -> RefPtr<Gfx::Bitmap> { return *source->bitmap(); }, [](JS::Handle<SVG::SVGImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> {
[](JS::Handle<ImageBitmap> const& source) -> RefPtr<Gfx::Bitmap> { return *source->bitmap(); }); return source->current_image_bitmap();
},
[](JS::Handle<HTMLCanvasElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> {
auto surface = source->surface();
if (!surface)
return {};
return source->surface()->create_snapshot();
},
[](JS::Handle<HTMLVideoElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); },
[](JS::Handle<ImageBitmap> const& source) -> RefPtr<Gfx::ImmutableBitmap> {
return Gfx::ImmutableBitmap::create(*source->bitmap());
});
if (!bitmap) if (!bitmap)
return {}; return {};
@ -394,7 +402,6 @@ WebIDL::ExceptionOr<JS::GCPtr<ImageData>> CanvasRenderingContext2D::get_image_da
if (!canvas_element().surface()) if (!canvas_element().surface())
return image_data; return image_data;
auto const snapshot = canvas_element().surface()->create_snapshot(); auto const snapshot = canvas_element().surface()->create_snapshot();
auto const& bitmap = snapshot->bitmap();
// 5. Let the source rectangle be the rectangle whose corners are the four points (sx, sy), (sx+sw, sy), (sx+sw, sy+sh), (sx, sy+sh). // 5. Let the source rectangle be the rectangle whose corners are the four points (sx, sy), (sx+sw, sy), (sx+sw, sy+sh), (sx, sy+sh).
auto source_rect = Gfx::Rect { x, y, abs_width, abs_height }; auto source_rect = Gfx::Rect { x, y, abs_width, abs_height };
@ -405,17 +412,17 @@ WebIDL::ExceptionOr<JS::GCPtr<ImageData>> CanvasRenderingContext2D::get_image_da
if (width < 0 || height < 0) { if (width < 0 || height < 0) {
source_rect = source_rect.translated(min(width, 0), min(height, 0)); source_rect = source_rect.translated(min(width, 0), min(height, 0));
} }
auto source_rect_intersected = source_rect.intersected(bitmap.rect()); auto source_rect_intersected = source_rect.intersected(snapshot->rect());
// 6. Set the pixel values of imageData to be the pixels of this's output bitmap in the area specified by the source rectangle in the bitmap's coordinate space units, converted from this's color space to imageData's colorSpace using 'relative-colorimetric' rendering intent. // 6. Set the pixel values of imageData to be the pixels of this's output bitmap in the area specified by the source rectangle in the bitmap's coordinate space units, converted from this's color space to imageData's colorSpace using 'relative-colorimetric' rendering intent.
// NOTE: Internally we must use premultiplied alpha, but ImageData should hold unpremultiplied alpha. This conversion // NOTE: Internally we must use premultiplied alpha, but ImageData should hold unpremultiplied alpha. This conversion
// might result in a loss of precision, but is according to spec. // might result in a loss of precision, but is according to spec.
// See: https://html.spec.whatwg.org/multipage/canvas.html#premultiplied-alpha-and-the-2d-rendering-context // See: https://html.spec.whatwg.org/multipage/canvas.html#premultiplied-alpha-and-the-2d-rendering-context
ASSERT(bitmap.alpha_type() == Gfx::AlphaType::Premultiplied); ASSERT(snapshot->alpha_type() == Gfx::AlphaType::Premultiplied);
ASSERT(image_data->bitmap().alpha_type() == Gfx::AlphaType::Unpremultiplied); ASSERT(image_data->bitmap().alpha_type() == Gfx::AlphaType::Unpremultiplied);
auto painter = Gfx::Painter::create(image_data->bitmap()); auto painter = Gfx::Painter::create(image_data->bitmap());
painter->draw_bitmap(image_data->bitmap().rect().to_type<float>(), bitmap, source_rect_intersected, Gfx::ScalingMode::NearestNeighbor, drawing_state().global_alpha); painter->draw_bitmap(image_data->bitmap().rect().to_type<float>(), *snapshot, source_rect_intersected, Gfx::ScalingMode::NearestNeighbor, drawing_state().global_alpha);
// 7. Set the pixels values of imageData for areas of the source rectangle that are outside of the output bitmap to transparent black. // 7. Set the pixels values of imageData for areas of the source rectangle that are outside of the output bitmap to transparent black.
// NOTE: No-op, already done during creation. // NOTE: No-op, already done during creation.
@ -428,7 +435,7 @@ void CanvasRenderingContext2D::put_image_data(ImageData const& image_data, float
{ {
if (auto* painter = this->painter()) { if (auto* painter = this->painter()) {
auto dst_rect = Gfx::FloatRect(x, y, image_data.width(), image_data.height()); auto dst_rect = Gfx::FloatRect(x, y, image_data.width(), image_data.height());
painter->draw_bitmap(dst_rect, image_data.bitmap(), image_data.bitmap().rect(), Gfx::ScalingMode::NearestNeighbor, 1.0f); painter->draw_bitmap(dst_rect, Gfx::ImmutableBitmap::create(image_data.bitmap()), image_data.bitmap().rect(), Gfx::ScalingMode::NearestNeighbor, 1.0f);
did_draw(dst_rect); did_draw(dst_rect);
} }
} }
@ -622,11 +629,11 @@ WebIDL::ExceptionOr<CanvasImageSourceUsability> check_usability_of_image(CanvasI
// FIXME: If image's current request's state is broken, then throw an "InvalidStateError" DOMException. // FIXME: If image's current request's state is broken, then throw an "InvalidStateError" DOMException.
// If image is not fully decodable, then return bad. // If image is not fully decodable, then return bad.
if (!image_element->bitmap()) if (!image_element->immutable_bitmap())
return { CanvasImageSourceUsability::Bad }; return { CanvasImageSourceUsability::Bad };
// If image has an intrinsic width or intrinsic height (or both) equal to zero, then return bad. // If image has an intrinsic width or intrinsic height (or both) equal to zero, then return bad.
if (image_element->bitmap()->width() == 0 || image_element->bitmap()->height() == 0) if (image_element->immutable_bitmap()->width() == 0 || image_element->immutable_bitmap()->height() == 0)
return { CanvasImageSourceUsability::Bad }; return { CanvasImageSourceUsability::Bad };
return Optional<CanvasImageSourceUsability> {}; return Optional<CanvasImageSourceUsability> {};
}, },
@ -635,11 +642,11 @@ WebIDL::ExceptionOr<CanvasImageSourceUsability> check_usability_of_image(CanvasI
// FIXME: If image's current request's state is broken, then throw an "InvalidStateError" DOMException. // FIXME: If image's current request's state is broken, then throw an "InvalidStateError" DOMException.
// If image is not fully decodable, then return bad. // If image is not fully decodable, then return bad.
if (!image_element->bitmap()) if (!image_element->current_image_bitmap())
return { CanvasImageSourceUsability::Bad }; return { CanvasImageSourceUsability::Bad };
// If image has an intrinsic width or intrinsic height (or both) equal to zero, then return bad. // If image has an intrinsic width or intrinsic height (or both) equal to zero, then return bad.
if (image_element->bitmap()->width() == 0 || image_element->bitmap()->height() == 0) if (image_element->current_image_bitmap()->width() == 0 || image_element->current_image_bitmap()->height() == 0)
return { CanvasImageSourceUsability::Bad }; return { CanvasImageSourceUsability::Bad };
return Optional<CanvasImageSourceUsability> {}; return Optional<CanvasImageSourceUsability> {};
}, },

View file

@ -269,7 +269,9 @@ String HTMLCanvasElement::to_data_url(StringView type, Optional<double> quality)
// 3. Let file be a serialization of this canvas element's bitmap as a file, passing type and quality if given. // 3. Let file be a serialization of this canvas element's bitmap as a file, passing type and quality if given.
auto snapshot = m_surface->create_snapshot(); auto snapshot = m_surface->create_snapshot();
auto file = serialize_bitmap(snapshot->bitmap(), type, move(quality)); auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, m_surface->size()));
m_surface->read_into_bitmap(*bitmap);
auto file = serialize_bitmap(bitmap, type, move(quality));
// 4. If file is null then return "data:,". // 4. If file is null then return "data:,".
if (file.is_error()) { if (file.is_error()) {
@ -301,8 +303,8 @@ WebIDL::ExceptionOr<void> HTMLCanvasElement::to_blob(JS::NonnullGCPtr<WebIDL::Ca
// 3. If this canvas element's bitmap has pixels (i.e., neither its horizontal dimension nor its vertical dimension is zero), // 3. If this canvas element's bitmap has pixels (i.e., neither its horizontal dimension nor its vertical dimension is zero),
// then set result to a copy of this canvas element's bitmap. // then set result to a copy of this canvas element's bitmap.
if (m_surface) { if (m_surface) {
auto snapshot = m_surface->create_snapshot(); bitmap_result = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, m_surface->size()));
bitmap_result = snapshot->bitmap(); m_surface->read_into_bitmap(*bitmap_result);
} }
// 4. Run these steps in parallel: // 4. Run these steps in parallel:

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -120,13 +121,6 @@ RefPtr<Gfx::ImmutableBitmap> HTMLImageElement::immutable_bitmap() const
return current_image_bitmap(); return current_image_bitmap();
} }
RefPtr<Gfx::Bitmap const> HTMLImageElement::bitmap() const
{
if (auto immutable_bitmap = this->immutable_bitmap())
return immutable_bitmap->bitmap();
return {};
}
bool HTMLImageElement::is_image_available() const bool HTMLImageElement::is_image_available() const
{ {
return m_current_request && m_current_request->is_available(); return m_current_request && m_current_request->is_available();

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -43,7 +44,6 @@ public:
String src() const { return get_attribute_value(HTML::AttributeNames::src); } String src() const { return get_attribute_value(HTML::AttributeNames::src); }
RefPtr<Gfx::ImmutableBitmap> immutable_bitmap() const; RefPtr<Gfx::ImmutableBitmap> immutable_bitmap() const;
RefPtr<Gfx::Bitmap const> bitmap() const;
unsigned width() const; unsigned width() const;
WebIDL::ExceptionOr<void> set_width(unsigned); WebIDL::ExceptionOr<void> set_width(unsigned);

View file

@ -1,6 +1,7 @@
/* /*
* Copyright (c) 2020-2021, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2020-2021, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021, Max Wipfli <mail@maxwipfli.ch> * Copyright (c) 2021, Max Wipfli <mail@maxwipfli.ch>
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -337,7 +338,7 @@ EventResult EventHandler::handle_mouseup(CSSPixelPoint viewport_position, CSSPix
if (is<HTML::HTMLImageElement>(*node)) { if (is<HTML::HTMLImageElement>(*node)) {
auto& image_element = verify_cast<HTML::HTMLImageElement>(*node); auto& image_element = verify_cast<HTML::HTMLImageElement>(*node);
auto image_url = image_element.document().parse_url(image_element.src()); auto image_url = image_element.document().parse_url(image_element.src());
m_navigable->page().client().page_did_request_image_context_menu(viewport_position, image_url, "", modifiers, image_element.bitmap()); m_navigable->page().client().page_did_request_image_context_menu(viewport_position, image_url, "", modifiers, image_element.immutable_bitmap()->bitmap());
} else if (is<HTML::HTMLMediaElement>(*node)) { } else if (is<HTML::HTMLMediaElement>(*node)) {
auto& media_element = verify_cast<HTML::HTMLMediaElement>(*node); auto& media_element = verify_cast<HTML::HTMLMediaElement>(*node);

View file

@ -381,18 +381,13 @@ void DisplayListPlayerSkia::draw_scaled_immutable_bitmap(DrawScaledImmutableBitm
{ {
auto src_rect = to_skia_rect(command.src_rect); auto src_rect = to_skia_rect(command.src_rect);
auto dst_rect = to_skia_rect(command.dst_rect); auto dst_rect = to_skia_rect(command.dst_rect);
auto bitmap = to_skia_bitmap(command.bitmap->bitmap());
auto image = SkImages::RasterFromBitmap(bitmap);
auto& canvas = surface().canvas(); auto& canvas = surface().canvas();
SkPaint paint; SkPaint paint;
canvas.drawImageRect(image, src_rect, dst_rect, to_skia_sampling_options(command.scaling_mode), &paint, SkCanvas::kStrict_SrcRectConstraint); canvas.drawImageRect(command.bitmap->sk_image(), src_rect, dst_rect, to_skia_sampling_options(command.scaling_mode), &paint, SkCanvas::kStrict_SrcRectConstraint);
} }
void DisplayListPlayerSkia::draw_repeated_immutable_bitmap(DrawRepeatedImmutableBitmap const& command) void DisplayListPlayerSkia::draw_repeated_immutable_bitmap(DrawRepeatedImmutableBitmap const& command)
{ {
auto bitmap = to_skia_bitmap(command.bitmap->bitmap());
auto image = SkImages::RasterFromBitmap(bitmap);
SkMatrix matrix; SkMatrix matrix;
auto dst_rect = command.dst_rect.to_type<float>(); auto dst_rect = command.dst_rect.to_type<float>();
auto src_size = command.bitmap->size().to_type<float>(); auto src_size = command.bitmap->size().to_type<float>();
@ -402,7 +397,7 @@ void DisplayListPlayerSkia::draw_repeated_immutable_bitmap(DrawRepeatedImmutable
auto tile_mode_x = command.repeat.x ? SkTileMode::kRepeat : SkTileMode::kDecal; auto tile_mode_x = command.repeat.x ? SkTileMode::kRepeat : SkTileMode::kDecal;
auto tile_mode_y = command.repeat.y ? SkTileMode::kRepeat : SkTileMode::kDecal; auto tile_mode_y = command.repeat.y ? SkTileMode::kRepeat : SkTileMode::kDecal;
auto shader = image->makeShader(tile_mode_x, tile_mode_y, sampling_options, matrix); auto shader = command.bitmap->sk_image()->makeShader(tile_mode_x, tile_mode_y, sampling_options, matrix);
SkPaint paint; SkPaint paint;
paint.setShader(shader); paint.setShader(shader);

View file

@ -29,15 +29,6 @@ public:
Gfx::Rect<CSSPixels> bounding_box() const; Gfx::Rect<CSSPixels> bounding_box() const;
// FIXME: This is a hack for images used as CanvasImageSource. Do something more elegant.
RefPtr<Gfx::Bitmap> bitmap() const
{
auto bitmap = current_image_bitmap();
if (!bitmap)
return nullptr;
return bitmap->bitmap();
}
// ^Layout::ImageProvider // ^Layout::ImageProvider
virtual bool is_image_available() const override; virtual bool is_image_available() const override;
virtual Optional<CSSPixels> intrinsic_width() const override; virtual Optional<CSSPixels> intrinsic_width() const override;

View file

@ -52,7 +52,9 @@ ErrorOr<JS::NonnullGCPtr<HTML::HTMLCanvasElement>, WebDriver::Error> draw_boundi
// - Height: paint height // - Height: paint height
Gfx::IntRect paint_rect { rect.x(), rect.y(), paint_width, paint_height }; Gfx::IntRect paint_rect { rect.x(), rect.y(), paint_width, paint_height };
auto backing_store = Web::Painting::BitmapBackingStore(canvas.surface()->create_snapshot()->bitmap()); auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, canvas.surface()->size()));
canvas.surface()->read_into_bitmap(*bitmap);
auto backing_store = Web::Painting::BitmapBackingStore(bitmap);
browsing_context.page().client().paint(paint_rect.to_type<Web::DevicePixels>(), backing_store); browsing_context.page().client().paint(paint_rect.to_type<Web::DevicePixels>(), backing_store);
// 7. Return success with canvas. // 7. Return success with canvas.

View file

@ -15,7 +15,7 @@ class OpenGLContext {
public: public:
static OwnPtr<OpenGLContext> create(Gfx::PaintingSurface&); static OwnPtr<OpenGLContext> create(Gfx::PaintingSurface&);
virtual void present(Gfx::Bitmap const&) = 0; virtual void present() = 0;
void clear_buffer_to_default_values(); void clear_buffer_to_default_values();
virtual GLenum gl_get_error() = 0; virtual GLenum gl_get_error() = 0;

View file

@ -53,7 +53,7 @@ void WebGLRenderingContextBase::present()
// FIXME: Is this the operation it means? // FIXME: Is this the operation it means?
m_context->gl_flush(); m_context->gl_flush();
m_context->present(canvas_element().surface()->create_snapshot()->bitmap()); m_context->present();
// "By default, after compositing the contents of the drawing buffer shall be cleared to their default values, as shown in the table above. // "By default, after compositing the contents of the drawing buffer shall be cleared to their default values, as shown in the table above.
// This default behavior can be changed by setting the preserveDrawingBuffer attribute of the WebGLContextAttributes object. // This default behavior can be changed by setting the preserveDrawingBuffer attribute of the WebGLContextAttributes object.