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.
tdelibs/kimgio/jp2.cpp

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(&parameters);
if (OPJ_FALSE == opj_setup_decoder(jp2.codec, &parameters))
{
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