1565 lines
38 KiB
C
1565 lines
38 KiB
C
/* load jpeg2000
|
|
*
|
|
* 18/3/20
|
|
* - from heifload.c
|
|
* 4/11/21
|
|
* - add untiled load
|
|
* 17/1/22
|
|
* - left-justify bits for eg. 12-bit read
|
|
*/
|
|
|
|
/*
|
|
|
|
This file is part of VIPS.
|
|
|
|
VIPS is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, 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 Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
02110-1301 USA
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
|
|
|
|
*/
|
|
|
|
/*
|
|
#define DEBUG_VERBOSE
|
|
#define DEBUG
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif /*HAVE_CONFIG_H*/
|
|
#include <glib/gi18n-lib.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <vips/vips.h>
|
|
#include <vips/debug.h>
|
|
#include <vips/internal.h>
|
|
|
|
#ifdef HAVE_LIBOPENJP2
|
|
|
|
#include <openjpeg.h>
|
|
|
|
#include "pforeign.h"
|
|
|
|
/* Surely enough ... does anyone do multispectral imaging with jp2k?
|
|
*/
|
|
#define MAX_BANDS (100)
|
|
|
|
typedef struct _VipsForeignLoadJp2k {
|
|
VipsForeignLoad parent_object;
|
|
|
|
/* Source to load from (set by subclasses).
|
|
*/
|
|
VipsSource *source;
|
|
|
|
/* Page set by user, then we translate that into shrink factor.
|
|
*/
|
|
int page;
|
|
int shrink;
|
|
|
|
/* Decompress state.
|
|
*/
|
|
opj_stream_t *stream; /* Source as an opj stream */
|
|
OPJ_CODEC_FORMAT format; /* libopenjp2 format */
|
|
opj_codec_t *codec; /* Decompress codec */
|
|
opj_dparameters_t parameters; /* Core decompress params */
|
|
opj_image_t *image; /* Read image to here */
|
|
opj_codestream_info_v2_t *info; /* Tile geometry */
|
|
|
|
/* Number of errors reported during load -- use this to block load of
|
|
* corrupted images.
|
|
*/
|
|
int n_errors;
|
|
|
|
/* If we need to upsample tiles read from opj.
|
|
*/
|
|
gboolean upsample;
|
|
|
|
/* If we need to do ycc->rgb conversion on load.
|
|
*/
|
|
gboolean ycc_to_rgb;
|
|
} VipsForeignLoadJp2k;
|
|
|
|
typedef VipsForeignLoadClass VipsForeignLoadJp2kClass;
|
|
|
|
G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadJp2k, vips_foreign_load_jp2k,
|
|
VIPS_TYPE_FOREIGN_LOAD );
|
|
|
|
static void
|
|
vips_foreign_load_jp2k_dispose( GObject *gobject )
|
|
{
|
|
VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) gobject;
|
|
|
|
#ifdef DEBUG
|
|
printf( "vips_foreign_load_jp2k_dispose:\n" );
|
|
#endif /*DEBUG*/
|
|
|
|
/*
|
|
* FIXME ... do we need this? seems to just cause warnings
|
|
*
|
|
if( jp2k->codec &&
|
|
jp2k->stream )
|
|
opj_end_decompress( jp2k->codec, jp2k->stream );
|
|
*
|
|
*/
|
|
|
|
if( jp2k->info )
|
|
opj_destroy_cstr_info( &jp2k->info );
|
|
VIPS_FREEF( opj_destroy_codec, jp2k->codec );
|
|
VIPS_FREEF( opj_stream_destroy, jp2k->stream );
|
|
VIPS_FREEF( opj_image_destroy, jp2k->image );
|
|
VIPS_UNREF( jp2k->source );
|
|
|
|
G_OBJECT_CLASS( vips_foreign_load_jp2k_parent_class )->
|
|
dispose( gobject );
|
|
}
|
|
|
|
static OPJ_SIZE_T
|
|
vips_foreign_load_jp2k_read_source( void *buffer, size_t length, void *client )
|
|
{
|
|
VipsSource *source = VIPS_SOURCE( client );
|
|
gint64 bytes_read = vips_source_read( source, buffer, length );
|
|
|
|
/* openjpeg read uses -1 for both EOF and error return.
|
|
*/
|
|
return( bytes_read == 0 ? -1 : bytes_read );
|
|
}
|
|
|
|
static OPJ_OFF_T
|
|
vips_foreign_load_jp2k_skip_source( OPJ_OFF_T n_bytes, void *client )
|
|
{
|
|
VipsSource *source = VIPS_SOURCE( client );
|
|
|
|
if( vips_source_seek( source, n_bytes, SEEK_CUR ) == -1 )
|
|
/* openjpeg skip uses -1 for both end of stream and error.
|
|
*/
|
|
return( -1 );
|
|
|
|
return( n_bytes );
|
|
}
|
|
|
|
static OPJ_BOOL
|
|
vips_foreign_load_jp2k_seek_source( OPJ_OFF_T position, void *client )
|
|
{
|
|
VipsSource *source = VIPS_SOURCE( client );
|
|
|
|
if( vips_source_seek( source, position, SEEK_SET ) == -1 )
|
|
/* openjpeg seek uses FALSE for both end of stream and error.
|
|
*/
|
|
return( OPJ_FALSE );
|
|
|
|
return( OPJ_TRUE );
|
|
}
|
|
|
|
/* Make a libopenjp2 stream that wraps a VipsSource.
|
|
*/
|
|
static opj_stream_t *
|
|
vips_foreign_load_jp2k_stream( VipsSource *source )
|
|
{
|
|
opj_stream_t *stream;
|
|
|
|
/* TRUE means a read stream.
|
|
*/
|
|
if( !(stream = opj_stream_create( OPJ_J2K_STREAM_CHUNK_SIZE, TRUE )) )
|
|
return( NULL );
|
|
|
|
opj_stream_set_user_data( stream, source, NULL );
|
|
/* Unfortunately, jp2k requires the length, so pipe sources will have
|
|
* to buffer in memory.
|
|
*/
|
|
opj_stream_set_user_data_length( stream,
|
|
vips_source_length( source ) );
|
|
opj_stream_set_read_function( stream,
|
|
vips_foreign_load_jp2k_read_source );
|
|
opj_stream_set_skip_function( stream,
|
|
vips_foreign_load_jp2k_skip_source );
|
|
opj_stream_set_seek_function( stream,
|
|
vips_foreign_load_jp2k_seek_source );
|
|
|
|
return( stream );
|
|
}
|
|
|
|
static int
|
|
vips_foreign_load_jp2k_build( VipsObject *object )
|
|
{
|
|
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
|
|
VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) object;
|
|
|
|
#ifdef DEBUG
|
|
printf( "vips_foreign_load_jp2k_build:\n" );
|
|
#endif /*DEBUG*/
|
|
|
|
/* Default parameters.
|
|
*/
|
|
jp2k->parameters.decod_format = -1;
|
|
jp2k->parameters.cod_format = -1;
|
|
opj_set_default_decoder_parameters( &jp2k->parameters );
|
|
|
|
/* Link the openjpeg stream to our VipsSource.
|
|
*/
|
|
if( jp2k->source ) {
|
|
jp2k->stream = vips_foreign_load_jp2k_stream( jp2k->source );
|
|
if( !jp2k->stream ) {
|
|
vips_error( class->nickname,
|
|
"%s", _( "unable to create jp2k stream" ) );
|
|
return( -1 );
|
|
}
|
|
}
|
|
|
|
if( VIPS_OBJECT_CLASS( vips_foreign_load_jp2k_parent_class )->
|
|
build( object ) )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
#define JP2_RFC3745_MAGIC "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a"
|
|
#define JP2_MAGIC "\x0d\x0a\x87\x0a"
|
|
/* position 45: "\xff\x52" */
|
|
#define J2K_CODESTREAM_MAGIC "\xff\x4f\xff\x51"
|
|
|
|
/* Return the image format. OpenJPEG supports several different image types.
|
|
*/
|
|
static OPJ_CODEC_FORMAT
|
|
vips_foreign_load_jp2k_get_format( VipsSource *source )
|
|
{
|
|
unsigned char *data;
|
|
|
|
if( vips_source_sniff_at_most( source, &data, 12 ) < 12 )
|
|
return( -1 );
|
|
|
|
/* There's also OPJ_CODEC_JPT for xxx.jpt files, but we don't support
|
|
* that.
|
|
*/
|
|
if( memcmp( data, JP2_RFC3745_MAGIC, 12) == 0 ||
|
|
memcmp( data, JP2_MAGIC, 4 ) == 0 )
|
|
return( OPJ_CODEC_JP2 );
|
|
else if( memcmp( data, J2K_CODESTREAM_MAGIC, 4 ) == 0 )
|
|
return( OPJ_CODEC_J2K );
|
|
else
|
|
return( -1 );
|
|
}
|
|
|
|
static gboolean
|
|
vips_foreign_load_jp2k_is_a_source( VipsSource *source )
|
|
{
|
|
return( vips_foreign_load_jp2k_get_format( source ) != -1 );
|
|
}
|
|
|
|
static VipsForeignFlags
|
|
vips_foreign_load_jp2k_get_flags( VipsForeignLoad *load )
|
|
{
|
|
return( VIPS_FOREIGN_PARTIAL );
|
|
}
|
|
|
|
/* The openjpeg info and warning callbacks are incredibly chatty.
|
|
*/
|
|
static void
|
|
vips_foreign_load_jp2k_info_callback( const char *msg, void *client )
|
|
{
|
|
#ifdef DEBUG
|
|
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( client );
|
|
|
|
g_info( "%s: %s", class->nickname, msg );
|
|
#endif /*DEBUG*/
|
|
}
|
|
|
|
/* The openjpeg info and warning callbacks are incredibly chatty.
|
|
*/
|
|
static void
|
|
vips_foreign_load_jp2k_warning_callback( const char *msg, void *client )
|
|
{
|
|
#ifdef DEBUG
|
|
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( client );
|
|
|
|
g_warning( "%s: %s", class->nickname, msg );
|
|
#endif /*DEBUG*/
|
|
}
|
|
|
|
static void
|
|
vips_foreign_load_jp2k_error_callback( const char *msg, void *client )
|
|
{
|
|
VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) client;
|
|
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( jp2k );
|
|
|
|
#ifdef DEBUG
|
|
printf( "%s: %s", class->nickname, msg );
|
|
#endif /*DEBUG*/
|
|
|
|
vips_error( class->nickname, "%s", msg );
|
|
jp2k->n_errors += 1;
|
|
}
|
|
|
|
static void
|
|
vips_foreign_load_jp2k_attach_handlers( VipsForeignLoadJp2k *jp2k,
|
|
opj_codec_t *codec )
|
|
{
|
|
opj_set_info_handler( codec,
|
|
vips_foreign_load_jp2k_info_callback, jp2k );
|
|
opj_set_warning_handler( codec,
|
|
vips_foreign_load_jp2k_warning_callback, jp2k );
|
|
opj_set_error_handler( codec,
|
|
vips_foreign_load_jp2k_error_callback, jp2k );
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static void
|
|
vips_foreign_load_jp2k_print_image( opj_image_t *image )
|
|
{
|
|
printf( "image:\n" );
|
|
printf( "x0 = %u, y0 = %u, x1 = %u, y1 = %u, numcomps = %u, "
|
|
"color_space = %u\n",
|
|
image->x0, image->y0, image->x1, image->y1,
|
|
image->numcomps, image->color_space );
|
|
printf( "icc_profile_buf = %p, icc_profile_len = %x\n",
|
|
image->icc_profile_buf, image->icc_profile_len );
|
|
}
|
|
|
|
static void
|
|
vips_foreign_load_jp2k_print( VipsForeignLoadJp2k *jp2k )
|
|
{
|
|
int i;
|
|
|
|
vips_foreign_load_jp2k_print_image( jp2k->image );
|
|
|
|
printf( "components:\n" );
|
|
for( i = 0; i < jp2k->image->numcomps; i++ ) {
|
|
opj_image_comp_t *this = &jp2k->image->comps[i];
|
|
|
|
printf( "%i) dx = %u, dy = %u, w = %u, h = %u, "
|
|
"x0 = %u, y0 = %u\n",
|
|
i, this->dx, this->dy, this->w, this->h,
|
|
this->x0, this->y0 );
|
|
printf( " prec = %d, sgnd = %x, "
|
|
"resno_decoded = %u, factor = %u\n",
|
|
this->prec, this->sgnd,
|
|
this->resno_decoded, this->factor );
|
|
printf( " data = %p, alpha = %u\n",
|
|
this->data, this->alpha );
|
|
}
|
|
|
|
printf( "info:\n" );
|
|
printf( "tx0 = %u, ty0 = %d, tdx = %u, tdy = %u, tw = %u, th = %u\n",
|
|
jp2k->info->tx0, jp2k->info->ty0,
|
|
jp2k->info->tdx, jp2k->info->tdy,
|
|
jp2k->info->tw, jp2k->info->th );
|
|
printf( "nbcomps = %u, tile_info = %p\n",
|
|
jp2k->info->nbcomps, jp2k->info->tile_info );
|
|
if( jp2k->info->tw == 1 &&
|
|
jp2k->info->th == 1 )
|
|
printf( "untiled\n" );
|
|
}
|
|
#endif /*DEBUG*/
|
|
|
|
static int
|
|
vips_foreign_load_jp2k_set_header( VipsForeignLoadJp2k *jp2k, VipsImage *out )
|
|
{
|
|
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( jp2k );
|
|
opj_image_comp_t *first = &jp2k->image->comps[0];
|
|
|
|
VipsBandFormat format;
|
|
VipsInterpretation interpretation;
|
|
|
|
/* OpenJPEG only supports up to 31 bits per pixel. Treat it as 32.
|
|
*/
|
|
if( first->prec <= 8 )
|
|
format = first->sgnd ? VIPS_FORMAT_CHAR : VIPS_FORMAT_UCHAR;
|
|
else if( first->prec <= 16 )
|
|
format = first->sgnd ? VIPS_FORMAT_SHORT : VIPS_FORMAT_USHORT;
|
|
else
|
|
format = first->sgnd ? VIPS_FORMAT_INT : VIPS_FORMAT_UINT;
|
|
|
|
switch( jp2k->image->color_space ) {
|
|
case OPJ_CLRSPC_SYCC:
|
|
case OPJ_CLRSPC_EYCC:
|
|
/* Map these to RGB.
|
|
*/
|
|
interpretation = vips_format_sizeof( format ) == 1 ?
|
|
VIPS_INTERPRETATION_sRGB :
|
|
VIPS_INTERPRETATION_RGB16;
|
|
jp2k->ycc_to_rgb = TRUE;
|
|
break;
|
|
|
|
case OPJ_CLRSPC_GRAY:
|
|
interpretation = vips_format_sizeof( format ) == 1 ?
|
|
VIPS_INTERPRETATION_B_W :
|
|
VIPS_INTERPRETATION_GREY16;
|
|
break;
|
|
|
|
case OPJ_CLRSPC_SRGB:
|
|
interpretation = vips_format_sizeof( format ) == 1 ?
|
|
VIPS_INTERPRETATION_sRGB :
|
|
VIPS_INTERPRETATION_RGB16;
|
|
break;
|
|
|
|
case OPJ_CLRSPC_CMYK:
|
|
interpretation = VIPS_INTERPRETATION_CMYK;
|
|
break;
|
|
|
|
case OPJ_CLRSPC_UNSPECIFIED:
|
|
/* Try to guess something sensible.
|
|
*/
|
|
if( jp2k->image->numcomps < 3 )
|
|
interpretation = vips_format_sizeof( format ) == 1 ?
|
|
VIPS_INTERPRETATION_B_W :
|
|
VIPS_INTERPRETATION_GREY16;
|
|
else
|
|
interpretation = vips_format_sizeof( format ) == 1 ?
|
|
VIPS_INTERPRETATION_sRGB :
|
|
VIPS_INTERPRETATION_RGB16;
|
|
|
|
/* Unspecified with three bands and subsampling on bands 2 and
|
|
* 3 is usually YCC.
|
|
*/
|
|
if( jp2k->image->numcomps == 3 &&
|
|
jp2k->image->comps[0].dx == 1 &&
|
|
jp2k->image->comps[0].dy == 1 &&
|
|
jp2k->image->comps[1].dx > 1 &&
|
|
jp2k->image->comps[1].dy > 1 &&
|
|
jp2k->image->comps[2].dx > 1 &&
|
|
jp2k->image->comps[2].dy > 1)
|
|
jp2k->ycc_to_rgb = TRUE;
|
|
|
|
break;
|
|
|
|
default:
|
|
vips_error( class->nickname,
|
|
_( "unsupported colourspace %d" ),
|
|
jp2k->image->color_space );
|
|
return( -1 );
|
|
}
|
|
|
|
/* We use a tilecache on our output.
|
|
*/
|
|
if( vips_image_pipelinev( out, VIPS_DEMAND_STYLE_SMALLTILE, NULL ) )
|
|
return( -1 );
|
|
|
|
vips_image_init_fields( out,
|
|
first->w, first->h, jp2k->image->numcomps, format,
|
|
VIPS_CODING_NONE, interpretation, 1.0, 1.0 );
|
|
|
|
/* openjpeg allows left and top of the coordinate grid to be
|
|
* non-zero. These are always in unshrunk coordinates.
|
|
*/
|
|
out->Xoffset =
|
|
-VIPS_ROUND_INT( (double) jp2k->image->x0 / jp2k->shrink );
|
|
out->Yoffset =
|
|
-VIPS_ROUND_INT( (double) jp2k->image->y0 / jp2k->shrink );
|
|
|
|
if( jp2k->image->icc_profile_buf &&
|
|
jp2k->image->icc_profile_len > 0 )
|
|
vips_image_set_blob_copy( out, VIPS_META_ICC_NAME,
|
|
jp2k->image->icc_profile_buf,
|
|
jp2k->image->icc_profile_len );
|
|
|
|
/* Map number of layers in image to pages.
|
|
*/
|
|
if( jp2k->info &&
|
|
jp2k->info->m_default_tile_info.tccp_info )
|
|
vips_image_set_int( out, VIPS_META_N_PAGES,
|
|
jp2k->info->m_default_tile_info.tccp_info->
|
|
numresolutions );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
vips_foreign_load_jp2k_header( VipsForeignLoad *load )
|
|
{
|
|
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load );
|
|
VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) load;
|
|
|
|
opj_image_comp_t *first;
|
|
int i;
|
|
|
|
#ifdef DEBUG
|
|
printf( "vips_foreign_load_jp2k_header:\n" );
|
|
#endif /*DEBUG*/
|
|
|
|
jp2k->format = vips_foreign_load_jp2k_get_format( jp2k->source );
|
|
vips_source_rewind( jp2k->source );
|
|
if( !(jp2k->codec = opj_create_decompress( jp2k->format )) )
|
|
return( -1 );
|
|
|
|
vips_foreign_load_jp2k_attach_handlers( jp2k, jp2k->codec );
|
|
|
|
jp2k->shrink = 1 << jp2k->page;
|
|
jp2k->parameters.cp_reduce = jp2k->page;
|
|
if( !opj_setup_decoder( jp2k->codec, &jp2k->parameters ) )
|
|
return( -1 );
|
|
|
|
opj_codec_set_threads( jp2k->codec, vips_concurrency_get() );
|
|
|
|
if( !opj_read_header( jp2k->stream, jp2k->codec, &jp2k->image ) )
|
|
return( -1 );
|
|
if( !(jp2k->info = opj_get_cstr_info( jp2k->codec )) )
|
|
return( -1 );
|
|
|
|
#ifdef DEBUG
|
|
vips_foreign_load_jp2k_print( jp2k );
|
|
#endif /*DEBUG*/
|
|
|
|
/* We only allow images where all components have the same format.
|
|
*/
|
|
if( jp2k->image->numcomps > MAX_BANDS ) {
|
|
vips_error( class->nickname,
|
|
"%s", _( "too many image bands" ) );
|
|
return( -1 );
|
|
}
|
|
if( jp2k->image->numcomps == 0 ) {
|
|
vips_error( class->nickname,
|
|
"%s", _( "no image components" ) );
|
|
return( -1 );
|
|
}
|
|
first = &jp2k->image->comps[0];
|
|
for( i = 1; i < jp2k->image->numcomps; i++ ) {
|
|
opj_image_comp_t *this = &jp2k->image->comps[i];
|
|
|
|
if( this->x0 != first->x0 ||
|
|
this->y0 != first->y0 ||
|
|
this->w * this->dx != first->w * first->dx ||
|
|
this->h * this->dy != first->h * first->dy ||
|
|
this->resno_decoded != first->resno_decoded ||
|
|
this->factor != first->factor ) {
|
|
vips_error( class->nickname,
|
|
"%s", _( "components differ in geometry" ) );
|
|
return( -1 );
|
|
}
|
|
|
|
if( this->prec != first->prec ||
|
|
this->sgnd != first->sgnd ) {
|
|
vips_error( class->nickname,
|
|
"%s", _( "components differ in precision" ) );
|
|
return( -1 );
|
|
}
|
|
|
|
/* If dx/dy are not 1, we'll need to upsample components during
|
|
* tile packing.
|
|
*/
|
|
if( this->dx != first->dx ||
|
|
this->dy != first->dy ||
|
|
first->dx != 1 ||
|
|
first->dy != 1 )
|
|
jp2k->upsample = TRUE;
|
|
}
|
|
|
|
if( vips_foreign_load_jp2k_set_header( jp2k, load->out ) )
|
|
return( -1 );
|
|
|
|
VIPS_SETSTR( load->out->filename,
|
|
vips_connection_filename( VIPS_CONNECTION( jp2k->source ) ) );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
#define PACK( TYPE ) { \
|
|
TYPE *tq = (TYPE *) q; \
|
|
\
|
|
for( x = 0; x < length; x++ ) { \
|
|
for( i = 0; i < b; i++ ) \
|
|
tq[i] = planes[i][x]; \
|
|
\
|
|
tq += b; \
|
|
} \
|
|
}
|
|
|
|
#define PACK_UPSAMPLE( TYPE ) { \
|
|
TYPE *tq = (TYPE *) q; \
|
|
\
|
|
for( x = 0; x < length; x++ ) { \
|
|
for( i = 0; i < b; i++ ) { \
|
|
int dx = image->comps[i].dx; \
|
|
int pixel = planes[i][x / dx]; \
|
|
\
|
|
tq[i] = pixel; \
|
|
} \
|
|
\
|
|
tq += b; \
|
|
} \
|
|
}
|
|
|
|
/* Pack a line of openjpeg pixels into libvips format. left/top are the
|
|
* offsets into the opj image in pixel coordinates where we should start
|
|
* reading.
|
|
*
|
|
* Set upsample if any opj component is subsampled.
|
|
*/
|
|
static void
|
|
vips_foreign_load_jp2k_pack( gboolean upsample,
|
|
opj_image_t *image, VipsImage *im,
|
|
VipsPel *q, int left, int top, int length )
|
|
{
|
|
int *planes[MAX_BANDS];
|
|
int b = image->numcomps;
|
|
|
|
int x, i;
|
|
|
|
#ifdef DEBUG_VERBOSE
|
|
printf( "vips_foreign_load_jp2k_pack: "
|
|
"upsample = %d, left = %d, top = %d, length = %d\n",
|
|
upsample, left, top, length );
|
|
#endif /*DEBUG_VERBOSE*/
|
|
|
|
for( i = 0; i < b; i++ ) {
|
|
opj_image_comp_t *comp = &image->comps[i];
|
|
|
|
planes[i] = comp->data + (top / comp->dy) * comp->w +
|
|
(left / comp->dx);
|
|
}
|
|
|
|
if( upsample )
|
|
switch( im->BandFmt ) {
|
|
case VIPS_FORMAT_CHAR:
|
|
case VIPS_FORMAT_UCHAR:
|
|
PACK_UPSAMPLE( unsigned char );
|
|
break;
|
|
|
|
case VIPS_FORMAT_SHORT:
|
|
case VIPS_FORMAT_USHORT:
|
|
PACK_UPSAMPLE( unsigned short );
|
|
break;
|
|
|
|
case VIPS_FORMAT_INT:
|
|
case VIPS_FORMAT_UINT:
|
|
PACK_UPSAMPLE( unsigned int );
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
else
|
|
/* Fast no-upsample path.
|
|
*/
|
|
switch( im->BandFmt ) {
|
|
case VIPS_FORMAT_CHAR:
|
|
case VIPS_FORMAT_UCHAR:
|
|
PACK( unsigned char );
|
|
break;
|
|
|
|
case VIPS_FORMAT_SHORT:
|
|
case VIPS_FORMAT_USHORT:
|
|
PACK( unsigned short );
|
|
break;
|
|
|
|
case VIPS_FORMAT_INT:
|
|
case VIPS_FORMAT_UINT:
|
|
PACK( unsigned int );
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* ycc->rgb coversion adapted from openjpeg src/bin/common/color.c
|
|
*
|
|
* See also https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion
|
|
*/
|
|
#define YCC_TO_RGB( TYPE ) { \
|
|
TYPE *tq = (TYPE *) q; \
|
|
\
|
|
for( x = 0; x < length; x++ ) { \
|
|
int y = tq[0]; \
|
|
int cb = tq[1] - offset; \
|
|
int cr = tq[2] - offset; \
|
|
\
|
|
int r, g, b; \
|
|
\
|
|
r = y + (int)(1.402 * (float)cr); \
|
|
tq[0] = VIPS_CLIP( 0, r, upb ); \
|
|
\
|
|
g = y - (int)(0.344 * (float)cb + 0.714 * (float)cr); \
|
|
tq[1] = VIPS_CLIP( 0, g, upb ); \
|
|
\
|
|
b = y + (int)(1.772 * (float)cb); \
|
|
tq[2] = VIPS_CLIP( 0, b, upb ); \
|
|
\
|
|
tq += 3; \
|
|
} \
|
|
}
|
|
|
|
/* YCC->RGB for a line of pels.
|
|
*/
|
|
static void
|
|
vips_foreign_load_jp2k_ycc_to_rgb( opj_image_t *image, VipsImage *im,
|
|
VipsPel *q, int length )
|
|
{
|
|
int prec = image->comps[0].prec;
|
|
int offset = 1 << (prec - 1);
|
|
int upb = (1 << prec) - 1;
|
|
|
|
int x;
|
|
|
|
switch( im->BandFmt ) {
|
|
case VIPS_FORMAT_CHAR:
|
|
case VIPS_FORMAT_UCHAR:
|
|
YCC_TO_RGB( unsigned char );
|
|
break;
|
|
|
|
case VIPS_FORMAT_SHORT:
|
|
case VIPS_FORMAT_USHORT:
|
|
YCC_TO_RGB( unsigned short );
|
|
break;
|
|
|
|
case VIPS_FORMAT_INT:
|
|
case VIPS_FORMAT_UINT:
|
|
YCC_TO_RGB( unsigned int );
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define LSHIFT( TYPE ) { \
|
|
TYPE *tq = (TYPE *) q; \
|
|
\
|
|
for( x = 0; x < n_elements; x++ ) \
|
|
tq[x] = VIPS_LSHIFT_INT( tq[x], shift ); \
|
|
}
|
|
|
|
/* Left-justify to the libvips pixel bits. We need 12 bit precision images
|
|
* (for example) to fill 0-65535.
|
|
*/
|
|
static void
|
|
vips_foreign_load_jp2k_ljust( opj_image_t *image, VipsImage *im,
|
|
VipsPel *q, int length )
|
|
{
|
|
int prec = image->comps[0].prec;
|
|
int shift = VIPS_IMAGE_SIZEOF_ELEMENT( im ) * 8 - prec;
|
|
|
|
if( shift != 0 ) {
|
|
int n_elements = length * im->Bands;
|
|
|
|
int x;
|
|
|
|
switch( im->BandFmt ) {
|
|
case VIPS_FORMAT_CHAR:
|
|
case VIPS_FORMAT_UCHAR:
|
|
LSHIFT( unsigned char );
|
|
break;
|
|
|
|
case VIPS_FORMAT_SHORT:
|
|
case VIPS_FORMAT_USHORT:
|
|
LSHIFT( unsigned short );
|
|
break;
|
|
|
|
case VIPS_FORMAT_INT:
|
|
case VIPS_FORMAT_UINT:
|
|
LSHIFT( unsigned int );
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Read a tile from an untiled jp2k file.
|
|
*/
|
|
static int
|
|
vips_foreign_load_jp2k_generate_untiled( VipsRegion *out,
|
|
void *seq, void *a, void *b, gboolean *stop )
|
|
{
|
|
VipsForeignLoad *load = (VipsForeignLoad *) a;
|
|
VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) load;
|
|
VipsRect *r = &out->valid;
|
|
|
|
VipsRect opj;
|
|
VipsRect image;
|
|
int y;
|
|
|
|
#ifdef DEBUG_VERBOSE
|
|
printf( "vips_foreign_load_jp2k_generate_untiled: "
|
|
"left = %d, top = %d, width = %d, height = %d\n",
|
|
r->left, r->top, r->width, r->height );
|
|
#endif /*DEBUG_VERBOSE*/
|
|
|
|
/* If openjpeg has flagged an error, the library is not in a known
|
|
* state and it's not safe to call again.
|
|
*/
|
|
if( jp2k->n_errors )
|
|
return( 0 );
|
|
|
|
/* Coordinates are always in the highest res level.
|
|
*/
|
|
opj.left = r->left * jp2k->shrink;
|
|
opj.top = r->top * jp2k->shrink;
|
|
opj.width = r->width * jp2k->shrink;
|
|
opj.height = r->height * jp2k->shrink;
|
|
|
|
/* And must be clipped against the image size.
|
|
*/
|
|
image.left = 0;
|
|
image.top = 0;
|
|
image.width = jp2k->info->tdx;
|
|
image.height = jp2k->info->tdy;
|
|
vips_rect_intersectrect( &opj, &image, &opj );
|
|
|
|
if( !opj_set_decode_area( jp2k->codec, jp2k->image,
|
|
opj.left, opj.top,
|
|
VIPS_RECT_RIGHT( &opj ), VIPS_RECT_BOTTOM( &opj ) ) )
|
|
return( -1 );
|
|
|
|
if( !opj_decode( jp2k->codec, jp2k->stream, jp2k->image ) )
|
|
return( -1 );
|
|
|
|
/* Unpack decoded pixels to buffer in vips layout.
|
|
*/
|
|
for( y = 0; y < r->height; y++ ) {
|
|
VipsPel *q = VIPS_REGION_ADDR( out, r->left, r->top + y );
|
|
|
|
vips_foreign_load_jp2k_pack( jp2k->upsample,
|
|
jp2k->image, out->im, q, 0, y, r->width );
|
|
|
|
if( jp2k->ycc_to_rgb )
|
|
vips_foreign_load_jp2k_ycc_to_rgb( jp2k->image,
|
|
out->im, q, r->width );
|
|
|
|
vips_foreign_load_jp2k_ljust( jp2k->image,
|
|
out->im, q, r->width );
|
|
}
|
|
|
|
/* jp2k files can't be truncated (they fail to open), so all we can
|
|
* spot is errors.
|
|
*/
|
|
if( load->fail_on >= VIPS_FAIL_ON_ERROR &&
|
|
jp2k->n_errors > 0 )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Read a tile from the file. libvips tiles can be much larger or smaller than
|
|
* openjpeg tiles, so we must loop over the output region, painting in
|
|
* tiles from the file.
|
|
*/
|
|
static int
|
|
vips_foreign_load_jp2k_generate_tiled( VipsRegion *out,
|
|
void *seq, void *a, void *b, gboolean *stop )
|
|
{
|
|
VipsForeignLoad *load = (VipsForeignLoad *) a;
|
|
VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) load;
|
|
VipsRect *r = &out->valid;
|
|
|
|
/* jp2k get smaller with the layer size.
|
|
*/
|
|
int tile_width = VIPS_ROUND_UINT(
|
|
(double) jp2k->info->tdx / jp2k->shrink );
|
|
int tile_height = VIPS_ROUND_UINT(
|
|
(double) jp2k->info->tdy / jp2k->shrink );
|
|
|
|
/* ... so tiles_across is always the same.
|
|
*/
|
|
int tiles_across = jp2k->info->tw;
|
|
|
|
int x, y, z;
|
|
|
|
#ifdef DEBUG_VERBOSE
|
|
printf( "vips_foreign_load_jp2k_generate: "
|
|
"left = %d, top = %d, width = %d, height = %d\n",
|
|
r->left, r->top, r->width, r->height );
|
|
#endif /*DEBUG_VERBOSE*/
|
|
|
|
/* If openjpeg has flagged an error, the library is not in a known
|
|
* state and it's not safe to call again.
|
|
*/
|
|
if( jp2k->n_errors )
|
|
return( 0 );
|
|
|
|
y = 0;
|
|
while( y < r->height ) {
|
|
VipsRect tile, hit;
|
|
|
|
/* Not necessary, but it stops static analyzers complaining
|
|
* about a used-before-set.
|
|
*/
|
|
hit.height = 0;
|
|
|
|
x = 0;
|
|
while( x < r->width ) {
|
|
/* Tile the xy falls in, in tile numbers.
|
|
*/
|
|
int tx = (r->left + x) / tile_width;
|
|
int ty = (r->top + y) / tile_height;
|
|
|
|
/* Pixel coordinates of the tile that xy falls in.
|
|
*/
|
|
int xs = tx * tile_width;
|
|
int ys = ty * tile_height;
|
|
|
|
int tile_index = ty * tiles_across + tx;
|
|
|
|
/* Fetch the tile.
|
|
*/
|
|
#ifdef DEBUG_VERBOSE
|
|
printf( " fetch tile %d\n", tile_index );
|
|
#endif /*DEBUG_VERBOSE*/
|
|
if( !opj_get_decoded_tile( jp2k->codec,
|
|
jp2k->stream, jp2k->image, tile_index ) )
|
|
return( -1 );
|
|
|
|
/* Intersect tile with request to get pixels we need
|
|
* to copy out.
|
|
*/
|
|
tile.left = xs;
|
|
tile.top = ys;
|
|
tile.width = tile_width;
|
|
tile.height = tile_height;
|
|
vips_rect_intersectrect( &tile, r, &hit );
|
|
|
|
/* Unpack hit pixels to buffer in vips layout.
|
|
*/
|
|
for( z = 0; z < hit.height; z++ ) {
|
|
VipsPel *q = VIPS_REGION_ADDR( out,
|
|
hit.left, hit.top + z );
|
|
|
|
vips_foreign_load_jp2k_pack( jp2k->upsample,
|
|
jp2k->image, out->im, q,
|
|
hit.left - tile.left,
|
|
hit.top - tile.top + z,
|
|
hit.width );
|
|
|
|
if( jp2k->ycc_to_rgb )
|
|
vips_foreign_load_jp2k_ycc_to_rgb(
|
|
jp2k->image, out->im, q,
|
|
hit.width );
|
|
|
|
vips_foreign_load_jp2k_ljust( jp2k->image,
|
|
out->im, q, hit.width );
|
|
}
|
|
|
|
x += hit.width;
|
|
}
|
|
|
|
/* This will be the same for all tiles in the row we've just
|
|
* done.
|
|
*/
|
|
y += hit.height;
|
|
}
|
|
|
|
/* jp2k files can't be truncated (they fail to open), so all we can
|
|
* spot is errors.
|
|
*/
|
|
if( load->fail_on >= VIPS_FAIL_ON_ERROR &&
|
|
jp2k->n_errors > 0 )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
vips_foreign_load_jp2k_load( VipsForeignLoad *load )
|
|
{
|
|
VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) load;
|
|
VipsImage **t = (VipsImage **)
|
|
vips_object_local_array( VIPS_OBJECT( load ), 3 );
|
|
|
|
int vips_tile_width;
|
|
int vips_tile_height;
|
|
int vips_tiles_across;
|
|
|
|
#ifdef DEBUG
|
|
printf( "vips_foreign_load_jp2k_load:\n" );
|
|
#endif /*DEBUG*/
|
|
|
|
t[0] = vips_image_new();
|
|
if( vips_foreign_load_jp2k_set_header( jp2k, t[0] ) )
|
|
return( -1 );
|
|
|
|
/* Untiled jp2k images need a different read API.
|
|
*/
|
|
if( jp2k->info->tw == 1 &&
|
|
jp2k->info->th == 1 ) {
|
|
vips_tile_width = 512;
|
|
vips_tile_height = 512;
|
|
vips_tiles_across =
|
|
VIPS_ROUND_UP( t[0]->Xsize, vips_tile_width ) /
|
|
vips_tile_width;
|
|
|
|
if( vips_image_generate( t[0],
|
|
NULL, vips_foreign_load_jp2k_generate_untiled, NULL,
|
|
jp2k, NULL ) )
|
|
return( -1 );
|
|
}
|
|
else {
|
|
vips_tile_width = jp2k->info->tdx;
|
|
vips_tile_height = jp2k->info->tdy;
|
|
vips_tiles_across = jp2k->info->tw;
|
|
|
|
if( vips_image_generate( t[0],
|
|
NULL, vips_foreign_load_jp2k_generate_tiled, NULL,
|
|
jp2k, NULL ) )
|
|
return( -1 );
|
|
}
|
|
|
|
/* Copy to out, adding a cache. Enough tiles for two complete
|
|
* rows, plus 50%.
|
|
*/
|
|
if( vips_tilecache( t[0], &t[1],
|
|
"tile_width", vips_tile_width,
|
|
"tile_height", vips_tile_height,
|
|
"max_tiles", 3 * vips_tiles_across,
|
|
NULL ) )
|
|
return( -1 );
|
|
|
|
if( vips_image_write( t[1], load->real ) )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static void
|
|
vips_foreign_load_jp2k_class_init( VipsForeignLoadJp2kClass *class )
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
|
|
VipsObjectClass *object_class = (VipsObjectClass *) class;
|
|
VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class );
|
|
VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
|
|
|
|
gobject_class->dispose = vips_foreign_load_jp2k_dispose;
|
|
gobject_class->set_property = vips_object_set_property;
|
|
gobject_class->get_property = vips_object_get_property;
|
|
|
|
object_class->nickname = "jp2kload_base";
|
|
object_class->description = _( "load JPEG2000 image" );
|
|
object_class->build = vips_foreign_load_jp2k_build;
|
|
|
|
/* OpenJPEG is fuzzed, but not by us.
|
|
*/
|
|
operation_class->flags |= VIPS_OPERATION_UNTRUSTED;
|
|
|
|
load_class->get_flags = vips_foreign_load_jp2k_get_flags;
|
|
load_class->header = vips_foreign_load_jp2k_header;
|
|
load_class->load = vips_foreign_load_jp2k_load;
|
|
|
|
VIPS_ARG_INT( class, "page", 20,
|
|
_( "Page" ),
|
|
_( "Load this page from the image" ),
|
|
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
|
G_STRUCT_OFFSET( VipsForeignLoadJp2k, page ),
|
|
0, 100000, 0 );
|
|
|
|
}
|
|
|
|
static void
|
|
vips_foreign_load_jp2k_init( VipsForeignLoadJp2k *jp2k )
|
|
{
|
|
}
|
|
|
|
typedef struct _VipsForeignLoadJp2kFile {
|
|
VipsForeignLoadJp2k parent_object;
|
|
|
|
/* Filename for load.
|
|
*/
|
|
char *filename;
|
|
|
|
} VipsForeignLoadJp2kFile;
|
|
|
|
typedef VipsForeignLoadJp2kClass VipsForeignLoadJp2kFileClass;
|
|
|
|
G_DEFINE_TYPE( VipsForeignLoadJp2kFile, vips_foreign_load_jp2k_file,
|
|
vips_foreign_load_jp2k_get_type() );
|
|
|
|
static int
|
|
vips_foreign_load_jp2k_file_build( VipsObject *object )
|
|
{
|
|
VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) object;
|
|
VipsForeignLoadJp2kFile *file = (VipsForeignLoadJp2kFile *) object;
|
|
|
|
if( file->filename &&
|
|
!(jp2k->source = vips_source_new_from_file( file->filename )) )
|
|
return( -1 );
|
|
|
|
if( VIPS_OBJECT_CLASS( vips_foreign_load_jp2k_file_parent_class )->
|
|
build( object ) )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
const char *vips__jp2k_suffs[] =
|
|
{ ".j2k", ".jp2", ".jpt", ".j2c", ".jpc", NULL };
|
|
|
|
static int
|
|
vips_foreign_load_jp2k_is_a( const char *filename )
|
|
{
|
|
VipsSource *source;
|
|
gboolean result;
|
|
|
|
if( !(source = vips_source_new_from_file( filename )) )
|
|
return( FALSE );
|
|
result = vips_foreign_load_jp2k_is_a_source( source );
|
|
VIPS_UNREF( source );
|
|
|
|
return( result );
|
|
}
|
|
|
|
static void
|
|
vips_foreign_load_jp2k_file_class_init(
|
|
VipsForeignLoadJp2kFileClass *class )
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
|
|
VipsObjectClass *object_class = (VipsObjectClass *) class;
|
|
VipsForeignClass *foreign_class = (VipsForeignClass *) class;
|
|
VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
|
|
|
|
gobject_class->set_property = vips_object_set_property;
|
|
gobject_class->get_property = vips_object_get_property;
|
|
|
|
object_class->nickname = "jp2kload";
|
|
object_class->build = vips_foreign_load_jp2k_file_build;
|
|
|
|
foreign_class->suffs = vips__jp2k_suffs;
|
|
|
|
load_class->is_a = vips_foreign_load_jp2k_is_a;
|
|
|
|
VIPS_ARG_STRING( class, "filename", 1,
|
|
_( "Filename" ),
|
|
_( "Filename to load from" ),
|
|
VIPS_ARGUMENT_REQUIRED_INPUT,
|
|
G_STRUCT_OFFSET( VipsForeignLoadJp2kFile, filename ),
|
|
NULL );
|
|
|
|
}
|
|
|
|
static void
|
|
vips_foreign_load_jp2k_file_init( VipsForeignLoadJp2kFile *jp2k )
|
|
{
|
|
}
|
|
|
|
typedef struct _VipsForeignLoadJp2kBuffer {
|
|
VipsForeignLoadJp2k parent_object;
|
|
|
|
/* Load from a buffer.
|
|
*/
|
|
VipsArea *buf;
|
|
|
|
} VipsForeignLoadJp2kBuffer;
|
|
|
|
typedef VipsForeignLoadJp2kClass VipsForeignLoadJp2kBufferClass;
|
|
|
|
G_DEFINE_TYPE( VipsForeignLoadJp2kBuffer, vips_foreign_load_jp2k_buffer,
|
|
vips_foreign_load_jp2k_get_type() );
|
|
|
|
static int
|
|
vips_foreign_load_jp2k_buffer_build( VipsObject *object )
|
|
{
|
|
VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) object;
|
|
VipsForeignLoadJp2kBuffer *buffer =
|
|
(VipsForeignLoadJp2kBuffer *) object;
|
|
|
|
if( buffer->buf )
|
|
if( !(jp2k->source = vips_source_new_from_memory(
|
|
VIPS_AREA( buffer->buf )->data,
|
|
VIPS_AREA( buffer->buf )->length )) )
|
|
return( -1 );
|
|
|
|
if( VIPS_OBJECT_CLASS( vips_foreign_load_jp2k_file_parent_class )->
|
|
build( object ) )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static gboolean
|
|
vips_foreign_load_jp2k_buffer_is_a( const void *buf, size_t len )
|
|
{
|
|
VipsSource *source;
|
|
gboolean result;
|
|
|
|
if( !(source = vips_source_new_from_memory( buf, len )) )
|
|
return( FALSE );
|
|
result = vips_foreign_load_jp2k_is_a_source( source );
|
|
VIPS_UNREF( source );
|
|
|
|
return( result );
|
|
}
|
|
|
|
static void
|
|
vips_foreign_load_jp2k_buffer_class_init(
|
|
VipsForeignLoadJp2kBufferClass *class )
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
|
|
VipsObjectClass *object_class = (VipsObjectClass *) class;
|
|
VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
|
|
|
|
gobject_class->set_property = vips_object_set_property;
|
|
gobject_class->get_property = vips_object_get_property;
|
|
|
|
object_class->nickname = "jp2kload_buffer";
|
|
object_class->build = vips_foreign_load_jp2k_buffer_build;
|
|
|
|
load_class->is_a_buffer = vips_foreign_load_jp2k_buffer_is_a;
|
|
|
|
VIPS_ARG_BOXED( class, "buffer", 1,
|
|
_( "Buffer" ),
|
|
_( "Buffer to load from" ),
|
|
VIPS_ARGUMENT_REQUIRED_INPUT,
|
|
G_STRUCT_OFFSET( VipsForeignLoadJp2kBuffer, buf ),
|
|
VIPS_TYPE_BLOB );
|
|
|
|
}
|
|
|
|
static void
|
|
vips_foreign_load_jp2k_buffer_init( VipsForeignLoadJp2kBuffer *buffer )
|
|
{
|
|
}
|
|
|
|
typedef struct _VipsForeignLoadJp2kSource {
|
|
VipsForeignLoadJp2k parent_object;
|
|
|
|
/* Load from a source.
|
|
*/
|
|
VipsSource *source;
|
|
|
|
} VipsForeignLoadJp2kSource;
|
|
|
|
typedef VipsForeignLoadJp2kClass VipsForeignLoadJp2kSourceClass;
|
|
|
|
G_DEFINE_TYPE( VipsForeignLoadJp2kSource, vips_foreign_load_jp2k_source,
|
|
vips_foreign_load_jp2k_get_type() );
|
|
|
|
static int
|
|
vips_foreign_load_jp2k_source_build( VipsObject *object )
|
|
{
|
|
VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) object;
|
|
VipsForeignLoadJp2kSource *source =
|
|
(VipsForeignLoadJp2kSource *) object;
|
|
|
|
if( source->source ) {
|
|
jp2k->source = source->source;
|
|
g_object_ref( jp2k->source );
|
|
}
|
|
|
|
if( VIPS_OBJECT_CLASS(
|
|
vips_foreign_load_jp2k_source_parent_class )->
|
|
build( object ) )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static void
|
|
vips_foreign_load_jp2k_source_class_init(
|
|
VipsForeignLoadJp2kSourceClass *class )
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
|
|
VipsObjectClass *object_class = (VipsObjectClass *) class;
|
|
VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class );
|
|
VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
|
|
|
|
gobject_class->set_property = vips_object_set_property;
|
|
gobject_class->get_property = vips_object_get_property;
|
|
|
|
object_class->nickname = "jp2kload_source";
|
|
object_class->build = vips_foreign_load_jp2k_source_build;
|
|
|
|
operation_class->flags |= VIPS_OPERATION_NOCACHE;
|
|
|
|
load_class->is_a_source = vips_foreign_load_jp2k_is_a_source;
|
|
|
|
VIPS_ARG_OBJECT( class, "source", 1,
|
|
_( "Source" ),
|
|
_( "Source to load from" ),
|
|
VIPS_ARGUMENT_REQUIRED_INPUT,
|
|
G_STRUCT_OFFSET( VipsForeignLoadJp2kSource, source ),
|
|
VIPS_TYPE_SOURCE );
|
|
|
|
}
|
|
|
|
static void
|
|
vips_foreign_load_jp2k_source_init(
|
|
VipsForeignLoadJp2kSource *jp2k )
|
|
{
|
|
}
|
|
|
|
static void
|
|
info_callback( const char *msg G_GNUC_UNUSED, void *data G_GNUC_UNUSED )
|
|
{
|
|
/* There's a lot of info as well ...
|
|
*/
|
|
#ifdef DEBUG
|
|
printf( "info_callback: %s\n", msg );
|
|
#endif /*DEBUG*/
|
|
}
|
|
|
|
static void
|
|
warning_callback( const char *msg G_GNUC_UNUSED, void *data G_GNUC_UNUSED )
|
|
{
|
|
/* There are a lot of warnings ...
|
|
*/
|
|
#ifdef DEBUG
|
|
printf( "warning_callback: %s\n", msg );
|
|
#endif /*DEBUG*/
|
|
}
|
|
|
|
static void
|
|
error_callback( const char *msg, void *data )
|
|
{
|
|
#ifdef DEBUG
|
|
printf( "error_callback: %s\n", msg );
|
|
#endif /*DEBUG*/
|
|
|
|
vips_error( "OpenJPEG", "%s", msg );
|
|
}
|
|
|
|
typedef struct _TileDecompress {
|
|
VipsSource *source;
|
|
opj_stream_t *stream;
|
|
opj_codec_t *codec;
|
|
opj_image_t *image;
|
|
} TileDecompress;
|
|
|
|
static void
|
|
vips__foreign_load_jp2k_decompress_free( TileDecompress *decompress )
|
|
{
|
|
VIPS_FREEF( opj_destroy_codec, decompress->codec );
|
|
VIPS_FREEF( opj_image_destroy, decompress->image );
|
|
VIPS_FREEF( opj_stream_destroy, decompress->stream );
|
|
VIPS_UNREF( decompress->source );
|
|
}
|
|
|
|
/* Called from tiff2vips to decode a jp2k-compressed tile.
|
|
*
|
|
* width/height is the tile size. If this is an edge tile, and smaller than
|
|
* this, we still write a full-size tile and our caller will clip.
|
|
*/
|
|
int
|
|
vips__foreign_load_jp2k_decompress( VipsImage *out,
|
|
int width, int height, gboolean ycc_to_rgb,
|
|
void *from, size_t from_length,
|
|
void *to, size_t to_length )
|
|
{
|
|
size_t pel_size = VIPS_IMAGE_SIZEOF_PEL( out );
|
|
size_t line_size = pel_size * width;
|
|
|
|
TileDecompress decompress = { 0 };
|
|
opj_dparameters_t parameters;
|
|
int i;
|
|
gboolean upsample;
|
|
VipsPel *q;
|
|
int y;
|
|
|
|
#ifdef DEBUG
|
|
printf( "vips__foreign_load_jp2k_decompress: width = %d, height = %d, "
|
|
"ycc_to_rgb = %d, from_length = %zd, to_length = %zd\n",
|
|
width, height, ycc_to_rgb, from_length, to_length );
|
|
#endif /*DEBUG*/
|
|
|
|
/* Our ycc->rgb only works for exactly 3 bands.
|
|
*/
|
|
ycc_to_rgb = ycc_to_rgb && out->Bands == 3;
|
|
|
|
opj_set_default_decoder_parameters( ¶meters );
|
|
decompress.codec = opj_create_decompress( OPJ_CODEC_J2K );
|
|
opj_set_info_handler( decompress.codec, info_callback, NULL );
|
|
opj_set_warning_handler( decompress.codec, warning_callback, NULL );
|
|
opj_set_error_handler( decompress.codec, error_callback, NULL );
|
|
opj_setup_decoder( decompress.codec, ¶meters );
|
|
|
|
decompress.source = vips_source_new_from_memory( from, from_length );
|
|
decompress.stream = vips_foreign_load_jp2k_stream( decompress.source );
|
|
|
|
if( !opj_read_header( decompress.stream,
|
|
decompress.codec, &decompress.image ) ) {
|
|
vips_error( "jp2kload", "%s", ( "header error" ) );
|
|
vips__foreign_load_jp2k_decompress_free( &decompress );
|
|
return( -1 );
|
|
}
|
|
|
|
if( decompress.image->x1 > width ||
|
|
decompress.image->y1 > height ||
|
|
line_size * height > to_length ) {
|
|
vips_error( "jp2kload", "%s", ( "bad dimensions" ) );
|
|
vips__foreign_load_jp2k_decompress_free( &decompress );
|
|
return( -1 );
|
|
}
|
|
|
|
if( !opj_decode( decompress.codec,
|
|
decompress.stream, decompress.image ) ) {
|
|
vips_error( "jp2kload", "%s", ( "decode error" ) );
|
|
vips__foreign_load_jp2k_decompress_free( &decompress );
|
|
return( -1 );
|
|
}
|
|
|
|
/* Do any components need upsampling?
|
|
*/
|
|
upsample = FALSE;
|
|
for( i = 0; i < decompress.image->numcomps; i++ ) {
|
|
opj_image_comp_t *this = &decompress.image->comps[i];
|
|
|
|
if( this->dx > 1 ||
|
|
this->dy > 1 )
|
|
upsample = TRUE;
|
|
}
|
|
|
|
/* Unpack hit pixels to buffer in vips layout.
|
|
*/
|
|
q = to;
|
|
for( y = 0; y < height; y++ ) {
|
|
vips_foreign_load_jp2k_pack( upsample,
|
|
decompress.image, out, q,
|
|
0, y, width );
|
|
|
|
if( ycc_to_rgb )
|
|
vips_foreign_load_jp2k_ycc_to_rgb(
|
|
decompress.image, out, q,
|
|
width );
|
|
|
|
vips_foreign_load_jp2k_ljust( decompress.image,
|
|
out, q, width );
|
|
|
|
q += line_size;
|
|
}
|
|
|
|
vips__foreign_load_jp2k_decompress_free( &decompress );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
#else /*!HAVE_LIBOPENJP2*/
|
|
|
|
int
|
|
vips__foreign_load_jp2k_decompress( VipsImage *out,
|
|
int width, int height, gboolean ycc_to_rgb,
|
|
void *from, size_t from_length,
|
|
void *to, size_t to_length )
|
|
{
|
|
vips_error( "jp2k",
|
|
"%s", _( "libvips built without JPEG2000 support" ) );
|
|
return( -1 );
|
|
}
|
|
|
|
#endif /*HAVE_LIBOPENJP2*/
|
|
|
|
/**
|
|
* vips_jp2kload:
|
|
* @filename: file to load
|
|
* @out: (out): decompressed image
|
|
* @...: %NULL-terminated list of optional named arguments
|
|
*
|
|
* Optional arguments:
|
|
*
|
|
* * @page: %gint, load this page
|
|
* * @fail_on: #VipsFailOn, types of read error to fail on
|
|
*
|
|
* Read a JPEG2000 image. The loader supports 8, 16 and 32-bit int pixel
|
|
* values, signed and unsigned. It supports greyscale, RGB, YCC, CMYK and
|
|
* multispectral colour spaces. It will read any ICC profile on the image.
|
|
*
|
|
* It will only load images where all channels have the same format.
|
|
*
|
|
* Use @page to set the page to load, where page 0 is the base resolution
|
|
* image and higher-numbered pages are x2 reductions. Use the metadata item
|
|
* "n-pages" to find the number of pyramid layers.
|
|
*
|
|
* Use @fail_on to set the type of error that will cause load to fail. By
|
|
* default, loaders are permissive, that is, #VIPS_FAIL_ON_NONE.
|
|
*
|
|
* See also: vips_image_new_from_file().
|
|
*
|
|
* Returns: 0 on success, -1 on error.
|
|
*/
|
|
int
|
|
vips_jp2kload( const char *filename, VipsImage **out, ... )
|
|
{
|
|
va_list ap;
|
|
int result;
|
|
|
|
va_start( ap, out );
|
|
result = vips_call_split( "jp2kload", ap, filename, out );
|
|
va_end( ap );
|
|
|
|
return( result );
|
|
}
|
|
|
|
/**
|
|
* vips_jp2kload_buffer:
|
|
* @buf: (array length=len) (element-type guint8): memory area to load
|
|
* @len: (type gsize): size of memory area
|
|
* @out: (out): image to write
|
|
* @...: %NULL-terminated list of optional named arguments
|
|
*
|
|
* Optional arguments:
|
|
*
|
|
* * @page: %gint, load this page
|
|
* * @fail_on: #VipsFailOn, types of read error to fail on
|
|
*
|
|
* Exactly as vips_jp2kload(), but read from a buffer.
|
|
*
|
|
* You must not free the buffer while @out is active. The
|
|
* #VipsObject::postclose signal on @out is a good place to free.
|
|
*
|
|
* Returns: 0 on success, -1 on error.
|
|
*/
|
|
int
|
|
vips_jp2kload_buffer( void *buf, size_t len, VipsImage **out, ... )
|
|
{
|
|
va_list ap;
|
|
VipsBlob *blob;
|
|
int result;
|
|
|
|
/* We don't take a copy of the data or free it.
|
|
*/
|
|
blob = vips_blob_new( NULL, buf, len );
|
|
|
|
va_start( ap, out );
|
|
result = vips_call_split( "jp2kload_buffer", ap, blob, out );
|
|
va_end( ap );
|
|
|
|
vips_area_unref( VIPS_AREA( blob ) );
|
|
|
|
return( result );
|
|
}
|
|
|
|
/**
|
|
* vips_jp2kload_source:
|
|
* @source: source to load from
|
|
* @out: (out): decompressed image
|
|
* @...: %NULL-terminated list of optional named arguments
|
|
*
|
|
* Optional arguments:
|
|
*
|
|
* * @page: %gint, load this page
|
|
* * @fail_on: #VipsFailOn, types of read error to fail on
|
|
*
|
|
* Exactly as vips_jp2kload(), but read from a source.
|
|
*
|
|
* Returns: 0 on success, -1 on error.
|
|
*/
|
|
int
|
|
vips_jp2kload_source( VipsSource *source, VipsImage **out, ... )
|
|
{
|
|
va_list ap;
|
|
int result;
|
|
|
|
va_start( ap, out );
|
|
result = vips_call_split( "jp2kload_source", ap, source, out );
|
|
va_end( ap );
|
|
|
|
return( result );
|
|
}
|