From 144907f5bd9f8a66c0d884c1b554119194566e84 Mon Sep 17 00:00:00 2001 From: justus2510 Date: Wed, 16 Oct 2024 05:51:39 +0200 Subject: [PATCH] LibGfx: Apply Exif orientation for PNG images Fixes wpt/png/exif-chunk.html. At some point there should probably be some mechanism to handle this outside of the individual decoder plugins. The TIFF decoder seems to have its own version of this, and as far as I can tell, the JPEG decoder doesn't handle this at all, even though that's probably the most common use case for Exif orientations. :^) --- Tests/LibGfx/TestImageDecoder.cpp | 5 ++- .../LibGfx/ImageFormats/PNGLoader.cpp | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index d58797273ff..a723775c309 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -345,10 +345,13 @@ TEST_CASE(test_exif) EXPECT(Gfx::PNGImageDecoderPlugin::sniff(file->bytes())); auto plugin_decoder = TRY_OR_FAIL(Gfx::PNGImageDecoderPlugin::create(file->bytes())); - TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 100, 200 })); + auto frame = TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 200, 100 })); EXPECT(plugin_decoder->metadata().has_value()); auto const& exif_metadata = static_cast(plugin_decoder->metadata().value()); EXPECT_EQ(*exif_metadata.orientation(), Gfx::TIFF::Orientation::Rotate90Clockwise); + + EXPECT_EQ(frame.image->get_pixel(65, 70), Gfx::Color(0, 255, 0)); + EXPECT_EQ(frame.image->get_pixel(190, 10), Gfx::Color(255, 0, 0)); } TEST_CASE(test_png_malformed_frame) diff --git a/Userland/Libraries/LibGfx/ImageFormats/PNGLoader.cpp b/Userland/Libraries/LibGfx/ImageFormats/PNGLoader.cpp index 4937ffff7cb..53783df6cb9 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/PNGLoader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/PNGLoader.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -67,6 +68,7 @@ struct PNGLoadingContext { RefPtr decoded_frame_bitmap; ErrorOr read_frames(png_structp, png_infop); + ErrorOr apply_exif_orientation(); }; ErrorOr> PNGImageDecoderPlugin::create(ReadonlyBytes bytes) @@ -196,10 +198,40 @@ ErrorOr PNGImageDecoderPlugin::initialize() m_context->exif_metadata = TRY(TIFFImageDecoderPlugin::read_exif_metadata({ exif_data, exif_length })); } + if (m_context->exif_metadata) { + if (auto result = m_context->apply_exif_orientation(); result.is_error()) + dbgln("Could not apply eXIf chunk orientation for PNG: {}", result.error()); + } + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); return true; } +ErrorOr PNGLoadingContext::apply_exif_orientation() +{ + auto orientation = exif_metadata->orientation().value_or(TIFF::Orientation::Default); + if (orientation == TIFF::Orientation::Default) + return {}; + + for (auto& img_frame_descriptor : frame_descriptors) { + auto& img = img_frame_descriptor.image; + auto oriented_bmp = TRY(ExifOrientedBitmap::create(orientation, img->size(), img->format())); + + for (int y = 0; y < img->size().height(); ++y) { + for (int x = 0; x < img->size().width(); ++x) { + auto pixel = img->get_pixel(x, y); + oriented_bmp.set_pixel(x, y, pixel.value()); + } + } + + img_frame_descriptor.image = oriented_bmp.bitmap(); + } + + size = ExifOrientedBitmap::oriented_size(size, orientation); + + return {}; +} + static ErrorOr> render_animation_frame(AnimationFrame const& prev_animation_frame, AnimationFrame const& animation_frame, Bitmap const& decoded_frame_bitmap) { auto rendered_bitmap = TRY(prev_animation_frame.bitmap->clone());