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 <stigma@disroot.org>
issue/51/jasper-to-openjpeg
mio 2 months ago
parent 984447ca74
commit 308395a3f6

@ -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: "

Loading…
Cancel
Save