@ -1,7 +1,8 @@
// This library is distributed under the conditions of the GNU LGPL.
# include "config.h"
# ifdef HAVE_JASPER
# ifdef HAVE_OPENJPEG
# include <unistd.h>
# include "jp2.h"
@ -15,18 +16,322 @@
# ifdef HAVE_STDINT_H
# include <stdint.h>
# endif
# include <openjpeg.h>
# include <kdebug.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 :
* - 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 write images .
*
* Improvements :
* - kimgio_jp2_read_srgba / kimgio_jp2_read_grayscale could be merged .
*
* 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 ) ;
}
}
} ;
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_srgba ( 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 ;
OPJ_INT32 r , g , b , a = 0xFF ;
// Convert to unsigned
r = jp2 . image - > comps [ 0 ] . data [ offset ] ;
r + = ( jp2 . image - > comps [ 0 ] . sgnd ? 1 < < ( jp2 . image - > comps [ 0 ] . prec - 1 ) : 0 ) ;
g = jp2 . image - > comps [ 1 ] . data [ offset ] ;
g + = ( jp2 . image - > comps [ 0 ] . sgnd ? 1 < < ( jp2 . image - > comps [ 1 ] . prec - 1 ) : 0 ) ;
b = jp2 . image - > comps [ 2 ] . data [ offset ] ;
b + = ( jp2 . image - > comps [ 2 ] . sgnd ? 1 < < ( jp2 . image - > comps [ 2 ] . prec - 1 ) : 0 ) ;
if ( jp2 . image - > numcomps > 3 & & jp2 . image - > comps [ 3 ] . alpha )
{
a = jp2 . image - > comps [ 3 ] . data [ offset ] ;
a + = ( jp2 . image - > comps [ 3 ] . sgnd ? 1 < < ( jp2 . image - > comps [ 3 ] . prec - 1 ) : 0 ) ;
}
image . setPixel ( col , row , tqRgba ( r , g , b , a ) ) ;
alphaMask | = ( 255 - a ) ;
}
}
if ( alphaMask ! = 0x0 )
{
image . setAlphaBuffer ( true ) ;
}
}
static void kimgio_jp2_read_grayscale ( 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 ;
OPJ_INT32 g , a = 0xFF ;
// Convert to unsigned
g = jp2 . image - > comps [ 0 ] . data [ offset ] ;
g + = ( jp2 . image - > comps [ 0 ] . sgnd ? ( 1 < < jp2 . image - > comps [ 0 ] . prec - 1 ) : 0 ) ;
if ( jp2 . image - > numcomps > 1 & & jp2 . image - > comps [ 1 ] . alpha )
{
a = jp2 . image - > comps [ 1 ] . data [ offset ] ;
a = ( jp2 . image - > comps [ 1 ] . sgnd ? ( 1 < < jp2 . image - > comps [ 1 ] . prec - 1 ) : 0 ) ;
}
image . setPixel ( col , row , tqRgba ( g , g , g , a ) ) ;
alphaMask | = ( 255 - a ) ;
}
}
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 " ;
}
}
}
// dirty, but avoids a warning because jasper.h includes jas_config.h.
# undef PACKAGE
# undef VERSION
# include <jasper/jasper.h>
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 ;
}
}
// code taken in parts from JasPer's jiv.c
// 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 :
{
kimgio_jp2_read_srgba ( image , jp2 ) ;
break ;
}
case OPJ_CLRSPC_GRAY :
{
kimgio_jp2_read_grayscale ( 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
@ -192,7 +497,7 @@ create_image( const TQImage& qi )
cmptparms [ i ] . sgnd = false ;
}
jas_image_t * ji = jas_image_create ( 3 /* number components */ , cmptparms , JAS_CLRSPC_UNKNOWN ) ;
jas_image_t * ji = jas_image_create ( 3 /* number components */ /*, cmptparms, JAS_CLRSPC_UNKNOWN );
delete [ ] cmptparms ;
// returning 0 is ok
@ -324,5 +629,7 @@ kimgio_jp2_write( TQImageIO* io )
io - > setStatus ( IO_Ok ) ;
} // kimgio_jp2_write
# endif // HAVE_JASPER
*/
# endif // HAVE_OPENJPEG