summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2022-04-07 16:52:53 +0200
committerAndreas Kling <kling@serenityos.org>2022-04-07 17:06:02 +0200
commitcb3a2b347b783e56c3851f75532689b2b6754243 (patch)
treee3b65aeb381adfbaa1f4bb13655f1071f82f140f /Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
parent3c4bdd7cfa1b387e7a36df1e84a1a3c2f0b03f4f (diff)
downloadserenity-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.cpp55
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))