From 234d084876ceba8df8abc5517d38e19f00efe124 Mon Sep 17 00:00:00 2001 From: Lucas CHOLLET Date: Fri, 8 Dec 2023 23:02:22 -0500 Subject: [PATCH] LibGfx/TIFF: Add support for bit-depth up to 32 bits per sample This makes us support every "minisblack" and "rgb-contig" images from the depth folder of libtiff's test suite: https://libtiff.gitlab.io/libtiff/images.html --- Tests/LibGfx/TestImageDecoder.cpp | 12 +++++ Tests/LibGfx/test-inputs/tiff/16_bits.tiff | Bin 0 -> 37589 bytes .../LibGfx/ImageFormats/TIFFLoader.cpp | 42 +++++++++++++----- 3 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 Tests/LibGfx/test-inputs/tiff/16_bits.tiff diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index 50f723bf678..40479c0eb32 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -431,6 +431,18 @@ TEST_CASE(test_tiff_grayscale) EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color(130, 130, 130)); } +TEST_CASE(test_tiff_16_bits) +{ + auto file = MUST(Core::MappedFile::map(TEST_INPUT("tiff/16_bits.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_webp_simple_lossy) { auto file = MUST(Core::MappedFile::map(TEST_INPUT("webp/simple-vp8.webp"sv))); diff --git a/Tests/LibGfx/test-inputs/tiff/16_bits.tiff b/Tests/LibGfx/test-inputs/tiff/16_bits.tiff new file mode 100644 index 0000000000000000000000000000000000000000..73b1368703e4deef99a5149d6e4cc143d180bf33 GIT binary patch literal 37589 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 S_|F`9^0CJp^Dh6d read_component(BigEndianInputBitStream& stream, u8 bits) + { + // FIXME: This function truncates everything to 8-bits + auto const value = TRY(stream.read_bits(bits)); + + if (bits > 8) + return value >> (bits - 8); + return value << (8 - bits); + } + + ErrorOr read_color(BigEndianInputBitStream& stream) + { + auto bits_per_sample = *m_metadata.bits_per_sample(); + if (m_metadata.samples_per_pixel().value_or(3) == 3) { + auto const first_component = TRY(read_component(stream, bits_per_sample[0])); + auto const second_component = TRY(read_component(stream, bits_per_sample[1])); + auto const third_component = TRY(read_component(stream, bits_per_sample[2])); + return Color(first_component, second_component, third_component); + } + + if (*m_metadata.samples_per_pixel() == 1) { + auto const luminosity = TRY(read_component(stream, bits_per_sample[0])); + return Color(luminosity, luminosity, luminosity); + } + + return Error::from_string_literal("Unsupported number of sample per pixel"); + } + template, u32> StripDecoder> ErrorOr loop_over_pixels(StripDecoder&& strip_decoder) { @@ -87,6 +115,7 @@ private: auto const decoded_bytes = TRY(strip_decoder(strip_byte_counts[strip_index])); 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; @@ -96,16 +125,7 @@ private: Optional last_color {}; for (u32 column = 0; column < *m_metadata.image_width(); ++column) { - Color color {}; - - if (m_metadata.samples_per_pixel().value_or(3) == 3) { - color = Color { TRY(decoded_strip->template read_value()), TRY(decoded_strip->template read_value()), TRY(decoded_strip->template read_value()) }; - } else if (*m_metadata.samples_per_pixel() == 1) { - auto luminosity = TRY(decoded_strip->template read_value()); - color = Color { luminosity, luminosity, luminosity }; - } else { - return Error::from_string_literal("Unsupported number of sample per pixel"); - } + auto color = TRY(read_color(*decoded_stream)); if (m_metadata.predictor() == Predictor::HorizontalDifferencing && last_color.has_value()) { color.set_red(last_color->red() + color.red()); @@ -116,6 +136,8 @@ private: last_color = color; m_bitmap->set_pixel(column, scanline, color); } + + decoded_stream->align_to_byte_boundary(); } }