1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 05:27:43 +00:00

LibGfx/TIFF: Add support for CMYK

The test case has been generated with Krita.
This commit is contained in:
Lucas CHOLLET 2024-01-18 19:57:05 -05:00 committed by Andrew Kaster
parent 984272d83e
commit a17041fe7f
3 changed files with 67 additions and 12 deletions

View file

@ -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 (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));
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());
// 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 };
}