From 8e21bbf7bf82c0303b9384018659fe91a6ada424 Mon Sep 17 00:00:00 2001 From: Lucas CHOLLET Date: Fri, 9 Feb 2024 21:35:33 -0500 Subject: [PATCH] LibGfx/TIFF: Add support for tiled images A tile is basically a strip with a user-defined width. With that in mind, adding support for them is quite straightforward. As a lot the common code was named after 'strips', to avoid future confusion I renamed everything that interact with either strips or tiles to a global term: 'segment'. Note that tiled images are supposed to always have a 'TileOffsets' tag instead of 'StripOffset'. However, this doesn't seem to be enforced by encoders, so we support having either of them indifferently. The test case was generated with the following Python script: import pyvips img = pyvips.Image.new_from_file('deflate.tiff') img.write_to_file('tiled.tiff', compression=pyvips.ForeignTiffCompression.DEFLATE, tile=True, tile_width=64, tile_height=64) --- Tests/LibGfx/TestImageDecoder.cpp | 12 +++ Tests/LibGfx/test-inputs/tiff/tiled.tiff | Bin 0 -> 13160 bytes .../LibGfx/ImageFormats/TIFFLoader.cpp | 75 +++++++++++------- 3 files changed, 57 insertions(+), 30 deletions(-) create mode 100644 Tests/LibGfx/test-inputs/tiff/tiled.tiff diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index aa68add43d..7f7e15836b 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -776,6 +776,18 @@ TEST_CASE(test_tiff_cmyk) EXPECT_NE(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::White); } +TEST_CASE(test_tiff_tiled) +{ + auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/tiled.tiff"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_invalid_tag) { auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/invalid_tag.tiff"sv))); diff --git a/Tests/LibGfx/test-inputs/tiff/tiled.tiff b/Tests/LibGfx/test-inputs/tiff/tiled.tiff new file mode 100644 index 0000000000000000000000000000000000000000..3a63b6ea05ff6b5e4b0677c8b03c53051fdf1568 GIT binary patch literal 13160 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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 94afecac90..7eb7aa6cb2 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp @@ -81,6 +81,11 @@ public: return m_metadata.strip_byte_counts().has_value() ? m_metadata.strip_byte_counts() : m_metadata.tile_byte_counts(); } + bool is_tiled() const + { + return m_metadata.tile_width().has_value() && m_metadata.tile_length().has_value(); + } + ErrorOr ensure_baseline_tags_are_correct() const { if (!segment_offsets().has_value()) @@ -92,7 +97,7 @@ public: if (segment_offsets()->size() != segment_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) + if (!m_metadata.rows_per_strip().has_value() && segment_byte_counts()->size() != 1 && !is_tiled()) 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; })) @@ -278,12 +283,15 @@ private: return CMYK { first_component, second_component, third_component, fourth_component }; } - template, u32, IntSize> StripDecoder> - ErrorOr loop_over_pixels(StripDecoder&& strip_decoder) + template, u32, IntSize> SegmentDecoder> + ErrorOr loop_over_pixels(SegmentDecoder&& segment_decoder) { - 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_length()); + auto const offsets = *segment_offsets(); + auto const byte_counts = *segment_byte_counts(); + + auto const segment_length = m_metadata.tile_length().value_or(m_metadata.rows_per_strip().value_or(*m_metadata.image_length())); + auto const segment_width = m_metadata.tile_width().value_or(*m_metadata.image_width()); + auto const segment_per_rows = m_metadata.tile_width().map([&](u32 w) { return ceil_div(*m_metadata.image_width(), w); }).value_or(1); Variant oriented_bitmap = TRY(([&]() -> ErrorOr> { if (metadata().photometric_interpretation() == PhotometricInterpretation::CMYK) @@ -291,27 +299,32 @@ private: return ExifOrientedBitmap::create(*metadata().orientation(), { *metadata().image_width(), *metadata().image_length() }, BitmapFormat::BGRA8888); }())); - for (u32 strip_index = 0; strip_index < strips_offset.size(); ++strip_index) { - TRY(m_stream->seek(strips_offset[strip_index])); + for (u32 segment_index = 0; segment_index < offsets.size(); ++segment_index) { + TRY(m_stream->seek(offsets[segment_index])); - auto const strip_width = *m_metadata.image_width(); - auto const rows_in_strip = strip_index < strips_offset.size() - 1 ? rows_per_strip : *m_metadata.image_length() - rows_per_strip * strip_index; + auto const rows_in_segment = segment_index < offsets.size() - 1 ? segment_length : *m_metadata.image_length() - segment_length * segment_index; + auto const decoded_bytes = TRY(segment_decoder(byte_counts[segment_index], { segment_width, rows_in_segment })); + auto decoded_segment = make(decoded_bytes); + auto decoded_stream = make(move(decoded_segment)); - auto const decoded_bytes = TRY(strip_decoder(strip_byte_counts[strip_index], { strip_width, rows_in_strip })); - auto decoded_strip = make(decoded_bytes); - auto decoded_stream = make(move(decoded_strip)); - - for (u32 row = 0; row < rows_per_strip; row++) { - auto const image_row = row + rows_per_strip * strip_index; + for (u32 row = 0; row < segment_length; row++) { + auto const image_row = row + segment_length * (segment_index / segment_per_rows); if (image_row >= *m_metadata.image_length()) break; Optional last_color {}; - for (u32 column = 0; column < *m_metadata.image_width(); ++column) { + for (u32 column = 0; column < segment_width; ++column) { + // If image_length % segment_length != 0, the last tile will be padded. + // This variable helps us to skip these last columns. Note that we still + // need to read the sample from the stream. + auto const image_column = column + segment_width * (segment_index % segment_per_rows); + if (metadata().photometric_interpretation() == PhotometricInterpretation::CMYK) { auto const cmyk = TRY(read_color_cmyk(*decoded_stream)); - oriented_bitmap.get().set_pixel(column, image_row, cmyk); + if (image_column >= *m_metadata.image_width()) + continue; + oriented_bitmap.get().set_pixel(image_column, image_row, cmyk); } else { auto color = TRY(read_color(*decoded_stream)); @@ -326,7 +339,9 @@ private: } last_color = color; - oriented_bitmap.get().set_pixel(column, image_row, color.value()); + if (image_column >= *m_metadata.image_width()) + continue; + oriented_bitmap.get().set_pixel(image_column, image_row, color.value()); } } @@ -388,13 +403,13 @@ private: TRY(ensure_tags_are_correct_for_ccitt()); ByteBuffer decoded_bytes {}; - auto decode_ccitt_rle_strip = [&](u32 num_bytes, IntSize image_size) -> ErrorOr { + auto decode_ccitt_rle_segment = [&](u32 num_bytes, IntSize segment_size) -> ErrorOr { auto const encoded_bytes = TRY(read_bytes_considering_fill_order(num_bytes)); - decoded_bytes = TRY(CCITT::decode_ccitt_rle(encoded_bytes, image_size.width(), image_size.height())); + decoded_bytes = TRY(CCITT::decode_ccitt_rle(encoded_bytes, segment_size.width(), segment_size.height())); return decoded_bytes; }; - TRY(loop_over_pixels(move(decode_ccitt_rle_strip))); + TRY(loop_over_pixels(move(decode_ccitt_rle_segment))); break; } case Compression::Group3Fax: { @@ -402,22 +417,22 @@ private: auto const parameters = parse_t4_options(*m_metadata.t4_options()); ByteBuffer decoded_bytes {}; - auto decode_group3_strip = [&](u32 num_bytes, IntSize image_size) -> ErrorOr { + auto decode_group3_segment = [&](u32 num_bytes, IntSize segment_size) -> ErrorOr { auto const encoded_bytes = TRY(read_bytes_considering_fill_order(num_bytes)); - decoded_bytes = TRY(CCITT::decode_ccitt_group3(encoded_bytes, image_size.width(), image_size.height(), parameters)); + decoded_bytes = TRY(CCITT::decode_ccitt_group3(encoded_bytes, segment_size.width(), segment_size.height(), parameters)); return decoded_bytes; }; - TRY(loop_over_pixels(move(decode_group3_strip))); + TRY(loop_over_pixels(move(decode_group3_segment))); break; } case Compression::LZW: { ByteBuffer decoded_bytes {}; - auto decode_lzw_strip = [&](u32 num_bytes, IntSize) -> ErrorOr { + auto decode_lzw_segment = [&](u32 num_bytes, IntSize) -> ErrorOr { auto const encoded_bytes = TRY(m_stream->read_in_place(num_bytes)); if (encoded_bytes.is_empty()) - return Error::from_string_literal("TIFFImageDecoderPlugin: Unable to read from empty LZW strip"); + return Error::from_string_literal("TIFFImageDecoderPlugin: Unable to read from empty LZW segment"); // Note: AFAIK, there are two common ways to use LZW compression: // - With a LittleEndian stream and no Early-Change, this is used in the GIF format @@ -434,7 +449,7 @@ private: return decoded_bytes; }; - TRY(loop_over_pixels(move(decode_lzw_strip))); + TRY(loop_over_pixels(move(decode_lzw_segment))); break; } case Compression::AdobeDeflate: @@ -456,13 +471,13 @@ private: // Section 9: PackBits Compression ByteBuffer decoded_bytes {}; - auto decode_packbits_strip = [&](u32 num_bytes, IntSize) -> ErrorOr { + auto decode_packbits_segment = [&](u32 num_bytes, IntSize) -> ErrorOr { auto const encoded_bytes = TRY(m_stream->read_in_place(num_bytes)); decoded_bytes = TRY(Compress::PackBits::decode_all(encoded_bytes)); return decoded_bytes; }; - TRY(loop_over_pixels(move(decode_packbits_strip))); + TRY(loop_over_pixels(move(decode_packbits_segment))); break; } default: