diff --git a/Userland/Libraries/LibGfx/CMakeLists.txt b/Userland/Libraries/LibGfx/CMakeLists.txt index a9ca50b0b13..a451ff556ce 100644 --- a/Userland/Libraries/LibGfx/CMakeLists.txt +++ b/Userland/Libraries/LibGfx/CMakeLists.txt @@ -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/") diff --git a/Userland/Libraries/LibGfx/Forward.h b/Userland/Libraries/LibGfx/Forward.h index 42a8fd6eb2e..e08e6c7ea47 100644 --- a/Userland/Libraries/LibGfx/Forward.h +++ b/Userland/Libraries/LibGfx/Forward.h @@ -27,6 +27,7 @@ class Line; class AntiAliasingPainter; class DeprecatedPainter; +class Painter; class Palette; class PaletteImpl; class Path; diff --git a/Userland/Libraries/LibGfx/PaintStyle.h b/Userland/Libraries/LibGfx/PaintStyle.h index 21265e26e42..073d8d2399e 100644 --- a/Userland/Libraries/LibGfx/PaintStyle.h +++ b/Userland/Libraries/LibGfx/PaintStyle.h @@ -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; diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp new file mode 100644 index 00000000000..3de5f326887 --- /dev/null +++ b/Userland/Libraries/LibGfx/Painter.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Gfx { + +Painter::~Painter() = default; + +NonnullOwnPtr Painter::create(NonnullRefPtr target_bitmap) +{ + return make(move(target_bitmap)); +} + +} diff --git a/Userland/Libraries/LibGfx/Painter.h b/Userland/Libraries/LibGfx/Painter.h new file mode 100644 index 00000000000..510099ab3b3 --- /dev/null +++ b/Userland/Libraries/LibGfx/Painter.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Gfx { + +class Painter { +public: + static NonnullOwnPtr create(NonnullRefPtr); + + 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; +}; + +} diff --git a/Userland/Libraries/LibGfx/PainterSkia.cpp b/Userland/Libraries/LibGfx/PainterSkia.cpp new file mode 100644 index 00000000000..da12ede21de --- /dev/null +++ b/Userland/Libraries/LibGfx/PainterSkia.cpp @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2024, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define AK_DONT_REPLACE_STD + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gfx { + +struct PainterSkia::Impl { + NonnullRefPtr gfx_bitmap; + OwnPtr sk_bitmap; + OwnPtr sk_canvas; + + Impl(NonnullRefPtr target_bitmap) + : gfx_bitmap(move(target_bitmap)) + { + sk_bitmap = make(); + 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(*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 subpath_start_point; + Optional 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 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(static_cast(src_bitmap.scanline(0))), src_bitmap.pitch()); + + SkPaint paint; + paint.setAlpha(static_cast(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(style)) { + auto const& linear_gradient = static_cast(style); + auto const& color_stops = linear_gradient.color_stops(); + + SkPaint paint; + Vector colors; + colors.ensure_capacity(color_stops.size()); + Vector 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 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(style)) { + auto const& radial_gradient = static_cast(style); + auto const& color_stops = radial_gradient.color_stops(); + + SkPaint paint; + Vector colors; + colors.ensure_capacity(color_stops.size()); + Vector 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(); +} + +} diff --git a/Userland/Libraries/LibGfx/PainterSkia.h b/Userland/Libraries/LibGfx/PainterSkia.h new file mode 100644 index 00000000000..bbb7319be67 --- /dev/null +++ b/Userland/Libraries/LibGfx/PainterSkia.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Gfx { + +class PainterSkia final : public Painter { +public: + explicit PainterSkia(NonnullRefPtr); + 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 m_impl; +}; + +} diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasTransform.h b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasTransform.h index 4922c9e3fb3..79b72936419 100644 --- a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasTransform.h +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasTransform.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include #include @@ -71,6 +72,8 @@ public: // 2. Reset the current transformation matrix to the identity matrix. my_drawing_state().transform = {}; + if (auto* painter = static_cast(*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(matrix->a()), static_cast(matrix->b()), static_cast(matrix->c()), static_cast(matrix->d()), static_cast(matrix->e()), static_cast(matrix->f()) }; + auto transform = Gfx::AffineTransform { static_cast(matrix->a()), static_cast(matrix->b()), static_cast(matrix->c()), static_cast(matrix->d()), static_cast(matrix->e()), static_cast(matrix->f()) }; + my_drawing_state().transform = transform; + if (auto* painter = static_cast(*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(*this).painter()) + painter->set_transform({}); } protected: diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index 95c0cf3561c..937ed9f9647 100644 --- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2022, Andreas Kling + * Copyright (c) 2020-2024, Andreas Kling * Copyright (c) 2021-2022, Linus Groh * Copyright (c) 2023, MacDue * @@ -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 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(), *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(), 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(*canvas_element().bitmap()); + m_painter = Gfx::Painter::create(*canvas_element().bitmap()); } return m_painter.ptr(); } -Optional 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 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> 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()); + if (bitmap) + did_draw(bitmap->rect().to_type()); } // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-measuretext diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h index ef6d57db53f..3ef36eed475 100644 --- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h +++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2022, Andreas Kling + * Copyright (c) 2020-2024, Andreas Kling * Copyright (c) 2021-2022, Linus Groh * * SPDX-License-Identifier: BSD-2-Clause @@ -10,9 +10,9 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -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 - 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 current_font(); PreparedText prepare_text(ByteString const& text, float max_width = INFINITY); - Gfx::DeprecatedPainter* painter(); - Optional 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 max_width); @@ -147,7 +133,7 @@ private: void clip_internal(Gfx::Path&, Gfx::WindingRule); JS::NonnullGCPtr m_element; - OwnPtr m_painter; + OwnPtr m_painter; // https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-origin-clean bool m_origin_clean { true };