add jp2k tests

and buffer load/save
This commit is contained in:
John Cupitt 2021-03-20 12:16:10 +00:00
parent 74d2472966
commit 2575d963bd
8 changed files with 263 additions and 4 deletions

View File

@ -20,8 +20,7 @@
- add vips_fitsload_source(), vips_niftiload_source()
- png and gif load note background colour as metadata [781545872]
- add vips_image_[set|get]_array_double()
- add vips_jp2kload(), vips_jp2kload_source(), vips_jp2ksave(),
vips_jp2ksave_target()
- add JPEG2000 load and save
22/12/20 start 8.10.6
- don't seek on bad file descriptors [kleisauke]

View File

@ -2177,8 +2177,10 @@ vips_foreign_operation_init( void )
extern GType vips_foreign_load_svg_source_get_type( void );
extern GType vips_foreign_load_jp2k_file_get_type( void );
extern GType vips_foreign_load_jp2k_buffer_get_type( void );
extern GType vips_foreign_load_jp2k_source_get_type( void );
extern GType vips_foreign_save_jp2k_file_get_type( void );
extern GType vips_foreign_save_jp2k_buffer_get_type( void );
extern GType vips_foreign_save_jp2k_target_get_type( void );
extern GType vips_foreign_load_heif_file_get_type( void );
@ -2253,8 +2255,10 @@ vips_foreign_operation_init( void )
#ifdef HAVE_LIBOPENJP2
vips_foreign_load_jp2k_file_get_type();
vips_foreign_load_jp2k_buffer_get_type();
vips_foreign_load_jp2k_source_get_type();
vips_foreign_save_jp2k_file_get_type();
vips_foreign_save_jp2k_buffer_get_type();
vips_foreign_save_jp2k_target_get_type();
#endif /*HAVE_LIBOPENJP2*/

View File

@ -32,9 +32,9 @@
*/
/*
*/
#define DEBUG_VERBOSE
#define DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
@ -839,6 +839,84 @@ 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;
@ -939,6 +1017,41 @@ vips_jp2kload( const char *filename, VipsImage **out, ... )
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
*
* Exactly as vips_jp2kload(), but read from a source.
*
* 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

View File

@ -615,6 +615,70 @@ vips_foreign_save_jp2k_file_init( VipsForeignSaveJp2kFile *file )
{
}
typedef struct _VipsForeignSaveJp2kBuffer {
VipsForeignSaveJp2k parent_object;
/* Save to a buffer.
*/
VipsArea *buf;
} VipsForeignSaveJp2kBuffer;
typedef VipsForeignSaveJp2kClass VipsForeignSaveJp2kBufferClass;
G_DEFINE_TYPE( VipsForeignSaveJp2kBuffer, vips_foreign_save_jp2k_buffer,
vips_foreign_save_jp2k_get_type() );
static int
vips_foreign_save_jp2k_buffer_build( VipsObject *object )
{
VipsForeignSaveJp2k *jp2k = (VipsForeignSaveJp2k *) object;
VipsForeignSaveJp2kBuffer *buffer =
(VipsForeignSaveJp2kBuffer *) object;
VipsBlob *blob;
if( !(jp2k->target = vips_target_new_to_memory()) )
return( -1 );
if( VIPS_OBJECT_CLASS( vips_foreign_save_jp2k_buffer_parent_class )->
build( object ) )
return( -1 );
g_object_get( jp2k->target, "blob", &blob, NULL );
g_object_set( buffer, "buffer", blob, NULL );
vips_area_unref( VIPS_AREA( blob ) );
return( 0 );
}
static void
vips_foreign_save_jp2k_buffer_class_init(
VipsForeignSaveJp2kBufferClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *object_class = (VipsObjectClass *) class;
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
object_class->nickname = "jp2ksave_buffer";
object_class->build = vips_foreign_save_jp2k_buffer_build;
VIPS_ARG_BOXED( class, "buffer", 1,
_( "Buffer" ),
_( "Buffer to save to" ),
VIPS_ARGUMENT_REQUIRED_OUTPUT,
G_STRUCT_OFFSET( VipsForeignSaveJp2kBuffer, buf ),
VIPS_TYPE_BLOB );
}
static void
vips_foreign_save_jp2k_buffer_init( VipsForeignSaveJp2kBuffer *buffer )
{
}
typedef struct _VipsForeignSaveJp2kTarget {
VipsForeignSaveJp2k parent_object;
@ -715,6 +779,54 @@ vips_jp2ksave( VipsImage *in, const char *filename, ... )
return( result );
}
/**
* vips_jp2ksave_buffer: (method)
* @in: image to save
* @buf: (array length=len) (element-type guint8): return output buffer here
* @len: (type gsize): return output length here
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* * @Q: %gint, quality factor
* * @lossless: %gboolean, enables lossless compression
* * @tile_width: %gint for tile size
* * @tile_height: %gint for tile size
*
* As vips_jp2ksave(), but save to a target.
*
* See also: vips_jp2ksave(), vips_image_write_to_target().
*
* Returns: 0 on success, -1 on error.
*/
int
vips_jp2ksave_buffer( VipsImage *in, void **buf, size_t *len, ... )
{
va_list ap;
VipsArea *area;
int result;
area = NULL;
va_start( ap, len );
result = vips_call_split( "jp2ksave_buffer", ap, in, &area );
va_end( ap );
if( !result &&
area ) {
if( buf ) {
*buf = area->data;
area->free_fn = NULL;
}
if( len )
*len = area->length;
vips_area_unref( area );
}
return( result );
}
/**
* vips_jp2ksave_target: (method)
* @in: image to save

View File

@ -678,10 +678,14 @@ int vips_niftisave( VipsImage *in, const char *filename, ... )
int vips_jp2kload( const char *filename, VipsImage **out, ... )
__attribute__((sentinel));
int vips_jp2kload_buffer( void *buf, size_t len, VipsImage **out, ... )
__attribute__((sentinel));
int vips_jp2kload_source( VipsSource *source, VipsImage **out, ... )
__attribute__((sentinel));
int vips_jp2ksave( VipsImage *in, const char *filename, ... )
__attribute__((sentinel));
int vips_heifsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
__attribute__((sentinel));
int vips_jp2ksave_target( VipsImage *in, VipsTarget *target, ... )
__attribute__((sentinel));

View File

@ -56,6 +56,7 @@ MOSAIC_MARKS = [[489, 140], [66, 141],
MOSAIC_VERTICAL_MARKS = [[388, 44], [364, 346],
[384, 17], [385, 629],
[527, 42], [503, 959]]
JP2K_FILE = os.path.join(IMAGES, "world.jp2")
unsigned_formats = [pyvips.BandFormat.UCHAR,
pyvips.BandFormat.USHORT,

Binary file not shown.

View File

@ -18,7 +18,7 @@ from helpers import \
GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, \
temp_filename, assert_almost_equal_objects, have, skip_if_no, \
TIF1_FILE, TIF2_FILE, TIF4_FILE, WEBP_LOOKS_LIKE_SVG_FILE, \
WEBP_ANIMATED_FILE
WEBP_ANIMATED_FILE, JP2K_FILE
class TestForeign:
tempdir = None
@ -1136,6 +1136,32 @@ class TestForeign:
y = pyvips.Image.new_from_buffer(buf, "")
assert y.get("exif-ifd0-Make").split(" ")[0] == "banana"
@skip_if_no("jp2kload")
def test_jp2kload(self):
def jp2k_valid(im):
a = im(402, 73)
assert_almost_equal_objects(a, [141, 144, 73], threshold=2)
assert im.width == 800
assert im.height == 400
assert im.bands == 3
self.file_loader("jp2kload", JP2K_FILE, jp2k_valid)
self.buffer_loader("jp2kload_buffer", JP2K_FILE, jp2k_valid)
@skip_if_no("jp2ksave")
def test_jp2ksave(self):
self.save_load_buffer("jp2ksave_buffer", "jp2kload_buffer",
self.colour, 80)
buf = self.colour.jp2ksave_buffer(lossless=True)
im2 = pyvips.Image.new_from_buffer(buf, "")
assert self.colour.avg() == im2.avg()
# higher Q should mean a bigger buffer
b1 = self.mono.jp2ksave_buffer(Q=10)
b2 = self.mono.jp2ksave_buffer(Q=90)
assert len(b2) > len(b1)
if __name__ == '__main__':
pytest.main()