LibGfx: Introduce a new Gfx::Painter with a Skia backend

This new painter is written with a virtual interface from the start,
and we begin with a Skia backend.

This patch adds enough to support our basic 2D HTML canvas usecase.
This commit is contained in:
Andreas Kling 2024-07-05 15:36:55 +02:00 committed by Andreas Kling
parent 0c7670b226
commit de50d27870
Notes: github-actions[bot] 2024-08-20 07:38:27 +00:00
10 changed files with 507 additions and 78 deletions

View file

@ -58,6 +58,8 @@ set(SOURCES
Palette.cpp
Path.cpp
PathClipper.cpp
Painter.cpp
PainterSkia.cpp
Point.cpp
Rect.cpp
ShareableBitmap.cpp
@ -73,6 +75,9 @@ serenity_lib(LibGfx gfx)
find_package(harfbuzz REQUIRED)
target_link_libraries(LibGfx PRIVATE LibCompress LibCore LibCrypto LibFileSystem LibRIFF LibTextCodec LibIPC LibUnicode LibURL ${SKIA_LIBRARIES} harfbuzz)
find_package(unofficial-skia CONFIG REQUIRED)
target_link_libraries(LibGfx PRIVATE unofficial::skia::skia)
set(generated_sources TIFFMetadata.h TIFFTagHandler.cpp)
list(TRANSFORM generated_sources PREPEND "ImageFormats/")

View file

@ -27,6 +27,7 @@ class Line;
class AntiAliasingPainter;
class DeprecatedPainter;
class Painter;
class Palette;
class PaletteImpl;
class Path;

View file

@ -242,6 +242,9 @@ public:
return adopt_nonnull_ref_or_enomem(new (nothrow) CanvasLinearGradientPaintStyle(p0, p1));
}
FloatPoint start_point() const { return m_p0; }
FloatPoint end_point() const { return m_p1; }
private:
virtual void paint(IntRect physical_bounding_box, PaintFunction paint) const override;
@ -282,6 +285,11 @@ public:
return adopt_nonnull_ref_or_enomem(new (nothrow) CanvasRadialGradientPaintStyle(start_center, start_radius, end_center, end_radius));
}
Gfx::FloatPoint start_center() const { return m_start_center; }
float start_radius() const { return m_start_radius; }
Gfx::FloatPoint end_center() const { return m_end_center; }
float end_radius() const { return m_end_radius; }
private:
virtual void paint(IntRect physical_bounding_box, PaintFunction paint) const override;

View file

@ -0,0 +1,19 @@
/*
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Painter.h>
#include <LibGfx/PainterSkia.h>
namespace Gfx {
Painter::~Painter() = default;
NonnullOwnPtr<Painter> Painter::create(NonnullRefPtr<Gfx::Bitmap> target_bitmap)
{
return make<PainterSkia>(move(target_bitmap));
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <LibGfx/Forward.h>
#include <LibGfx/PaintStyle.h>
#include <LibGfx/ScalingMode.h>
#include <LibGfx/WindingRule.h>
namespace Gfx {
class Painter {
public:
static NonnullOwnPtr<Gfx::Painter> create(NonnullRefPtr<Gfx::Bitmap>);
virtual ~Painter();
virtual void clear_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 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 fill_path(Gfx::Path const&, Gfx::Color, Gfx::WindingRule) = 0;
virtual void fill_path(Gfx::Path const&, Gfx::PaintStyle const&, float global_alpha, Gfx::WindingRule) = 0;
virtual void set_transform(Gfx::AffineTransform const&) = 0;
virtual void save() = 0;
virtual void restore() = 0;
};
}

View file

@ -0,0 +1,326 @@
/*
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#define AK_DONT_REPLACE_STD
#include <AK/OwnPtr.h>
#include <LibGfx/PainterSkia.h>
#include <LibGfx/Path.h>
#include <AK/TypeCasts.h>
#include <core/SkBitmap.h>
#include <core/SkBlurTypes.h>
#include <core/SkCanvas.h>
#include <core/SkColorFilter.h>
#include <core/SkMaskFilter.h>
#include <core/SkPath.h>
#include <core/SkPathBuilder.h>
#include <core/SkRRect.h>
#include <core/SkSurface.h>
#include <effects/SkGradientShader.h>
#include <effects/SkImageFilters.h>
#include <gpu/GrDirectContext.h>
#include <gpu/ganesh/SkSurfaceGanesh.h>
#include <pathops/SkPathOps.h>
namespace Gfx {
struct PainterSkia::Impl {
NonnullRefPtr<Gfx::Bitmap> gfx_bitmap;
OwnPtr<SkBitmap> sk_bitmap;
OwnPtr<SkCanvas> sk_canvas;
Impl(NonnullRefPtr<Gfx::Bitmap> target_bitmap)
: gfx_bitmap(move(target_bitmap))
{
sk_bitmap = make<SkBitmap>();
SkImageInfo info = SkImageInfo::Make(gfx_bitmap->width(), gfx_bitmap->height(), kBGRA_8888_SkColorType, kUnpremul_SkAlphaType);
sk_bitmap->installPixels(info, gfx_bitmap->scanline(0), gfx_bitmap->pitch());
sk_canvas = make<SkCanvas>(*sk_bitmap);
}
SkCanvas* canvas() { return sk_canvas; }
};
static constexpr SkRect to_skia_rect(auto const& rect)
{
return SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());
}
static constexpr SkColor to_skia_color(Gfx::Color const& color)
{
return SkColorSetARGB(color.alpha(), color.red(), color.green(), color.blue());
}
static SkPath to_skia_path(Gfx::Path const& path)
{
Optional<Gfx::FloatPoint> subpath_start_point;
Optional<Gfx::FloatPoint> subpath_last_point;
SkPathBuilder path_builder;
auto close_subpath_if_needed = [&](auto last_point) {
if (subpath_start_point == last_point)
path_builder.close();
};
for (auto const& segment : path) {
auto point = segment.point();
switch (segment.command()) {
case Gfx::PathSegment::Command::MoveTo: {
if (subpath_start_point.has_value() && subpath_last_point.has_value())
close_subpath_if_needed(subpath_last_point.value());
subpath_start_point = point;
path_builder.moveTo({ point.x(), point.y() });
break;
}
case Gfx::PathSegment::Command::LineTo: {
if (!subpath_start_point.has_value())
subpath_start_point = Gfx::FloatPoint { 0.0f, 0.0f };
path_builder.lineTo({ point.x(), point.y() });
break;
}
case Gfx::PathSegment::Command::QuadraticBezierCurveTo: {
if (!subpath_start_point.has_value())
subpath_start_point = Gfx::FloatPoint { 0.0f, 0.0f };
SkPoint pt1 = { segment.through().x(), segment.through().y() };
SkPoint pt2 = { segment.point().x(), segment.point().y() };
path_builder.quadTo(pt1, pt2);
break;
}
case Gfx::PathSegment::Command::CubicBezierCurveTo: {
if (!subpath_start_point.has_value())
subpath_start_point = Gfx::FloatPoint { 0.0f, 0.0f };
SkPoint pt1 = { segment.through_0().x(), segment.through_0().y() };
SkPoint pt2 = { segment.through_1().x(), segment.through_1().y() };
SkPoint pt3 = { segment.point().x(), segment.point().y() };
path_builder.cubicTo(pt1, pt2, pt3);
break;
}
default:
VERIFY_NOT_REACHED();
}
subpath_last_point = point;
}
close_subpath_if_needed(subpath_last_point);
return path_builder.snapshot();
}
static SkPathFillType to_skia_path_fill_type(Gfx::WindingRule winding_rule)
{
switch (winding_rule) {
case Gfx::WindingRule::Nonzero:
return SkPathFillType::kWinding;
case Gfx::WindingRule::EvenOdd:
return SkPathFillType::kEvenOdd;
}
VERIFY_NOT_REACHED();
}
PainterSkia::PainterSkia(NonnullRefPtr<Gfx::Bitmap> target_bitmap)
: m_impl(adopt_own(*new Impl { move(target_bitmap) }))
{
}
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().canvas()->drawRect(to_skia_rect(rect), paint);
}
void PainterSkia::fill_rect(Gfx::FloatRect const& rect, Color color)
{
SkPaint paint;
paint.setColor(to_skia_color(color));
impl().canvas()->drawRect(to_skia_rect(rect), paint);
}
static SkSamplingOptions to_skia_sampling_options(Gfx::ScalingMode scaling_mode)
{
switch (scaling_mode) {
case Gfx::ScalingMode::NearestNeighbor:
return SkSamplingOptions(SkFilterMode::kNearest);
case Gfx::ScalingMode::BilinearBlend:
case Gfx::ScalingMode::SmoothPixels:
return SkSamplingOptions(SkFilterMode::kLinear);
case Gfx::ScalingMode::BoxSampling:
return SkSamplingOptions(SkCubicResampler::Mitchell());
default:
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;
default:
return kUnknown_SkColorType;
}
}
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)
{
SkBitmap sk_bitmap;
SkImageInfo info = SkImageInfo::Make(src_bitmap.width(), src_bitmap.height(), to_skia_color_type(src_bitmap.format()), kUnpremul_SkAlphaType);
sk_bitmap.installPixels(info, const_cast<void*>(static_cast<void const*>(src_bitmap.scanline(0))), src_bitmap.pitch());
SkPaint paint;
paint.setAlpha(static_cast<u8>(global_alpha * 255));
impl().canvas()->drawImageRect(
sk_bitmap.asImage(),
to_skia_rect(src_rect),
to_skia_rect(dst_rect),
to_skia_sampling_options(scaling_mode),
&paint,
SkCanvas::kStrict_SrcRectConstraint);
}
void PainterSkia::set_transform(Gfx::AffineTransform const& transform)
{
auto matrix = SkMatrix::MakeAll(
transform.a(), transform.c(), transform.e(),
transform.b(), transform.d(), transform.f(),
0, 0, 1);
impl().canvas()->setMatrix(matrix);
}
void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness)
{
// Skia treats zero thickness as a special case and will draw a hairline, while we want to draw nothing.
if (!thickness)
return;
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(thickness);
paint.setColor(to_skia_color(color));
auto sk_path = to_skia_path(path);
impl().canvas()->drawPath(sk_path, paint);
}
static SkPoint to_skia_point(auto const& point)
{
return SkPoint::Make(point.x(), point.y());
}
static SkPaint to_skia_paint(Gfx::PaintStyle const& style, Gfx::FloatRect const& bounding_rect)
{
if (is<Gfx::CanvasLinearGradientPaintStyle>(style)) {
auto const& linear_gradient = static_cast<Gfx::CanvasLinearGradientPaintStyle const&>(style);
auto const& color_stops = linear_gradient.color_stops();
SkPaint paint;
Vector<SkColor> colors;
colors.ensure_capacity(color_stops.size());
Vector<SkScalar> positions;
positions.ensure_capacity(color_stops.size());
for (auto const& color_stop : color_stops) {
colors.append(to_skia_color(color_stop.color));
positions.append(color_stop.position);
}
Array<SkPoint, 2> points;
points[0] = to_skia_point(linear_gradient.start_point());
points[1] = to_skia_point(linear_gradient.end_point());
SkMatrix matrix;
auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), positions.data(), color_stops.size(), SkTileMode::kClamp, 0, &matrix);
paint.setShader(shader);
return paint;
}
if (is<Gfx::CanvasRadialGradientPaintStyle>(style)) {
auto const& radial_gradient = static_cast<Gfx::CanvasRadialGradientPaintStyle const&>(style);
auto const& color_stops = radial_gradient.color_stops();
SkPaint paint;
Vector<SkColor> colors;
colors.ensure_capacity(color_stops.size());
Vector<SkScalar> positions;
positions.ensure_capacity(color_stops.size());
for (auto const& color_stop : color_stops) {
colors.append(to_skia_color(color_stop.color));
positions.append(color_stop.position);
}
auto start_center = radial_gradient.start_center();
auto end_center = radial_gradient.end_center();
auto start_radius = radial_gradient.start_radius();
auto end_radius = radial_gradient.end_radius();
start_center.translate_by(bounding_rect.location());
end_center.translate_by(bounding_rect.location());
auto start_sk_point = to_skia_point(start_center);
auto end_sk_point = to_skia_point(end_center);
SkMatrix matrix;
auto shader = SkGradientShader::MakeTwoPointConical(start_sk_point, start_radius, end_sk_point, end_radius, colors.data(), positions.data(), color_stops.size(), SkTileMode::kClamp, 0, &matrix);
paint.setShader(shader);
}
return {};
}
void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, float thickness, float global_alpha)
{
// Skia treats zero thickness as a special case and will draw a hairline, while we want to draw nothing.
if (!thickness)
return;
auto sk_path = to_skia_path(path);
auto paint = to_skia_paint(paint_style, path.bounding_box());
paint.setAntiAlias(true);
paint.setAlphaf(global_alpha);
paint.setStyle(SkPaint::Style::kStroke_Style);
paint.setStrokeWidth(thickness);
impl().canvas()->drawPath(sk_path, paint);
}
void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::WindingRule winding_rule)
{
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(to_skia_color(color));
auto sk_path = to_skia_path(path);
sk_path.setFillType(to_skia_path_fill_type(winding_rule));
impl().canvas()->drawPath(sk_path, paint);
}
void PainterSkia::fill_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, float global_alpha, Gfx::WindingRule winding_rule)
{
auto sk_path = to_skia_path(path);
sk_path.setFillType(to_skia_path_fill_type(winding_rule));
auto paint = to_skia_paint(paint_style, path.bounding_box());
paint.setAntiAlias(true);
paint.setAlphaf(global_alpha);
impl().canvas()->drawPath(sk_path, paint);
}
void PainterSkia::save()
{
impl().canvas()->save();
}
void PainterSkia::restore()
{
impl().canvas()->restore();
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullOwnPtr.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Painter.h>
namespace Gfx {
class PainterSkia final : public Painter {
public:
explicit PainterSkia(NonnullRefPtr<Gfx::Bitmap>);
virtual ~PainterSkia() override;
virtual void clear_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 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 fill_path(Gfx::Path const&, Gfx::Color, Gfx::WindingRule) override;
virtual void fill_path(Gfx::Path const&, Gfx::PaintStyle const&, float global_alpha, Gfx::WindingRule) override;
virtual void set_transform(Gfx::AffineTransform const&) override;
virtual void save() override;
virtual void restore() override;
private:
struct Impl;
Impl& impl() { return *m_impl; }
NonnullOwnPtr<Impl> m_impl;
};
}

View file

@ -9,6 +9,7 @@
#pragma once
#include <AK/Debug.h>
#include <LibGfx/Painter.h>
#include <LibWeb/Geometry/DOMMatrix.h>
#include <LibWeb/HTML/Canvas/CanvasState.h>
@ -71,6 +72,8 @@ public:
// 2. Reset the current transformation matrix to the identity matrix.
my_drawing_state().transform = {};
if (auto* painter = static_cast<IncludingClass&>(*this).painter())
painter->set_transform({});
// 3. Invoke the transform(a, b, c, d, e, f) method with the same arguments.
transform(a, b, c, d, e, f);
@ -88,7 +91,10 @@ public:
return {};
// 3. Reset the current transformation matrix to matrix.
my_drawing_state().transform = { static_cast<float>(matrix->a()), static_cast<float>(matrix->b()), static_cast<float>(matrix->c()), static_cast<float>(matrix->d()), static_cast<float>(matrix->e()), static_cast<float>(matrix->f()) };
auto transform = Gfx::AffineTransform { static_cast<float>(matrix->a()), static_cast<float>(matrix->b()), static_cast<float>(matrix->c()), static_cast<float>(matrix->d()), static_cast<float>(matrix->e()), static_cast<float>(matrix->f()) };
my_drawing_state().transform = transform;
if (auto* painter = static_cast<IncludingClass&>(*this).painter())
painter->set_transform(transform);
return {};
}
@ -97,6 +103,8 @@ public:
{
// The resetTransform() method, when invoked, must reset the current transformation matrix to the identity matrix.
my_drawing_state().transform = {};
if (auto* painter = static_cast<IncludingClass&>(*this).painter())
painter->set_transform({});
}
protected:

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
@ -97,11 +97,11 @@ void CanvasRenderingContext2D::fill_rect(float x, float y, float width, float he
void CanvasRenderingContext2D::clear_rect(float x, float y, float width, float height)
{
draw_clipped([&](auto& painter) {
auto rect = drawing_state().transform.map(Gfx::FloatRect(x, y, width, height));
painter.underlying_painter().clear_rect(enclosing_int_rect(rect), Color());
return rect;
});
if (auto* painter = this->painter()) {
auto rect = Gfx::FloatRect(x, y, width, height);
painter->clear_rect(rect, Color::Transparent);
did_draw(rect);
}
}
void CanvasRenderingContext2D::stroke_rect(float x, float y, float width, float height)
@ -154,21 +154,20 @@ WebIDL::ExceptionOr<void> CanvasRenderingContext2D::draw_image_internal(CanvasIm
return {};
// 6. Paint the region of the image argument specified by the source rectangle on the region of the rendering context's output bitmap specified by the destination rectangle, after applying the current transformation matrix to the destination rectangle.
draw_clipped([&](auto& painter) {
auto scaling_mode = Gfx::ScalingMode::NearestNeighbor;
if (drawing_state().image_smoothing_enabled) {
// FIXME: Honor drawing_state().image_smoothing_quality
scaling_mode = Gfx::ScalingMode::BilinearBlend;
}
auto scaling_mode = Gfx::ScalingMode::NearestNeighbor;
if (drawing_state().image_smoothing_enabled) {
// FIXME: Honor drawing_state().image_smoothing_quality
scaling_mode = Gfx::ScalingMode::BilinearBlend;
}
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);
if (auto* painter = this->painter()) {
painter->draw_bitmap(destination_rect, *bitmap, source_rect.to_rounded<int>(), scaling_mode, drawing_state().global_alpha);
did_draw(destination_rect);
}
// 7. If image is not origin-clean, then set the CanvasRenderingContext2D's origin-clean flag to false.
if (image_is_not_origin_clean(image))
m_origin_clean = false;
return destination_rect;
});
// 7. If image is not origin-clean, then set the CanvasRenderingContext2D's origin-clean flag to false.
if (image_is_not_origin_clean(image))
m_origin_clean = false;
return {};
}
@ -181,25 +180,17 @@ void CanvasRenderingContext2D::did_draw(Gfx::FloatRect const&)
canvas_element().paintable()->set_needs_display();
}
Gfx::DeprecatedPainter* CanvasRenderingContext2D::painter()
Gfx::Painter* CanvasRenderingContext2D::painter()
{
if (!canvas_element().bitmap()) {
if (!canvas_element().create_bitmap())
return nullptr;
canvas_element().document().invalidate_display_list();
m_painter = make<Gfx::DeprecatedPainter>(*canvas_element().bitmap());
m_painter = Gfx::Painter::create(*canvas_element().bitmap());
}
return m_painter.ptr();
}
Optional<Gfx::AntiAliasingPainter> CanvasRenderingContext2D::antialiased_painter()
{
auto painter = this->painter();
if (painter)
return Gfx::AntiAliasingPainter { *painter };
return {};
}
Gfx::Path CanvasRenderingContext2D::text_path(StringView text, float x, float y, Optional<double> max_width)
{
if (max_width.has_value() && max_width.value() <= 0)
@ -271,15 +262,19 @@ void CanvasRenderingContext2D::begin_path()
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->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, drawing_state.global_alpha);
}
return path.bounding_box();
});
auto* painter = this->painter();
if (!painter)
return;
auto& state = drawing_state();
if (auto color = state.stroke_style.as_color(); color.has_value()) {
painter->stroke_path(path, color->with_opacity(state.global_alpha), state.line_width);
} else {
painter->stroke_path(path, state.stroke_style.to_gfx_paint_style(), state.line_width, state.global_alpha);
}
did_draw(path.bounding_box());
}
void CanvasRenderingContext2D::stroke()
@ -305,17 +300,20 @@ static Gfx::WindingRule parse_fill_rule(StringView fill_rule)
void CanvasRenderingContext2D::fill_internal(Gfx::Path const& path, Gfx::WindingRule winding_rule)
{
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_to_fill, color->with_opacity(drawing_state.global_alpha), winding_rule);
} else {
painter.fill_path(path_to_fill, drawing_state.fill_style.to_gfx_paint_style(), drawing_state.global_alpha, winding_rule);
}
return path_to_fill.bounding_box();
});
auto* painter = this->painter();
if (!painter)
return;
auto path_to_fill = path;
path_to_fill.close_all_subpaths();
auto& state = this->drawing_state();
if (auto color = state.fill_style.as_color(); color.has_value()) {
painter->fill_path(path_to_fill, color->with_opacity(state.global_alpha), winding_rule);
} else {
painter->fill_path(path_to_fill, state.fill_style.to_gfx_paint_style(), state.global_alpha, winding_rule);
}
did_draw(path_to_fill.bounding_box());
}
void CanvasRenderingContext2D::fill(StringView fill_rule)
@ -381,20 +379,21 @@ WebIDL::ExceptionOr<JS::GCPtr<ImageData>> CanvasRenderingContext2D::get_image_da
void CanvasRenderingContext2D::put_image_data(ImageData const& image_data, float x, float y)
{
draw_clipped([&](auto& painter) {
painter.underlying_painter().blit(Gfx::IntPoint(x, y), image_data.bitmap(), image_data.bitmap().rect());
return Gfx::FloatRect(x, y, image_data.width(), image_data.height());
});
if (auto* painter = this->painter()) {
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);
did_draw(dst_rect);
}
}
// https://html.spec.whatwg.org/multipage/canvas.html#reset-the-rendering-context-to-its-default-state
void CanvasRenderingContext2D::reset_to_default_state()
{
auto painter = this->painter();
auto* bitmap = canvas_element().bitmap();
// 1. Clear canvas's bitmap to transparent black.
if (painter)
painter->clear_rect(painter->target().rect(), Color::Transparent);
if (bitmap)
bitmap->fill(Gfx::Color::Transparent);
// 2. Empty the list of subpaths in context's current default path.
path().clear();
@ -405,8 +404,8 @@ void CanvasRenderingContext2D::reset_to_default_state()
// 4. Reset everything that drawing state consists of to their initial values.
reset_drawing_state();
if (painter)
did_draw(painter->target().rect().to_type<float>());
if (bitmap)
did_draw(bitmap->rect().to_type<float>());
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-measuretext

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -10,9 +10,9 @@
#include <AK/String.h>
#include <AK/Variant.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/Color.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Painter.h>
#include <LibGfx/Path.h>
#include <LibGfx/PathClipper.h>
#include <LibWeb/Bindings/PlatformObject.h>
@ -99,6 +99,8 @@ public:
HTMLCanvasElement& canvas_element();
HTMLCanvasElement const& canvas_element() const;
[[nodiscard]] Gfx::Painter* painter();
private:
explicit CanvasRenderingContext2D(JS::Realm&, HTMLCanvasElement&);
@ -118,26 +120,10 @@ private:
void did_draw(Gfx::FloatRect const&);
template<typename TDrawFunction>
void draw_clipped(TDrawFunction draw_function)
{
auto painter = this->antialiased_painter();
if (!painter.has_value())
return;
Gfx::ScopedPathClip clipper(painter->underlying_painter(), drawing_state().clip);
auto draw_rect = draw_function(*painter);
if (drawing_state().clip.has_value())
draw_rect.intersect(drawing_state().clip->path.bounding_box());
did_draw(draw_rect);
}
RefPtr<Gfx::Font const> current_font();
PreparedText prepare_text(ByteString const& text, float max_width = INFINITY);
Gfx::DeprecatedPainter* painter();
Optional<Gfx::AntiAliasingPainter> antialiased_painter();
Gfx::Path rect_path(float x, float y, float width, float height);
Gfx::Path text_path(StringView text, float x, float y, Optional<double> max_width);
@ -147,7 +133,7 @@ private:
void clip_internal(Gfx::Path&, Gfx::WindingRule);
JS::NonnullGCPtr<HTMLCanvasElement> m_element;
OwnPtr<Gfx::DeprecatedPainter> m_painter;
OwnPtr<Gfx::Painter> m_painter;
// https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-origin-clean
bool m_origin_clean { true };