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.
digikam/digikam/libs/dimg/loaders/jp2kloader.cpp

716 lines
23 KiB

/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-06-14
* Description : A JPEG2000 IO file for DImg framework
*
* Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This implementation use Jasper API
* library : http://www.ece.uvic.ca/~mdadams/jasper
* Other JPEG2000 encoder-decoder : http://www.openjpeg.org
*
* Others Linux JPEG2000 Loader implementation using Jasper:
* http://cvs.graphicsmagick.org/cgi-bin/cvsweb.cgi/GraphicsMagick/coders/jp2.c
* https://subversion.imagemagick.org/subversion/ImageMagick/trunk/coders/jp2.c
* http://svn.ghostscript.com:8080/jasper/trunk/src/appl/jasper.c
* http://websvn.kde.org/trunk/KDE/tdelibs/kimgio/jp2.cpp
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
// This line must be commented to prevent any latency time
// when we use threaded image loader interface for each image
// files io. Uncomment this line only for debugging.
//#define ENABLE_DEBUG_MESSAGES
// C ANSI includes.
extern "C"
{
#if !defined(__STDC_LIMIT_MACROS)
#define __STDC_LIMIT_MACROS
#endif
#include <stdint.h>
}
// TQt includes.
#include <tqfile.h>
#include <tqcstring.h>
// Local includes.
#include "ddebug.h"
#include "dimg.h"
#include "dimgloaderobserver.h"
#include "jp2kloader.h"
namespace Digikam
{
JP2KLoader::JP2KLoader(DImg* image)
: DImgLoader(image)
{
m_hasAlpha = false;
m_sixteenBit = false;
}
bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
{
readMetadata(filePath, DImg::JPEG);
FILE *file = fopen(TQFile::encodeName(filePath), "rb");
if (!file)
return false;
unsigned char header[9];
if (fread(&header, 9, 1, file) != 1)
{
fclose(file);
return false;
}
unsigned char jp2ID[5] = { 0x6A, 0x50, 0x20, 0x20, 0x0D, };
unsigned char jpcID[2] = { 0xFF, 0x4F };
if (memcmp(&header[4], &jp2ID, 5) != 0 &&
memcmp(&header, &jpcID, 2) != 0)
{
// not a jpeg2000 file
fclose(file);
return false;
}
fclose(file);
// -------------------------------------------------------------------
// Initialize JPEG 2000 API.
long i, x, y;
int components[4];
unsigned int maximum_component_depth, scale[4], x_step[4], y_step[4];
unsigned long number_components;
jas_image_t *jp2_image = 0;
jas_stream_t *jp2_stream = 0;
jas_matrix_t *pixels[4];
int init = jas_init();
if (init != 0)
{
DDebug() << "Unable to init JPEG2000 decoder" << endl;
return false;
}
jp2_stream = jas_stream_fopen(TQFile::encodeName(filePath), "rb");
if (jp2_stream == 0)
{
DDebug() << "Unable to open JPEG2000 stream" << endl;
return false;
}
jp2_image = jas_image_decode(jp2_stream, -1, 0);
if (jp2_image == 0)
{
jas_stream_close(jp2_stream);
DDebug() << "Unable to decode JPEG2000 image" << endl;
return false;
}
jas_stream_close(jp2_stream);
// some pseudo-progress
if (observer)
observer->progressInfo(m_image, 0.1);
// -------------------------------------------------------------------
// Check color space.
switch (jas_clrspc_fam(jas_image_clrspc(jp2_image)))
{
case JAS_CLRSPC_FAM_RGB:
{
components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_R);
components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_G);
components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_B);
if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0))
{
jas_image_destroy(jp2_image);
DDebug() << "Error parsing JPEG2000 image : Missing Image Channel" << endl;
return false;
}
number_components = 3;
components[3] = jas_image_getcmptbytype(jp2_image, 3);
if (components[3] > 0)
{
m_hasAlpha = true;
number_components++;
}
break;
}
case JAS_CLRSPC_FAM_GRAY:
{
components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_GRAY_Y);
if (components[0] < 0)
{
jas_image_destroy(jp2_image);
DDebug() << "Error parsing JP2000 image : Missing Image Channel" << endl;
return false;
}
number_components=1;
break;
}
case JAS_CLRSPC_FAM_YCBCR:
{
components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_Y);
components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CB);
components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CR);
if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0))
{
jas_image_destroy(jp2_image);
DDebug() << "Error parsing JP2000 image : Missing Image Channel" << endl;
return false;
}
number_components = 3;
components[3] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_UNKNOWN);
if (components[3] > 0)
{
m_hasAlpha = true;
number_components++;
}
// FIXME : image->colorspace=YCbCrColorspace;
break;
}
default:
{
jas_image_destroy(jp2_image);
DDebug() << "Error parsing JP2000 image : Colorspace Model Is Not Supported" << endl;
return false;
}
}
// -------------------------------------------------------------------
// Check image geometry.
imageWidth() = jas_image_width(jp2_image);
imageHeight() = jas_image_height(jp2_image);
for (i = 0; i < (long)number_components; i++)
{
if ((((jas_image_cmptwidth(jp2_image, components[i])*
jas_image_cmpthstep(jp2_image, components[i])) != (long)imageWidth())) ||
(((jas_image_cmptheight(jp2_image, components[i])*
jas_image_cmptvstep(jp2_image, components[i])) != (long)imageHeight())) ||
(jas_image_cmpttlx(jp2_image, components[i]) != 0) ||
(jas_image_cmpttly(jp2_image, components[i]) != 0) ||
(jas_image_cmptsgnd(jp2_image, components[i]) != false))
{
jas_image_destroy(jp2_image);
DDebug() << "Error parsing JPEG2000 image : Irregular Channel Geometry Not Supported" << endl;
return false;
}
x_step[i] = jas_image_cmpthstep(jp2_image, components[i]);
y_step[i] = jas_image_cmptvstep(jp2_image, components[i]);
}
// -------------------------------------------------------------------
// Convert image data.
m_hasAlpha = number_components > 3;
maximum_component_depth = 0;
for (i = 0; i < (long)number_components; i++)
{
maximum_component_depth = TQMAX(jas_image_cmptprec(jp2_image,components[i]),
(long)maximum_component_depth);
pixels[i] = jas_matrix_create(1, ((unsigned int)imageWidth())/x_step[i]);
if (!pixels[i])
{
jas_image_destroy(jp2_image);
DDebug() << "Error decoding JPEG2000 image data : Memory Allocation Failed" << endl;
return false;
}
}
if (maximum_component_depth > 8)
m_sixteenBit = true;
for (i = 0 ; i < (long)number_components ; i++)
{
scale[i] = 1;
int prec = jas_image_cmptprec(jp2_image, components[i]);
if (m_sixteenBit && prec < 16)
scale[i] = (1 << (16 - jas_image_cmptprec(jp2_image, components[i])));
}
uchar* data = 0;
if (m_sixteenBit) // 16 bits image.
data = new uchar[imageWidth()*imageHeight()*8];
else
data = new uchar[imageWidth()*imageHeight()*4];
if (!data)
{
DDebug() << "Error decoding JPEG2000 image data : Memory Allocation Failed" << endl;
jas_image_destroy(jp2_image);
for (i = 0 ; i < (long)number_components ; i++)
jas_matrix_destroy(pixels[i]);
jas_cleanup();
return false;
}
uint checkPoint = 0;
uchar *dst = data;
unsigned short *dst16 = (unsigned short *)data;
for (y = 0 ; y < (long)imageHeight() ; y++)
{
for (i = 0 ; i < (long)number_components; i++)
{
int ret = jas_image_readcmpt(jp2_image, (short)components[i], 0,
((unsigned int) y) / y_step[i],
((unsigned int) imageWidth()) / x_step[i],
1, pixels[i]);
if (ret != 0)
{
DDebug() << "Error decoding JPEG2000 image data" << endl;
delete [] data;
jas_image_destroy(jp2_image);
for (i = 0 ; i < (long)number_components ; i++)
jas_matrix_destroy(pixels[i]);
jas_cleanup();
return false;
}
}
switch (number_components)
{
case 1: // Grayscale.
{
for (x = 0 ; x < (long)imageWidth() ; x++)
{
dst[0] = (uchar)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0]));
dst[1] = dst[0];
dst[2] = dst[0];
dst[3] = 0xFF;
dst += 4;
}
break;
}
case 3: // RGB.
{
if (!m_sixteenBit) // 8 bits image.
{
for (x = 0 ; x < (long)imageWidth() ; x++)
{
// Blue
dst[0] = (uchar)(scale[2]*jas_matrix_getv(pixels[2], x/x_step[2]));
// Green
dst[1] = (uchar)(scale[1]*jas_matrix_getv(pixels[1], x/x_step[1]));
// Red
dst[2] = (uchar)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0]));
// Alpha
dst[3] = 0xFF;
dst += 4;
}
}
else // 16 bits image.
{
for (x = 0 ; x < (long)imageWidth() ; x++)
{
// Blue
dst16[0] = (unsigned short)(scale[2]*jas_matrix_getv(pixels[2], x/x_step[2]));
// Green
dst16[1] = (unsigned short)(scale[1]*jas_matrix_getv(pixels[1], x/x_step[1]));
// Red
dst16[2] = (unsigned short)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0]));
// Alpha
dst16[3] = 0xFFFF;
dst16 += 4;
}
}
break;
}
case 4: // RGBA.
{
if (!m_sixteenBit) // 8 bits image.
{
for (x = 0 ; x < (long)imageWidth() ; x++)
{
// Blue
dst[0] = (uchar)(scale[2] * jas_matrix_getv(pixels[2], x/x_step[2]));
// Green
dst[1] = (uchar)(scale[1] * jas_matrix_getv(pixels[1], x/x_step[1]));
// Red
dst[2] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x/x_step[0]));
// Alpha
dst[3] = (uchar)(scale[3] * jas_matrix_getv(pixels[3], x/x_step[3]));
dst += 4;
}
}
else // 16 bits image.
{
for (x = 0 ; x < (long)imageWidth() ; x++)
{
// Blue
dst16[0] = (unsigned short)(scale[2]*jas_matrix_getv(pixels[2], x/x_step[2]));
// Green
dst16[1] = (unsigned short)(scale[1]*jas_matrix_getv(pixels[1], x/x_step[1]));
// Red
dst16[2] = (unsigned short)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0]));
// Alpha
dst16[3] = (unsigned short)(scale[3]*jas_matrix_getv(pixels[3], x/x_step[3]));
dst16 += 4;
}
}
break;
}
}
// use 0-10% and 90-100% for pseudo-progress
if (observer && y >= (long)checkPoint)
{
checkPoint += granularity(observer, y, 0.8);
if (!observer->continueQuery(m_image))
{
delete [] data;
jas_image_destroy(jp2_image);
for (i = 0 ; i < (long)number_components ; i++)
jas_matrix_destroy(pixels[i]);
jas_cleanup();
return false;
}
observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)y)/((float)imageHeight()) )));
}
}
// -------------------------------------------------------------------
// Get ICC color profile.
jas_iccprof_t *icc_profile = 0;
jas_stream_t *icc_stream = 0;
jas_cmprof_t *cm_profile = 0;
cm_profile = jas_image_cmprof(jp2_image);
if (cm_profile != 0)
icc_profile = jas_iccprof_createfromcmprof(cm_profile);
if (icc_profile != 0)
{
icc_stream = jas_stream_memopen(NULL, 0);
if (icc_stream != 0)
{
if (jas_iccprof_save(icc_profile, icc_stream) == 0)
{
if (jas_stream_flush(icc_stream) == 0)
{
TQMap<int, TQByteArray>& metaData = imageMetaData();
jas_stream_memobj_t *blob = (jas_stream_memobj_t *) icc_stream->obj_;
TQByteArray profile_rawdata(blob->len_);
memcpy(profile_rawdata.data(), blob->buf_, blob->len_);
metaData.insert(DImg::ICC, profile_rawdata);
jas_stream_close(icc_stream);
}
}
}
}
if (observer)
observer->progressInfo(m_image, 1.0);
imageSetAttribute("format", "JP2K");
imageData() = data;
jas_image_destroy(jp2_image);
for (i = 0 ; i < (long)number_components ; i++)
jas_matrix_destroy(pixels[i]);
jas_cleanup();
return true;
}
bool JP2KLoader::save(const TQString& filePath, DImgLoaderObserver *observer)
{
FILE *file = fopen(TQFile::encodeName(filePath), "wb");
if (!file)
return false;
fclose(file);
// -------------------------------------------------------------------
// Initialize JPEG 2000 API.
long i, x, y;
unsigned long number_components;
jas_image_t *jp2_image = 0;
jas_stream_t *jp2_stream = 0;
jas_matrix_t *pixels[4];
jas_image_cmptparm_t component_info[4];
int init = jas_init();
if (init != 0)
{
DDebug() << "Unable to init JPEG2000 decoder" << endl;
return false;
}
jp2_stream = jas_stream_fopen(TQFile::encodeName(filePath), "wb");
if (jp2_stream == 0)
{
DDebug() << "Unable to open JPEG2000 stream" << endl;
return false;
}
number_components = imageHasAlpha() ? 4 : 3;
for (i = 0 ; i < (long)number_components ; i++)
{
component_info[i].tlx = 0;
component_info[i].tly = 0;
component_info[i].hstep = 1;
component_info[i].vstep = 1;
component_info[i].width = imageWidth();
component_info[i].height = imageHeight();
component_info[i].prec = imageBitsDepth();
component_info[i].sgnd = false;
}
jp2_image = jas_image_create(number_components, component_info, JAS_CLRSPC_UNKNOWN);
if (jp2_image == 0)
{
jas_stream_close(jp2_stream);
DDebug() << "Unable to create JPEG2000 image" << endl;
return false;
}
if (observer)
observer->progressInfo(m_image, 0.1);
// -------------------------------------------------------------------
// Check color space.
if (number_components >= 3 ) // RGB & RGBA
{
// Alpha Channel
if (number_components == 4 )
jas_image_setcmpttype(jp2_image, 3, JAS_IMAGE_CT_OPACITY);
jas_image_setclrspc(jp2_image, JAS_CLRSPC_SRGB);
jas_image_setcmpttype(jp2_image, 0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R));
jas_image_setcmpttype(jp2_image, 1, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G));
jas_image_setcmpttype(jp2_image, 2, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B));
}
// -------------------------------------------------------------------
// Set ICC color profile.
// FIXME : doesn't work yet!
jas_cmprof_t *cm_profile = 0;
jas_iccprof_t *icc_profile = 0;
TQByteArray profile_rawdata = m_image->getICCProfil();
icc_profile = jas_iccprof_createfrombuf((uchar*)profile_rawdata.data(), profile_rawdata.size());
if (icc_profile != 0)
{
cm_profile = jas_cmprof_createfromiccprof(icc_profile);
if (cm_profile != 0)
{
jas_image_setcmprof(jp2_image, cm_profile);
}
}
// -------------------------------------------------------------------
// Convert to JPEG 2000 pixels.
for (i = 0 ; i < (long)number_components ; i++)
{
pixels[i] = jas_matrix_create(1, (unsigned int)imageWidth());
if (pixels[i] == 0)
{
for (x = 0 ; x < i ; x++)
jas_matrix_destroy(pixels[x]);
jas_image_destroy(jp2_image);
DDebug() << "Error encoding JPEG2000 image data : Memory Allocation Failed" << endl;
return false;
}
}
unsigned char* data = imageData();
unsigned char* pixel;
unsigned short r, g, b, a=0;
uint checkpoint = 0;
for (y = 0 ; y < (long)imageHeight() ; y++)
{
if (observer && y == (long)checkpoint)
{
checkpoint += granularity(observer, imageHeight(), 0.8);
if (!observer->continueQuery(m_image))
{
jas_image_destroy(jp2_image);
for (i = 0 ; i < (long)number_components ; i++)
jas_matrix_destroy(pixels[i]);
jas_cleanup();
return false;
}
observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)y)/((float)imageHeight()) )));
}
for (x = 0 ; x < (long)imageWidth() ; x++)
{
pixel = &data[((y * imageWidth()) + x) * imageBytesDepth()];
if ( imageSixteenBit() ) // 16 bits image.
{
b = (unsigned short)(pixel[0]+256*pixel[1]);
g = (unsigned short)(pixel[2]+256*pixel[3]);
r = (unsigned short)(pixel[4]+256*pixel[5]);
if (imageHasAlpha())
a = (unsigned short)(pixel[6]+256*pixel[7]);
}
else // 8 bits image.
{
b = (unsigned short)pixel[0];
g = (unsigned short)pixel[1];
r = (unsigned short)pixel[2];
if (imageHasAlpha())
a = (unsigned short)(pixel[3]);
}
jas_matrix_setv(pixels[0], x, r);
jas_matrix_setv(pixels[1], x, g);
jas_matrix_setv(pixels[2], x, b);
if (number_components > 3)
jas_matrix_setv(pixels[3], x, a);
}
for (i = 0 ; i < (long)number_components ; i++)
{
int ret = jas_image_writecmpt(jp2_image, (short) i, 0, (unsigned int)y,
(unsigned int)imageWidth(), 1, pixels[i]);
if (ret != 0)
{
DDebug() << "Error encoding JPEG2000 image data" << endl;
jas_image_destroy(jp2_image);
for (i = 0 ; i < (long)number_components ; i++)
jas_matrix_destroy(pixels[i]);
jas_cleanup();
return false;
}
}
}
TQVariant qualityAttr = imageGetAttribute("quality");
int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90;
if (quality < 0)
quality = 90;
if (quality > 100)
quality = 100;
TQString rate;
TQTextStream ts( &rate, IO_WriteOnly );
// NOTE: to have a lossless compression use quality=100.
// jp2_encode()::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
ts << "rate=" << ( quality / 100.0F );
DDebug() << "JPEG2000 quality: " << quality << endl;
DDebug() << "JPEG2000 " << rate << endl;
# if defined(JAS_VERSION_MAJOR) && (JAS_VERSION_MAJOR >= 3)
const jas_image_fmtinfo_t *jp2_fmtinfo = jas_image_lookupfmtbyname("jp2");
int ret = -1;
if (jp2_fmtinfo)
{
ret = jas_image_encode(jp2_image, jp2_stream, jp2_fmtinfo->id, rate.utf8().data());
}
# else
int ret = jp2_encode(jp2_image, jp2_stream, rate.utf8().data());
# endif
if (ret != 0)
{
DDebug() << "Unable to encode JPEG2000 image" << endl;
jas_image_destroy(jp2_image);
jas_stream_close(jp2_stream);
for (i = 0 ; i < (long)number_components ; i++)
jas_matrix_destroy(pixels[i]);
jas_cleanup();
return false;
}
if (observer)
observer->progressInfo(m_image, 1.0);
imageSetAttribute("savedformat", "JP2K");
saveMetadata(filePath);
jas_image_destroy(jp2_image);
jas_stream_close(jp2_stream);
for (i = 0 ; i < (long)number_components ; i++)
jas_matrix_destroy(pixels[i]);
jas_cleanup();
return true;
}
bool JP2KLoader::hasAlpha() const
{
return m_hasAlpha;
}
bool JP2KLoader::sixteenBit() const
{
return m_sixteenBit;
}
} // NameSpace Digikam