From 556addc5cbd138425ce407870649f57eb12e9a32 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Wed, 24 Jan 2024 21:51:22 -0500 Subject: [PATCH] LibGFX/PAM: Allow reading CMYK .pam files These are written by `mutool extract` for CMYK images. They don't contain color profiles so they're not super convenient. But `image` can convert them (*) to sRGB as long as you use it with `--assign-color-profile` pointing to some CMYK icc profile of your choice. *: Once #22922 is merged. --- .../LibGfx/ImageFormats/PAMLoader.cpp | 47 ++++++++++++------- .../Libraries/LibGfx/ImageFormats/PAMLoader.h | 1 + .../ImageFormats/PortableImageMapLoader.h | 40 ++++++++++++++++ 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/Userland/Libraries/LibGfx/ImageFormats/PAMLoader.cpp b/Userland/Libraries/LibGfx/ImageFormats/PAMLoader.cpp index e184a06f47..6a18b13d76 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/PAMLoader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/PAMLoader.cpp @@ -19,30 +19,41 @@ ErrorOr read_image_data(PAMLoadingContext& context) bool is_rgb = context.format_details.depth == 3 && context.format_details.tupl_type == "RGB"sv; bool is_rgba = context.format_details.depth == 4 && context.format_details.tupl_type == "RGB_ALPHA"sv; - if (!is_gray && !is_gray_alpha && !is_rgb && !is_rgba) - return Error::from_string_view("Unsupported PAM depth"sv); + bool is_cmyk = context.format_details.depth == 4 && context.format_details.tupl_type == "CMYK"sv; - TRY(create_bitmap(context)); + if (!is_gray && !is_gray_alpha && !is_rgb && !is_rgba && !is_cmyk) + return Error::from_string_view("Unsupported PAM depth"sv); auto& stream = *context.stream; - for (u64 i = 0; i < context.width * context.height; ++i) { - if (is_gray) { - Array pixel; - TRY(stream.read_until_filled(pixel)); - context.bitmap->set_pixel(i % context.width, i / context.width, { pixel[0], pixel[0], pixel[0] }); - } else if (is_gray_alpha) { - Array pixel; - TRY(stream.read_until_filled(pixel)); - context.bitmap->set_pixel(i % context.width, i / context.width, { pixel[0], pixel[0], pixel[0], pixel[1] }); - } else if (is_rgb) { - Array pixel; - TRY(stream.read_until_filled(pixel)); - context.bitmap->set_pixel(i % context.width, i / context.width, { pixel[0], pixel[1], pixel[2] }); - } else if (is_rgba) { + if (is_cmyk) { + context.format_details.cmyk_bitmap = TRY(CMYKBitmap::create_with_size({ context.width, context.height })); + CMYK* data = context.format_details.cmyk_bitmap.value()->begin(); + for (u64 i = 0; i < context.width * context.height; ++i) { Array pixel; TRY(stream.read_until_filled(pixel)); - context.bitmap->set_pixel(i % context.width, i / context.width, { pixel[0], pixel[1], pixel[2], pixel[3] }); + data[i] = { pixel[0], pixel[1], pixel[2], pixel[3] }; + } + } else { + TRY(create_bitmap(context)); + for (u64 i = 0; i < context.width * context.height; ++i) { + if (is_gray) { + Array pixel; + TRY(stream.read_until_filled(pixel)); + context.bitmap->set_pixel(i % context.width, i / context.width, { pixel[0], pixel[0], pixel[0] }); + } else if (is_gray_alpha) { + Array pixel; + TRY(stream.read_until_filled(pixel)); + context.bitmap->set_pixel(i % context.width, i / context.width, { pixel[0], pixel[0], pixel[0], pixel[1] }); + } else if (is_rgb) { + Array pixel; + TRY(stream.read_until_filled(pixel)); + context.bitmap->set_pixel(i % context.width, i / context.width, { pixel[0], pixel[1], pixel[2] }); + } else if (is_rgba) { + Array pixel; + TRY(stream.read_until_filled(pixel)); + context.bitmap->set_pixel(i % context.width, i / context.width, { pixel[0], pixel[1], pixel[2], pixel[3] }); + } } } diff --git a/Userland/Libraries/LibGfx/ImageFormats/PAMLoader.h b/Userland/Libraries/LibGfx/ImageFormats/PAMLoader.h index 0a674097f3..ee2dd0bb08 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/PAMLoader.h +++ b/Userland/Libraries/LibGfx/ImageFormats/PAMLoader.h @@ -18,6 +18,7 @@ struct PAM { u16 max_val { 0 }; u16 depth { 0 }; String tupl_type {}; + Optional> cmyk_bitmap {}; }; using PAMLoadingContext = PortableImageMapLoadingContext; diff --git a/Userland/Libraries/LibGfx/ImageFormats/PortableImageMapLoader.h b/Userland/Libraries/LibGfx/ImageFormats/PortableImageMapLoader.h index cbf73334d3..416c1ecdcc 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/PortableImageMapLoader.h +++ b/Userland/Libraries/LibGfx/ImageFormats/PortableImageMapLoader.h @@ -62,6 +62,9 @@ public: virtual ErrorOr frame(size_t index, Optional ideal_size = {}) override; + virtual NaturalFrameFormat natural_frame_format() const override; + virtual ErrorOr> cmyk_frame() override; + private: PortableImageDecoderPlugin(NonnullOwnPtr stream); @@ -126,8 +129,45 @@ ErrorOr PortableImageDecoderPlugin::frame(size_t } } + if constexpr (requires { TContext::FormatDetails::cmyk_bitmap; }) { + if (m_context->format_details.cmyk_bitmap.has_value()) + m_context->bitmap = TRY(m_context->format_details.cmyk_bitmap.value()->to_low_quality_rgb()); + } + VERIFY(m_context->bitmap); return ImageFrameDescriptor { m_context->bitmap, 0 }; } +template +NaturalFrameFormat PortableImageDecoderPlugin::natural_frame_format() const +{ + if constexpr (requires { TContext::FormatDetails::cmyk_bitmap; }) { + if (m_context->format_details.depth == 4 && m_context->format_details.tupl_type == "CMYK"sv) + return NaturalFrameFormat::CMYK; + } + + return NaturalFrameFormat::RGB; +} + +template +ErrorOr> PortableImageDecoderPlugin::cmyk_frame() +{ + if constexpr (requires { TContext::FormatDetails::cmyk_bitmap; }) { + VERIFY(natural_frame_format() == NaturalFrameFormat::CMYK); + if (m_context->state == TContext::State::Error) + return Error::from_string_literal("PortableImageDecoderPlugin: Decoding failed"); + + if (m_context->state < TContext::State::BitmapDecoded) { + if (decode(*m_context).is_error()) { + m_context->state = TContext::State::Error; + return Error::from_string_literal("PortableImageDecoderPlugin: Decoding failed"); + } + } + + return *m_context->format_details.cmyk_bitmap.value(); + } + + VERIFY_NOT_REACHED(); +} + }