You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1096 lines
29 KiB
1096 lines
29 KiB
// This library is distributed under the conditions of the GNU LGPL.
|
|
#include "config.h"
|
|
|
|
#ifdef HAVE_OPENJPEG
|
|
|
|
#include <unistd.h>
|
|
#include "jp2.h"
|
|
|
|
#if !defined(__STDC_LIMIT_MACROS)
|
|
#define __STDC_LIMIT_MACROS
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
#ifdef HAVE_STDINT_H
|
|
#include <stdint.h>
|
|
#endif
|
|
|
|
#include <openjpeg.h>
|
|
|
|
#include <kdebug.h>
|
|
#include <tdeglobal.h>
|
|
#include <tdetempfile.h>
|
|
#include <tqcolor.h>
|
|
#include <tqcstring.h>
|
|
#include <tqfile.h>
|
|
#include <tqimage.h>
|
|
#include <tqmemarray.h>
|
|
|
|
/*
|
|
* JPEG-2000 Plugin for KImageIO.
|
|
*
|
|
* Current limitations:
|
|
* - 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':
|
|
* https://github.com/uclouvain/openjpeg/blob/master/src/bin/jp2/opj_decompress.c
|
|
* https://github.com/uclouvain/openjpeg/blob/master/src/bin/jp2/opj_compress.c
|
|
*/
|
|
|
|
// kdDebug category
|
|
constexpr int kCategory = 399;
|
|
|
|
struct KIMGJP2Wrapper
|
|
{
|
|
public:
|
|
opj_codec_t *codec { nullptr };
|
|
opj_image_t *image { nullptr };
|
|
opj_stream_t *stream { nullptr };
|
|
KTempFile tempFile;
|
|
|
|
~KIMGJP2Wrapper()
|
|
{
|
|
if (stream)
|
|
{
|
|
opj_stream_destroy(stream);
|
|
}
|
|
if (image)
|
|
{
|
|
opj_image_destroy(image);
|
|
}
|
|
if (codec)
|
|
{
|
|
opj_destroy_codec(codec);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
static void kimgio_jp2_info_handler(const char *message, void *data)
|
|
{
|
|
// Reports status, e.g.: Main header has been correctly decoded.
|
|
// kdDebug(kCategory) << "JP2 decoding message: " << message;
|
|
}
|
|
|
|
static void kimgio_jp2_warn_handler(const char *message, void *data)
|
|
{
|
|
kdWarning(kCategory) << "Warning decoding JP2 image: " << message;
|
|
}
|
|
|
|
static void kimgio_jp2_read_image(TQImage &image, const KIMGJP2Wrapper &jp2)
|
|
{
|
|
const int height = image.height();
|
|
const int width = image.width();
|
|
|
|
unsigned char alphaMask = 0x0;
|
|
|
|
for (int row = 0; row < height; row++)
|
|
{
|
|
for (int col = 0; col < width; col++)
|
|
{
|
|
const int offset = row * width + col;
|
|
uint8_t rgba[4] = { 0x00, 0x00, 0x00, 0xFF };
|
|
|
|
for (int comp = 0; comp < jp2.image->numcomps; comp++)
|
|
{
|
|
OPJ_INT32 value = jp2.image->comps[comp].data[offset];
|
|
value += (jp2.image->comps[comp].sgnd ? (1 << jp2.image->comps[comp].prec - 1) : 0);
|
|
value = kClamp(value, 0, 255);
|
|
|
|
switch (comp)
|
|
{
|
|
case 0:
|
|
{
|
|
// Red or Grayscale
|
|
rgba[0] = value;
|
|
rgba[1] = value;
|
|
rgba[2] = value;
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
if ((jp2.image->color_space == OPJ_CLRSPC_GRAY) &&
|
|
(jp2.image->comps[comp].alpha != 0))
|
|
{
|
|
// Grayscale with Alpha
|
|
rgba[3] = value;
|
|
}
|
|
else
|
|
{
|
|
// Green
|
|
rgba[1] = value;
|
|
}
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
// Blue
|
|
rgba[2] = value;
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
// Alpha?
|
|
if (jp2.image->comps[comp].alpha != 0)
|
|
{
|
|
rgba[3] = value;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
image.setPixel(col, row, tqRgba(rgba[0], rgba[1], rgba[2], rgba[3]));
|
|
alphaMask |= (255 - rgba[3]);
|
|
}
|
|
}
|
|
|
|
if (alphaMask != 0x0)
|
|
{
|
|
image.setAlphaBuffer(true);
|
|
}
|
|
}
|
|
|
|
static const char *colorspaceToString(COLOR_SPACE clr)
|
|
{
|
|
switch (clr)
|
|
{
|
|
case OPJ_CLRSPC_SRGB:
|
|
{
|
|
return "sRGB";
|
|
}
|
|
case OPJ_CLRSPC_GRAY:
|
|
{
|
|
return "sRGB Grayscale";
|
|
}
|
|
case OPJ_CLRSPC_SYCC:
|
|
{
|
|
return "YUV";
|
|
}
|
|
case OPJ_CLRSPC_EYCC:
|
|
{
|
|
return "YCbCr";
|
|
}
|
|
case OPJ_CLRSPC_CMYK:
|
|
{
|
|
return "CMYK";
|
|
}
|
|
case OPJ_CLRSPC_UNSPECIFIED:
|
|
{
|
|
return "Unspecified";
|
|
}
|
|
default:
|
|
{
|
|
return "Unknown";
|
|
}
|
|
}
|
|
}
|
|
|
|
TDE_EXPORT void kimgio_jp2_read(TQImageIO* io)
|
|
{
|
|
KIMGJP2Wrapper jp2;
|
|
opj_dparameters_t parameters;
|
|
|
|
if (auto tqfile = dynamic_cast<TQFile *>(io->ioDevice()))
|
|
{
|
|
jp2.stream = opj_stream_create_default_file_stream(tqfile->name().local8Bit().data(), OPJ_TRUE);
|
|
}
|
|
else
|
|
{
|
|
// 4096 (=4k) is a common page size.
|
|
constexpr int pageSize = 4096;
|
|
|
|
// Use a temporary file, since TQSocket::size() reports bytes
|
|
// available to read *now*, not the file size.
|
|
if (jp2.tempFile.status() != 0)
|
|
{
|
|
kdError(kCategory) << "Failed to create temporary file for non-TQFile IO" << endl;
|
|
return;
|
|
}
|
|
jp2.tempFile.setAutoDelete(true);
|
|
|
|
TQFile *tempFile = jp2.tempFile.file();
|
|
TQByteArray b(pageSize);
|
|
TQ_LONG bytesRead;
|
|
|
|
// 0 or -1 is EOF / error
|
|
while ((bytesRead = io->ioDevice()->readBlock(b.data(), pageSize)) > 0)
|
|
{
|
|
if ((tempFile->writeBlock(b.data(), bytesRead)) == -1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// flush everything out to disk
|
|
tempFile->flush();
|
|
jp2.stream = opj_stream_create_default_file_stream(tempFile->name().local8Bit().data(), OPJ_TRUE);
|
|
}
|
|
|
|
if (nullptr == jp2.stream)
|
|
{
|
|
kdError(kCategory) << "Failed to create input stream for JP2" << endl;
|
|
io->setStatus(IO_ResourceError);
|
|
return;
|
|
}
|
|
|
|
jp2.codec = opj_create_decompress(OPJ_CODEC_JP2);
|
|
if (nullptr == jp2.codec)
|
|
{
|
|
kdError(kCategory) << "Unable to create decompressor for JP2" << endl;
|
|
io->setStatus(IO_ResourceError);
|
|
return;
|
|
}
|
|
opj_set_error_handler(jp2.codec, kimgio_jp2_err_handler, nullptr);
|
|
opj_set_info_handler(jp2.codec, kimgio_jp2_info_handler, nullptr);
|
|
opj_set_warning_handler(jp2.codec, kimgio_jp2_warn_handler, nullptr);
|
|
|
|
opj_set_default_decoder_parameters(¶meters);
|
|
if (OPJ_FALSE == opj_setup_decoder(jp2.codec, ¶meters))
|
|
{
|
|
kdError(kCategory) << "Failed to setup decoder for JP2" << endl;
|
|
io->setStatus(IO_ResourceError);
|
|
return;
|
|
}
|
|
|
|
if (OPJ_FALSE == opj_read_header(jp2.stream, jp2.codec, &jp2.image))
|
|
{
|
|
kdError(kCategory) << "Failed to read JP2 header" << endl;
|
|
io->setStatus(IO_ReadError);
|
|
return;
|
|
}
|
|
|
|
if (OPJ_FALSE == opj_decode(jp2.codec, jp2.stream, jp2.image))
|
|
{
|
|
kdError(kCategory) << "Failed to decode JP2 image" << endl;
|
|
io->setStatus(IO_ReadError);
|
|
return;
|
|
}
|
|
|
|
if (OPJ_FALSE == opj_end_decompress(jp2.codec, jp2.stream))
|
|
{
|
|
kdError(kCategory) << "Failed to decode JP2 image ending" << endl;
|
|
io->setStatus(IO_ReadError);
|
|
return;
|
|
}
|
|
|
|
OPJ_UINT32 width = jp2.image->x1 - jp2.image->x0;
|
|
OPJ_UINT32 height = jp2.image->y1 - jp2.image->y0;
|
|
TQImage image(width, height, 32);
|
|
|
|
switch (jp2.image->color_space)
|
|
{
|
|
case OPJ_CLRSPC_SRGB:
|
|
case OPJ_CLRSPC_GRAY:
|
|
{
|
|
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: "
|
|
<< colorspaceToString(jp2.image->color_space)
|
|
<< endl;
|
|
io->setStatus(IO_ReadError);
|
|
return;
|
|
}
|
|
}
|
|
|
|
io->setImage(image);
|
|
io->setStatus(IO_Ok);
|
|
}
|
|
|
|
|
|
static void kimgio_jp2_write_handler(void *buffer, OPJ_SIZE_T buffer_size, void *user_data)
|
|
{
|
|
// TODO(mio):
|
|
}
|
|
|
|
TDE_EXPORT void kimgio_jp2_write(TQImageIO *io)
|
|
{
|
|
kdDebug(kCategory) << "Writing JP2 with OpenJPEG is not supported yet." << endl;
|
|
}
|
|
|
|
/*
|
|
|
|
#define DEFAULT_RATE 0.10
|
|
#define MAXCMPTS 256
|
|
|
|
|
|
typedef struct {
|
|
jas_image_t* image;
|
|
|
|
int cmptlut[MAXCMPTS];
|
|
|
|
jas_image_t* altimage;
|
|
} gs_t;
|
|
|
|
|
|
jas_image_t*
|
|
read_image( const TQImageIO* io )
|
|
{
|
|
jas_stream_t* in = 0;
|
|
// for TQIODevice's other than TQFile, a temp. file is used.
|
|
KTempFile* tempf = 0;
|
|
|
|
TQFile* qf = 0;
|
|
if( ( qf = dynamic_cast<TQFile*>( io->ioDevice() ) ) ) {
|
|
// great, it's a TQFile. Let's just take the filename.
|
|
in = jas_stream_fopen( TQFile::encodeName( qf->name() ), "rb" );
|
|
} else {
|
|
// not a TQFile. Copy the whole data to a temp. file.
|
|
tempf = new KTempFile();
|
|
if( tempf->status() != 0 ) {
|
|
delete tempf;
|
|
return 0;
|
|
} // if
|
|
tempf->setAutoDelete( true );
|
|
TQFile* out = tempf->file();
|
|
// 4096 (=4k) is a common page size.
|
|
TQByteArray b( 4096 );
|
|
TQ_LONG size;
|
|
// 0 or -1 is EOF / error
|
|
while( ( size = io->ioDevice()->readBlock( b.data(), 4096 ) ) > 0 ) {
|
|
// in case of a write error, still give the decoder a try
|
|
if( ( out->writeBlock( b.data(), size ) ) == -1 ) break;
|
|
} // while
|
|
// flush everything out to disk
|
|
out->flush();
|
|
|
|
in = jas_stream_fopen( TQFile::encodeName( tempf->name() ), "rb" );
|
|
} // else
|
|
if( !in ) {
|
|
delete tempf;
|
|
return 0;
|
|
} // if
|
|
|
|
jas_image_t* image = jas_image_decode( in, -1, 0 );
|
|
jas_stream_close( in );
|
|
delete tempf;
|
|
|
|
// image may be 0, but that's Ok
|
|
return image;
|
|
} // read_image
|
|
|
|
static bool
|
|
convert_colorspace( gs_t& gs )
|
|
{
|
|
jas_cmprof_t *outprof = jas_cmprof_createfromclrspc( JAS_CLRSPC_SRGB );
|
|
if( !outprof ) return false;
|
|
|
|
gs.altimage = jas_image_chclrspc( gs.image, outprof,
|
|
JAS_CMXFORM_INTENT_PER );
|
|
if( !gs.altimage ) return false;
|
|
|
|
return true;
|
|
} // convert_colorspace
|
|
|
|
static bool
|
|
render_view( gs_t& gs, TQImage& qti )
|
|
{
|
|
if((gs.cmptlut[0] = jas_image_getcmptbytype(gs.altimage,
|
|
JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R))) < 0 ||
|
|
(gs.cmptlut[1] = jas_image_getcmptbytype(gs.altimage,
|
|
JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G))) < 0 ||
|
|
(gs.cmptlut[2] = jas_image_getcmptbytype(gs.altimage,
|
|
JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B))) < 0) {
|
|
return false;
|
|
} // if
|
|
|
|
const int* cmptlut = gs.cmptlut;
|
|
int v[3];
|
|
|
|
// check that all components have the same size.
|
|
const int width = jas_image_cmptwidth( gs.altimage, cmptlut[0] );
|
|
const int height = jas_image_cmptheight( gs.altimage, cmptlut[0] );
|
|
for( int i = 1; i < 3; ++i ) {
|
|
if (jas_image_cmptwidth( gs.altimage, cmptlut[i] ) != width ||
|
|
jas_image_cmptheight( gs.altimage, cmptlut[i] ) != height)
|
|
return false;
|
|
} // for
|
|
|
|
if( !qti.create( jas_image_width( gs.altimage ),
|
|
jas_image_height( gs.altimage ), 32 ) )
|
|
return false;
|
|
|
|
uint32_t* data = (uint32_t*)qti.bits();
|
|
|
|
for( int y = 0; y < height; ++y ) {
|
|
for( int x = 0; x < width; ++x ) {
|
|
for( int k = 0; k < 3; ++k ) {
|
|
v[k] = jas_image_readcmptsample( gs.altimage, cmptlut[k], x, y );
|
|
// if the precision of the component is too small, increase
|
|
// it to use the complete value range.
|
|
v[k] <<= 8 - jas_image_cmptprec( gs.altimage, cmptlut[k] );
|
|
|
|
if( v[k] < 0 ) v[k] = 0;
|
|
else if( v[k] > 255 ) v[k] = 255;
|
|
} // for k
|
|
|
|
*data++ = tqRgb( v[0], v[1], v[2] );
|
|
} // for x
|
|
} // for y
|
|
return true;
|
|
} // render_view
|
|
|
|
|
|
TDE_EXPORT void
|
|
kimgio_jp2_read( TQImageIO* io )
|
|
{
|
|
if( jas_init() ) return;
|
|
|
|
gs_t gs;
|
|
if( !(gs.image = read_image( io )) ) return;
|
|
|
|
if( !convert_colorspace( gs ) ) return;
|
|
|
|
TQImage image;
|
|
render_view( gs, image );
|
|
|
|
if( gs.image ) jas_image_destroy( gs.image );
|
|
if( gs.altimage ) jas_image_destroy( gs.altimage );
|
|
|
|
io->setImage( image );
|
|
io->setStatus( 0 );
|
|
} // kimgio_jp2_read
|
|
|
|
|
|
static jas_image_t*
|
|
create_image( const TQImage& qi )
|
|
{
|
|
// prepare the component parameters
|
|
jas_image_cmptparm_t* cmptparms = new jas_image_cmptparm_t[ 3 ];
|
|
|
|
for ( int i = 0; i < 3; ++i ) {
|
|
// x and y offset
|
|
cmptparms[i].tlx = 0;
|
|
cmptparms[i].tly = 0;
|
|
|
|
// the resulting image will be hstep*width x vstep*height !
|
|
cmptparms[i].hstep = 1;
|
|
cmptparms[i].vstep = 1;
|
|
cmptparms[i].width = qi.width();
|
|
cmptparms[i].height = qi.height();
|
|
|
|
// we write everything as 24bit truecolor ATM
|
|
cmptparms[i].prec = 8;
|
|
cmptparms[i].sgnd = false;
|
|
}
|
|
|
|
jas_image_t* ji = jas_image_create( 3 /* number components *//*, cmptparms, JAS_CLRSPC_UNKNOWN );
|
|
delete[] cmptparms;
|
|
|
|
// returning 0 is ok
|
|
return ji;
|
|
} // create_image
|
|
|
|
|
|
static bool
|
|
write_components( jas_image_t* ji, const TQImage& qi )
|
|
{
|
|
const unsigned height = qi.height();
|
|
const unsigned width = qi.width();
|
|
|
|
jas_matrix_t* m = jas_matrix_create( height, width );
|
|
if( !m ) return false;
|
|
|
|
jas_image_setclrspc( ji, JAS_CLRSPC_SRGB );
|
|
|
|
jas_image_setcmpttype( ji, 0, JAS_IMAGE_CT_RGB_R );
|
|
for( uint y = 0; y < height; ++y )
|
|
for( uint x = 0; x < width; ++x )
|
|
jas_matrix_set( m, y, x, tqRed( qi.pixel( x, y ) ) );
|
|
jas_image_writecmpt( ji, 0, 0, 0, width, height, m );
|
|
|
|
jas_image_setcmpttype( ji, 1, JAS_IMAGE_CT_RGB_G );
|
|
for( uint y = 0; y < height; ++y )
|
|
for( uint x = 0; x < width; ++x )
|
|
jas_matrix_set( m, y, x, tqGreen( qi.pixel( x, y ) ) );
|
|
jas_image_writecmpt( ji, 1, 0, 0, width, height, m );
|
|
|
|
jas_image_setcmpttype( ji, 2, JAS_IMAGE_CT_RGB_B );
|
|
for( uint y = 0; y < height; ++y )
|
|
for( uint x = 0; x < width; ++x )
|
|
jas_matrix_set( m, y, x, tqBlue( qi.pixel( x, y ) ) );
|
|
jas_image_writecmpt( ji, 2, 0, 0, width, height, m );
|
|
jas_matrix_destroy( m );
|
|
|
|
return true;
|
|
} // write_components
|
|
|
|
TDE_EXPORT void
|
|
kimgio_jp2_write( TQImageIO* io )
|
|
{
|
|
if( jas_init() ) return;
|
|
|
|
// open the stream. we write directly to the file if possible, to a
|
|
// temporary file otherwise.
|
|
jas_stream_t* stream = 0;
|
|
|
|
TQFile* qf = 0;
|
|
KTempFile* ktempf = 0;
|
|
if( ( qf = dynamic_cast<TQFile*>( io->ioDevice() ) ) ) {
|
|
// jas_stream_fdopen works here, but not when reading...
|
|
stream = jas_stream_fdopen( dup( qf->handle() ), "w" );
|
|
} else {
|
|
ktempf = new KTempFile;
|
|
ktempf->setAutoDelete( true );
|
|
stream = jas_stream_fdopen( dup( ktempf->handle()), "w" );
|
|
} // else
|
|
|
|
|
|
// by here, a jas_stream_t is open
|
|
if( !stream ) return;
|
|
|
|
jas_image_t* ji = create_image( io->image() );
|
|
if( !ji ) {
|
|
delete ktempf;
|
|
jas_stream_close( stream );
|
|
return;
|
|
} // if
|
|
|
|
if( !write_components( ji, io->image() ) ) {
|
|
delete ktempf;
|
|
jas_stream_close( stream );
|
|
jas_image_destroy( ji );
|
|
return;
|
|
} // if
|
|
|
|
// optstr:
|
|
// - rate=#B => the resulting file size is about # bytes
|
|
// - rate=0.0 .. 1.0 => the resulting file size is about the factor times
|
|
// the uncompressed size
|
|
TQString rate;
|
|
TQTextStream ts( &rate, IO_WriteOnly );
|
|
ts << "rate="
|
|
<< ( (io->quality() < 0) ? DEFAULT_RATE : io->quality() / 100.0F );
|
|
# if defined(JAS_VERSION_MAJOR) && (JAS_VERSION_MAJOR >= 3)
|
|
const jas_image_fmtinfo_t *jp2_fmtinfo = jas_image_lookupfmtbyname("jp2");
|
|
int i = -1;
|
|
if (jp2_fmtinfo)
|
|
{
|
|
i = jas_image_encode(ji, stream, jp2_fmtinfo->id, rate.utf8().data());
|
|
}
|
|
# else
|
|
int i = jp2_encode( ji, stream, rate.utf8().data() );
|
|
# endif
|
|
|
|
jas_image_destroy( ji );
|
|
jas_stream_close( stream );
|
|
|
|
if( i != 0 ) { delete ktempf; return; }
|
|
|
|
if( ktempf ) {
|
|
// We've written to a tempfile. Copy the data to the final destination.
|
|
TQFile* in = ktempf->file();
|
|
|
|
TQByteArray b( 4096 );
|
|
TQ_LONG size;
|
|
|
|
// seek to the beginning of the file.
|
|
if( !in->at( 0 ) ) { delete ktempf; return; }
|
|
|
|
// 0 or -1 is EOF / error
|
|
while( ( size = in->readBlock( b.data(), 4096 ) ) > 0 ) {
|
|
if( ( io->ioDevice()->writeBlock( b.data(), size ) ) == -1 ) {
|
|
delete ktempf;
|
|
return;
|
|
} // if
|
|
} // while
|
|
io->ioDevice()->flush();
|
|
delete ktempf;
|
|
|
|
// see if we've left the while loop due to an error.
|
|
if( size == -1 ) return;
|
|
} // if
|
|
|
|
|
|
// everything went fine
|
|
io->setStatus( IO_Ok );
|
|
} // kimgio_jp2_write
|
|
|
|
*/
|
|
|
|
#endif // HAVE_OPENJPEG
|
|
|