Skip to content

Commit d268206

Browse files
committed
LibGfx/ICC+image: Implement conversion to CMYK color spaces :^)
This used to fail: % Build/lagom/bin/image \ --assign-color-profile serenity-sRGB.icc \ --convert-to-color-profile \ ./Build/lagom/Root/res/icc/Adobe/CMYK/USWebCoatedSWOP.icc \ -o buggie-cmyk.jpg \ Base/res/graphics/buggie.png Runtime error: Can only convert to RGB at the moment, but destination color space is not RGB Now it works. It only works for CMYK profiles that use an mft1 tag for the BToA tags, because SerenityOS#26452 added support for converting from PCS to mft1. Most CMYK profiles use mft1, but not all of them. Support for `mft2` and `mBA ` tag types will be in a (straightforward) follow-up. Implementation-wise, Profile grows two more methods for converting RGB and CMYK bitmaps to CMYK. We now have 2x2 implementations for {RGB,CMYK} -> {RGB,CMYK}. For N different channel types, we need O(N^2) of these methods. Instead, we could have conversion methods between RGB bitmap and PCS bitmap and between CMYK and PCS bitmap. With this approach, we'd only need O(N) (2*N) of these methods. Concretely, that'd be 2x2 methods too though, and since there are two PCS types, depending on how it's implemented it's actually 4*N (i.e. 8 for RGB and CMYK). So the current 2x2 approach seems not terrible. It *is* a bit repetitive. We then call the right of these 4 methods in `image`. See the PR that added this commit for rough quantitative quality evaluation of the implementation.
1 parent a3eb2df commit d268206

File tree

3 files changed

+63
-9
lines changed

3 files changed

+63
-9
lines changed

Userland/Libraries/LibGfx/ICC/Profile.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1763,6 +1763,43 @@ ErrorOr<void> Profile::convert_cmyk_image(Bitmap& out, CMYKBitmap const& in, Pro
17631763
return {};
17641764
}
17651765

1766+
ErrorOr<void> Profile::convert_cmyk_image_to_cmyk_image(CMYKBitmap& bitmap, Profile const& source_profile) const
1767+
{
1768+
for (auto& pixel : bitmap) {
1769+
u8 cmyk[] = { pixel.c, pixel.m, pixel.y, pixel.k };
1770+
auto pcs = TRY(source_profile.to_pcs(cmyk));
1771+
TRY(from_pcs(source_profile, pcs, cmyk));
1772+
pixel = CMYK { cmyk[0], cmyk[1], cmyk[2], cmyk[3] };
1773+
}
1774+
1775+
return {};
1776+
}
1777+
1778+
ErrorOr<void> Profile::convert_image_to_cmyk_image(CMYKBitmap& out, Bitmap const& in, Profile const& source_profile) const
1779+
{
1780+
if (out.size() != in.size())
1781+
return Error::from_string_literal("ICC::Profile::convert_image_to_cmyk_image: out and in must have the same dimensions");
1782+
1783+
// Might fail if `out` has a scale_factor() != 1.
1784+
if (out.data_size() != in.data_size())
1785+
return Error::from_string_literal("ICC::Profile::convert_image_to_cmyk_image: out and in must have the same buffer size");
1786+
1787+
static_assert(sizeof(ARGB32) == sizeof(CMYK));
1788+
CMYK* out_data = out.begin();
1789+
ARGB32 const* in_data = in.begin();
1790+
1791+
for (size_t i = 0; i < in.data_size() / sizeof(CMYK); ++i) {
1792+
u8 rgb[] = { Color::from_argb(in_data[i]).red(), Color::from_argb(in_data[i]).green(), Color::from_argb(in_data[i]).blue() };
1793+
auto pcs = TRY(source_profile.to_pcs(rgb));
1794+
1795+
u8 cmyk[4];
1796+
TRY(from_pcs(source_profile, pcs, cmyk));
1797+
out_data[i] = CMYK { cmyk[0], cmyk[1], cmyk[2], cmyk[3] };
1798+
}
1799+
1800+
return {};
1801+
}
1802+
17661803
XYZ const& Profile::red_matrix_column() const { return xyz_data(redMatrixColumnTag); }
17671804
XYZ const& Profile::green_matrix_column() const { return xyz_data(greenMatrixColumnTag); }
17681805
XYZ const& Profile::blue_matrix_column() const { return xyz_data(blueMatrixColumnTag); }

Userland/Libraries/LibGfx/ICC/Profile.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ class Profile : public RefCounted<Profile> {
295295
ErrorOr<void> convert_image(Bitmap&, Profile const& source_profile) const;
296296
ErrorOr<void> convert_cmyk_image(Bitmap&, CMYKBitmap const&, Profile const& source_profile) const;
297297

298+
ErrorOr<void> convert_cmyk_image_to_cmyk_image(CMYKBitmap&, Profile const& source_profile) const;
299+
ErrorOr<void> convert_image_to_cmyk_image(CMYKBitmap&, Bitmap const&, Profile const& source_profile) const;
300+
298301
// Only call these if you know that this is an RGB matrix-based profile.
299302
XYZ const& red_matrix_column() const;
300303
XYZ const& green_matrix_column() const;

Userland/Utilities/image.cpp

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -169,23 +169,37 @@ static ErrorOr<void> convert_image_profile(LoadedImage& image, StringView conver
169169
auto source_profile = TRY(Gfx::ICC::Profile::try_load_from_externally_owned_memory(source_icc_data));
170170
auto destination_profile = TRY(Gfx::ICC::Profile::try_load_from_externally_owned_memory(*image.icc_data));
171171

172-
if (destination_profile->data_color_space() != Gfx::ICC::ColorSpace::RGB)
173-
return Error::from_string_literal("Can only convert to RGB at the moment, but destination color space is not RGB");
172+
if (destination_profile->data_color_space() != Gfx::ICC::ColorSpace::RGB
173+
&& destination_profile->data_color_space() != Gfx::ICC::ColorSpace::CMYK)
174+
return Error::from_string_literal("Can only convert to RGB and CMYK at the moment, but destination color space is neither");
174175

175176
if (image.bitmap.has<RefPtr<Gfx::CMYKBitmap>>()) {
176177
if (source_profile->data_color_space() != Gfx::ICC::ColorSpace::CMYK)
177178
return Error::from_string_literal("Source image data is CMYK but source color space is not CMYK");
178179

179-
auto& cmyk_frame = image.bitmap.get<RefPtr<Gfx::CMYKBitmap>>();
180-
auto rgb_frame = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, cmyk_frame->size()));
181-
TRY(destination_profile->convert_cmyk_image(*rgb_frame, *cmyk_frame, *source_profile));
182-
image.bitmap = RefPtr(move(rgb_frame));
183-
image.internal_format = Gfx::NaturalFrameFormat::RGB;
180+
if (destination_profile->data_color_space() == Gfx::ICC::ColorSpace::CMYK) {
181+
auto& cmyk_frame = image.bitmap.get<RefPtr<Gfx::CMYKBitmap>>();
182+
TRY(destination_profile->convert_cmyk_image_to_cmyk_image(*cmyk_frame, *source_profile));
183+
} else {
184+
auto& cmyk_frame = image.bitmap.get<RefPtr<Gfx::CMYKBitmap>>();
185+
auto rgb_frame = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, cmyk_frame->size()));
186+
TRY(destination_profile->convert_cmyk_image(*rgb_frame, *cmyk_frame, *source_profile));
187+
image.bitmap = RefPtr(move(rgb_frame));
188+
image.internal_format = Gfx::NaturalFrameFormat::RGB;
189+
}
184190
} else {
185191
// FIXME: This likely wrong for grayscale images because they've been converted to
186192
// RGB at this point, but their embedded color profile is still for grayscale.
187-
auto& frame = image.bitmap.get<RefPtr<Gfx::Bitmap>>();
188-
TRY(destination_profile->convert_image(*frame, *source_profile));
193+
if (destination_profile->data_color_space() == Gfx::ICC::ColorSpace::CMYK) {
194+
auto& rgb_frame = image.bitmap.get<RefPtr<Gfx::Bitmap>>();
195+
auto cmyk_frame = TRY(Gfx::CMYKBitmap::create_with_size(rgb_frame->size()));
196+
TRY(destination_profile->convert_image_to_cmyk_image(*cmyk_frame, *rgb_frame, *source_profile));
197+
image.bitmap = RefPtr(move(cmyk_frame));
198+
image.internal_format = Gfx::NaturalFrameFormat::CMYK;
199+
} else {
200+
auto& frame = image.bitmap.get<RefPtr<Gfx::Bitmap>>();
201+
TRY(destination_profile->convert_image(*frame, *source_profile));
202+
}
189203
}
190204

191205
return {};

0 commit comments

Comments
 (0)