diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index e4823b61ad..eb2c5e4ffa 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -465,6 +465,30 @@ TEST_CASE(test_tiff_grayscale) EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color(130, 130, 130)); } +TEST_CASE(test_tiff_grayscale_alpha) +{ + auto file = MUST(Core::MappedFile::map(TEST_INPUT("tiff/grayscale_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(130, 130, 130)); +} + +TEST_CASE(test_tiff_rgb_alpha) +{ + auto file = MUST(Core::MappedFile::map(TEST_INPUT("tiff/rgb_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/grayscale_alpha.tiff b/Tests/LibGfx/test-inputs/tiff/grayscale_alpha.tiff new file mode 100644 index 0000000000..8d779b8586 Binary files /dev/null and b/Tests/LibGfx/test-inputs/tiff/grayscale_alpha.tiff differ diff --git a/Tests/LibGfx/test-inputs/tiff/rgb_alpha.tiff b/Tests/LibGfx/test-inputs/tiff/rgb_alpha.tiff new file mode 100644 index 0000000000..4338e82504 Binary files /dev/null and b/Tests/LibGfx/test-inputs/tiff/rgb_alpha.tiff differ diff --git a/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp b/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp index 493d2a9dc0..a39f0e61cf 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp @@ -100,6 +100,18 @@ private: } } + Optional alpha_channel_index() const + { + if (m_metadata.extra_samples().has_value()) { + auto const extra_samples = m_metadata.extra_samples().value(); + for (u8 i = 0; i < extra_samples.size(); ++i) { + if (extra_samples[i] == ExtraSample::UnassociatedAlpha) + return i + samples_for_photometric_interpretation(); + } + } + return OptionalNone {}; + } + ErrorOr read_color(BigEndianInputBitStream& stream) { auto bits_per_sample = *m_metadata.bits_per_sample(); @@ -107,19 +119,32 @@ private: // Section 7: Additional Baseline TIFF Requirements // Some TIFF files may have more components per pixel than you think. A Baseline TIFF reader must skip over // them gracefully, using the values of the SamplesPerPixel and BitsPerSample fields. - auto discard_unknown_channels = [&]() -> ErrorOr { - auto const unknown_channels = *m_metadata.samples_per_pixel() - samples_for_photometric_interpretation(); - for (u8 i = bits_per_sample.size() - unknown_channels; i < bits_per_sample.size(); ++i) - TRY(read_component(stream, bits_per_sample[i])); - return {}; + auto manage_extra_channels = [&]() -> ErrorOr { + // Both unknown and alpha channels are considered as extra channels, so let's iterate over + // them, conserve the alpha value (if any) and discard everything else. + + auto const number_base_channels = samples_for_photometric_interpretation(); + auto const alpha_index = alpha_channel_index(); + + Optional alpha {}; + + for (u8 i = number_base_channels; i < bits_per_sample.size(); ++i) { + if (alpha_index == i) + alpha = TRY(read_component(stream, bits_per_sample[i])); + else + TRY(read_component(stream, bits_per_sample[i])); + } + + return alpha.value_or(NumericLimits::max()); }; if (m_metadata.photometric_interpretation() == PhotometricInterpretation::RGB) { 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])); - TRY(discard_unknown_channels()); - return Color(first_component, second_component, third_component); + + auto const alpha = TRY(manage_extra_channels()); + return Color(first_component, second_component, third_component, alpha); } if (*m_metadata.photometric_interpretation() == PhotometricInterpretation::WhiteIsZero @@ -129,8 +154,8 @@ private: if (m_metadata.photometric_interpretation() == PhotometricInterpretation::WhiteIsZero) luminosity = ~luminosity; - TRY(discard_unknown_channels()); - return Color(luminosity, luminosity, luminosity); + auto const alpha = TRY(manage_extra_channels()); + return Color(luminosity, luminosity, luminosity, alpha); } return Error::from_string_literal("Unsupported value for PhotometricInterpretation");