From 308395a3f603ac2b72875b019a2050fe21e38573 Mon Sep 17 00:00:00 2001 From: mio Date: Thu, 19 Sep 2024 16:33:29 +1000 Subject: Add support for YCbCr images The code has been adopted from the OpenJPEG project: https://github.com/uclouvain/openjpeg/blob/master/src/bin/common/color.c Full credits to the authors. Signed-off-by: mio --- kimgio/jp2.cpp | 467 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 466 insertions(+), 1 deletion(-) diff --git a/kimgio/jp2.cpp b/kimgio/jp2.cpp index 5f087e228..b7b86165b 100644 --- a/kimgio/jp2.cpp +++ b/kimgio/jp2.cpp @@ -32,11 +32,11 @@ * JPEG-2000 Plugin for KImageIO. * * Current limitations: - * - Only reads sRGB/Grayscale images (doesn't convert colorspace). * - Doesn't support writing images. * - Doesn't support OPJ_CODEC_J2K. * - Doesn't support subsampling. * - Doesn't read ICC profiles. + * - Doesn't support esycc or cymk colorspaces. * * The API documentation is rather poor, so good references on how to use OpenJPEG * are the tools provided by OpenJPEG, such as 'opj_decompress': @@ -72,6 +72,460 @@ struct KIMGJP2Wrapper } }; +/* + * The following sycc* functions come from OpenJPEG + * https://github.com/uclouvain/openjpeg/blob/master/src/bin/common/color.c + * + * It has beens slightly adjusted to better fit the code style of this file. + * + * The copyright in this software is being made available under the 2-clauses + * BSD License, included below. This software may be subject to other third + * party and contributor rights, including patent rights, and no such rights + * are granted under this license. + * + * Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium + * Copyright (c) 2002-2014, Professor Benoit Macq + * Copyright (c) 2001-2003, David Janssens + * Copyright (c) 2002-2003, Yannick Verschueren + * Copyright (c) 2003-2007, Francois-Olivier Devaux + * Copyright (c) 2003-2014, Antonin Descampe + * Copyright (c) 2005, Herve Drolon, FreeImage Team + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +static void sycc_to_rgb(int offset, int upb, int y, int cb, int cr, + int *out_r, int *out_g, int *out_b) +{ + int r, g, b; + + cb -= offset; + cr -= offset; + r = y + (int)(1.402 * (float)cr); + if (r < 0) + { + r = 0; + } + else if (r > upb) + { + r = upb; + } + *out_r = r; + + g = y - (int)(0.344 * (float)cb + 0.714 * (float)cr); + if (g < 0) + { + g = 0; + } + else if (g > upb) + { + g = upb; + } + *out_g = g; + + b = y + (int)(1.772 * (float)cb); + if (b < 0) + { + b = 0; + } + else if (b > upb) + { + b = upb; + } + *out_b = b; +} + +static bool sycc444_to_rgb(opj_image_t *img) +{ + int *d0, *d1, *d2, *r, *g, *b; + const int *y, *cb, *cr; + size_t maxw, maxh, max, i; + int offset, upb; + + upb = (int)img->comps[0].prec; + offset = 1 << (upb - 1); + upb = (1 << upb) - 1; + + maxw = (size_t)img->comps[0].w; + maxh = (size_t)img->comps[0].h; + max = maxw * maxh; + + y = img->comps[0].data; + cb = img->comps[1].data; + cr = img->comps[2].data; + + d0 = r = (int*)opj_image_data_alloc(sizeof(int) * max); + d1 = g = (int*)opj_image_data_alloc(sizeof(int) * max); + d2 = b = (int*)opj_image_data_alloc(sizeof(int) * max); + + if (r == nullptr || g == nullptr || b == nullptr) + { + goto fails; + } + + for (i = 0U; i < max; ++i) + { + sycc_to_rgb(offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++cb; + ++cr; + ++r; + ++g; + ++b; + } + opj_image_data_free(img->comps[0].data); + img->comps[0].data = d0; + opj_image_data_free(img->comps[1].data); + img->comps[1].data = d1; + opj_image_data_free(img->comps[2].data); + img->comps[2].data = d2; + img->color_space = OPJ_CLRSPC_SRGB; + return true; + +fails: + opj_image_data_free(r); + opj_image_data_free(g); + opj_image_data_free(b); + return false; +} + +static bool sycc422_to_rgb(opj_image_t *img) +{ + int *d0, *d1, *d2, *r, *g, *b; + const int *y, *cb, *cr; + size_t maxw, maxh, max, offx, loopmaxw; + int offset, upb; + size_t i; + + upb = (int)img->comps[0].prec; + offset = 1 << (upb - 1); + upb = (1 << upb) - 1; + + maxw = (size_t)img->comps[0].w; + maxh = (size_t)img->comps[0].h; + max = maxw * maxh; + + y = img->comps[0].data; + cb = img->comps[1].data; + cr = img->comps[2].data; + + d0 = r = (int*)opj_image_data_alloc(sizeof(int) * max); + d1 = g = (int*)opj_image_data_alloc(sizeof(int) * max); + d2 = b = (int*)opj_image_data_alloc(sizeof(int) * max); + + if (r == nullptr || g == nullptr || b == nullptr) + { + goto fails; + } + + /* if img->x0 is odd, then first column shall use Cb/Cr = 0 */ + offx = img->x0 & 1U; + loopmaxw = maxw - offx; + + for (i = 0U; i < maxh; ++i) + { + size_t j; + + if (offx > 0U) + { + sycc_to_rgb(offset, upb, *y, 0, 0, r, g, b); + ++y; + ++r; + ++g; + ++b; + } + + for (j = 0U; j < (loopmaxw & ~(size_t)1U); j += 2U) + { + sycc_to_rgb(offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + sycc_to_rgb(offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + ++cb; + ++cr; + } + if (j < loopmaxw) + { + sycc_to_rgb(offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + ++cb; + ++cr; + } + } + + opj_image_data_free(img->comps[0].data); + img->comps[0].data = d0; + opj_image_data_free(img->comps[1].data); + img->comps[1].data = d1; + opj_image_data_free(img->comps[2].data); + img->comps[2].data = d2; + + img->comps[1].w = img->comps[2].w = img->comps[0].w; + img->comps[1].h = img->comps[2].h = img->comps[0].h; + img->comps[1].dx = img->comps[2].dx = img->comps[0].dx; + img->comps[1].dy = img->comps[2].dy = img->comps[0].dy; + img->color_space = OPJ_CLRSPC_SRGB; + return true; + +fails: + opj_image_data_free(r); + opj_image_data_free(g); + opj_image_data_free(b); + return false; +} + +static bool sycc420_to_rgb(opj_image_t *img) +{ + int *d0, *d1, *d2, *r, *g, *b, *nr, *ng, *nb; + const int *y, *cb, *cr, *ny; + size_t maxw, maxh, max, offx, loopmaxw, offy, loopmaxh; + int offset, upb; + size_t i; + + upb = (int)img->comps[0].prec; + offset = 1 << (upb - 1); + upb = (1 << upb) - 1; + + maxw = (size_t)img->comps[0].w; + maxh = (size_t)img->comps[0].h; + max = maxw * maxh; + + y = img->comps[0].data; + cb = img->comps[1].data; + cr = img->comps[2].data; + + d0 = r = (int*)opj_image_data_alloc(sizeof(int) * max); + d1 = g = (int*)opj_image_data_alloc(sizeof(int) * max); + d2 = b = (int*)opj_image_data_alloc(sizeof(int) * max); + + if (r == nullptr || g == nullptr || b == nullptr) + { + goto fails; + } + + /* if img->x0 is odd, then first column shall use Cb/Cr = 0 */ + offx = img->x0 & 1U; + loopmaxw = maxw - offx; + /* if img->y0 is odd, then first line shall use Cb/Cr = 0 */ + offy = img->y0 & 1U; + loopmaxh = maxh - offy; + + if (offy > 0U) + { + size_t j; + + for (j = 0; j < maxw; ++j) + { + sycc_to_rgb(offset, upb, *y, 0, 0, r, g, b); + ++y; + ++r; + ++g; + ++b; + } + } + + for (i = 0U; i < (loopmaxh & ~(size_t)1U); i += 2U) + { + size_t j; + + ny = y + maxw; + nr = r + maxw; + ng = g + maxw; + nb = b + maxw; + + if (offx > 0U) + { + sycc_to_rgb(offset, upb, *y, 0, 0, r, g, b); + ++y; + ++r; + ++g; + ++b; + sycc_to_rgb(offset, upb, *ny, *cb, *cr, nr, ng, nb); + ++ny; + ++nr; + ++ng; + ++nb; + } + + for (j = 0; j < (loopmaxw & ~(size_t)1U); j += 2U) + { + sycc_to_rgb(offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + sycc_to_rgb(offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + + sycc_to_rgb(offset, upb, *ny, *cb, *cr, nr, ng, nb); + ++ny; + ++nr; + ++ng; + ++nb; + sycc_to_rgb(offset, upb, *ny, *cb, *cr, nr, ng, nb); + ++ny; + ++nr; + ++ng; + ++nb; + ++cb; + ++cr; + } + if (j < loopmaxw) + { + sycc_to_rgb(offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + + sycc_to_rgb(offset, upb, *ny, *cb, *cr, nr, ng, nb); + ++ny; + ++nr; + ++ng; + ++nb; + ++cb; + ++cr; + } + y += maxw; + r += maxw; + g += maxw; + b += maxw; + } + if (i < loopmaxh) + { + size_t j; + + if (offx > 0U) + { + sycc_to_rgb(offset, upb, *y, 0, 0, r, g, b); + ++y; + ++r; + ++g; + ++b; + } + + for (j = 0U; j < (loopmaxw & ~(size_t)1U); j += 2U) + { + sycc_to_rgb(offset, upb, *y, *cb, *cr, r, g, b); + + ++y; + ++r; + ++g; + ++b; + + sycc_to_rgb(offset, upb, *y, *cb, *cr, r, g, b); + + ++y; + ++r; + ++g; + ++b; + ++cb; + ++cr; + } + if (j < loopmaxw) + { + sycc_to_rgb(offset, upb, *y, *cb, *cr, r, g, b); + } + } + + opj_image_data_free(img->comps[0].data); + img->comps[0].data = d0; + opj_image_data_free(img->comps[1].data); + img->comps[1].data = d1; + opj_image_data_free(img->comps[2].data); + img->comps[2].data = d2; + + img->comps[1].w = img->comps[2].w = img->comps[0].w; + img->comps[1].h = img->comps[2].h = img->comps[0].h; + img->comps[1].dx = img->comps[2].dx = img->comps[0].dx; + img->comps[1].dy = img->comps[2].dy = img->comps[0].dy; + img->color_space = OPJ_CLRSPC_SRGB; + return true; + +fails: + opj_image_data_free(r); + opj_image_data_free(g); + opj_image_data_free(b); + return false; +} + +static bool color_sycc_to_rgb(opj_image_t *img) +{ + if (img->numcomps < 3) + { + img->color_space = OPJ_CLRSPC_GRAY; + return true; + } + + if ((img->comps[0].dx == 1) && + (img->comps[1].dx == 2) && + (img->comps[2].dx == 2) && + (img->comps[0].dy == 1) && + (img->comps[1].dy == 2) && + (img->comps[2].dy == 2)) + { + /* horizontal and vertical sub-sample */ + return sycc420_to_rgb(img); + } + else if ((img->comps[0].dx == 1) && + (img->comps[1].dx == 2) && + (img->comps[2].dx == 2) && + (img->comps[0].dy == 1) && + (img->comps[1].dy == 1) && + (img->comps[2].dy == 1)) + { + /* horizontal sub-sample only */ + return sycc422_to_rgb(img); + } + else if ((img->comps[0].dx == 1) && + (img->comps[1].dx == 1) && + (img->comps[2].dx == 1) && + (img->comps[0].dy == 1) && + (img->comps[1].dy == 1) && + (img->comps[2].dy == 1)) + { + /* no sub-sample */ + return sycc444_to_rgb(img); + } + else + { + kdWarning(kCategory) << "Can not convert in color_sycc_to_rgb" << endl; + return false; + } +} + static void kimgio_jp2_err_handler(const char *message, void *data) { kdError(kCategory) << "Error decoding JP2 image: " << message; @@ -301,6 +755,17 @@ TDE_EXPORT void kimgio_jp2_read(TQImageIO* io) kimgio_jp2_read_image(image, jp2); break; } + case OPJ_CLRSPC_SYCC: + { + if (false == color_sycc_to_rgb(jp2.image)) + { + kdError(kCategory) << "Could not convert YCbCr JP2 encoded image to sRGB." << endl; + io->setStatus(IO_UnspecifiedError); + return; + } + kimgio_jp2_read_image(image, jp2); + break; + } default: { kdError(kCategory) << "Unsupported colorspace detected: " -- cgit v1.2.1