mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 03:27:45 +00:00
LibGfx/TIFF: Add support for CMYK
The test case has been generated with Krita.
This commit is contained in:
parent
984272d83e
commit
a17041fe7f
3 changed files with 67 additions and 12 deletions
|
@ -700,6 +700,19 @@ TEST_CASE(test_tiff_16_bits)
|
||||||
EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::Red);
|
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)
|
TEST_CASE(test_tiff_invalid_tag)
|
||||||
{
|
{
|
||||||
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/invalid_tag.tiff"sv)));
|
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/invalid_tag.tiff"sv)));
|
||||||
|
|
BIN
Tests/LibGfx/test-inputs/tiff/cmyk.tiff
Normal file
BIN
Tests/LibGfx/test-inputs/tiff/cmyk.tiff
Normal file
Binary file not shown.
|
@ -12,6 +12,7 @@
|
||||||
#include <LibCompress/LZWDecoder.h>
|
#include <LibCompress/LZWDecoder.h>
|
||||||
#include <LibCompress/PackBitsDecoder.h>
|
#include <LibCompress/PackBitsDecoder.h>
|
||||||
#include <LibCompress/Zlib.h>
|
#include <LibCompress/Zlib.h>
|
||||||
|
#include <LibGfx/CMYKBitmap.h>
|
||||||
#include <LibGfx/ImageFormats/CCITTDecoder.h>
|
#include <LibGfx/ImageFormats/CCITTDecoder.h>
|
||||||
#include <LibGfx/ImageFormats/ExifOrientedBitmap.h>
|
#include <LibGfx/ImageFormats/ExifOrientedBitmap.h>
|
||||||
#include <LibGfx/ImageFormats/TIFFMetadata.h>
|
#include <LibGfx/ImageFormats/TIFFMetadata.h>
|
||||||
|
@ -114,6 +115,11 @@ public:
|
||||||
return m_state;
|
return m_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RefPtr<CMYKBitmap> cmyk_bitmap() const
|
||||||
|
{
|
||||||
|
return m_cmyk_bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
RefPtr<Bitmap> bitmap() const
|
RefPtr<Bitmap> bitmap() const
|
||||||
{
|
{
|
||||||
return m_bitmap;
|
return m_bitmap;
|
||||||
|
@ -144,6 +150,8 @@ private:
|
||||||
return 1;
|
return 1;
|
||||||
case PhotometricInterpretation::RGB:
|
case PhotometricInterpretation::RGB:
|
||||||
return 3;
|
return 3;
|
||||||
|
case PhotometricInterpretation::CMYK:
|
||||||
|
return 4;
|
||||||
default:
|
default:
|
||||||
TODO();
|
TODO();
|
||||||
}
|
}
|
||||||
|
@ -238,6 +246,22 @@ private:
|
||||||
return Error::from_string_literal("Unsupported value for PhotometricInterpretation");
|
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>
|
template<CallableAs<ErrorOr<ReadonlyBytes>, u32, u32> StripDecoder>
|
||||||
ErrorOr<void> loop_over_pixels(StripDecoder&& strip_decoder)
|
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 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 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) {
|
for (u32 strip_index = 0; strip_index < strips_offset.size(); ++strip_index) {
|
||||||
TRY(m_stream->seek(strips_offset[strip_index]));
|
TRY(m_stream->seek(strips_offset[strip_index]));
|
||||||
|
@ -263,25 +291,35 @@ private:
|
||||||
Optional<Color> last_color {};
|
Optional<Color> last_color {};
|
||||||
|
|
||||||
for (u32 column = 0; column < *m_metadata.image_width(); ++column) {
|
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()) {
|
// FIXME: We should do the differencing at the byte-stream level, that would make it
|
||||||
color.set_red(last_color->red() + color.red());
|
// compatible with both LibPDF and all color formats.
|
||||||
color.set_green(last_color->green() + color.green());
|
if (m_metadata.predictor() == Predictor::HorizontalDifferencing && last_color.has_value()) {
|
||||||
color.set_blue(last_color->blue() + color.blue());
|
color.set_red(last_color->red() + color.red());
|
||||||
if (alpha_channel_index().has_value())
|
color.set_green(last_color->green() + color.green());
|
||||||
color.set_alpha(last_color->alpha() + color.alpha());
|
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();
|
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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -567,6 +605,7 @@ private:
|
||||||
NonnullOwnPtr<FixedMemoryStream> m_stream;
|
NonnullOwnPtr<FixedMemoryStream> m_stream;
|
||||||
State m_state {};
|
State m_state {};
|
||||||
RefPtr<Bitmap> m_bitmap {};
|
RefPtr<Bitmap> m_bitmap {};
|
||||||
|
RefPtr<CMYKBitmap> m_cmyk_bitmap {};
|
||||||
|
|
||||||
ByteOrder m_byte_order {};
|
ByteOrder m_byte_order {};
|
||||||
Optional<u32> m_next_ifd {};
|
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)
|
if (m_context->state() < TIFF::TIFFLoadingContext::State::FrameDecoded)
|
||||||
TRY(m_context->decode_frame());
|
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 };
|
return ImageFrameDescriptor { m_context->bitmap(), 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue