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 50f723bf67..40479c0eb3 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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 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(); } }