diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp index c2d3468341..84c936e7b8 100644 --- a/Userland/Libraries/LibGfx/Painter.cpp +++ b/Userland/Libraries/LibGfx/Painter.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -2423,4 +2424,60 @@ void Painter::draw_text_run(FloatPoint const& baseline_start, Utf8View const& st } } +void Painter::draw_scaled_bitmap_with_transform(Gfx::IntRect const& dst_rect, Gfx::Bitmap const& bitmap, Gfx::FloatRect const& src_rect, Gfx::AffineTransform const& transform, float opacity, Gfx::Painter::ScalingMode scaling_mode) +{ + if (transform.is_identity_or_translation()) { + translate(transform.e(), transform.f()); + draw_scaled_bitmap(dst_rect, bitmap, src_rect, opacity, scaling_mode); + translate(-transform.e(), -transform.f()); + } else { + // The painter has an affine transform, we have to draw through it! + + // FIXME: This is *super* inefficient. + // What we currently do, roughly: + // - Map the destination rect through the context's transform. + // - Compute the bounding rect of the destination quad. + // - For each point in the computed bounding rect, reverse-map it to a point in the source image. + // - Sample the source image at the computed point. + // - Set or blend (depending on alpha values) one pixel in the canvas. + // - Loop. + + // FIXME: Painter should have an affine transform as part of its state and handle all of this instead. + + auto inverse_transform = transform.inverse(); + if (!inverse_transform.has_value()) + return; + + auto destination_quad = transform.map_to_quad(dst_rect.to_type()); + auto destination_bounding_rect = destination_quad.bounding_rect().to_rounded(); + + Gfx::AffineTransform source_transform; + source_transform.translate(src_rect.x(), src_rect.y()); + source_transform.scale(src_rect.width() / dst_rect.width(), src_rect.height() / dst_rect.height()); + source_transform.translate(-dst_rect.x(), -dst_rect.y()); + + for (int y = destination_bounding_rect.y(); y <= destination_bounding_rect.bottom(); ++y) { + for (int x = destination_bounding_rect.x(); x <= destination_bounding_rect.right(); ++x) { + auto destination_point = Gfx::IntPoint { x, y }; + if (!clip_rect().contains(destination_point)) + continue; + if (!destination_quad.contains(destination_point.to_type())) + continue; + auto source_point = source_transform.map(inverse_transform->map(destination_point)).to_rounded(); + if (!bitmap.rect().contains(source_point)) + continue; + auto source_color = bitmap.get_pixel(source_point); + if (source_color.alpha() == 0) + continue; + if (source_color.alpha() == 255) { + set_pixel(destination_point, source_color); + continue; + } + auto dst_color = target()->get_pixel(destination_point); + set_pixel(destination_point, dst_color.blend(source_color)); + } + } + } +} + } diff --git a/Userland/Libraries/LibGfx/Painter.h b/Userland/Libraries/LibGfx/Painter.h index 238ad0d0c3..ca52b455a4 100644 --- a/Userland/Libraries/LibGfx/Painter.h +++ b/Userland/Libraries/LibGfx/Painter.h @@ -56,6 +56,7 @@ public: void draw_bitmap(IntPoint const&, GlyphBitmap const&, Color = Color()); void draw_scaled_bitmap(IntRect const& dst_rect, Gfx::Bitmap const&, IntRect const& src_rect, float opacity = 1.0f, ScalingMode = ScalingMode::NearestNeighbor); void draw_scaled_bitmap(IntRect const& dst_rect, Gfx::Bitmap const&, FloatRect const& src_rect, float opacity = 1.0f, ScalingMode = ScalingMode::NearestNeighbor); + void draw_scaled_bitmap_with_transform(IntRect const& dst_rect, Gfx::Bitmap const&, FloatRect const& src_rect, Gfx::AffineTransform const&, float opacity = 1.0f, ScalingMode = ScalingMode::NearestNeighbor); void draw_triangle(IntPoint const&, IntPoint const&, IntPoint const&, Color); void draw_triangle(IntPoint const& offset, Span, Color); void draw_ellipse_intersecting(IntRect const&, Color, int thickness = 1); diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index cad1a07bf8..29e916fc27 100644 --- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -155,60 +155,7 @@ DOM::ExceptionOr CanvasRenderingContext2D::draw_image_internal(CanvasImage if (!painter) return {}; - auto& drawing_state = this->drawing_state(); - - if (drawing_state.transform.is_identity_or_translation()) { - painter->translate(drawing_state.transform.e(), drawing_state.transform.f()); - painter->draw_scaled_bitmap(destination_rect.to_rounded(), *bitmap, source_rect, 1.0f, Gfx::Painter::ScalingMode::BilinearBlend); - painter->translate(-drawing_state.transform.e(), -drawing_state.transform.f()); - } else { - // The context has an affine transform, we have to draw through it! - - // FIXME: This is *super* inefficient. - // What we currently do, roughly: - // - Map the destination rect through the context's transform. - // - Compute the bounding rect of the destination quad. - // - For each point in the computed bounding rect, reverse-map it to a point in the source image. - // - Sample the source image at the computed point. - // - Set or blend (depending on alpha values) one pixel in the canvas. - // - Loop. - - // FIXME: Gfx::Painter should have an affine transform as part of its state and handle all of this instead. - - auto inverse_transform = drawing_state.transform.inverse(); - if (!inverse_transform.has_value()) - return {}; - - auto destination_quad = drawing_state.transform.map_to_quad(destination_rect); - auto destination_bounding_rect = destination_quad.bounding_rect().to_rounded(); - - Gfx::AffineTransform source_transform; - source_transform.translate(source_x, source_y); - source_transform.scale(source_width / destination_width, source_height / destination_height); - source_transform.translate(-destination_x, -destination_y); - - for (int y = destination_bounding_rect.y(); y <= destination_bounding_rect.bottom(); ++y) { - for (int x = destination_bounding_rect.x(); x <= destination_bounding_rect.right(); ++x) { - auto destination_point = Gfx::IntPoint { x, y }; - if (!painter->clip_rect().contains(destination_point)) - continue; - if (!destination_quad.contains(destination_point.to_type())) - continue; - auto source_point = source_transform.map(inverse_transform->map(destination_point)).to_rounded(); - if (!bitmap->rect().contains(source_point)) - continue; - auto source_color = bitmap->get_pixel(source_point); - if (source_color.alpha() == 0) - continue; - if (source_color.alpha() == 255) { - painter->set_pixel(destination_point, source_color); - continue; - } - auto dst_color = painter->target()->get_pixel(destination_point); - painter->set_pixel(destination_point, dst_color.blend(source_color)); - } - } - } + painter->draw_scaled_bitmap_with_transform(destination_rect.to_rounded(), *bitmap, source_rect, drawing_state().transform, 1.0f, Gfx::Painter::ScalingMode::BilinearBlend); // 7. If image is not origin-clean, then set the CanvasRenderingContext2D's origin-clean flag to false. if (image_is_not_origin_clean(image))