瀏覽代碼

LibGfx/TIFF: Add support for CMYK

The test case has been generated with Krita.
Lucas CHOLLET 1 年之前
父節點
當前提交
a17041fe7f

+ 13 - 0
Tests/LibGfx/TestImageDecoder.cpp

@@ -700,6 +700,19 @@ TEST_CASE(test_tiff_16_bits)
     EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::Red);
 }
 
+TEST_CASE(test_tiff_cmyk)
+{
+    auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/cmyk.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);
+    // I stripped the ICC profile from the image, so we can't test for equality with Red here.
+    EXPECT_NE(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::White);
+}
+
 TEST_CASE(test_tiff_invalid_tag)
 {
     auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/invalid_tag.tiff"sv)));

二進制
Tests/LibGfx/test-inputs/tiff/cmyk.tiff


+ 55 - 13
Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp

@@ -12,6 +12,7 @@
 #include <LibCompress/LZWDecoder.h>
 #include <LibCompress/PackBitsDecoder.h>
 #include <LibCompress/Zlib.h>
+#include <LibGfx/CMYKBitmap.h>
 #include <LibGfx/ImageFormats/CCITTDecoder.h>
 #include <LibGfx/ImageFormats/ExifOrientedBitmap.h>
 #include <LibGfx/ImageFormats/TIFFMetadata.h>
@@ -114,6 +115,11 @@ public:
         return m_state;
     }
 
+    RefPtr<CMYKBitmap> cmyk_bitmap() const
+    {
+        return m_cmyk_bitmap;
+    }
+
     RefPtr<Bitmap> bitmap() const
     {
         return m_bitmap;
@@ -144,6 +150,8 @@ private:
             return 1;
         case PhotometricInterpretation::RGB:
             return 3;
+        case PhotometricInterpretation::CMYK:
+            return 4;
         default:
             TODO();
         }
@@ -238,6 +246,22 @@ private:
         return Error::from_string_literal("Unsupported value for PhotometricInterpretation");
     }
 
+    ErrorOr<CMYK> read_color_cmyk(BigEndianInputBitStream& stream)
+    {
+        VERIFY(m_metadata.photometric_interpretation() == PhotometricInterpretation::CMYK);
+        auto bits_per_sample = *m_metadata.bits_per_sample();
+
+        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]));
+        auto const fourth_component = TRY(read_component(stream, bits_per_sample[3]));
+
+        // FIXME: We probably won't encounter CMYK images with an alpha channel, but if
+        //        we do: the first step to support them is not dropping the value here!
+        [[maybe_unused]] auto const alpha = TRY(manage_extra_channels(stream, bits_per_sample));
+        return CMYK { first_component, second_component, third_component, fourth_component };
+    }
+
     template<CallableAs<ErrorOr<ReadonlyBytes>, u32, u32> StripDecoder>
     ErrorOr<void> loop_over_pixels(StripDecoder&& strip_decoder)
     {
@@ -245,7 +269,11 @@ private:
         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_height());
 
-        auto oriented_bitmap = TRY(ExifOrientedBitmap::create(*metadata().orientation(), { *metadata().image_width(), *metadata().image_height() }, BitmapFormat::BGRA8888));
+        Variant<ExifOrientedBitmap, ExifOrientedCMYKBitmap> oriented_bitmap = TRY(([&]() -> ErrorOr<Variant<ExifOrientedBitmap, ExifOrientedCMYKBitmap>> {
+            if (metadata().photometric_interpretation() == PhotometricInterpretation::CMYK)
+                return ExifOrientedCMYKBitmap::create(*metadata().orientation(), { *metadata().image_width(), *metadata().image_height() });
+            return ExifOrientedBitmap::create(*metadata().orientation(), { *metadata().image_width(), *metadata().image_height() }, BitmapFormat::BGRA8888);
+        }()));
 
         for (u32 strip_index = 0; strip_index < strips_offset.size(); ++strip_index) {
             TRY(m_stream->seek(strips_offset[strip_index]));
@@ -263,25 +291,35 @@ private:
                 Optional<Color> last_color {};
 
                 for (u32 column = 0; column < *m_metadata.image_width(); ++column) {
-                    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());
-                        color.set_green(last_color->green() + color.green());
-                        color.set_blue(last_color->blue() + color.blue());
-                        if (alpha_channel_index().has_value())
-                            color.set_alpha(last_color->alpha() + color.alpha());
+                    if (metadata().photometric_interpretation() == PhotometricInterpretation::CMYK) {
+                        auto const cmyk = TRY(read_color_cmyk(*decoded_stream));
+                        oriented_bitmap.get<ExifOrientedCMYKBitmap>().set_pixel(column, scanline, cmyk);
+                    } else {
+                        auto color = TRY(read_color(*decoded_stream));
+
+                        // FIXME:  We should do the differencing at the byte-stream level, that would make it
+                        //         compatible with both LibPDF and all color formats.
+                        if (m_metadata.predictor() == Predictor::HorizontalDifferencing && last_color.has_value()) {
+                            color.set_red(last_color->red() + color.red());
+                            color.set_green(last_color->green() + color.green());
+                            color.set_blue(last_color->blue() + color.blue());
+                            if (alpha_channel_index().has_value())
+                                color.set_alpha(last_color->alpha() + color.alpha());
+                        }
+
+                        last_color = color;
+                        oriented_bitmap.get<ExifOrientedBitmap>().set_pixel(column, scanline, color.value());
                     }
-
-                    last_color = color;
-                    oriented_bitmap.set_pixel(column, scanline, color.value());
                 }
 
                 decoded_stream->align_to_byte_boundary();
             }
         }
 
-        m_bitmap = oriented_bitmap.bitmap();
+        if (m_metadata.photometric_interpretation() == PhotometricInterpretation::CMYK)
+            m_cmyk_bitmap = oriented_bitmap.get<ExifOrientedCMYKBitmap>().bitmap();
+        else
+            m_bitmap = oriented_bitmap.get<ExifOrientedBitmap>().bitmap();
 
         return {};
     }
@@ -567,6 +605,7 @@ private:
     NonnullOwnPtr<FixedMemoryStream> m_stream;
     State m_state {};
     RefPtr<Bitmap> m_bitmap {};
+    RefPtr<CMYKBitmap> m_cmyk_bitmap {};
 
     ByteOrder m_byte_order {};
     Optional<u32> m_next_ifd {};
@@ -614,6 +653,9 @@ ErrorOr<ImageFrameDescriptor> TIFFImageDecoderPlugin::frame(size_t index, Option
     if (m_context->state() < TIFF::TIFFLoadingContext::State::FrameDecoded)
         TRY(m_context->decode_frame());
 
+    if (m_context->cmyk_bitmap())
+        return ImageFrameDescriptor { TRY(m_context->cmyk_bitmap()->to_low_quality_rgb()), 0 };
+
     return ImageFrameDescriptor { m_context->bitmap(), 0 };
 }