/* 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( &parameters );
	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, &parameters );

	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 );
}
