From 6bc30099f276e28ece113e517f2a01ba6e012749 Mon Sep 17 00:00:00 2001 From: Lucas CHOLLET Date: Tue, 21 Mar 2023 18:24:37 -0400 Subject: LibGfx/JPEG: Add YCCK and CMYK to RGB color transformations It means that we now fully support JPEGs with four components :^). --- .../Libraries/LibGfx/ImageFormats/JPEGLoader.cpp | 106 +++++++++++++++++++-- 1 file changed, 98 insertions(+), 8 deletions(-) diff --git a/Userland/Libraries/LibGfx/ImageFormats/JPEGLoader.cpp b/Userland/Libraries/LibGfx/ImageFormats/JPEGLoader.cpp index 05dd008c6c..cfeea6f6ab 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/JPEGLoader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/JPEGLoader.cpp @@ -1356,6 +1356,100 @@ static void ycbcr_to_rgb(JPEGLoadingContext const& context, Vector& } } +static void invert_colors_for_adobe_images(JPEGLoadingContext const& context, Vector& macroblocks) +{ + if (!context.color_transform.has_value()) + return; + + // From libjpeg-turbo's libjpeg.txt: + // https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/libjpeg.txt + // CAUTION: it appears that Adobe Photoshop writes inverted data in CMYK JPEG + // files: 0 represents 100% ink coverage, rather than 0% ink as you'd expect. + // This is arguably a bug in Photoshop, but if you need to work with Photoshop + // CMYK files, you will have to deal with it in your application. + for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) { + for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) { + for (u8 vfactor_i = 0; vfactor_i < context.vsample_factor; ++vfactor_i) { + for (u8 hfactor_i = 0; hfactor_i < context.hsample_factor; ++hfactor_i) { + u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hcursor + hfactor_i); + for (u8 i = 0; i < 8; ++i) { + for (u8 j = 0; j < 8; ++j) { + macroblocks[mb_index].r[i * 8 + j] = NumericLimits::max() - macroblocks[mb_index].r[i * 8 + j]; + macroblocks[mb_index].g[i * 8 + j] = NumericLimits::max() - macroblocks[mb_index].g[i * 8 + j]; + macroblocks[mb_index].b[i * 8 + j] = NumericLimits::max() - macroblocks[mb_index].b[i * 8 + j]; + macroblocks[mb_index].k[i * 8 + j] = NumericLimits::max() - macroblocks[mb_index].k[i * 8 + j]; + } + } + } + } + } + } +} + +static void cmyk_to_rgb(JPEGLoadingContext const& context, Vector& macroblocks) +{ + invert_colors_for_adobe_images(context, macroblocks); + + for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) { + for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) { + for (u8 vfactor_i = context.vsample_factor - 1; vfactor_i < context.vsample_factor; --vfactor_i) { + for (u8 hfactor_i = context.hsample_factor - 1; hfactor_i < context.hsample_factor; --hfactor_i) { + u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hcursor + hfactor_i); + i32* c = macroblocks[mb_index].y; + i32* m = macroblocks[mb_index].cb; + i32* y = macroblocks[mb_index].cr; + i32* k = macroblocks[mb_index].k; + for (u8 i = 0; i < 8; ++i) { + for (u8 j = 0; j < 8; ++j) { + u8 const pixel = i * 8 + j; + + static constexpr auto max_value = NumericLimits::max(); + + auto const black_component = max_value - k[pixel]; + int const r = ((max_value - c[pixel]) * black_component) / max_value; + int const g = ((max_value - m[pixel]) * black_component) / max_value; + int const b = ((max_value - y[pixel]) * black_component) / max_value; + + c[pixel] = clamp(r, 0, max_value); + m[pixel] = clamp(g, 0, max_value); + y[pixel] = clamp(b, 0, max_value); + } + } + } + } + } + } +} + +static void ycck_to_rgb(JPEGLoadingContext const& context, Vector& macroblocks) +{ + // 7 - Conversions between colour encodings + // YCCK is obtained from CMYK by converting the CMY channels to YCC channel. + + // To convert back into RGB, we only need the 3 first components, which are baseline YCbCr + ycbcr_to_rgb(context, macroblocks); + + // RGB to CMYK, as mentioned in https://www.smcm.iqfr.csic.es/docs/intel/ipp/ipp_manual/IPPI/ippi_ch15/functn_YCCKToCMYK_JPEG.htm#functn_YCCKToCMYK_JPEG + for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) { + for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) { + for (u8 vfactor_i = 0; vfactor_i < context.vsample_factor; ++vfactor_i) { + for (u8 hfactor_i = 0; hfactor_i < context.hsample_factor; ++hfactor_i) { + u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hcursor + hfactor_i); + for (u8 i = 0; i < 8; ++i) { + for (u8 j = 0; j < 8; ++j) { + macroblocks[mb_index].r[i * 8 + j] = NumericLimits::max() - macroblocks[mb_index].r[i * 8 + j]; + macroblocks[mb_index].g[i * 8 + j] = NumericLimits::max() - macroblocks[mb_index].g[i * 8 + j]; + macroblocks[mb_index].b[i * 8 + j] = NumericLimits::max() - macroblocks[mb_index].b[i * 8 + j]; + } + } + } + } + } + } + + cmyk_to_rgb(context, macroblocks); +} + static ErrorOr handle_color_transform(JPEGLoadingContext const& context, Vector& macroblocks) { if (context.color_transform.has_value()) { @@ -1365,8 +1459,7 @@ static ErrorOr handle_color_transform(JPEGLoadingContext const& context, V switch (*context.color_transform) { case ColorTransform::CmykOrRgb: if (context.components.size() == 4) { - // FIXME: implement CMYK - dbgln("CMYK isn't supported yet"); + cmyk_to_rgb(context, macroblocks); } else if (context.components.size() == 3) { // Note: components.size() == 3 means that we have an RGB image, so no color transformation is needed. } else { @@ -1377,8 +1470,7 @@ static ErrorOr handle_color_transform(JPEGLoadingContext const& context, V ycbcr_to_rgb(context, macroblocks); break; case ColorTransform::YCCK: - // FIXME: implement YCCK - dbgln("YCCK isn't supported yet"); + ycck_to_rgb(context, macroblocks); break; } @@ -1389,10 +1481,8 @@ static ErrorOr handle_color_transform(JPEGLoadingContext const& context, V // - 1 components means grayscale // - 3 components means YCbCr // - 4 components means CMYK - if (context.components.size() == 4) { - // FIXME: implement CMYK - dbgln("CMYK isn't supported yet"); - } + if (context.components.size() == 4) + cmyk_to_rgb(context, macroblocks); if (context.components.size() == 3) ycbcr_to_rgb(context, macroblocks); -- cgit v1.2.3