From c2c73654943e322aba4f4e5140073f7ff40b6167 Mon Sep 17 00:00:00 2001 From: Lucas CHOLLET Date: Tue, 16 Jan 2024 00:27:52 -0500 Subject: [PATCH] LibGfx/TIFF: Accept images with a single strip and no RowsPerStrip tag This tag is required by the specification, but some encoders (at least Krita) don't write it for images with a single strip. The test file was generated by opening deflate.tiff in Krita and saving it with the DEFLATE compression. --- Tests/LibGfx/TestImageDecoder.cpp | 12 ++++++++++++ Tests/LibGfx/test-inputs/tiff/krita.tif | Bin 0 -> 7446 bytes .../Libraries/LibGfx/ImageFormats/TIFFLoader.cpp | 10 +++++++--- Userland/Libraries/LibGfx/TIFFGenerator.py | 2 +- 4 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 Tests/LibGfx/test-inputs/tiff/krita.tif diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index 596964dd27..069daa4d27 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -591,6 +591,18 @@ TEST_CASE(test_tiff_deflate) EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::Red); } +TEST_CASE(test_tiff_krita) +{ + auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/krita.tif"sv))); + EXPECT(Gfx::TIFFImageDecoderPlugin::sniff(file->bytes())); + auto plugin_decoder = TRY_OR_FAIL(Gfx::TIFFImageDecoderPlugin::create(file->bytes())); + + auto frame = TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 400, 300 })); + + EXPECT_EQ(frame.image->get_pixel(0, 0), Gfx::Color::NamedColor::White); + EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::Red); +} + TEST_CASE(test_tiff_orientation) { auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/orientation.tiff"sv))); diff --git a/Tests/LibGfx/test-inputs/tiff/krita.tif b/Tests/LibGfx/test-inputs/tiff/krita.tif new file mode 100644 index 0000000000000000000000000000000000000000..182657a0b7250993567609d89e182ab322a69de8 GIT binary patch literal 7446 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 literal 0 HcmV?d00001 diff --git a/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp b/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp index f6b60ddd51..ff5bedee69 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp @@ -75,6 +75,9 @@ public: if (m_metadata.strip_offsets()->size() != m_metadata.strip_byte_counts()->size()) return Error::from_string_literal("TIFFImageDecoderPlugin: StripsOffset and StripByteCount have different sizes"); + if (!m_metadata.rows_per_strip().has_value() && m_metadata.strip_byte_counts()->size() != 1) + return Error::from_string_literal("TIFFImageDecoderPlugin: RowsPerStrip is not provided and impossible to deduce"); + if (any_of(*m_metadata.bits_per_sample(), [](auto bit_depth) { return bit_depth == 0 || bit_depth > 32; })) return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid value in BitsPerSample"); @@ -238,19 +241,20 @@ private: { auto const strips_offset = *m_metadata.strip_offsets(); auto const strip_byte_counts = *m_metadata.strip_byte_counts(); + auto const rows_per_strip = m_metadata.rows_per_strip().value_or(*m_metadata.image_height()); auto oriented_bitmap = TRY(ExifOrientedBitmap::create(BitmapFormat::BGRA8888, { *metadata().image_width(), *metadata().image_height() }, *metadata().orientation())); for (u32 strip_index = 0; strip_index < strips_offset.size(); ++strip_index) { TRY(m_stream->seek(strips_offset[strip_index])); - auto const rows_in_strip = strip_index < strips_offset.size() - 1 ? *m_metadata.rows_per_strip() : *m_metadata.image_height() - *m_metadata.rows_per_strip() * strip_index; + auto const rows_in_strip = strip_index < strips_offset.size() - 1 ? rows_per_strip : *m_metadata.image_height() - rows_per_strip * strip_index; auto const decoded_bytes = TRY(strip_decoder(strip_byte_counts[strip_index], rows_in_strip)); auto decoded_strip = make(decoded_bytes); auto decoded_stream = make(move(decoded_strip)); - for (u32 row = 0; row < *m_metadata.rows_per_strip(); row++) { - auto const scanline = row + *m_metadata.rows_per_strip() * strip_index; + for (u32 row = 0; row < rows_per_strip; row++) { + auto const scanline = row + rows_per_strip * strip_index; if (scanline >= *m_metadata.image_height()) break; diff --git a/Userland/Libraries/LibGfx/TIFFGenerator.py b/Userland/Libraries/LibGfx/TIFFGenerator.py index b95d409697..ceead495e9 100755 --- a/Userland/Libraries/LibGfx/TIFFGenerator.py +++ b/Userland/Libraries/LibGfx/TIFFGenerator.py @@ -128,7 +128,7 @@ known_tags: List[Tag] = [ Tag('273', [TIFFType.UnsignedShort, TIFFType.UnsignedLong], [], None, "StripOffsets", is_required=True), Tag('274', [TIFFType.UnsignedShort], [1], Orientation.Default, "Orientation", Orientation), Tag('277', [TIFFType.UnsignedShort], [1], None, "SamplesPerPixel", is_required=True), - Tag('278', [TIFFType.UnsignedShort, TIFFType.UnsignedLong], [1], None, "RowsPerStrip", is_required=True), + Tag('278', [TIFFType.UnsignedShort, TIFFType.UnsignedLong], [1], None, "RowsPerStrip", is_required=False), Tag('279', [TIFFType.UnsignedShort, TIFFType.UnsignedLong], [], None, "StripByteCounts", is_required=True), Tag('282', [TIFFType.UnsignedRational], [1], None, "XResolution"), Tag('283', [TIFFType.UnsignedRational], [1], None, "YResolution"),