mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-01-22 09:12:13 -05:00
LibWeb: Delegate painting surface allocation to canvas's active context
This change prepares for the addition of WebGL support, where painting surface allocation process will differ from that of context2d.
This commit is contained in:
parent
e683700fe6
commit
f719b05ab9
Notes:
github-actions[bot]
2024-12-03 22:37:53 +00:00
Author: https://github.com/kalenikaliaksandr Commit: https://github.com/LadybirdBrowser/ladybird/commit/f719b05ab9d Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2688 Reviewed-by: https://github.com/ADKaster ✅
8 changed files with 116 additions and 67 deletions
|
@ -21,6 +21,7 @@
|
|||
#include <LibWeb/HTML/ImageData.h>
|
||||
#include <LibWeb/HTML/Path2D.h>
|
||||
#include <LibWeb/HTML/TextMetrics.h>
|
||||
#include <LibWeb/HTML/TraversableNavigable.h>
|
||||
#include <LibWeb/Infra/CharacterTypes.h>
|
||||
#include <LibWeb/Layout/TextNode.h>
|
||||
#include <LibWeb/Painting/Paintable.h>
|
||||
|
@ -41,6 +42,7 @@ CanvasRenderingContext2D::CanvasRenderingContext2D(JS::Realm& realm, HTMLCanvasE
|
|||
: PlatformObject(realm)
|
||||
, CanvasPath(static_cast<Bindings::PlatformObject&>(*this), *this)
|
||||
, m_element(element)
|
||||
, m_size(element.bitmap_size_for_canvas())
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -198,14 +200,29 @@ void CanvasRenderingContext2D::did_draw(Gfx::FloatRect const&)
|
|||
Gfx::Painter* CanvasRenderingContext2D::painter()
|
||||
{
|
||||
if (!canvas_element().surface()) {
|
||||
if (!canvas_element().allocate_painting_surface())
|
||||
return nullptr;
|
||||
allocate_painting_surface_if_needed();
|
||||
canvas_element().document().invalidate_display_list();
|
||||
m_painter = make<Gfx::PainterSkia>(*canvas_element().surface());
|
||||
}
|
||||
return m_painter.ptr();
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::set_size(Gfx::IntSize const& size)
|
||||
{
|
||||
if (m_size == size)
|
||||
return;
|
||||
m_size = size;
|
||||
m_surface = nullptr;
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::allocate_painting_surface_if_needed()
|
||||
{
|
||||
if (m_surface)
|
||||
return;
|
||||
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);
|
||||
}
|
||||
|
||||
Gfx::Path CanvasRenderingContext2D::text_path(StringView text, float x, float y, Optional<double> max_width)
|
||||
{
|
||||
if (max_width.has_value() && max_width.value() <= 0)
|
||||
|
|
|
@ -113,6 +113,11 @@ public:
|
|||
|
||||
[[nodiscard]] Gfx::Painter* painter();
|
||||
|
||||
void set_size(Gfx::IntSize const&);
|
||||
|
||||
RefPtr<Gfx::PaintingSurface> surface() { return m_surface; }
|
||||
void allocate_painting_surface_if_needed();
|
||||
|
||||
private:
|
||||
explicit CanvasRenderingContext2D(JS::Realm&, HTMLCanvasElement&);
|
||||
|
||||
|
@ -148,6 +153,9 @@ private:
|
|||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-origin-clean
|
||||
bool m_origin_clean { true };
|
||||
|
||||
Gfx::IntSize m_size;
|
||||
RefPtr<Gfx::PaintingSurface> m_surface;
|
||||
};
|
||||
|
||||
enum class CanvasImageSourceUsability {
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <LibWeb/HTML/TraversableNavigable.h>
|
||||
#include <LibWeb/Layout/CanvasBox.h>
|
||||
#include <LibWeb/Platform/EventLoopPlugin.h>
|
||||
#include <LibWeb/WebGL/WebGLRenderingContext.h>
|
||||
#include <LibWeb/WebIDL/AbstractOperations.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
@ -128,13 +129,27 @@ void HTMLCanvasElement::reset_context_to_default_state()
|
|||
});
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> HTMLCanvasElement::set_width(WebIDL::UnsignedLong value)
|
||||
void HTMLCanvasElement::notify_context_about_canvas_size_change()
|
||||
{
|
||||
m_context.visit(
|
||||
[&](GC::Ref<CanvasRenderingContext2D>& context) {
|
||||
context->set_size(bitmap_size_for_canvas());
|
||||
},
|
||||
[&](GC::Ref<WebGL::WebGLRenderingContext>&) {
|
||||
TODO();
|
||||
},
|
||||
[](Empty) {
|
||||
// Do nothing.
|
||||
});
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> HTMLCanvasElement::set_width(unsigned value)
|
||||
{
|
||||
if (value > 2147483647)
|
||||
value = 300;
|
||||
|
||||
TRY(set_attribute(HTML::AttributeNames::width, String::number(value)));
|
||||
m_surface = nullptr;
|
||||
notify_context_about_canvas_size_change();
|
||||
reset_context_to_default_state();
|
||||
return {};
|
||||
}
|
||||
|
@ -145,7 +160,7 @@ WebIDL::ExceptionOr<void> HTMLCanvasElement::set_height(WebIDL::UnsignedLong val
|
|||
value = 150;
|
||||
|
||||
TRY(set_attribute(HTML::AttributeNames::height, String::number(value)));
|
||||
m_surface = nullptr;
|
||||
notify_context_about_canvas_size_change();
|
||||
reset_context_to_default_state();
|
||||
return {};
|
||||
}
|
||||
|
@ -214,10 +229,10 @@ JS::ThrowCompletionOr<HTMLCanvasElement::RenderingContext> HTMLCanvasElement::ge
|
|||
return Empty {};
|
||||
}
|
||||
|
||||
static Gfx::IntSize bitmap_size_for_canvas(HTMLCanvasElement const& canvas, size_t minimum_width, size_t minimum_height)
|
||||
Gfx::IntSize HTMLCanvasElement::bitmap_size_for_canvas(size_t minimum_width, size_t minimum_height) const
|
||||
{
|
||||
auto width = max(canvas.width(), minimum_width);
|
||||
auto height = max(canvas.height(), minimum_height);
|
||||
auto width = max(this->width(), minimum_width);
|
||||
auto height = max(this->height(), minimum_height);
|
||||
|
||||
Checked<size_t> area = width;
|
||||
area *= height;
|
||||
|
@ -233,25 +248,6 @@ static Gfx::IntSize bitmap_size_for_canvas(HTMLCanvasElement const& canvas, size
|
|||
return Gfx::IntSize(width, height);
|
||||
}
|
||||
|
||||
bool HTMLCanvasElement::allocate_painting_surface(size_t minimum_width, size_t minimum_height)
|
||||
{
|
||||
if (m_surface)
|
||||
return true;
|
||||
|
||||
auto traversable = document().navigable()->traversable_navigable();
|
||||
VERIFY(traversable);
|
||||
|
||||
auto size = bitmap_size_for_canvas(*this, minimum_width, minimum_height);
|
||||
if (size.is_empty()) {
|
||||
m_surface = nullptr;
|
||||
return false;
|
||||
}
|
||||
if (!m_surface || m_surface->size() != size) {
|
||||
m_surface = Gfx::PaintingSurface::create_with_size(traversable->skia_backend_context(), size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
|
||||
}
|
||||
return m_surface;
|
||||
}
|
||||
|
||||
struct SerializeBitmapResult {
|
||||
ByteBuffer buffer;
|
||||
StringView mime_type;
|
||||
|
@ -283,21 +279,26 @@ static ErrorOr<SerializeBitmapResult> serialize_bitmap(Gfx::Bitmap const& bitmap
|
|||
String HTMLCanvasElement::to_data_url(StringView type, JS::Value quality)
|
||||
{
|
||||
// It is possible the the canvas doesn't have a associated bitmap so create one
|
||||
if (!m_surface) {
|
||||
allocate_painting_surface();
|
||||
allocate_painting_surface_if_needed();
|
||||
auto surface = this->surface();
|
||||
auto size = bitmap_size_for_canvas();
|
||||
if (!size.is_empty()) {
|
||||
// If the context is not initialized yet, we need to allocate transparent surface for serialization
|
||||
auto skia_backend_context = navigable()->traversable_navigable()->skia_backend_context();
|
||||
surface = Gfx::PaintingSurface::create_with_size(skia_backend_context, size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
|
||||
}
|
||||
|
||||
// FIXME: 1. If this canvas element's bitmap's origin-clean flag is set to false, then throw a "SecurityError" DOMException.
|
||||
|
||||
// 2. If this canvas element's bitmap has no pixels (i.e. either its horizontal dimension or its vertical dimension is zero)
|
||||
// then return the string "data:,". (This is the shortest data: URL; it represents the empty string in a text/plain resource.)
|
||||
if (!m_surface)
|
||||
if (!surface)
|
||||
return "data:,"_string;
|
||||
|
||||
// 3. Let file be a serialization of this canvas element's bitmap as a file, passing type and quality if given.
|
||||
auto snapshot = Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*m_surface);
|
||||
auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, m_surface->size()));
|
||||
m_surface->read_into_bitmap(*bitmap);
|
||||
auto snapshot = Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*surface);
|
||||
auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, surface->size()));
|
||||
surface->read_into_bitmap(*bitmap);
|
||||
auto file = serialize_bitmap(bitmap, type, move(quality));
|
||||
|
||||
// 4. If file is null then return "data:,".
|
||||
|
@ -318,8 +319,13 @@ String HTMLCanvasElement::to_data_url(StringView type, JS::Value quality)
|
|||
WebIDL::ExceptionOr<void> HTMLCanvasElement::to_blob(GC::Ref<WebIDL::CallbackType> callback, StringView type, JS::Value quality)
|
||||
{
|
||||
// It is possible the the canvas doesn't have a associated bitmap so create one
|
||||
if (!m_surface) {
|
||||
allocate_painting_surface();
|
||||
allocate_painting_surface_if_needed();
|
||||
auto surface = this->surface();
|
||||
auto size = bitmap_size_for_canvas();
|
||||
if (!size.is_empty()) {
|
||||
// If the context is not initialized yet, we need to allocate transparent surface for serialization
|
||||
auto skia_backend_context = navigable()->traversable_navigable()->skia_backend_context();
|
||||
surface = Gfx::PaintingSurface::create_with_size(skia_backend_context, size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
|
||||
}
|
||||
|
||||
// FIXME: 1. If this canvas element's bitmap's origin-clean flag is set to false, then throw a "SecurityError" DOMException.
|
||||
|
@ -329,9 +335,9 @@ WebIDL::ExceptionOr<void> HTMLCanvasElement::to_blob(GC::Ref<WebIDL::CallbackTyp
|
|||
|
||||
// 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.
|
||||
if (m_surface) {
|
||||
bitmap_result = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, m_surface->size()));
|
||||
m_surface->read_into_bitmap(*bitmap_result);
|
||||
if (surface) {
|
||||
bitmap_result = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, surface->size()));
|
||||
surface->read_into_bitmap(*bitmap_result);
|
||||
}
|
||||
|
||||
// 4. Run these steps in parallel:
|
||||
|
@ -364,9 +370,8 @@ WebIDL::ExceptionOr<void> HTMLCanvasElement::to_blob(GC::Ref<WebIDL::CallbackTyp
|
|||
|
||||
void HTMLCanvasElement::present()
|
||||
{
|
||||
if (m_surface) {
|
||||
m_surface->flush();
|
||||
}
|
||||
if (auto surface = this->surface())
|
||||
surface->flush();
|
||||
|
||||
m_context.visit(
|
||||
[](GC::Ref<CanvasRenderingContext2D>&) {
|
||||
|
@ -380,4 +385,32 @@ void HTMLCanvasElement::present()
|
|||
});
|
||||
}
|
||||
|
||||
RefPtr<Gfx::PaintingSurface> HTMLCanvasElement::surface() const
|
||||
{
|
||||
return m_context.visit(
|
||||
[&](GC::Ref<CanvasRenderingContext2D> const& context) {
|
||||
return context->surface();
|
||||
},
|
||||
[&](GC::Ref<WebGL::WebGLRenderingContext> const&) -> RefPtr<Gfx::PaintingSurface> {
|
||||
TODO();
|
||||
},
|
||||
[](Empty) -> RefPtr<Gfx::PaintingSurface> {
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
void HTMLCanvasElement::allocate_painting_surface_if_needed()
|
||||
{
|
||||
m_context.visit(
|
||||
[&](GC::Ref<CanvasRenderingContext2D>& context) {
|
||||
context->allocate_painting_surface_if_needed();
|
||||
},
|
||||
[&](GC::Ref<WebGL::WebGLRenderingContext>&) {
|
||||
TODO();
|
||||
},
|
||||
[](Empty) {
|
||||
// Do nothing.
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,11 +24,14 @@ public:
|
|||
|
||||
virtual ~HTMLCanvasElement() override;
|
||||
|
||||
bool allocate_painting_surface(size_t minimum_width = 0, size_t minimum_height = 0);
|
||||
RefPtr<Gfx::PaintingSurface> surface() { return m_surface; }
|
||||
RefPtr<Gfx::PaintingSurface const> surface() const { return m_surface; }
|
||||
Gfx::IntSize bitmap_size_for_canvas(size_t minimum_width = 0, size_t minimum_height = 0) const;
|
||||
|
||||
JS::ThrowCompletionOr<RenderingContext> get_context(String const& type, JS::Value options);
|
||||
enum class HasOrCreatedContext {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
HasOrCreatedContext create_2d_context();
|
||||
|
||||
WebIDL::UnsignedLong width() const;
|
||||
WebIDL::UnsignedLong height() const;
|
||||
|
@ -41,6 +44,9 @@ public:
|
|||
|
||||
void present();
|
||||
|
||||
RefPtr<Gfx::PaintingSurface> surface() const;
|
||||
void allocate_painting_surface_if_needed();
|
||||
|
||||
private:
|
||||
HTMLCanvasElement(DOM::Document&, DOM::QualifiedName);
|
||||
|
||||
|
@ -52,16 +58,9 @@ private:
|
|||
virtual GC::Ptr<Layout::Node> create_layout_node(CSS::StyleProperties) override;
|
||||
virtual void adjust_computed_style(CSS::StyleProperties&) override;
|
||||
|
||||
enum class HasOrCreatedContext {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
HasOrCreatedContext create_2d_context();
|
||||
JS::ThrowCompletionOr<HasOrCreatedContext> create_webgl_context(JS::Value options);
|
||||
void reset_context_to_default_state();
|
||||
|
||||
RefPtr<Gfx::PaintingSurface> m_surface;
|
||||
void notify_context_about_canvas_size_change();
|
||||
|
||||
Variant<GC::Ref<HTML::CanvasRenderingContext2D>, GC::Ref<WebGL::WebGLRenderingContext>, Empty> m_context;
|
||||
};
|
||||
|
|
|
@ -42,8 +42,10 @@ ErrorOr<GC::Ref<HTML::HTMLCanvasElement>, WebDriver::Error> draw_bounding_box_fr
|
|||
MUST(canvas.set_height(paint_height));
|
||||
|
||||
// FIXME: 5. Let context, a canvas context mode, be the result of invoking the 2D context creation algorithm given canvas as the target.
|
||||
if (!canvas.allocate_painting_surface(paint_width, paint_height))
|
||||
return Error::from_code(ErrorCode::UnableToCaptureScreen, "Unable to create a screenshot bitmap"sv);
|
||||
canvas.create_2d_context();
|
||||
canvas.allocate_painting_surface_if_needed();
|
||||
if (!canvas.surface())
|
||||
return Error::from_code(ErrorCode::UnableToCaptureScreen, "Failed to allocate painting surface"sv);
|
||||
|
||||
// 6. Complete implementation specific steps equivalent to drawing the region of the framebuffer specified by the following coordinates onto context:
|
||||
// - X coordinate: rectangle x coordinate
|
||||
|
|
|
@ -5,15 +5,12 @@
|
|||
*/
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibWeb/WebGL/OpenGLContext.h>
|
||||
|
||||
namespace Web::WebGL {
|
||||
|
||||
OwnPtr<OpenGLContext> OpenGLContext::create(Gfx::PaintingSurface& bitmap)
|
||||
OwnPtr<OpenGLContext> OpenGLContext::create()
|
||||
{
|
||||
(void)bitmap;
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace Web::WebGL {
|
|||
|
||||
class OpenGLContext {
|
||||
public:
|
||||
static OwnPtr<OpenGLContext> create(Gfx::PaintingSurface&);
|
||||
static OwnPtr<OpenGLContext> create();
|
||||
|
||||
virtual void present() = 0;
|
||||
void clear_buffer_to_default_values();
|
||||
|
|
|
@ -39,14 +39,7 @@ JS::ThrowCompletionOr<GC::Ptr<WebGLRenderingContext>> WebGLRenderingContext::cre
|
|||
// We should be coming here from getContext being called on a wrapped <canvas> element.
|
||||
auto context_attributes = TRY(convert_value_to_context_attributes_dictionary(canvas_element.vm(), options));
|
||||
|
||||
bool created_surface = canvas_element.allocate_painting_surface(/* minimum_width= */ 1, /* minimum_height= */ 1);
|
||||
if (!created_surface) {
|
||||
fire_webgl_context_creation_error(canvas_element);
|
||||
return GC::Ptr<WebGLRenderingContext> { nullptr };
|
||||
}
|
||||
|
||||
VERIFY(canvas_element.surface());
|
||||
auto context = OpenGLContext::create(*canvas_element.surface());
|
||||
auto context = OpenGLContext::create();
|
||||
|
||||
if (!context) {
|
||||
fire_webgl_context_creation_error(canvas_element);
|
||||
|
|
Loading…
Reference in a new issue