From 67522fab2e160b4e01d42fcd34215c21233cae8e Mon Sep 17 00:00:00 2001 From: Lucas CHOLLET Date: Sun, 17 Dec 2023 23:35:21 -0500 Subject: [PATCH] LibGfx/TIFF: Add support for RGBPalette images TIFF images with the PhotometricInterpretation tag set to RGBPalette are based on indexed colors instead of explicitly describing the color for each pixel. Let's add support for them. The test case was generated with GIMP using the Indexed image mode after adding an alpha layer. Not all decoders are able to open this image, but GIMP can. --- Tests/LibGfx/TestImageDecoder.cpp | 12 +++++++++ .../test-inputs/tiff/rgb_palette_alpha.tiff | Bin 0 -> 9096 bytes .../LibGfx/ImageFormats/TIFFLoader.cpp | 24 ++++++++++++++++++ Userland/Libraries/LibGfx/TIFFGenerator.py | 1 + 4 files changed, 37 insertions(+) create mode 100644 Tests/LibGfx/test-inputs/tiff/rgb_palette_alpha.tiff diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index eb2c5e4ffa..b449d4ea2f 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -489,6 +489,18 @@ TEST_CASE(test_tiff_rgb_alpha) EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::Red); } +TEST_CASE(test_tiff_palette_alpha) +{ + auto file = MUST(Core::MappedFile::map(TEST_INPUT("tiff/rgb_palette_alpha.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).alpha(), 0); + EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::Red); +} + TEST_CASE(test_tiff_16_bits) { auto file = MUST(Core::MappedFile::map(TEST_INPUT("tiff/16_bits.tiff"sv))); diff --git a/Tests/LibGfx/test-inputs/tiff/rgb_palette_alpha.tiff b/Tests/LibGfx/test-inputs/tiff/rgb_palette_alpha.tiff new file mode 100644 index 0000000000000000000000000000000000000000..bbd37e86c0f8d1cb83f1c6adb35e0069682e6a68 GIT binary patch literal 9096 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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 a39f0e61cf..78f87d3b0c 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp @@ -92,6 +92,7 @@ private: switch (*m_metadata.photometric_interpretation()) { case PhotometricInterpretation::WhiteIsZero: case PhotometricInterpretation::BlackIsZero: + case PhotometricInterpretation::RGBPalette: return 1; case PhotometricInterpretation::RGB: return 3; @@ -147,6 +148,29 @@ private: return Color(first_component, second_component, third_component, alpha); } + if (m_metadata.photometric_interpretation() == PhotometricInterpretation::RGBPalette) { + auto const index = TRY(stream.read_bits(bits_per_sample[0])); + auto const alpha = TRY(manage_extra_channels()); + + // SamplesPerPixel == 1 is a requirement for RGBPalette + // From description of PhotometricInterpretation in Section 8: Baseline Field Reference Guide + // "In a TIFF ColorMap, all the Red values come first, followed by the Green values, + // then the Blue values." + auto const size = 1 << (*m_metadata.bits_per_sample())[0]; + auto const red_offset = 0 * size; + auto const green_offset = 1 * size; + auto const blue_offset = 2 * size; + + auto const color_map = *m_metadata.color_map(); + + // FIXME: ColorMap's values are always 16-bits, stop truncating them when we support 16 bits bitmaps + return Color( + color_map[red_offset + index] >> 8, + color_map[green_offset + index] >> 8, + color_map[blue_offset + index] >> 8, + alpha); + } + if (*m_metadata.photometric_interpretation() == PhotometricInterpretation::WhiteIsZero || *m_metadata.photometric_interpretation() == PhotometricInterpretation::BlackIsZero) { auto luminosity = TRY(read_component(stream, bits_per_sample[0])); diff --git a/Userland/Libraries/LibGfx/TIFFGenerator.py b/Userland/Libraries/LibGfx/TIFFGenerator.py index 3470a6465d..c655dca594 100755 --- a/Userland/Libraries/LibGfx/TIFFGenerator.py +++ b/Userland/Libraries/LibGfx/TIFFGenerator.py @@ -123,6 +123,7 @@ known_tags: List[Tag] = [ Tag('296', [TIFFType.UnsignedShort], [], ResolutionUnit.Inch, "ResolutionUnit", ResolutionUnit), Tag('339', [TIFFType.UnsignedShort], [], SampleFormat.Unsigned, "SampleFormat", SampleFormat), Tag('317', [TIFFType.UnsignedShort], [1], Predictor.NoPrediction, "Predictor", Predictor), + Tag('320', [TIFFType.UnsignedShort], [], None, "ColorMap"), Tag('338', [TIFFType.UnsignedShort], [], None, "ExtraSamples", ExtraSample), Tag('34675', [TIFFType.Undefined], [], None, "ICCProfile"), ]