diff options
author | Andreas Kling <kling@serenityos.org> | 2022-04-07 16:52:53 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-04-07 17:06:02 +0200 |
commit | cb3a2b347b783e56c3851f75532689b2b6754243 (patch) | |
tree | e3b65aeb381adfbaa1f4bb13655f1071f82f140f /Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp | |
parent | 3c4bdd7cfa1b387e7a36df1e84a1a3c2f0b03f4f (diff) | |
download | serenity-cb3a2b347b783e56c3851f75532689b2b6754243.zip |
LibWeb: Support CRC2D.drawImage() with affine transform
Previously, we only remapped the destination rect through the context's
affine transform, but didn't actually paint through it.
This patch fixes that by implementing a very inefficient algorithm for
rasterizing a transformed bitmap. When the context has a plain identity
transform, we bypass this algorithm in favor of calling Gfx::Painter
directly as we did before.
This makes the player character in "Biolab Disaster" able to turn left!
Diffstat (limited to 'Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp')
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp | 55 |
1 files changed, 53 insertions, 2 deletions
diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index d3994da800..b369f386f6 100644 --- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -9,6 +9,7 @@ #include <AK/ExtraMathConstants.h> #include <AK/OwnPtr.h> #include <LibGfx/Painter.h> +#include <LibGfx/Quad.h> #include <LibGfx/Rect.h> #include <LibWeb/Bindings/CanvasRenderingContext2DWrapper.h> #include <LibWeb/Bindings/WindowObject.h> @@ -179,8 +180,58 @@ DOM::ExceptionOr<void> CanvasRenderingContext2D::draw_image(CanvasImageSource co auto painter = this->painter(); if (!painter) return {}; - auto transformed_destination_rect = m_drawing_state.transform.map(destination_rect); - painter->draw_scaled_bitmap(transformed_destination_rect.to_rounded<int>(), *bitmap, source_rect, 1.0f, Gfx::Painter::ScalingMode::BilinearBlend); + + if (m_drawing_state.transform.is_identity()) { + // There's no affine transformation to worry about, we can just call Gfx::Painter. + painter->draw_scaled_bitmap(destination_rect.to_rounded<int>(), *bitmap, source_rect, 1.0f, Gfx::Painter::ScalingMode::BilinearBlend); + } 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 = m_drawing_state.transform.inverse(); + if (!inverse_transform.has_value()) + return {}; + + auto destination_quad = m_drawing_state.transform.map_to_quad(destination_rect); + auto destination_bounding_rect = destination_quad.bounding_rect().to_rounded<int>(); + + 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<float>())) + continue; + auto source_point = source_transform.map(inverse_transform->map(destination_point)).to_rounded<int>(); + 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)); + } + } + } // 7. If image is not origin-clean, then set the CanvasRenderingContext2D's origin-clean flag to false. if (image_is_not_origin_clean(image)) |