From 96ef05a9d157c92cc2f5c1eb7afd7cb9e6ffa73d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 11 Nov 2016 10:14:03 +0000 Subject: [PATCH 01/46] fix a crash in arg handling on Windows we were not updating argc in vips.c after all calls to g_option_context_parse_strv() on Windows, leading to a crash in some cases see https://github.com/jcupitt/libvips/issues/553 --- ChangeLog | 3 +++ configure.ac | 6 +++--- tools/vips.c | 8 +++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 90d493d4..5cf8f631 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +11/11/16 started 8.4.4 +- fix crash in vips.exe arg parsing on Windows, thanks Yury + 18/10/16 started 8.4.3 - fix error detection in gif_close, thanks aaron42net - fix tiny threading memleak diff --git a/configure.ac b/configure.ac index 563294bb..c8192ddc 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # also update the version number in the m4 macros below -AC_INIT([vips], [8.4.3], [vipsip@jiscmail.ac.uk]) +AC_INIT([vips], [8.4.4], [vipsip@jiscmail.ac.uk]) # required for gobject-introspection AC_PREREQ(2.62) @@ -18,7 +18,7 @@ AC_CONFIG_MACRO_DIR([m4]) # user-visible library versioning m4_define([vips_major_version], [8]) m4_define([vips_minor_version], [4]) -m4_define([vips_micro_version], [3]) +m4_define([vips_micro_version], [4]) m4_define([vips_version], [vips_major_version.vips_minor_version.vips_micro_version]) @@ -38,7 +38,7 @@ VIPS_VERSION_STRING=$VIPS_VERSION-`date` # binary interface changes not backwards compatible?: reset age to 0 LIBRARY_CURRENT=48 -LIBRARY_REVISION=3 +LIBRARY_REVISION=4 LIBRARY_AGE=6 # patched into include/vips/version.h diff --git a/tools/vips.c b/tools/vips.c index caa35929..eff1ffcb 100644 --- a/tools/vips.c +++ b/tools/vips.c @@ -1041,12 +1041,18 @@ parse_options( GOptionContext *context, int *argc, char **argv ) vips_error_exit( NULL ); } + /* On Windows, argc will not have been updated by + * g_option_context_parse_strv(). + */ + for( *argc = 0; argv[*argc]; (*argc)++ ) + ; + /* Remove any "--" argument. If one of our arguments is a negative * number, the user will need to have added the "--" flag to stop * GOption parsing. But "--" is still passed down to us and we need to * ignore it. */ - for( i = 1; i < *argc - 1; i++ ) + for( i = 1; i < *argc; i++ ) if( strcmp( argv[i], "--" ) == 0 ) { for( j = i; j < *argc; j++ ) argv[j] = argv[j + 1]; From e72d145ae95e07a9ef317ba744e5b00c1547c3b9 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 11 Nov 2016 16:24:18 +0000 Subject: [PATCH 02/46] better >4gb detection for zip dzsave older libgsfs can't save zip64 and will fail silently for very large output trees ... improve the slightly sketchy >4gb detection in dzsave this a a version of the patch in https://github.com/jcupitt/libvips/pull/462 --- ChangeLog | 1 + libvips/foreign/dzsave.c | 76 ++++++++++++++++++++++++++++++++++------ 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/ChangeLog b/ChangeLog index 79e28138..cc72ccf7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,7 @@ - better vipsheader behaviour with complex field types - added vips_image_hasalpha() - added vips_thumbnail() / vips_thumbnail_buffer() +- better >4gb detect for zip dzsave output [Felix Bünemann] 11/11/16 started 8.4.4 - fix crash in vips.exe arg parsing on Windows, thanks Yury diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index fae5584b..f9897e8e 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -69,6 +69,8 @@ * - move vips-properties out of subdir for gm and zoomify layouts * 15/10/16 * - add dzsave_buffer + * 11/11/16 Felix Bünemann + * - better >4gb detection for zip output on older libgsfs */ /* @@ -322,6 +324,12 @@ typedef struct _VipsGsfDirectory { */ GsfOutput *container; + /* Track number of files in tree and total length of filenames. We use + * this to estimate zip size to spot a >4gb write. + */ + size_t file_count; + size_t filename_lengths; + /* Set deflate compression level for zip container. */ gint deflate_level; @@ -392,6 +400,8 @@ vips_gsf_tree_new( GsfOutput *out, gint deflate_level ) tree->children = NULL; tree->out = out; tree->container = NULL; + tree->file_count = 0; + tree->filename_lengths = 0; tree->deflate_level = deflate_level; return( tree ); @@ -429,6 +439,8 @@ vips_gsf_dir_new( VipsGsfDirectory *parent, const char *name ) dir->name = g_strdup( name ); dir->children = NULL; dir->container = NULL; + dir->file_count = 0; + dir->filename_lengths = 0; dir->deflate_level = parent->deflate_level; if( GSF_IS_OUTFILE_ZIP( parent->out ) ) @@ -467,13 +479,23 @@ vips_gsf_path( VipsGsfDirectory *tree, const char *name, ... ) char *dir_name; GsfOutput *obj; + /* vips_gsf_path() always makes a new file, though it may add to an + * existing directory. Note the file, and note the length of the full + * path we are creating. + */ + tree->file_count += 1; + tree->filename_lengths += strlen( tree->out->name ) + strlen( name ) + 1; + dir = tree; va_start( ap, name ); - while( (dir_name = va_arg( ap, char * )) ) + while( (dir_name = va_arg( ap, char * )) ) { if( (child = vips_gsf_child_by_name( dir, dir_name )) ) dir = child; else dir = vips_gsf_dir_new( dir, dir_name ); + + tree->filename_lengths += strlen( dir_name ) + 1; + } va_end( ap ); if( GSF_IS_OUTFILE_ZIP( dir->out ) ) { @@ -1220,6 +1242,29 @@ tile_equal( VipsImage *image, VipsPel * restrict ink ) return( TRUE ); } +#define VIPS_ZIP_FIXED_LH_SIZE (30 + 29) +#define VIPS_ZIP_FIXED_CD_SIZE (46 + 9) +#define VIPS_ZIP_EOCD_SIZE 22 + +#ifndef HAVE_GSF_ZIP64 +static size_t +estimate_zip_size( VipsForeignSaveDz *dz ) +{ + size_t estimated_zip_size = dz->bytes_written + + dz->tree->file_count * VIPS_ZIP_FIXED_LH_SIZE + + dz->tree->filename_lengths + + dz->tree->file_count * VIPS_ZIP_FIXED_CD_SIZE + + dz->tree->filename_lengths + + VIPS_ZIP_EOCD_SIZE; + +#ifdef DEBUG_VERBOSE + printf( "estimate_zip_size: %zd\n", estimated_zip_size ); +#endif /*DEBUG_VERBOSE*/ + + return( estimated_zip_size ); +} +#endif /*HAVE_GSF_ZIP64*/ + static int strip_work( VipsThreadState *state, void *a ) { @@ -1339,17 +1384,26 @@ strip_work( VipsThreadState *state, void *a ) } #ifndef HAVE_GSF_ZIP64 - /* Allow a 100,000 byte margin. This probably isn't enough: we don't - * include the space zip needs for the index nor anything we are - * outputting apart from the gsf_output_write() above. - */ - if( dz->container == VIPS_FOREIGN_DZ_CONTAINER_ZIP && - dz->bytes_written > (size_t) UINT_MAX - 100000 ) { - g_mutex_unlock( vips__global_lock ); + if( dz->container == VIPS_FOREIGN_DZ_CONTAINER_ZIP ) { + /* Leave 3 entry headroom for blank.png and metadata files. + */ + if( dz->tree->file_count + 3 >= (unsigned int) USHRT_MAX ) { + g_mutex_unlock( vips__global_lock ); - vips_error( class->nickname, - "%s", _( "output file too large" ) ); - return( -1 ); + vips_error( class->nickname, + "%s", _( "too many files in zip" ) ); + return( -1 ); + } + + /* Leave 16k headroom for blank.png and metadata files. + */ + if( estimate_zip_size( dz ) > (size_t) UINT_MAX - 16384) { + g_mutex_unlock( vips__global_lock ); + + vips_error( class->nickname, + "%s", _( "output file too large" ) ); + return( -1 ); + } } #endif /*HAVE_GSF_ZIP64*/ From bb0a6643f94e69294e36d2b253f9bdd60c8c40ed Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 12 Nov 2016 15:33:35 +0000 Subject: [PATCH 03/46] move @fail from jpegload into the base load class and add fail support to csv and openslide see https://github.com/jcupitt/libvips/issues/546 --- ChangeLog | 2 ++ libvips/deprecated/im_csv2vips.c | 2 +- libvips/foreign/csv.c | 23 +++++++++++++++-------- libvips/foreign/csvload.c | 9 +++++++-- libvips/foreign/foreign.c | 7 +++++++ libvips/foreign/jpegload.c | 19 ++++--------------- libvips/foreign/openslide2vips.c | 26 ++++++++++++++++---------- libvips/foreign/openslideload.c | 9 ++++++--- libvips/foreign/pforeign.h | 12 +++++++----- libvips/foreign/tiff.c | 3 +++ libvips/include/vips/foreign.h | 4 ++++ 11 files changed, 72 insertions(+), 44 deletions(-) diff --git a/ChangeLog b/ChangeLog index cc72ccf7..89763f00 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,8 @@ - added vips_image_hasalpha() - added vips_thumbnail() / vips_thumbnail_buffer() - better >4gb detect for zip dzsave output [Felix Bünemann] +- all loaders have a @fail option, meaning fail on first warning, though it + only does anything for jpg, csv, openslide 11/11/16 started 8.4.4 - fix crash in vips.exe arg parsing on Windows, thanks Yury diff --git a/libvips/deprecated/im_csv2vips.c b/libvips/deprecated/im_csv2vips.c index 55900b93..a2bf48e0 100644 --- a/libvips/deprecated/im_csv2vips.c +++ b/libvips/deprecated/im_csv2vips.c @@ -76,7 +76,7 @@ im_csv2vips( const char *filename, IMAGE *out ) } if( vips__csv_read( name, out, - start_skip, lines, whitespace, separator ) ) + start_skip, lines, whitespace, separator, FALSE ) ) return( -1 ); return( 0 ); diff --git a/libvips/foreign/csv.c b/libvips/foreign/csv.c index 5ea4cbcf..95f71f18 100644 --- a/libvips/foreign/csv.c +++ b/libvips/foreign/csv.c @@ -172,7 +172,7 @@ skip_to_sep( FILE *fp, const char sepmap[256] ) */ static int read_double( FILE *fp, const char whitemap[256], const char sepmap[256], - int lineno, int colno, double *out ) + int lineno, int colno, double *out, gboolean fail ) { int ch; @@ -198,6 +198,8 @@ read_double( FILE *fp, const char whitemap[256], const char sepmap[256], vips_warn( "csv2vips", _( "error parsing number, line %d, column %d" ), lineno, colno ); + if( fail ) + return( EOF ); /* Step over the bad data to the next separator. */ @@ -222,7 +224,8 @@ read_csv( FILE *fp, VipsImage *out, int skip, int lines, const char *whitespace, const char *separator, - gboolean read_image ) + gboolean read_image, + gboolean fail ) { int i; char whitemap[256]; @@ -265,7 +268,7 @@ read_csv( FILE *fp, VipsImage *out, } for( columns = 0; (ch = read_double( fp, whitemap, sepmap, - skip + 1, columns + 1, &d )) == 0; + skip + 1, columns + 1, &d, fail )) == 0; columns++ ) ; (void) fsetpos( fp, &pos ); @@ -308,7 +311,7 @@ read_csv( FILE *fp, VipsImage *out, int colno = x + 1; ch = read_double( fp, whitemap, sepmap, - lineno, colno, &d ); + lineno, colno, &d, fail ); if( ch == EOF ) { vips_error( "csv2vips", _( "unexpected EOF, line %d col %d" ), @@ -342,13 +345,15 @@ read_csv( FILE *fp, VipsImage *out, int vips__csv_read( const char *filename, VipsImage *out, - int skip, int lines, const char *whitespace, const char *separator ) + int skip, int lines, const char *whitespace, const char *separator, + gboolean fail ) { FILE *fp; if( !(fp = vips__file_open_read( filename, NULL, TRUE )) ) return( -1 ); - if( read_csv( fp, out, skip, lines, whitespace, separator, TRUE ) ) { + if( read_csv( fp, out, + skip, lines, whitespace, separator, TRUE, fail ) ) { fclose( fp ); return( -1 ); } @@ -359,13 +364,15 @@ vips__csv_read( const char *filename, VipsImage *out, int vips__csv_read_header( const char *filename, VipsImage *out, - int skip, int lines, const char *whitespace, const char *separator ) + int skip, int lines, const char *whitespace, const char *separator, + gboolean fail ) { FILE *fp; if( !(fp = vips__file_open_read( filename, NULL, TRUE )) ) return( -1 ); - if( read_csv( fp, out, skip, lines, whitespace, separator, FALSE ) ) { + if( read_csv( fp, out, + skip, lines, whitespace, separator, FALSE, fail ) ) { fclose( fp ); return( -1 ); } diff --git a/libvips/foreign/csvload.c b/libvips/foreign/csvload.c index 9ee62135..59c37fe2 100644 --- a/libvips/foreign/csvload.c +++ b/libvips/foreign/csvload.c @@ -89,7 +89,8 @@ vips_foreign_load_csv_header( VipsForeignLoad *load ) VipsForeignLoadCsv *csv = (VipsForeignLoadCsv *) load; if( vips__csv_read_header( csv->filename, load->out, - csv->skip, csv->lines, csv->whitespace, csv->separator ) ) + csv->skip, csv->lines, csv->whitespace, csv->separator, + load->fail ) ) return( -1 ); VIPS_SETSTR( load->out->filename, csv->filename ); @@ -103,7 +104,8 @@ vips_foreign_load_csv_load( VipsForeignLoad *load ) VipsForeignLoadCsv *csv = (VipsForeignLoadCsv *) load; if( vips__csv_read( csv->filename, load->real, - csv->skip, csv->lines, csv->whitespace, csv->separator ) ) + csv->skip, csv->lines, csv->whitespace, csv->separator, + load->fail ) ) return( -1 ); return( 0 ); @@ -191,6 +193,7 @@ vips_foreign_load_csv_init( VipsForeignLoadCsv *csv ) * * @lines: read this many lines from file * * @whitespace: set of whitespace characters * * @separator: set of separator characters + * * @fail: %gboolean, fail on warnings * * Load a CSV (comma-separated values) file. The output image is always 1 * band (monochrome), #VIPS_FORMAT_DOUBLE. Use vips_bandfold() to turn @@ -218,6 +221,8 @@ vips_foreign_load_csv_init( VipsForeignLoadCsv *csv ) * @separator sets the characters that separate fields. * Default ;,tab. Separators are never run together. * + * Setting @fail to %TRUE makes the reader fail on any warnings. + * * See also: vips_image_new_from_file(), vips_bandfold(). * * Returns: 0 on success, -1 on error. diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 3313b9e6..ea7d03e3 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -986,6 +986,13 @@ vips_foreign_load_class_init( VipsForeignLoadClass *class ) G_STRUCT_OFFSET( VipsForeignLoad, sequential ), FALSE ); + VIPS_ARG_BOOL( class, "fail", 11, + _( "Fail" ), + _( "Fail on first warning" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoad, fail ), + FALSE ); + } static void diff --git a/libvips/foreign/jpegload.c b/libvips/foreign/jpegload.c index 186fd28f..9e53914e 100644 --- a/libvips/foreign/jpegload.c +++ b/libvips/foreign/jpegload.c @@ -77,10 +77,6 @@ typedef struct _VipsForeignLoadJpeg { */ int shrink; - /* Fail on first warning. - */ - gboolean fail; - /* Autorotate using exif orientation tag. */ gboolean autorotate; @@ -144,13 +140,6 @@ vips_foreign_load_jpeg_class_init( VipsForeignLoadJpegClass *class ) G_STRUCT_OFFSET( VipsForeignLoadJpeg, shrink ), 1, 16, 1 ); - VIPS_ARG_BOOL( class, "fail", 11, - _( "Fail" ), - _( "Fail on first warning" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadJpeg, fail ), - FALSE ); - VIPS_ARG_BOOL( class, "autorotate", 12, _( "Autorotate" ), _( "Rotate image using exif orientation" ), @@ -201,7 +190,7 @@ vips_foreign_load_jpeg_file_header( VipsForeignLoad *load ) VipsForeignLoadJpegFile *file = (VipsForeignLoadJpegFile *) load; if( vips__jpeg_read_file( file->filename, load->out, - TRUE, jpeg->shrink, jpeg->fail, FALSE, jpeg->autorotate ) ) + TRUE, jpeg->shrink, load->fail, FALSE, jpeg->autorotate ) ) return( -1 ); return( 0 ); @@ -214,7 +203,7 @@ vips_foreign_load_jpeg_file_load( VipsForeignLoad *load ) VipsForeignLoadJpegFile *file = (VipsForeignLoadJpegFile *) load; if( vips__jpeg_read_file( file->filename, load->real, - FALSE, jpeg->shrink, jpeg->fail, + FALSE, jpeg->shrink, load->fail, load->access == VIPS_ACCESS_SEQUENTIAL, jpeg->autorotate ) ) return( -1 ); @@ -283,7 +272,7 @@ vips_foreign_load_jpeg_buffer_header( VipsForeignLoad *load ) VipsForeignLoadJpegBuffer *buffer = (VipsForeignLoadJpegBuffer *) load; if( vips__jpeg_read_buffer( buffer->buf->data, buffer->buf->length, - load->out, TRUE, jpeg->shrink, jpeg->fail, FALSE, + load->out, TRUE, jpeg->shrink, load->fail, FALSE, jpeg->autorotate ) ) return( -1 ); @@ -297,7 +286,7 @@ vips_foreign_load_jpeg_buffer_load( VipsForeignLoad *load ) VipsForeignLoadJpegBuffer *buffer = (VipsForeignLoadJpegBuffer *) load; if( vips__jpeg_read_buffer( buffer->buf->data, buffer->buf->length, - load->real, FALSE, jpeg->shrink, jpeg->fail, + load->real, FALSE, jpeg->shrink, load->fail, load->access == VIPS_ACCESS_SEQUENTIAL, jpeg->autorotate ) ) return( -1 ); diff --git a/libvips/foreign/openslide2vips.c b/libvips/foreign/openslide2vips.c index bd94a196..27d8807b 100644 --- a/libvips/foreign/openslide2vips.c +++ b/libvips/foreign/openslide2vips.c @@ -115,6 +115,10 @@ typedef struct { */ int tile_width; int tile_height; + + /* Quit on warning. + */ + gboolean fail; } ReadSlide; int @@ -223,7 +227,7 @@ get_bounds( openslide_t *osr, VipsRect *rect ) static ReadSlide * readslide_new( const char *filename, VipsImage *out, - int level, gboolean autocrop, const char *associated ) + int level, gboolean autocrop, const char *associated, gboolean fail ) { ReadSlide *rslide; int64_t w, h; @@ -391,9 +395,9 @@ readslide_new( const char *filename, VipsImage *out, int vips__openslide_read_header( const char *filename, VipsImage *out, - int level, gboolean autocrop, char *associated ) + int level, gboolean autocrop, char *associated, gboolean fail ) { - if( !readslide_new( filename, out, level, autocrop, associated ) ) + if( !readslide_new( filename, out, level, autocrop, associated, fail ) ) return( -1 ); return( 0 ); @@ -475,13 +479,14 @@ vips__openslide_generate( VipsRegion *out, /* Only warn on error: we don't want to make the whole image unreadable * because of one broken tile. - * - * FIXME ... add a --fail option like jpegload */ error = openslide_get_error( rslide->osr ); - if( error ) + if( error ) { vips_warn( "openslide2vips", _( "reading region: %s" ), error ); + if( rslide->fail ) + return( -1 ); + } /* Since we are inside a cache, we know buf must be continuous. */ @@ -492,7 +497,7 @@ vips__openslide_generate( VipsRegion *out, int vips__openslide_read( const char *filename, VipsImage *out, - int level, gboolean autocrop ) + int level, gboolean autocrop, gboolean fail ) { ReadSlide *rslide; VipsImage *raw; @@ -505,7 +510,7 @@ vips__openslide_read( const char *filename, VipsImage *out, vips_object_local( out, raw ); if( !(rslide = readslide_new( filename, raw, - level, autocrop, NULL )) ) + level, autocrop, NULL, fail )) ) return( -1 ); if( vips_image_generate( raw, @@ -534,7 +539,7 @@ vips__openslide_read( const char *filename, VipsImage *out, int vips__openslide_read_associated( const char *filename, VipsImage *out, - const char *associated ) + const char *associated, gboolean fail ) { ReadSlide *rslide; VipsImage *raw; @@ -549,7 +554,8 @@ vips__openslide_read_associated( const char *filename, VipsImage *out, raw = vips_image_new_memory(); vips_object_local( out, raw ); - if( !(rslide = readslide_new( filename, raw, 0, FALSE, associated )) || + if( !(rslide = readslide_new( filename, raw, + 0, FALSE, associated, fail )) || vips_image_write_prepare( raw ) ) return( -1 ); buf = (uint32_t *) VIPS_IMAGE_ADDR( raw, 0, 0 ); diff --git a/libvips/foreign/openslideload.c b/libvips/foreign/openslideload.c index a02927fb..aef3eade 100644 --- a/libvips/foreign/openslideload.c +++ b/libvips/foreign/openslideload.c @@ -114,7 +114,7 @@ vips_foreign_load_openslide_header( VipsForeignLoad *load ) if( vips__openslide_read_header( openslide->filename, load->out, openslide->level, openslide->autocrop, - openslide->associated ) ) + openslide->associated, load->fail ) ) return( -1 ); VIPS_SETSTR( load->out->filename, openslide->filename ); @@ -129,13 +129,13 @@ vips_foreign_load_openslide_load( VipsForeignLoad *load ) if( !openslide->associated ) { if( vips__openslide_read( openslide->filename, load->real, - openslide->level, openslide->autocrop ) ) + openslide->level, openslide->autocrop, load->fail ) ) return( -1 ); } else { if( vips__openslide_read_associated( openslide->filename, load->real, - openslide->associated ) ) + openslide->associated, load->fail ) ) return( -1 ); } @@ -231,6 +231,7 @@ vips_foreign_load_openslide_init( VipsForeignLoadOpenslide *openslide ) * * @level: load this level * * @associated: load this associated image * * @autocrop: crop to image bounds + * * @fail: %gboolean, fail on warnings * * Read a virtual slide supported by the OpenSlide library into a VIPS image. * OpenSlide supports images in Aperio, Hamamatsu, MIRAX, Sakura, Trestle, @@ -250,6 +251,8 @@ vips_foreign_load_openslide_init( VipsForeignLoadOpenslide *openslide ) * * The output of this operator is always RGBA. * + * Setting @fail to %TRUE makes the reader fail on any warnings. + * * See also: vips_image_new_from_file(). * * Returns: 0 on success, -1 on error. diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 95b63ea8..f0a8557a 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -87,9 +87,11 @@ int vips__analyze_read( const char *filename, VipsImage *out ); extern const char *vips__foreign_csv_suffs[]; int vips__csv_read( const char *filename, VipsImage *out, - int skip, int lines, const char *whitespace, const char *separator ); + int skip, int lines, const char *whitespace, const char *separator, + gboolean fail ); int vips__csv_read_header( const char *filename, VipsImage *out, - int skip, int lines, const char *whitespace, const char *separator ); + int skip, int lines, const char *whitespace, const char *separator, + gboolean fail ); int vips__csv_write( VipsImage *in, const char *filename, const char *separator ); @@ -215,11 +217,11 @@ int vips__webp_write_buffer( VipsImage *out, void **buf, size_t *len, int vips__openslide_isslide( const char *filename ); int vips__openslide_read_header( const char *filename, VipsImage *out, - int level, gboolean autocrop, char *associated ); + int level, gboolean autocrop, char *associated, gboolean fail ); int vips__openslide_read( const char *filename, VipsImage *out, - int level, gboolean autocrop ); + int level, gboolean autocrop, gboolean fail ); int vips__openslide_read_associated( const char *filename, VipsImage *out, - const char *associated ); + const char *associated, gboolean fail ); #ifdef __cplusplus } diff --git a/libvips/foreign/tiff.c b/libvips/foreign/tiff.c index ba82f4ee..07bf926d 100644 --- a/libvips/foreign/tiff.c +++ b/libvips/foreign/tiff.c @@ -67,6 +67,9 @@ vips__thandler_error( const char *module, const char *fmt, va_list ap ) vips_verror( module, fmt, ap ); } +/* It'd be nice to be able to support the @fail option for the tiff loader, but + * there's no easy way to do this, since libtiff has a global warning handler. + */ static void vips__thandler_warning( const char *module, const char *fmt, va_list ap ) { diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 2245167b..cb510570 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -131,6 +131,10 @@ typedef struct _VipsForeignLoad { */ VipsForeignFlags flags; + /* Stop load on first warning. + */ + gboolean fail; + /* Deprecated and unused, just here for compat. */ gboolean sequential; From 4540a2c220484e33f785242c934ed7e8de31fd3f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 12 Nov 2016 15:36:45 +0000 Subject: [PATCH 04/46] oop, rank was not allowing index == 0 the desc for the index param to rank was incorrectly banning index == 0 see https://github.com/jcupitt/libvips/issues/555 --- libvips/morphology/rank.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libvips/morphology/rank.c b/libvips/morphology/rank.c index 67f2f5f1..09bcebc6 100644 --- a/libvips/morphology/rank.c +++ b/libvips/morphology/rank.c @@ -24,6 +24,8 @@ * - gtk-doc * 17/1/14 * - redone as a class + * 12/11/16 + * - oop, allow index == 0, thanks Rob */ /* @@ -430,7 +432,7 @@ vips_rank_class_init( VipsRankClass *class ) _( "Select pixel at index" ), VIPS_ARGUMENT_REQUIRED_INPUT, G_STRUCT_OFFSET( VipsRank, index ), - 1, 100000000, 50 ); + 0, 100000000, 50 ); } From 85be55fe4faa36361faba807fb33ddc73e877e7a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 12 Nov 2016 16:37:13 +0000 Subject: [PATCH 05/46] add vips_image_get_fields() helps bindings (which struglle with vips_image_map()) get a list of header fields works from py, but not ruby, I guess gchar** isn't a supported type for ruby-gnome see https://github.com/jcupitt/libvips/issues/533 --- ChangeLog | 1 + libvips/include/vips/header.h | 1 + libvips/iofuncs/header.c | 49 +++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/ChangeLog b/ChangeLog index 89763f00..8e6be44d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,7 @@ - better >4gb detect for zip dzsave output [Felix Bünemann] - all loaders have a @fail option, meaning fail on first warning, though it only does anything for jpg, csv, openslide +- add vips_image_get_fields() to help bindings 11/11/16 started 8.4.4 - fix crash in vips.exe arg parsing on Windows, thanks Yury diff --git a/libvips/include/vips/header.h b/libvips/include/vips/header.h index 8272aa7c..691a407f 100644 --- a/libvips/include/vips/header.h +++ b/libvips/include/vips/header.h @@ -170,6 +170,7 @@ gboolean vips_image_remove( VipsImage *image, const char *name ); typedef void *(*VipsImageMapFn)( VipsImage *image, const char *name, GValue *value, void *a ); void *vips_image_map( VipsImage *image, VipsImageMapFn fn, void *a ); +gchar **vips_image_get_fields( VipsImage *image ); void vips_image_set_area( VipsImage *image, const char *name, VipsCallbackFn free_fn, void *data ); diff --git a/libvips/iofuncs/header.c b/libvips/iofuncs/header.c index 78757617..4a4f6965 100644 --- a/libvips/iofuncs/header.c +++ b/libvips/iofuncs/header.c @@ -1176,6 +1176,55 @@ vips_image_map( VipsImage *image, VipsImageMapFn fn, void *a ) return( NULL ); } +static void * +count_fields( VipsImage *image, const char *field, GValue *value, void *a ) +{ + int *n_fields = (int *) a; + + n_fields += 1; + + return( NULL ); +} + +static void * +add_fields( VipsImage *image, const char *field, GValue *value, void *a ) +{ + gchar ***p = (gchar ***) a; + + **p = g_strdup( field ); + *p += 1; + + return( NULL ); +} + +/** + * vips_image_get_fields: + * @image: image to get fields from + * + * Get a %NULL-terminated array listing all the metadata field names on @image. + * Free the return result with g_strfreev(). + * + * This is handy for language bindings. From C, it's usually more convenient to + * use vips_image_map(). + * + * Returns: (transfer full): metadata fields in image, as a %NULL-terminated + * array. + */ +gchar ** +vips_image_get_fields( VipsImage *image ) +{ + int n_fields; + gchar **fields; + gchar **p; + + (void) vips_image_map( image, count_fields, &n_fields ); + fields = g_new0( gchar *, n_fields + 1 ); + p = fields; + (void) vips_image_map( image, add_fields, &p ); + + return( fields ); +} + /** * vips_image_set_area: * @image: image to attach the metadata to From 92c549e9951dadc568eec3bf94e02a5d31e103ba Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 14 Nov 2016 09:04:17 +0000 Subject: [PATCH 06/46] tiny tiff2vips cleanup --- libvips/foreign/tiff2vips.c | 50 +++++++++++++------------------------ 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 4276d9d5..550aab46 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -1824,35 +1824,31 @@ readtiff_new( VipsImage *out, return( rtiff ); } +static int +readtiff_set_directory( ReadTiff *rtiff, int page ) +{ + if( !TIFFSetDirectory( rtiff->tiff, page ) ) { + vips_error( "tiff2vips", + _( "TIFF does not contain page %d" ), rtiff->page ); + return( -1 ); + } + + return( 0 ); +} + static ReadTiff * readtiff_new_filename( const char *filename, VipsImage *out, int page, gboolean autorotate, gboolean readbehind ) { ReadTiff *rtiff; - int i; - if( !(rtiff = readtiff_new( out, page, autorotate, readbehind )) ) + if( !(rtiff = readtiff_new( out, page, autorotate, readbehind )) || + !(rtiff->tiff = vips__tiff_openin( filename )) || + readtiff_set_directory( rtiff, page ) ) return( NULL ); rtiff->filename = vips_strdup( VIPS_OBJECT( out ), filename ); - /* No mmap --- no performance advantage with libtiff, and it burns up - * our VM if the tiff file is large. - */ - if( !(rtiff->tiff = vips__tiff_openin( filename )) ) { - vips_error( "tiff2vips", _( "unable to open \"%s\" for input" ), - filename ); - return( NULL ); - } - - for( i = 0; i < page; i++ ) - if( !TIFFReadDirectory( rtiff->tiff ) ) { - vips_error( "tiff2vips", - _( "TIFF does not contain page %d" ), - rtiff->page ); - return( NULL ); - } - return( rtiff ); } @@ -1861,22 +1857,12 @@ readtiff_new_buffer( const void *buf, size_t len, VipsImage *out, int page, gboolean autorotate, gboolean readbehind ) { ReadTiff *rtiff; - int i; - if( !(rtiff = readtiff_new( out, page, autorotate, readbehind )) ) + if( !(rtiff = readtiff_new( out, page, autorotate, readbehind )) || + !(rtiff->tiff = vips__tiff_openin_buffer( out, buf, len )) || + readtiff_set_directory( rtiff, page ) ) return( NULL ); - if( !(rtiff->tiff = vips__tiff_openin_buffer( out, buf, len )) ) - return( NULL ); - - for( i = 0; i < page; i++ ) - if( !TIFFReadDirectory( rtiff->tiff ) ) { - vips_error( "tiff2vips", - _( "TIFF does not contain page %d" ), - rtiff->page ); - return( NULL ); - } - return( rtiff ); } From 3ef6a4695ae59c7393f12643ecdd5120a4f72120 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 15 Nov 2016 10:07:09 +0000 Subject: [PATCH 07/46] start adding read many page support but it's not easy ... we'll need to be very strict about every page being identical if we want to share readers --- TODO | 4 +++ libvips/deprecated/im_tiff2vips.c | 4 +-- libvips/foreign/pforeign.h | 8 ++--- libvips/foreign/tiff2vips.c | 51 ++++++++++++++++--------------- libvips/foreign/tiffload.c | 34 ++++++++++++++++----- 5 files changed, 63 insertions(+), 38 deletions(-) diff --git a/TODO b/TODO index 715f4c8f..aada39c7 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,7 @@ +- all toilet roll loaders need to set "page-height" + + magick, pdf and tiff need to use the same page/n interface + - not sure about utf8 error messages on win - strange: diff --git a/libvips/deprecated/im_tiff2vips.c b/libvips/deprecated/im_tiff2vips.c index 6fa25122..74479115 100644 --- a/libvips/deprecated/im_tiff2vips.c +++ b/libvips/deprecated/im_tiff2vips.c @@ -94,11 +94,11 @@ tiff2vips( const char *name, IMAGE *out, gboolean header_only ) } if( header_only ) { - if( vips__tiff_read_header( filename, out, page, FALSE ) ) + if( vips__tiff_read_header( filename, out, page, 1, FALSE ) ) return( -1 ); } else { - if( vips__tiff_read( filename, out, page, FALSE, TRUE ) ) + if( vips__tiff_read( filename, out, page, 1, FALSE, TRUE ) ) return( -1 ); } #else diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index f0a8557a..0313ac8b 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -66,17 +66,17 @@ int vips__tiff_write_buf( VipsImage *in, gboolean properties, gboolean strip ); int vips__tiff_read_header( const char *filename, VipsImage *out, - int page, gboolean autorotate ); + int page, int n, gboolean autorotate ); int vips__tiff_read( const char *filename, VipsImage *out, - int page, gboolean autorotate, gboolean readbehind ); + int page, int n, gboolean autorotate, gboolean readbehind ); gboolean vips__istifftiled( const char *filename ); gboolean vips__istiff_buffer( const void *buf, size_t len ); gboolean vips__istiff( const char *filename ); int vips__tiff_read_header_buffer( const void *buf, size_t len, VipsImage *out, - int page, gboolean autorotate ); + int page, int n, gboolean autorotate ); int vips__tiff_read_buffer( const void *buf, size_t len, VipsImage *out, - int page, gboolean autorotate, gboolean readbehind ); + int page, int n, gboolean autorotate, gboolean readbehind ); extern const char *vips__foreign_tiff_suffs[]; diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 550aab46..0d54cd79 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -1787,9 +1787,21 @@ readtiff_close( VipsObject *object, ReadTiff *rtiff ) readtiff_free( rtiff ); } +static int +readtiff_set_directory( ReadTiff *rtiff, int page ) +{ + if( !TIFFSetDirectory( rtiff->tiff, page ) ) { + vips_error( "tiff2vips", + _( "TIFF does not contain page %d" ), rtiff->page ); + return( -1 ); + } + + return( 0 ); +} + static ReadTiff * readtiff_new( VipsImage *out, - int page, gboolean autorotate, gboolean readbehind ) + int page, int n, gboolean autorotate, gboolean readbehind ) { ReadTiff *rtiff; @@ -1824,25 +1836,13 @@ readtiff_new( VipsImage *out, return( rtiff ); } -static int -readtiff_set_directory( ReadTiff *rtiff, int page ) -{ - if( !TIFFSetDirectory( rtiff->tiff, page ) ) { - vips_error( "tiff2vips", - _( "TIFF does not contain page %d" ), rtiff->page ); - return( -1 ); - } - - return( 0 ); -} - static ReadTiff * readtiff_new_filename( const char *filename, VipsImage *out, - int page, gboolean autorotate, gboolean readbehind ) + int page, int n, gboolean autorotate, gboolean readbehind ) { ReadTiff *rtiff; - if( !(rtiff = readtiff_new( out, page, autorotate, readbehind )) || + if( !(rtiff = readtiff_new( out, page, n, autorotate, readbehind )) || !(rtiff->tiff = vips__tiff_openin( filename )) || readtiff_set_directory( rtiff, page ) ) return( NULL ); @@ -1854,11 +1854,11 @@ readtiff_new_filename( const char *filename, VipsImage *out, static ReadTiff * readtiff_new_buffer( const void *buf, size_t len, VipsImage *out, - int page, gboolean autorotate, gboolean readbehind ) + int page, int n, gboolean autorotate, gboolean readbehind ) { ReadTiff *rtiff; - if( !(rtiff = readtiff_new( out, page, autorotate, readbehind )) || + if( !(rtiff = readtiff_new( out, page, n, autorotate, readbehind )) || !(rtiff->tiff = vips__tiff_openin_buffer( out, buf, len )) || readtiff_set_directory( rtiff, page ) ) return( NULL ); @@ -1889,7 +1889,7 @@ istiffpyramid( const char *name ) int vips__tiff_read( const char *filename, VipsImage *out, - int page, gboolean autorotate, gboolean readbehind ) + int page, int n, gboolean autorotate, gboolean readbehind ) { ReadTiff *rtiff; @@ -1901,7 +1901,7 @@ vips__tiff_read( const char *filename, VipsImage *out, vips__tiff_init(); if( !(rtiff = readtiff_new_filename( filename, - out, page, autorotate, readbehind )) ) + out, page, n, autorotate, readbehind )) ) return( -1 ); if( TIFFIsTiled( rtiff->tiff ) ) { @@ -1941,14 +1941,14 @@ vips__tiff_read_header_orientation( ReadTiff *rtiff, VipsImage *out ) int vips__tiff_read_header( const char *filename, VipsImage *out, - int page, gboolean autorotate ) + int page, int n, gboolean autorotate ) { ReadTiff *rtiff; vips__tiff_init(); if( !(rtiff = readtiff_new_filename( filename, out, - page, autorotate, FALSE )) ) + page, n, autorotate, FALSE )) ) return( -1 ); if( parse_header( rtiff, out ) ) @@ -2010,14 +2010,14 @@ vips__istiff( const char *filename ) int vips__tiff_read_header_buffer( const void *buf, size_t len, VipsImage *out, - int page, gboolean autorotate ) + int page, int n, gboolean autorotate ) { ReadTiff *rtiff; vips__tiff_init(); if( !(rtiff = readtiff_new_buffer( buf, len, out, - page, autorotate, FALSE )) ) + page, n, autorotate, FALSE )) ) return( -1 ); if( parse_header( rtiff, out ) ) @@ -2030,7 +2030,8 @@ vips__tiff_read_header_buffer( const void *buf, size_t len, VipsImage *out, int vips__tiff_read_buffer( const void *buf, size_t len, - VipsImage *out, int page, gboolean autorotate, gboolean readbehind ) + VipsImage *out, int page, int n, gboolean autorotate, + gboolean readbehind ) { ReadTiff *rtiff; @@ -2042,7 +2043,7 @@ vips__tiff_read_buffer( const void *buf, size_t len, vips__tiff_init(); if( !(rtiff = readtiff_new_buffer( buf, len, out, - page, autorotate, readbehind )) ) + page, n, autorotate, readbehind )) ) return( -1 ); if( TIFFIsTiled( rtiff->tiff ) ) { diff --git a/libvips/foreign/tiffload.c b/libvips/foreign/tiffload.c index d5aec59b..ccbd9501 100644 --- a/libvips/foreign/tiffload.c +++ b/libvips/foreign/tiffload.c @@ -59,6 +59,10 @@ typedef struct _VipsForeignLoadTiff { */ int page; + /* Load this many pages. + */ + int n; + /* Autorotate using orientation tag. */ gboolean autorotate; @@ -96,7 +100,14 @@ vips_foreign_load_tiff_class_init( VipsForeignLoadTiffClass *class ) G_STRUCT_OFFSET( VipsForeignLoadTiff, page ), 0, 100000, 0 ); - VIPS_ARG_BOOL( class, "autorotate", 11, + VIPS_ARG_INT( class, "n", 11, + _( "n" ), + _( "Load this many pages" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadTiff, n ), + -1, 100000, 1 ); + + VIPS_ARG_BOOL( class, "autorotate", 12, _( "Autorotate" ), _( "Rotate image using orientation tag" ), VIPS_ARGUMENT_OPTIONAL_INPUT, @@ -108,6 +119,7 @@ static void vips_foreign_load_tiff_init( VipsForeignLoadTiff *tiff ) { tiff->page = 0; + tiff->n = 1; } typedef struct _VipsForeignLoadTiffFile { @@ -154,7 +166,7 @@ vips_foreign_load_tiff_file_header( VipsForeignLoad *load ) VipsForeignLoadTiffFile *file = (VipsForeignLoadTiffFile *) load; if( vips__tiff_read_header( file->filename, load->out, - tiff->page, tiff->autorotate ) ) + tiff->page, tiff->n, tiff->autorotate ) ) return( -1 ); VIPS_SETSTR( load->out->filename, file->filename ); @@ -168,8 +180,9 @@ vips_foreign_load_tiff_file_load( VipsForeignLoad *load ) VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load; VipsForeignLoadTiffFile *file = (VipsForeignLoadTiffFile *) load; - if( vips__tiff_read( file->filename, load->real, tiff->page, - tiff->autorotate, load->access == VIPS_ACCESS_SEQUENTIAL ) ) + if( vips__tiff_read( file->filename, load->real, + tiff->page, tiff->n, tiff->autorotate, + load->access == VIPS_ACCESS_SEQUENTIAL ) ) return( -1 ); return( 0 ); @@ -239,7 +252,7 @@ vips_foreign_load_tiff_buffer_header( VipsForeignLoad *load ) if( vips__tiff_read_header_buffer( buffer->buf->data, buffer->buf->length, load->out, - tiff->page, tiff->autorotate ) ) + tiff->page, tiff->n, tiff->autorotate ) ) return( -1 ); return( 0 ); @@ -253,7 +266,7 @@ vips_foreign_load_tiff_buffer_load( VipsForeignLoad *load ) if( vips__tiff_read_buffer( buffer->buf->data, buffer->buf->length, load->real, - tiff->page, tiff->autorotate, + tiff->page, tiff->n, tiff->autorotate, load->access == VIPS_ACCESS_SEQUENTIAL ) ) return( -1 ); @@ -302,6 +315,7 @@ vips_foreign_load_tiff_buffer_init( VipsForeignLoadTiffBuffer *buffer ) * Optional arguments: * * * @page: %gint, load this page + * * @n: %gint, load this many pages * * @autorotate: %gboolean, use orientation tag to rotate the image * during load * @@ -310,7 +324,12 @@ vips_foreign_load_tiff_buffer_init( VipsForeignLoadTiffBuffer *buffer ) * pyramidal images and JPEG compression. including CMYK and YCbCr. * * @page means load this page from the file. By default the first page (page - * 0) is read. + * 0) is read. + * + * @n means load this many pages. By default a single page is read. All the + * pages must have the same dimensions, and they are loaded as a tall, thin + * "toilet roll" image. The "page-height" metadata tag gives the height in + * pixels of each page. Use -1 to load all pages. * * Setting @autorotate to %TRUE will make the loader interpret the * orientation tag and automatically rotate the image appropriately during @@ -357,6 +376,7 @@ vips_tiffload( const char *filename, VipsImage **out, ... ) * Optional arguments: * * * @page: %gint, load this page + * * @n: %gint, load this many pages * * @autorotate: %gboolean, use orientation tag to rotate the image * during load * From e213a9ded69d6a7d60819ed085341cd2a42000da Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 16 Nov 2016 07:41:31 +0000 Subject: [PATCH 08/46] remove @fail machinery from openslideload since openslide load errors are fatal ... see https://github.com/jcupitt/libvips/commit/bb0a6643f94e69294e36d2b253f9bdd60c8c40ed#commitcomment-19838911 --- ChangeLog | 2 +- libvips/foreign/openslide2vips.c | 34 ++++++++++++++------------------ libvips/foreign/openslideload.c | 12 ++++------- libvips/foreign/pforeign.h | 6 +++--- 4 files changed, 23 insertions(+), 31 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8e6be44d..dc9f5ec0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,7 +8,7 @@ - added vips_thumbnail() / vips_thumbnail_buffer() - better >4gb detect for zip dzsave output [Felix Bünemann] - all loaders have a @fail option, meaning fail on first warning, though it - only does anything for jpg, csv, openslide + only does anything for jpg and csv - add vips_image_get_fields() to help bindings 11/11/16 started 8.4.4 diff --git a/libvips/foreign/openslide2vips.c b/libvips/foreign/openslide2vips.c index 27d8807b..ed48e79c 100644 --- a/libvips/foreign/openslide2vips.c +++ b/libvips/foreign/openslide2vips.c @@ -47,8 +47,6 @@ * - do argb -> rgba for associated as well * 27/1/15 * - unpremultiplication speedups for fully opaque/transparent pixels - * 11/7/16 - * - just warn on tile read error */ /* @@ -115,10 +113,6 @@ typedef struct { */ int tile_width; int tile_height; - - /* Quit on warning. - */ - gboolean fail; } ReadSlide; int @@ -227,7 +221,7 @@ get_bounds( openslide_t *osr, VipsRect *rect ) static ReadSlide * readslide_new( const char *filename, VipsImage *out, - int level, gboolean autocrop, const char *associated, gboolean fail ) + int level, gboolean autocrop, const char *associated ) { ReadSlide *rslide; int64_t w, h; @@ -395,9 +389,9 @@ readslide_new( const char *filename, VipsImage *out, int vips__openslide_read_header( const char *filename, VipsImage *out, - int level, gboolean autocrop, char *associated, gboolean fail ) + int level, gboolean autocrop, char *associated ) { - if( !readslide_new( filename, out, level, autocrop, associated, fail ) ) + if( !readslide_new( filename, out, level, autocrop, associated ) ) return( -1 ); return( 0 ); @@ -477,15 +471,18 @@ vips__openslide_generate( VipsRegion *out, rslide->level, r->width, r->height ); - /* Only warn on error: we don't want to make the whole image unreadable - * because of one broken tile. + /* openslide errors are terminal. To support + * @fail we'd have to close the openslide_t and reopen, perhaps + * somehow marking this tile as unreadable. + * + * See + * https://github.com/jcupitt/libvips/commit/bb0a6643f94e69294e36d2b253f9bdd60c8c40ed#commitcomment-19838911 */ error = openslide_get_error( rslide->osr ); if( error ) { - vips_warn( "openslide2vips", + vips_error( "openslide2vips", _( "reading region: %s" ), error ); - if( rslide->fail ) - return( -1 ); + return( -1 ); } /* Since we are inside a cache, we know buf must be continuous. @@ -497,7 +494,7 @@ vips__openslide_generate( VipsRegion *out, int vips__openslide_read( const char *filename, VipsImage *out, - int level, gboolean autocrop, gboolean fail ) + int level, gboolean autocrop ) { ReadSlide *rslide; VipsImage *raw; @@ -510,7 +507,7 @@ vips__openslide_read( const char *filename, VipsImage *out, vips_object_local( out, raw ); if( !(rslide = readslide_new( filename, raw, - level, autocrop, NULL, fail )) ) + level, autocrop, NULL )) ) return( -1 ); if( vips_image_generate( raw, @@ -539,7 +536,7 @@ vips__openslide_read( const char *filename, VipsImage *out, int vips__openslide_read_associated( const char *filename, VipsImage *out, - const char *associated, gboolean fail ) + const char *associated ) { ReadSlide *rslide; VipsImage *raw; @@ -554,8 +551,7 @@ vips__openslide_read_associated( const char *filename, VipsImage *out, raw = vips_image_new_memory(); vips_object_local( out, raw ); - if( !(rslide = readslide_new( filename, raw, - 0, FALSE, associated, fail )) || + if( !(rslide = readslide_new( filename, raw, 0, FALSE, associated )) || vips_image_write_prepare( raw ) ) return( -1 ); buf = (uint32_t *) VIPS_IMAGE_ADDR( raw, 0, 0 ); diff --git a/libvips/foreign/openslideload.c b/libvips/foreign/openslideload.c index aef3eade..769373ca 100644 --- a/libvips/foreign/openslideload.c +++ b/libvips/foreign/openslideload.c @@ -114,7 +114,7 @@ vips_foreign_load_openslide_header( VipsForeignLoad *load ) if( vips__openslide_read_header( openslide->filename, load->out, openslide->level, openslide->autocrop, - openslide->associated, load->fail ) ) + openslide->associated ) ) return( -1 ); VIPS_SETSTR( load->out->filename, openslide->filename ); @@ -129,13 +129,12 @@ vips_foreign_load_openslide_load( VipsForeignLoad *load ) if( !openslide->associated ) { if( vips__openslide_read( openslide->filename, load->real, - openslide->level, openslide->autocrop, load->fail ) ) + openslide->level, openslide->autocrop ) ) return( -1 ); } else { - if( vips__openslide_read_associated( - openslide->filename, load->real, - openslide->associated, load->fail ) ) + if( vips__openslide_read_associated( openslide->filename, + load->real, openslide->associated ) ) return( -1 ); } @@ -231,7 +230,6 @@ vips_foreign_load_openslide_init( VipsForeignLoadOpenslide *openslide ) * * @level: load this level * * @associated: load this associated image * * @autocrop: crop to image bounds - * * @fail: %gboolean, fail on warnings * * Read a virtual slide supported by the OpenSlide library into a VIPS image. * OpenSlide supports images in Aperio, Hamamatsu, MIRAX, Sakura, Trestle, @@ -251,8 +249,6 @@ vips_foreign_load_openslide_init( VipsForeignLoadOpenslide *openslide ) * * The output of this operator is always RGBA. * - * Setting @fail to %TRUE makes the reader fail on any warnings. - * * See also: vips_image_new_from_file(). * * Returns: 0 on success, -1 on error. diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index f0a8557a..d63140c8 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -217,11 +217,11 @@ int vips__webp_write_buffer( VipsImage *out, void **buf, size_t *len, int vips__openslide_isslide( const char *filename ); int vips__openslide_read_header( const char *filename, VipsImage *out, - int level, gboolean autocrop, char *associated, gboolean fail ); + int level, gboolean autocrop, char *associated ); int vips__openslide_read( const char *filename, VipsImage *out, - int level, gboolean autocrop, gboolean fail ); + int level, gboolean autocrop ); int vips__openslide_read_associated( const char *filename, VipsImage *out, - const char *associated, gboolean fail ); + const char *associated ); #ifdef __cplusplus } From 2f53ae956a897c4728ced4a248dd9be23d02bced Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 16 Nov 2016 10:45:18 +0000 Subject: [PATCH 09/46] start refactoring the tiff reader loads to a separate header struct now --- libvips/foreign/tiff2vips.c | 352 +++++++++++++++++++++++++++++++----- 1 file changed, 302 insertions(+), 50 deletions(-) diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 0d54cd79..a237b776 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -218,6 +218,37 @@ #include "pforeign.h" #include "tiff.h" +/* What we read from the tiff dir to set our read strategy. For multipage + * read, we need to read and compare lots of these, so it needs to be broken + * out as a separate thing. + */ +typedef struct _RtiffHeader { + uint32 width; + uint32 height; + int samples_per_pixel; + int bits_per_sample; + int photometric_interpretation; + int sample_format; + gboolean separate; + int orientation; + + /* Result of TIFFIsTiled(). + */ + gboolean tiled; + + /* Fields for tiled images. + */ + uint32 tile_width; + uint32 tile_height; + + /* Fields for strip images. + */ + uint32 rows_per_strip; + tsize_t scanline_size; + tsize_t strip_size; + int number_of_strips; +} RtiffHeader; + /* Scanline-type process function. */ struct _ReadTiff; @@ -252,8 +283,12 @@ typedef struct _ReadTiff { */ size_t pos; - /* Geometry. + /* Geometry as read from the TIFF header. This is read for the first + * page, and equal for all other pages. */ + RtiffHeader header; + + uint32 width, height; uint32 twidth, theight; /* Tile size */ uint32 rows_per_strip; tsize_t scanline_size; @@ -265,6 +300,10 @@ typedef struct _ReadTiff { int sample_format; int orientation; + /* Result of TIFFIsTiled(). + */ + gboolean tiled; + /* Turn on separate plane reading. */ gboolean separate; @@ -1057,14 +1096,49 @@ pick_reader( ReadTiff *rtiff ) return( parse_copy ); } -/* Look at PhotometricInterpretation and BitsPerPixel and try to figure out +static int +rtiff_get_sample_format( TIFF *tiff ) +{ + int sample_format; + uint16 v; + + sample_format = SAMPLEFORMAT_INT; + + if( TIFFGetFieldDefaulted( tiff, TIFFTAG_SAMPLEFORMAT, &v ) ) { + /* Some images have this set to void, bizarre. + */ + if( v == SAMPLEFORMAT_VOID ) + v = SAMPLEFORMAT_UINT; + + sample_format = v; + } + + return( sample_format ); +} + +static int +rtiff_get_orientation( TIFF *tiff ) +{ + int orientation; + uint16 v; + + orientation = ORIENTATION_TOPLEFT; + + if( TIFFGetFieldDefaulted( tiff, TIFFTAG_ORIENTATION, &v ) ) + /* Can have mad values. + */ + orientation = VIPS_CLIP( 1, v, 8 ); + + return( orientation ); +} + +/* Look at PhotometricInterpretation, BitsPerPixel, etc. and try to figure out * which of the image classes this is. */ static int parse_header( ReadTiff *rtiff, VipsImage *out ) { uint32 data_length; - uint32 width, height; void *data; if( tfexists( rtiff->tiff, TIFFTAG_PLANARCONFIG ) ) { @@ -1078,8 +1152,8 @@ parse_header( ReadTiff *rtiff, VipsImage *out ) /* We always need dimensions. */ - if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, &width ) || - !tfget32( rtiff->tiff, TIFFTAG_IMAGELENGTH, &height ) || + if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, &rtiff->width ) || + !tfget32( rtiff->tiff, TIFFTAG_IMAGELENGTH, &rtiff->height ) || parse_resolution( rtiff->tiff, out ) || !tfget16( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, &rtiff->samples_per_pixel ) || @@ -1091,39 +1165,17 @@ parse_header( ReadTiff *rtiff, VipsImage *out ) /* Some optional fields. */ -{ - uint16 v; - - rtiff->sample_format = SAMPLEFORMAT_INT; - - if( TIFFGetFieldDefaulted( rtiff->tiff, TIFFTAG_SAMPLEFORMAT, &v ) ) { - /* Some images have this set to void, bizarre. - */ - if( v == SAMPLEFORMAT_VOID ) - v = SAMPLEFORMAT_UINT; - - rtiff->sample_format = v; - } -} - -{ - uint16 v; - - rtiff->orientation = ORIENTATION_TOPLEFT; - - if( TIFFGetFieldDefaulted( rtiff->tiff, TIFFTAG_ORIENTATION, &v ) ) - /* Can have mad values. - */ - rtiff->orientation = VIPS_CLIP( 1, v, 8 ); -} + rtiff->sample_format = rtiff_get_sample_format( rtiff->tiff ); + rtiff->orientation = rtiff_get_orientation( rtiff->tiff ); + rtiff->tiled = TIFFIsTiled( rtiff->tiff ); /* Arbitrary sanity-checking limits. */ - if( width <= 0 || - width > VIPS_MAX_COORD || - height <= 0 || - height > VIPS_MAX_COORD ) { + if( rtiff->width <= 0 || + rtiff->width > VIPS_MAX_COORD || + rtiff->height <= 0 || + rtiff->height > VIPS_MAX_COORD ) { vips_error( "tiff2vips", "%s", _( "width/height out of range" ) ); return( -1 ); @@ -1138,8 +1190,8 @@ parse_header( ReadTiff *rtiff, VipsImage *out ) return( -1 ); } - out->Xsize = width; - out->Ysize = height; + out->Xsize = rtiff->width; + out->Ysize = rtiff->height; /* Even though we could end up serving tiled data, always hint * THINSTRIP, since we're quite happy doing that too, and it could need @@ -1239,6 +1291,122 @@ parse_header( ReadTiff *rtiff, VipsImage *out ) return( 0 ); } +static int +readtiff_set_directory( ReadTiff *rtiff, int page ) +{ + if( !TIFFSetDirectory( rtiff->tiff, page ) ) { + vips_error( "tiff2vips", + _( "TIFF does not contain page %d" ), rtiff->page ); + return( -1 ); + } + + return( 0 ); +} + +static int +readtiff_n_directories( ReadTiff *rtiff ) +{ + int n; + + for( n = 0; TIFFSetDirectory( rtiff->tiff, n ); n++ ) + ; + + return( n ); +} + +/* Compare this page to the first one we read in. Are they compatible? + * + * Multipage tiffs need to all be readable by the same path, and need to all be + * the same width and height. + */ +static gboolean +is_compatible( ReadTiff *rtiff, int page ) +{ + uint32 width, height; + int samples_per_pixel; + int bits_per_sample; + int photometric_interpretation; + + if( readtiff_set_directory( rtiff, page ) ) + return( FALSE ); + + /* What a headache, don't try to support SEPARATE mutipage images. + */ + if( tfexists( rtiff->tiff, TIFFTAG_PLANARCONFIG ) ) { + int v; + + if( !tfget16( rtiff->tiff, TIFFTAG_PLANARCONFIG, &v ) ) + return( -1 ); + if( v == PLANARCONFIG_SEPARATE ) + return( FALSE ); + } + if( rtiff->separate ) + return( FALSE ); + + if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, &width ) || + !tfget32( rtiff->tiff, TIFFTAG_IMAGELENGTH, &height ) || + !tfget16( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, + &samples_per_pixel ) || + !tfget16( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, + &bits_per_sample ) || + !tfget16( rtiff->tiff, TIFFTAG_PHOTOMETRIC, + &photometric_interpretation ) ) + return( FALSE ); + if( width != rtiff->width || + height != rtiff->height || + samples_per_pixel != rtiff->samples_per_pixel || + bits_per_sample != rtiff->bits_per_sample || + photometric_interpretation != rtiff->photometric_interpretation ) + return( FALSE ); + + if( rtiff->sample_format != rtiff_get_sample_format( rtiff->tiff ) ) + return( FALSE ); + + if( rtiff->tiled != TIFFIsTiled( rtiff->tiff ) ) + return( FALSE ); + + if( rtiff->tiled ) { + uint32 twidth, theight; + + if( !tfget32( rtiff->tiff, TIFFTAG_TILEWIDTH, &twidth ) || + !tfget32( rtiff->tiff, TIFFTAG_TILELENGTH, &theight ) ) + return( FALSE ); + + if( width != rtiff->width || + height != rtiff->height ) + return( FALSE ); + } + else { + tsize_t scanline_size; + tsize_t strip_size; + int number_of_strips; + uint32 rows_per_strip; + + scanline_size = TIFFScanlineSize( rtiff->tiff ); + strip_size = TIFFStripSize( rtiff->tiff ); + number_of_strips = TIFFNumberOfStrips( rtiff->tiff ); + + if( !tfget32( rtiff->tiff, + TIFFTAG_ROWSPERSTRIP, &rows_per_strip ) ) + return( FALSE ); + + /* rows_per_strip can be 2 ** 32 - 1, meaning the whole image. + * Clip this down to image height to avoid confusing vips. + * + * And it musn't be zero. + */ + rows_per_strip = VIPS_CLIP( 1, rows_per_strip, rtiff->height ); + + if( scanline_size != rtiff->scanline_size || + strip_size != rtiff->strip_size || + number_of_strips != rtiff->number_of_strips || + rows_per_strip != rtiff->rows_per_strip ) + return( FALSE ); + } + + return( TRUE ); +} + /* The size of the buffer written by TIFFReadTile(). We can't use * TIFFTileSize() since that ignores the setting of TIFFTAG_JPEGCOLORMODE. If * this pseudo tag has been set and the tile is encoded with YCbCr, the tile @@ -1787,18 +1955,6 @@ readtiff_close( VipsObject *object, ReadTiff *rtiff ) readtiff_free( rtiff ); } -static int -readtiff_set_directory( ReadTiff *rtiff, int page ) -{ - if( !TIFFSetDirectory( rtiff->tiff, page ) ) { - vips_error( "tiff2vips", - _( "TIFF does not contain page %d" ), rtiff->page ); - return( -1 ); - } - - return( 0 ); -} - static ReadTiff * readtiff_new( VipsImage *out, int page, int n, gboolean autorotate, gboolean readbehind ) @@ -1836,6 +1992,100 @@ readtiff_new( VipsImage *out, return( rtiff ); } +/* Load from a tiff dir into one of our tiff header structs. + */ +static int +rtiff_header_read( ReadTiff *rtiff, RtiffHeader *header ) +{ + /* We always need dimensions. + */ + if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, &header->width ) || + !tfget32( rtiff->tiff, TIFFTAG_IMAGELENGTH, &header->height ) || + !tfget16( rtiff->tiff, + TIFFTAG_SAMPLESPERPIXEL, &header->samples_per_pixel ) || + !tfget16( rtiff->tiff, + TIFFTAG_BITSPERSAMPLE, &header->bits_per_sample ) || + !tfget16( rtiff->tiff, + TIFFTAG_PHOTOMETRIC, + &header->photometric_interpretation ) ) + return( -1 ); + + /* Some optional fields. + */ + header->sample_format = rtiff_get_sample_format( rtiff->tiff ); + header->orientation = rtiff_get_orientation( rtiff->tiff ); + + if( tfexists( rtiff->tiff, TIFFTAG_PLANARCONFIG ) ) { + int v; + + if( !tfget16( rtiff->tiff, TIFFTAG_PLANARCONFIG, &v ) ) + return( -1 ); + if( v == PLANARCONFIG_SEPARATE ) + header->separate = TRUE; + } + + /* Tiles and strip images have slightly different fields. + */ + header->tiled = TIFFIsTiled( rtiff->tiff ); + + if( header->tiled ) { + if( !tfget32( rtiff->tiff, + TIFFTAG_TILEWIDTH, &header->tile_width ) || + !tfget32( rtiff->tiff, + TIFFTAG_TILELENGTH, &header->tile_height ) ) + return( -1 ); + } + else { + if( !tfget32( rtiff->tiff, + TIFFTAG_ROWSPERSTRIP, &header->rows_per_strip ) ) + return( -1 ); + header->scanline_size = TIFFScanlineSize( rtiff->tiff ); + header->strip_size = TIFFStripSize( rtiff->tiff ); + header->number_of_strips = TIFFNumberOfStrips( rtiff->tiff ); + + /* rows_per_strip can be 2 ** 32 - 1, meaning the whole image. + * Clip this down to height to avoid confusing vips. + * + * And it musn't be zero. + */ + header->rows_per_strip = + VIPS_CLIP( 1, header->rows_per_strip, header->height ); + } + + return( 0 ); +} + +static int +rtiff_header_equal( RtiffHeader *h1, RtiffHeader *h2 ) +{ + if( h1->width != h2->width || + h1->height != h2->height || + h1->samples_per_pixel != h2->samples_per_pixel || + h1->bits_per_sample != h2->bits_per_sample || + h1->photometric_interpretation != + h2->photometric_interpretation || + h1->sample_format != h2->sample_format || + h1->separate != h2->separate || + h1->tiled != h2->tiled || + h1->orientation != h2->orientation ) + return( 0 ); + + if( h1->tiled ) { + if( h1->tile_width != h2->tile_width || + h1->tile_height != h2->tile_height ) + return( 0 ); + } + else { + if( h1->rows_per_strip != h2->rows_per_strip || + h1->scanline_size != h2->scanline_size || + h1->strip_size != h2->strip_size || + h1->number_of_strips != h2->number_of_strips ) + return( 0 ); + } + + return( 1 ); +} + static ReadTiff * readtiff_new_filename( const char *filename, VipsImage *out, int page, int n, gboolean autorotate, gboolean readbehind ) @@ -1844,7 +2094,8 @@ readtiff_new_filename( const char *filename, VipsImage *out, if( !(rtiff = readtiff_new( out, page, n, autorotate, readbehind )) || !(rtiff->tiff = vips__tiff_openin( filename )) || - readtiff_set_directory( rtiff, page ) ) + readtiff_set_directory( rtiff, page ) || + rtiff_header_read( rtiff, &rtiff->header ) ) return( NULL ); rtiff->filename = vips_strdup( VIPS_OBJECT( out ), filename ); @@ -1860,7 +2111,8 @@ readtiff_new_buffer( const void *buf, size_t len, VipsImage *out, if( !(rtiff = readtiff_new( out, page, n, autorotate, readbehind )) || !(rtiff->tiff = vips__tiff_openin_buffer( out, buf, len )) || - readtiff_set_directory( rtiff, page ) ) + readtiff_set_directory( rtiff, page ) || + rtiff_header_read( rtiff, &rtiff->header ) ) return( NULL ); return( rtiff ); From 3ecb6d6c8beff8057e10df720e1911d1c28f2ad8 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 16 Nov 2016 14:55:24 +0000 Subject: [PATCH 10/46] lots of refactoring and started adding many page check --- libvips/foreign/tiff2vips.c | 1066 ++++++++++++++++------------------- 1 file changed, 483 insertions(+), 583 deletions(-) diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index a237b776..54312727 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -251,18 +251,19 @@ typedef struct _RtiffHeader { /* Scanline-type process function. */ -struct _ReadTiff; -typedef void (*scanline_process_fn)( struct _ReadTiff *, +struct _Rtiff; +typedef void (*scanline_process_fn)( struct _Rtiff *, VipsPel *q, VipsPel *p, int n, void *client ); /* Stuff we track during a read. */ -typedef struct _ReadTiff { +typedef struct _Rtiff { /* Parameters. */ char *filename; VipsImage *out; int page; + int n; gboolean autorotate; gboolean readbehind; @@ -288,26 +289,6 @@ typedef struct _ReadTiff { */ RtiffHeader header; - uint32 width, height; - uint32 twidth, theight; /* Tile size */ - uint32 rows_per_strip; - tsize_t scanline_size; - tsize_t strip_size; - int number_of_strips; - int samples_per_pixel; - int bits_per_sample; - int photometric_interpretation; - int sample_format; - int orientation; - - /* Result of TIFFIsTiled(). - */ - gboolean tiled; - - /* Turn on separate plane reading. - */ - gboolean separate; - /* Hold a single strip or tile, possibly just an image plane. */ tdata_t plane_buf; @@ -316,7 +297,7 @@ typedef struct _ReadTiff { * strips or tiles interleaved. */ tdata_t contig_buf; -} ReadTiff; +} Rtiff; /* Test for field exists. */ @@ -368,9 +349,122 @@ tfget16( TIFF *tif, ttag_t tag, int *out ) } static int -check_samples( ReadTiff *rtiff, int samples_per_pixel ) +get_resolution( TIFF *tiff, VipsImage *out ) { - if( rtiff->samples_per_pixel != samples_per_pixel ) { + float x, y; + int ru; + + if( TIFFGetFieldDefaulted( tiff, TIFFTAG_XRESOLUTION, &x ) && + TIFFGetFieldDefaulted( tiff, TIFFTAG_YRESOLUTION, &y ) && + tfget16( tiff, TIFFTAG_RESOLUTIONUNIT, &ru ) ) { + switch( ru ) { + case RESUNIT_NONE: + break; + + case RESUNIT_INCH: + /* In pixels-per-inch ... convert to mm. + */ + x /= 10.0 * 2.54; + y /= 10.0 * 2.54; + vips_image_set_string( out, + VIPS_META_RESOLUTION_UNIT, "in" ); + break; + + case RESUNIT_CENTIMETER: + /* In pixels-per-centimetre ... convert to mm. + */ + x /= 10.0; + y /= 10.0; + vips_image_set_string( out, + VIPS_META_RESOLUTION_UNIT, "cm" ); + break; + + default: + vips_error( "tiff2vips", + "%s", _( "unknown resolution unit" ) ); + return( -1 ); + } + } + else { + vips_warn( "tiff2vips", _( "no resolution information for " + "TIFF image \"%s\" -- defaulting to 1 pixel per mm" ), + TIFFFileName( tiff ) ); + x = 1.0; + y = 1.0; + } + + out->Xres = x; + out->Yres = y; + + return( 0 ); +} + +static int +get_sample_format( TIFF *tiff ) +{ + int sample_format; + uint16 v; + + sample_format = SAMPLEFORMAT_INT; + + if( TIFFGetFieldDefaulted( tiff, TIFFTAG_SAMPLEFORMAT, &v ) ) { + /* Some images have this set to void, bizarre. + */ + if( v == SAMPLEFORMAT_VOID ) + v = SAMPLEFORMAT_UINT; + + sample_format = v; + } + + return( sample_format ); +} + +static int +get_orientation( TIFF *tiff ) +{ + int orientation; + uint16 v; + + orientation = ORIENTATION_TOPLEFT; + + if( TIFFGetFieldDefaulted( tiff, TIFFTAG_ORIENTATION, &v ) ) + /* Can have mad values. + */ + orientation = VIPS_CLIP( 1, v, 8 ); + + return( orientation ); +} + +static int +set_directory( TIFF *tiff, int page ) +{ + if( !TIFFSetDirectory( tiff, page ) ) { + vips_error( "tiff2vips", + _( "TIFF does not contain page %d" ), page ); + return( -1 ); + } + + return( 0 ); +} + +static int +n_directories( TIFF *tiff ) +{ + int n; + + /* Faster than doing set_directory() repeatedly. + */ + set_directory( tiff, 0 ); + for( n = 0; TIFFReadDirectory( tiff ); n++ ) + ; + + return( n ); +} + +static int +rtiff_check_samples( Rtiff *rtiff, int samples_per_pixel ) +{ + if( rtiff->header.samples_per_pixel != samples_per_pixel ) { vips_error( "tiff2vips", _( "not %d bands" ), samples_per_pixel ); return( -1 ); @@ -382,9 +476,9 @@ check_samples( ReadTiff *rtiff, int samples_per_pixel ) /* Check n and n+1 so we can have an alpha. */ static int -check_min_samples( ReadTiff *rtiff, int samples_per_pixel ) +rtiff_check_min_samples( Rtiff *rtiff, int samples_per_pixel ) { - if( rtiff->samples_per_pixel < samples_per_pixel ) { + if( rtiff->header.samples_per_pixel < samples_per_pixel ) { vips_error( "tiff2vips", _( "not at least %d samples per pixel" ), samples_per_pixel ); @@ -395,9 +489,10 @@ check_min_samples( ReadTiff *rtiff, int samples_per_pixel ) } static int -check_interpretation( ReadTiff *rtiff, int photometric_interpretation ) +rtiff_check_interpretation( Rtiff *rtiff, int photometric_interpretation ) { - if( rtiff->photometric_interpretation != photometric_interpretation ) { + if( rtiff->header.photometric_interpretation != + photometric_interpretation ) { vips_error( "tiff2vips", _( "not photometric interpretation %d" ), photometric_interpretation ); @@ -408,9 +503,9 @@ check_interpretation( ReadTiff *rtiff, int photometric_interpretation ) } static int -check_bits( ReadTiff *rtiff, int bits_per_sample ) +rtiff_check_bits( Rtiff *rtiff, int bits_per_sample ) { - if( rtiff->bits_per_sample != bits_per_sample ) { + if( rtiff->header.bits_per_sample != bits_per_sample ) { vips_error( "tiff2vips", _( "not %d bits per sample" ), bits_per_sample ); return( -1 ); @@ -420,16 +515,16 @@ check_bits( ReadTiff *rtiff, int bits_per_sample ) } static int -check_bits_palette( ReadTiff *rtiff ) +rtiff_check_bits_palette( Rtiff *rtiff ) { - if( rtiff->bits_per_sample != 16 && - rtiff->bits_per_sample != 8 && - rtiff->bits_per_sample != 4 && - rtiff->bits_per_sample != 2 && - rtiff->bits_per_sample != 1 ) { + if( rtiff->header.bits_per_sample != 16 && + rtiff->header.bits_per_sample != 8 && + rtiff->header.bits_per_sample != 4 && + rtiff->header.bits_per_sample != 2 && + rtiff->header.bits_per_sample != 1 ) { vips_error( "tiff2vips", _( "%d bits per sample palette image not supported" ), - rtiff->bits_per_sample ); + rtiff->header.bits_per_sample ); return( -1 ); } @@ -437,44 +532,47 @@ check_bits_palette( ReadTiff *rtiff ) } static VipsBandFormat -guess_format( ReadTiff *rtiff ) +rtiff_guess_format( Rtiff *rtiff ) { - switch( rtiff->bits_per_sample ) { + int bits_per_sample = rtiff->header.bits_per_sample; + int sample_format = rtiff->header.sample_format; + + switch( bits_per_sample ) { case 1: case 2: case 4: case 8: - if( rtiff->sample_format == SAMPLEFORMAT_INT ) + if( sample_format == SAMPLEFORMAT_INT ) return( VIPS_FORMAT_CHAR ); - if( rtiff->sample_format == SAMPLEFORMAT_UINT ) + if( sample_format == SAMPLEFORMAT_UINT ) return( VIPS_FORMAT_UCHAR ); break; case 16: - if( rtiff->sample_format == SAMPLEFORMAT_INT ) + if( sample_format == SAMPLEFORMAT_INT ) return( VIPS_FORMAT_SHORT ); - if( rtiff->sample_format == SAMPLEFORMAT_UINT ) + if( sample_format == SAMPLEFORMAT_UINT ) return( VIPS_FORMAT_USHORT ); break; case 32: - if( rtiff->sample_format == SAMPLEFORMAT_INT ) + if( sample_format == SAMPLEFORMAT_INT ) return( VIPS_FORMAT_INT ); - if( rtiff->sample_format == SAMPLEFORMAT_UINT ) + if( sample_format == SAMPLEFORMAT_UINT ) return( VIPS_FORMAT_UINT ); - if( rtiff->sample_format == SAMPLEFORMAT_IEEEFP ) + if( sample_format == SAMPLEFORMAT_IEEEFP ) return( VIPS_FORMAT_FLOAT ); break; case 64: - if( rtiff->sample_format == SAMPLEFORMAT_IEEEFP ) + if( sample_format == SAMPLEFORMAT_IEEEFP ) return( VIPS_FORMAT_DOUBLE ); - if( rtiff->sample_format == SAMPLEFORMAT_COMPLEXIEEEFP ) + if( sample_format == SAMPLEFORMAT_COMPLEXIEEEFP ) return( VIPS_FORMAT_COMPLEX ); break; case 128: - if( rtiff->sample_format == SAMPLEFORMAT_COMPLEXIEEEFP ) + if( sample_format == SAMPLEFORMAT_COMPLEXIEEEFP ) return( VIPS_FORMAT_DPCOMPLEX ); break; @@ -490,8 +588,10 @@ guess_format( ReadTiff *rtiff ) /* Per-scanline process function for VIPS_CODING_LABQ. */ static void -labpack_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy ) +rtiff_labpack_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy ) { + int samples_per_pixel = rtiff->header.samples_per_pixel; + int x; for( x = 0; x < n; x++ ) { @@ -501,18 +601,18 @@ labpack_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy ) q[3] = 0; q += 4; - p += rtiff->samples_per_pixel; + p += samples_per_pixel; } } /* Read an 8-bit LAB image. */ static int -parse_labpack( ReadTiff *rtiff, VipsImage *out ) +rtiff_parse_labpack( Rtiff *rtiff, VipsImage *out ) { - if( check_min_samples( rtiff, 3 ) || - check_bits( rtiff, 8 ) || - check_interpretation( rtiff, PHOTOMETRIC_CIELAB ) ) + if( rtiff_check_min_samples( rtiff, 3 ) || + rtiff_check_bits( rtiff, 8 ) || + rtiff_check_interpretation( rtiff, PHOTOMETRIC_CIELAB ) ) return( -1 ); out->Bands = 4; @@ -520,7 +620,7 @@ parse_labpack( ReadTiff *rtiff, VipsImage *out ) out->Coding = VIPS_CODING_LABQ; out->Type = VIPS_INTERPRETATION_LAB; - rtiff->sfn = labpack_line; + rtiff->sfn = rtiff_labpack_line; return( 0 ); } @@ -528,42 +628,46 @@ parse_labpack( ReadTiff *rtiff, VipsImage *out ) /* Per-scanline process function for LABS. */ static void -labs_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy ) +rtiff_labs_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy ) { + int samples_per_pixel = rtiff->header.samples_per_pixel; + + unsigned short *p1; + short *q1; int x; - unsigned short *p1 = (unsigned short *) p; - short *q1 = (short *) q; int i; + p1 = (unsigned short *) p; + q1 = (short *) q; for( x = 0; x < n; x++ ) { /* We use a signed int16 for L. */ q1[0] = p1[0] >> 1; - for( i = 1; i < rtiff->samples_per_pixel; i++ ) + for( i = 1; i < samples_per_pixel; i++ ) q1[i] = p1[i]; - q1 += rtiff->samples_per_pixel; - p1 += rtiff->samples_per_pixel; + q1 += samples_per_pixel; + p1 += samples_per_pixel; } } /* Read a 16-bit LAB image. */ static int -parse_labs( ReadTiff *rtiff, VipsImage *out ) +rtiff_parse_labs( Rtiff *rtiff, VipsImage *out ) { - if( check_min_samples( rtiff, 3 ) || - check_bits( rtiff, 16 ) || - check_interpretation( rtiff, PHOTOMETRIC_CIELAB ) ) + if( rtiff_check_min_samples( rtiff, 3 ) || + rtiff_check_bits( rtiff, 16 ) || + rtiff_check_interpretation( rtiff, PHOTOMETRIC_CIELAB ) ) return( -1 ); - out->Bands = rtiff->samples_per_pixel; + out->Bands = rtiff->header.samples_per_pixel; out->BandFmt = VIPS_FORMAT_SHORT; out->Coding = VIPS_CODING_NONE; out->Type = VIPS_INTERPRETATION_LABS; - rtiff->sfn = labs_line; + rtiff->sfn = rtiff_labs_line; return( 0 ); } @@ -571,13 +675,15 @@ parse_labs( ReadTiff *rtiff, VipsImage *out ) /* Per-scanline process function for 1 bit images. */ static void -onebit_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) +rtiff_onebit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) { + int photometric_interpretation = + rtiff->header.photometric_interpretation; + int x, i, z; VipsPel bits; - int black = - rtiff->photometric_interpretation == PHOTOMETRIC_MINISBLACK ? + int black = photometric_interpretation == PHOTOMETRIC_MINISBLACK ? 0 : 255; int white = black ^ 0xff; @@ -608,10 +714,10 @@ onebit_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) /* Read a 1-bit TIFF image. */ static int -parse_onebit( ReadTiff *rtiff, VipsImage *out ) +rtiff_parse_onebit( Rtiff *rtiff, VipsImage *out ) { - if( check_samples( rtiff, 1 ) || - check_bits( rtiff, 1 ) ) + if( rtiff_check_samples( rtiff, 1 ) || + rtiff_check_bits( rtiff, 1 ) ) return( -1 ); out->Bands = 1; @@ -619,7 +725,7 @@ parse_onebit( ReadTiff *rtiff, VipsImage *out ) out->Coding = VIPS_CODING_NONE; out->Type = VIPS_INTERPRETATION_B_W; - rtiff->sfn = onebit_line; + rtiff->sfn = rtiff_onebit_line; return( 0 ); } @@ -638,22 +744,24 @@ parse_onebit( ReadTiff *rtiff, VipsImage *out ) else \ q1[0] = p1[0]; \ \ - for( i = 1; i < rtiff->samples_per_pixel; i++ ) \ + for( i = 1; i < samples_per_pixel; i++ ) \ q1[i] = p1[i]; \ \ - q1 += rtiff->samples_per_pixel; \ - p1 += rtiff->samples_per_pixel; \ + q1 += samples_per_pixel; \ + p1 += samples_per_pixel; \ } \ } /* Per-scanline process function for greyscale images. */ static void -greyscale_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client ) +rtiff_greyscale_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client ) { - gboolean invert = - rtiff->photometric_interpretation == PHOTOMETRIC_MINISWHITE; - VipsBandFormat format = guess_format( rtiff ); + int samples_per_pixel = rtiff->header.samples_per_pixel; + int photometric_interpretation = + rtiff->header.photometric_interpretation; + gboolean invert = photometric_interpretation == PHOTOMETRIC_MINISWHITE; + VipsBandFormat format = rtiff_guess_format( rtiff ); int x, i; @@ -696,28 +804,28 @@ greyscale_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client ) * PHOTOMETRIC_MINISBLACK is set. */ static int -parse_greyscale( ReadTiff *rtiff, VipsImage *out ) +rtiff_parse_greyscale( Rtiff *rtiff, VipsImage *out ) { - if( check_min_samples( rtiff, 1 ) ) + if( rtiff_check_min_samples( rtiff, 1 ) ) return( -1 ); - out->Bands = rtiff->samples_per_pixel; - out->BandFmt = guess_format( rtiff ); + out->Bands = rtiff->header.samples_per_pixel; + out->BandFmt = rtiff_guess_format( rtiff ); if( out->BandFmt == VIPS_FORMAT_NOTSET ) return( -1 ); out->Coding = VIPS_CODING_NONE; - if( rtiff->bits_per_sample == 16 ) + if( rtiff->header.bits_per_sample == 16 ) out->Type = VIPS_INTERPRETATION_GREY16; else out->Type = VIPS_INTERPRETATION_B_W; - /* greyscale_line() doesn't do complex. + /* rtiff_greyscale_line() doesn't do complex. */ if( vips_check_noncomplex( "tiff2vips", out ) ) return( -1 ); - rtiff->sfn = greyscale_line; + rtiff->sfn = rtiff_greyscale_line; return( 0 ); } @@ -741,10 +849,12 @@ typedef struct { /* 1/2/4 bit samples with an 8-bit palette. */ static void -palette_line_bit( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client ) +rtiff_palette_line_bit( Rtiff *rtiff, + VipsPel *q, VipsPel *p, int n, void *client ) { PaletteRead *read = (PaletteRead *) client; - int samples = rtiff->samples_per_pixel; + int samples_per_pixel = rtiff->header.samples_per_pixel; + int bits_per_sample = rtiff->header.bits_per_sample; int bit; VipsPel data; @@ -752,7 +862,7 @@ palette_line_bit( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client ) bit = 0; data = 0; - for( x = 0; x < n * samples; x++ ) { + for( x = 0; x < n * samples_per_pixel; x++ ) { int i; if( bit <= 0 ) { @@ -760,14 +870,14 @@ palette_line_bit( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client ) bit = 8; } - i = data >> (8 - rtiff->bits_per_sample); - data <<= rtiff->bits_per_sample; - bit -= rtiff->bits_per_sample; + i = data >> (8 - bits_per_sample); + data <<= bits_per_sample; + bit -= bits_per_sample; /* The first band goes through the LUT, subsequent bands are * left-justified and copied. */ - if( x % samples == 0 ) { + if( x % samples_per_pixel == 0 ) { if( read->mono ) *q++ = read->red8[i]; else { @@ -778,18 +888,18 @@ palette_line_bit( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client ) } } else - *q++ = i << (8 - rtiff->bits_per_sample); + *q++ = i << (8 - bits_per_sample); } } /* 8-bit samples with an 8-bit palette. */ static void -palette_line8( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, +rtiff_palette_line8( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client ) { PaletteRead *read = (PaletteRead *) client; - int samples = rtiff->samples_per_pixel; + int samples_per_pixel = rtiff->header.samples_per_pixel; int x; int s; @@ -806,22 +916,22 @@ palette_line8( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, q += 2; } - for( s = 1; s < samples; s++ ) + for( s = 1; s < samples_per_pixel; s++ ) q[s] = p[s]; - q += samples; - p += samples; + q += samples_per_pixel; + p += samples_per_pixel; } } /* 16-bit samples with 16-bit data in the palette. */ static void -palette_line16( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, +rtiff_palette_line16( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client ) { PaletteRead *read = (PaletteRead *) client; - int samples = rtiff->samples_per_pixel; + int samples_per_pixel = rtiff->header.samples_per_pixel; guint16 *p16, *q16; int x; @@ -842,27 +952,30 @@ palette_line16( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, q16 += 2; } - for( s = 1; s < samples; s++ ) + for( s = 1; s < samples_per_pixel; s++ ) q16[s] = p16[s]; - q16 += samples; - p16 += samples; + q16 += samples_per_pixel; + p16 += samples_per_pixel; } } /* Read a palette-ised TIFF image. */ static int -parse_palette( ReadTiff *rtiff, VipsImage *out ) +rtiff_parse_palette( Rtiff *rtiff, VipsImage *out ) { + int samples_per_pixel = rtiff->header.samples_per_pixel; + int bits_per_sample = rtiff->header.bits_per_sample; + int len; PaletteRead *read; int i; - if( check_bits_palette( rtiff ) || - check_min_samples( rtiff, 1 ) ) + if( rtiff_check_bits_palette( rtiff ) || + rtiff_check_min_samples( rtiff, 1 ) ) return( -1 ); - len = 1 << rtiff->bits_per_sample; + len = 1 << bits_per_sample; if( !(read = VIPS_NEW( out, PaletteRead )) || !(read->red8 = VIPS_ARRAY( out, len, VipsPel )) || @@ -920,34 +1033,34 @@ parse_palette( ReadTiff *rtiff, VipsImage *out ) * just search the colormap. */ - if( rtiff->bits_per_sample <= 8 ) + if( bits_per_sample <= 8 ) out->BandFmt = VIPS_FORMAT_UCHAR; else out->BandFmt = VIPS_FORMAT_USHORT; out->Coding = VIPS_CODING_NONE; if( read->mono ) { - out->Bands = rtiff->samples_per_pixel; - if( rtiff->bits_per_sample <= 8 ) + out->Bands = samples_per_pixel; + if( bits_per_sample <= 8 ) out->Type = VIPS_INTERPRETATION_B_W; else out->Type = VIPS_INTERPRETATION_GREY16; } else { - out->Bands = rtiff->samples_per_pixel + 2; - if( rtiff->bits_per_sample <= 8 ) + out->Bands = samples_per_pixel + 2; + if( bits_per_sample <= 8 ) out->Type = VIPS_INTERPRETATION_sRGB; else out->Type = VIPS_INTERPRETATION_RGB16; } rtiff->client = read; - if( rtiff->bits_per_sample < 8 ) - rtiff->sfn = palette_line_bit; - else if( rtiff->bits_per_sample == 8 ) - rtiff->sfn = palette_line8; - else if( rtiff->bits_per_sample == 16 ) - rtiff->sfn = palette_line16; + if( bits_per_sample < 8 ) + rtiff->sfn = rtiff_palette_line_bit; + else if( bits_per_sample == 8 ) + rtiff->sfn = rtiff_palette_line8; + else if( bits_per_sample == 16 ) + rtiff->sfn = rtiff_palette_line16; else g_assert_not_reached(); @@ -957,7 +1070,7 @@ parse_palette( ReadTiff *rtiff, VipsImage *out ) /* Per-scanline process function when we just need to copy. */ static void -memcpy_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client ) +rtiff_memcpy_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client ) { VipsImage *im = (VipsImage *) client; size_t len = n * VIPS_IMAGE_SIZEOF_PEL( im ); @@ -969,17 +1082,21 @@ memcpy_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client ) * buffer. */ static int -parse_copy( ReadTiff *rtiff, VipsImage *out ) +rtiff_parse_copy( Rtiff *rtiff, VipsImage *out ) { - out->Bands = rtiff->samples_per_pixel; - out->BandFmt = guess_format( rtiff ); + int samples_per_pixel = rtiff->header.samples_per_pixel; + int photometric_interpretation = + rtiff->header.photometric_interpretation; + + out->Bands = samples_per_pixel; + out->BandFmt = rtiff_guess_format( rtiff ); if( out->BandFmt == VIPS_FORMAT_NOTSET ) return( -1 ); out->Coding = VIPS_CODING_NONE; - if( rtiff->samples_per_pixel >= 3 && - (rtiff->photometric_interpretation == PHOTOMETRIC_RGB || - rtiff->photometric_interpretation == PHOTOMETRIC_YCBCR) ) { + if( samples_per_pixel >= 3 && + (photometric_interpretation == PHOTOMETRIC_RGB || + photometric_interpretation == PHOTOMETRIC_YCBCR) ) { if( out->BandFmt == VIPS_FORMAT_USHORT ) out->Type = VIPS_INTERPRETATION_RGB16; else if( !vips_band_format_isint( out->BandFmt ) ) @@ -991,101 +1108,52 @@ parse_copy( ReadTiff *rtiff, VipsImage *out ) out->Type = VIPS_INTERPRETATION_sRGB; } - if( rtiff->samples_per_pixel >= 3 && - rtiff->photometric_interpretation == PHOTOMETRIC_CIELAB ) + if( samples_per_pixel >= 3 && + photometric_interpretation == PHOTOMETRIC_CIELAB ) out->Type = VIPS_INTERPRETATION_LAB; - if( rtiff->samples_per_pixel >= 4 && - rtiff->photometric_interpretation == PHOTOMETRIC_SEPARATED ) + if( samples_per_pixel >= 4 && + photometric_interpretation == PHOTOMETRIC_SEPARATED ) out->Type = VIPS_INTERPRETATION_CMYK; - rtiff->sfn = memcpy_line; + rtiff->sfn = rtiff_memcpy_line; rtiff->client = out; rtiff->memcpy = TRUE; return( 0 ); } -/* Read resolution from a TIFF image. - */ -static int -parse_resolution( TIFF *tiff, VipsImage *out ) -{ - float x, y; - int ru; - - if( TIFFGetFieldDefaulted( tiff, TIFFTAG_XRESOLUTION, &x ) && - TIFFGetFieldDefaulted( tiff, TIFFTAG_YRESOLUTION, &y ) && - tfget16( tiff, TIFFTAG_RESOLUTIONUNIT, &ru ) ) { - switch( ru ) { - case RESUNIT_NONE: - break; - - case RESUNIT_INCH: - /* In pixels-per-inch ... convert to mm. - */ - x /= 10.0 * 2.54; - y /= 10.0 * 2.54; - vips_image_set_string( out, - VIPS_META_RESOLUTION_UNIT, "in" ); - break; - - case RESUNIT_CENTIMETER: - /* In pixels-per-centimetre ... convert to mm. - */ - x /= 10.0; - y /= 10.0; - vips_image_set_string( out, - VIPS_META_RESOLUTION_UNIT, "cm" ); - break; - - default: - vips_error( "tiff2vips", - "%s", _( "unknown resolution unit" ) ); - return( -1 ); - } - } - else { - vips_warn( "tiff2vips", _( "no resolution information for " - "TIFF image \"%s\" -- defaulting to 1 pixel per mm" ), - TIFFFileName( tiff ) ); - x = 1.0; - y = 1.0; - } - - out->Xres = x; - out->Yres = y; - - return( 0 ); -} - -typedef int (*reader_fn)( ReadTiff *rtiff, VipsImage *out ); +typedef int (*reader_fn)( Rtiff *rtiff, VipsImage *out ); /* We have a range of output paths. Look at the tiff header and try to * route the input image to the best output path. */ static reader_fn -pick_reader( ReadTiff *rtiff ) +rtiff_pick_reader( Rtiff *rtiff ) { - if( rtiff->photometric_interpretation == PHOTOMETRIC_CIELAB ) { - if( rtiff->bits_per_sample == 8 ) - return( parse_labpack ); - if( rtiff->bits_per_sample == 16 ) - return( parse_labs ); + int bits_per_sample = rtiff->header.bits_per_sample; + int photometric_interpretation = + rtiff->header.photometric_interpretation; + + if( photometric_interpretation == PHOTOMETRIC_CIELAB ) { + if( bits_per_sample == 8 ) + return( rtiff_parse_labpack ); + if( bits_per_sample == 16 ) + return( rtiff_parse_labs ); } - if( rtiff->photometric_interpretation == PHOTOMETRIC_MINISWHITE || - rtiff->photometric_interpretation == PHOTOMETRIC_MINISBLACK ) { - if( rtiff->bits_per_sample == 1 ) - return( parse_onebit ); + if( photometric_interpretation == PHOTOMETRIC_MINISWHITE || + photometric_interpretation == PHOTOMETRIC_MINISBLACK ) { + if( bits_per_sample == 1 ) + return( rtiff_parse_onebit ); else - return( parse_greyscale ); + return( rtiff_parse_greyscale ); } - if( rtiff->photometric_interpretation == PHOTOMETRIC_PALETTE ) - return( parse_palette ); + if( photometric_interpretation == PHOTOMETRIC_PALETTE ) + return( rtiff_parse_palette ); - if( rtiff->photometric_interpretation == PHOTOMETRIC_YCBCR ) { + if( photometric_interpretation == PHOTOMETRIC_YCBCR ) { /* Sometimes JPEG in TIFF images are tagged as YCBCR. Ask * libtiff to convert to RGB for us. */ @@ -1093,105 +1161,20 @@ pick_reader( ReadTiff *rtiff ) TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB ); } - return( parse_copy ); + return( rtiff_parse_copy ); } -static int -rtiff_get_sample_format( TIFF *tiff ) -{ - int sample_format; - uint16 v; - - sample_format = SAMPLEFORMAT_INT; - - if( TIFFGetFieldDefaulted( tiff, TIFFTAG_SAMPLEFORMAT, &v ) ) { - /* Some images have this set to void, bizarre. - */ - if( v == SAMPLEFORMAT_VOID ) - v = SAMPLEFORMAT_UINT; - - sample_format = v; - } - - return( sample_format ); -} - -static int -rtiff_get_orientation( TIFF *tiff ) -{ - int orientation; - uint16 v; - - orientation = ORIENTATION_TOPLEFT; - - if( TIFFGetFieldDefaulted( tiff, TIFFTAG_ORIENTATION, &v ) ) - /* Can have mad values. - */ - orientation = VIPS_CLIP( 1, v, 8 ); - - return( orientation ); -} - -/* Look at PhotometricInterpretation, BitsPerPixel, etc. and try to figure out - * which of the image classes this is. +/* Set the header on @out from our rtiff. rtiff_header_read() has alreay been + * called. */ static int -parse_header( ReadTiff *rtiff, VipsImage *out ) +rtiff_set_header( Rtiff *rtiff, VipsImage *out ) { uint32 data_length; void *data; - if( tfexists( rtiff->tiff, TIFFTAG_PLANARCONFIG ) ) { - int v; - - if( !tfget16( rtiff->tiff, TIFFTAG_PLANARCONFIG, &v ) ) - return( -1 ); - if( v == PLANARCONFIG_SEPARATE ) - rtiff->separate = TRUE; - } - - /* We always need dimensions. - */ - if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, &rtiff->width ) || - !tfget32( rtiff->tiff, TIFFTAG_IMAGELENGTH, &rtiff->height ) || - parse_resolution( rtiff->tiff, out ) || - !tfget16( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, - &rtiff->samples_per_pixel ) || - !tfget16( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, - &rtiff->bits_per_sample ) || - !tfget16( rtiff->tiff, TIFFTAG_PHOTOMETRIC, - &rtiff->photometric_interpretation ) ) - return( -1 ); - - /* Some optional fields. - */ - rtiff->sample_format = rtiff_get_sample_format( rtiff->tiff ); - rtiff->orientation = rtiff_get_orientation( rtiff->tiff ); - rtiff->tiled = TIFFIsTiled( rtiff->tiff ); - - /* Arbitrary sanity-checking limits. - */ - - if( rtiff->width <= 0 || - rtiff->width > VIPS_MAX_COORD || - rtiff->height <= 0 || - rtiff->height > VIPS_MAX_COORD ) { - vips_error( "tiff2vips", - "%s", _( "width/height out of range" ) ); - return( -1 ); - } - - if( rtiff->samples_per_pixel <= 0 || - rtiff->samples_per_pixel > 10000 || - rtiff->bits_per_sample <= 0 || - rtiff->bits_per_sample > 32 ) { - vips_error( "tiff2vips", - "%s", _( "samples out of range" ) ); - return( -1 ); - } - - out->Xsize = rtiff->width; - out->Ysize = rtiff->height; + out->Xsize = rtiff->header.width; + out->Ysize = rtiff->header.height; /* Even though we could end up serving tiled data, always hint * THINSTRIP, since we're quite happy doing that too, and it could need @@ -1200,20 +1183,20 @@ parse_header( ReadTiff *rtiff, VipsImage *out ) vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); #ifdef DEBUG - printf( "parse_header: samples_per_pixel = %d\n", - rtiff->samples_per_pixel ); - printf( "parse_header: bits_per_sample = %d\n", - rtiff->bits_per_sample ); - printf( "parse_header: sample_format = %d\n", - rtiff->sample_format ); - printf( "parse_header: orientation = %d\n", - rtiff->orientation ); + printf( "rtiff_set_header: header.samples_per_pixel = %d\n", + rtiff->header.samples_per_pixel ); + printf( "rtiff_set_header: header.bits_per_sample = %d\n", + rtiff->header.bits_per_sample ); + printf( "rtiff_set_header: header.sample_format = %d\n", + rtiff->header.sample_format ); + printf( "rtiff_set_header: header.orientation = %d\n", + rtiff->header.orientation ); #endif /*DEBUG*/ /* We have a range of output paths. Look at the tiff header and try to * route the input image to the best output path. */ - if( pick_reader( rtiff )( rtiff, out ) ) + if( rtiff_pick_reader( rtiff )( rtiff, out ) ) return( -1 ); /* Read any ICC profile. @@ -1283,130 +1266,18 @@ parse_header( ReadTiff *rtiff, VipsImage *out ) VIPS_META_IMAGEDESCRIPTION, (char *) data ); } + if( get_resolution( rtiff->tiff, out ) ) + return( -1 ); + /* Set the "orientation" tag. This is picked up later by autorot, if * requested. */ - vips_image_set_int( out, VIPS_META_ORIENTATION, rtiff->orientation ); + vips_image_set_int( out, + VIPS_META_ORIENTATION, rtiff->header.orientation ); return( 0 ); } -static int -readtiff_set_directory( ReadTiff *rtiff, int page ) -{ - if( !TIFFSetDirectory( rtiff->tiff, page ) ) { - vips_error( "tiff2vips", - _( "TIFF does not contain page %d" ), rtiff->page ); - return( -1 ); - } - - return( 0 ); -} - -static int -readtiff_n_directories( ReadTiff *rtiff ) -{ - int n; - - for( n = 0; TIFFSetDirectory( rtiff->tiff, n ); n++ ) - ; - - return( n ); -} - -/* Compare this page to the first one we read in. Are they compatible? - * - * Multipage tiffs need to all be readable by the same path, and need to all be - * the same width and height. - */ -static gboolean -is_compatible( ReadTiff *rtiff, int page ) -{ - uint32 width, height; - int samples_per_pixel; - int bits_per_sample; - int photometric_interpretation; - - if( readtiff_set_directory( rtiff, page ) ) - return( FALSE ); - - /* What a headache, don't try to support SEPARATE mutipage images. - */ - if( tfexists( rtiff->tiff, TIFFTAG_PLANARCONFIG ) ) { - int v; - - if( !tfget16( rtiff->tiff, TIFFTAG_PLANARCONFIG, &v ) ) - return( -1 ); - if( v == PLANARCONFIG_SEPARATE ) - return( FALSE ); - } - if( rtiff->separate ) - return( FALSE ); - - if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, &width ) || - !tfget32( rtiff->tiff, TIFFTAG_IMAGELENGTH, &height ) || - !tfget16( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, - &samples_per_pixel ) || - !tfget16( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, - &bits_per_sample ) || - !tfget16( rtiff->tiff, TIFFTAG_PHOTOMETRIC, - &photometric_interpretation ) ) - return( FALSE ); - if( width != rtiff->width || - height != rtiff->height || - samples_per_pixel != rtiff->samples_per_pixel || - bits_per_sample != rtiff->bits_per_sample || - photometric_interpretation != rtiff->photometric_interpretation ) - return( FALSE ); - - if( rtiff->sample_format != rtiff_get_sample_format( rtiff->tiff ) ) - return( FALSE ); - - if( rtiff->tiled != TIFFIsTiled( rtiff->tiff ) ) - return( FALSE ); - - if( rtiff->tiled ) { - uint32 twidth, theight; - - if( !tfget32( rtiff->tiff, TIFFTAG_TILEWIDTH, &twidth ) || - !tfget32( rtiff->tiff, TIFFTAG_TILELENGTH, &theight ) ) - return( FALSE ); - - if( width != rtiff->width || - height != rtiff->height ) - return( FALSE ); - } - else { - tsize_t scanline_size; - tsize_t strip_size; - int number_of_strips; - uint32 rows_per_strip; - - scanline_size = TIFFScanlineSize( rtiff->tiff ); - strip_size = TIFFStripSize( rtiff->tiff ); - number_of_strips = TIFFNumberOfStrips( rtiff->tiff ); - - if( !tfget32( rtiff->tiff, - TIFFTAG_ROWSPERSTRIP, &rows_per_strip ) ) - return( FALSE ); - - /* rows_per_strip can be 2 ** 32 - 1, meaning the whole image. - * Clip this down to image height to avoid confusing vips. - * - * And it musn't be zero. - */ - rows_per_strip = VIPS_CLIP( 1, rows_per_strip, rtiff->height ); - - if( scanline_size != rtiff->scanline_size || - strip_size != rtiff->strip_size || - number_of_strips != rtiff->number_of_strips || - rows_per_strip != rtiff->rows_per_strip ) - return( FALSE ); - } - - return( TRUE ); -} - /* The size of the buffer written by TIFFReadTile(). We can't use * TIFFTileSize() since that ignores the setting of TIFFTAG_JPEGCOLORMODE. If * this pseudo tag has been set and the tile is encoded with YCbCr, the tile @@ -1415,22 +1286,22 @@ is_compatible( ReadTiff *rtiff, int page ) * This seems not to happen for old-style jpeg-compressed tiles. */ static size_t -tiff_tile_size( ReadTiff *rtiff ) +rtiff_tile_size( Rtiff *rtiff ) { - return( TIFFTileRowSize( rtiff->tiff ) * rtiff->theight ); + return( TIFFTileRowSize( rtiff->tiff ) * rtiff->header.tile_height ); } /* Allocate a tile buffer. Have one of these for each thread so we can unpack * to vips in parallel. */ static void * -tiff_seq_start( VipsImage *out, void *a, void *b ) +rtiff_seq_start( VipsImage *out, void *a, void *b ) { - ReadTiff *rtiff = (ReadTiff *) a; + Rtiff *rtiff = (Rtiff *) a; tsize_t size; tdata_t *buf; - size = tiff_tile_size( rtiff ); + size = rtiff_tile_size( rtiff ); if( !(buf = vips_malloc( NULL, size )) ) return( NULL ); @@ -1438,39 +1309,39 @@ tiff_seq_start( VipsImage *out, void *a, void *b ) } /* Paint a tile from the file. This is a - * special-case for a region is exactly a tiff tile, and pixels need no + * special-case for when a region is exactly a tiff tile, and pixels need no * conversion. In this case, libtiff can read tiles directly to our output * region. */ static int -tiff_fill_region_aligned( VipsRegion *out, void *seq, void *a, void *b ) +rtiff_fill_region_aligned( VipsRegion *out, void *seq, void *a, void *b ) { - ReadTiff *rtiff = (ReadTiff *) a; + Rtiff *rtiff = (Rtiff *) a; VipsRect *r = &out->valid; - g_assert( (r->left % rtiff->twidth) == 0 ); - g_assert( (r->top % rtiff->theight) == 0 ); - g_assert( r->width == rtiff->twidth ); - g_assert( r->height == rtiff->theight ); + g_assert( (r->left % rtiff->header.tile_width) == 0 ); + g_assert( (r->top % rtiff->header.tile_height) == 0 ); + g_assert( r->width == rtiff->header.tile_width ); + g_assert( r->height == rtiff->header.tile_height ); g_assert( VIPS_REGION_LSKIP( out ) == VIPS_REGION_SIZEOF_LINE( out ) ); #ifdef DEBUG - printf( "tiff_fill_region_aligned: left = %d, top = %d\n", + printf( "rtiff_fill_region_aligned: left = %d, top = %d\n", r->left, r->top ); #endif /*DEBUG*/ - VIPS_GATE_START( "tiff_fill_region_aligned: work" ); + VIPS_GATE_START( "rtiff_fill_region_aligned: work" ); /* Read that tile directly into the vips tile. */ if( TIFFReadTile( rtiff->tiff, VIPS_REGION_ADDR( out, r->left, r->top ), r->left, r->top, 0, 0 ) < 0 ) { - VIPS_GATE_STOP( "tiff_fill_region_aligned: work" ); + VIPS_GATE_STOP( "rtiff_fill_region_aligned: work" ); return( -1 ); } - VIPS_GATE_STOP( "tiff_fill_region_aligned: work" ); + VIPS_GATE_STOP( "rtiff_fill_region_aligned: work" ); return( 0 ); } @@ -1478,20 +1349,22 @@ tiff_fill_region_aligned( VipsRegion *out, void *seq, void *a, void *b ) /* Loop over the output region painting in tiles from the file. */ static int -tiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop ) +rtiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop ) { tdata_t *buf = (tdata_t *) seq; - ReadTiff *rtiff = (ReadTiff *) a; + Rtiff *rtiff = (Rtiff *) a; + int tile_width = rtiff->header.tile_width; + int tile_height = rtiff->header.tile_height; VipsRect *r = &out->valid; /* Find top left of tiles we need. */ - int xs = (r->left / rtiff->twidth) * rtiff->twidth; - int ys = (r->top / rtiff->theight) * rtiff->theight; + int xs = (r->left / tile_width) * tile_width; + int ys = (r->top / tile_height) * tile_height; /* Sizeof a line of bytes in the TIFF tile. */ - int tls = tiff_tile_size( rtiff ) / rtiff->theight; + int tls = rtiff_tile_size( rtiff ) / tile_height; /* Sizeof a pel in the TIFF file. This won't work for formats which * are <1 byte per pel, like onebit :-( Fortunately, it's only used @@ -1499,7 +1372,7 @@ tiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop ) * vips_tilecache(), we will never have to calculate positions not * within a tile. */ - int tps = tls / rtiff->twidth; + int tps = tls / tile_width; int x, y, z; @@ -1507,24 +1380,24 @@ tiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop ) * the tiff tile and we have no repacking to do for this format. */ if( rtiff->memcpy && - r->left % rtiff->twidth == 0 && - r->top % rtiff->theight == 0 && - r->width == rtiff->twidth && - r->height == rtiff->theight && + r->left % tile_width == 0 && + r->top % tile_height == 0 && + r->width == tile_width && + r->height == tile_height && VIPS_REGION_LSKIP( out ) == VIPS_REGION_SIZEOF_LINE( out ) ) - return( tiff_fill_region_aligned( out, seq, a, b ) ); + return( rtiff_fill_region_aligned( out, seq, a, b ) ); - VIPS_GATE_START( "tiff_fill_region: work" ); + VIPS_GATE_START( "rtiff_fill_region: work" ); - for( y = ys; y < VIPS_RECT_BOTTOM( r ); y += rtiff->theight ) - for( x = xs; x < VIPS_RECT_RIGHT( r ); x += rtiff->twidth ) { + for( y = ys; y < VIPS_RECT_BOTTOM( r ); y += tile_height ) + for( x = xs; x < VIPS_RECT_RIGHT( r ); x += tile_width ) { VipsRect tile; VipsRect hit; /* Read that tile. */ if( TIFFReadTile( rtiff->tiff, buf, x, y, 0, 0 ) < 0 ) { - VIPS_GATE_STOP( "tiff_fill_region: work" ); + VIPS_GATE_STOP( "rtiff_fill_region: work" ); return( -1 ); } @@ -1532,8 +1405,8 @@ tiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop ) */ tile.left = x; tile.top = y; - tile.width = rtiff->twidth; - tile.height = rtiff->theight; + tile.width = tile_width; + tile.height = tile_height; /* The section that hits the region we are building. */ @@ -1554,13 +1427,13 @@ tiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop ) } } - VIPS_GATE_STOP( "tiff_fill_region: work" ); + VIPS_GATE_STOP( "rtiff_fill_region: work" ); return( 0 ); } static int -tiff_seq_stop( void *seq, void *a, void *b ) +rtiff_seq_stop( void *seq, void *a, void *b ) { vips_free( seq ); @@ -1570,7 +1443,7 @@ tiff_seq_stop( void *seq, void *a, void *b ) /* Auto-rotate handling. */ static int -vips_tiff_autorotate( ReadTiff *rtiff, VipsImage *in, VipsImage **out ) +rtiff_autorotate( Rtiff *rtiff, VipsImage *in, VipsImage **out ) { VipsAngle angle = vips_autorot_get_angle( in ); @@ -1612,36 +1485,30 @@ vips_tiff_autorotate( ReadTiff *rtiff, VipsImage *in, VipsImage **out ) * the im and do it all partially. */ static int -read_tilewise( ReadTiff *rtiff, VipsImage *out ) +rtiff_read_tilewise( Rtiff *rtiff, VipsImage *out ) { + int tile_width = rtiff->header.tile_width; + int tile_height = rtiff->header.tile_height; VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( out ), 3 ); #ifdef DEBUG - printf( "tiff2vips: read_tilewise\n" ); + printf( "tiff2vips: rtiff_read_tilewise\n" ); #endif /*DEBUG*/ /* I don't have a sample images for tiled + separate, ban it for now. */ - if( rtiff->separate ) { + if( rtiff->header.separate ) { vips_error( "tiff2vips", "%s", _( "tiled separate planes not supported" ) ); return( -1 ); } - /* Get tiling geometry. - */ - if( !tfget32( rtiff->tiff, TIFFTAG_TILEWIDTH, &rtiff->twidth ) || - !tfget32( rtiff->tiff, TIFFTAG_TILELENGTH, &rtiff->theight ) ) - return( -1 ); - /* Read to this image, then cache to out, see below. */ t[0] = vips_image_new(); - /* Parse the TIFF header and set up. - */ - if( parse_header( rtiff, t[0] ) ) + if( rtiff_set_header( rtiff, t[0] ) ) return( -1 ); /* Double check: in memcpy mode, the vips tilesize should exactly @@ -1651,9 +1518,9 @@ read_tilewise( ReadTiff *rtiff, VipsImage *out ) size_t vips_tile_size; vips_tile_size = VIPS_IMAGE_SIZEOF_PEL( t[0] ) * - rtiff->twidth * rtiff->theight; + tile_width * tile_height; - if( tiff_tile_size( rtiff ) != vips_tile_size ) { + if( rtiff_tile_size( rtiff ) != vips_tile_size ) { vips_error( "tiff2vips", "%s", _( "unsupported tiff image type" ) ); return( -1 ); @@ -1667,19 +1534,19 @@ read_tilewise( ReadTiff *rtiff, VipsImage *out ) vips_image_pipelinev( t[0], VIPS_DEMAND_STYLE_THINSTRIP, NULL ); if( vips_image_generate( t[0], - tiff_seq_start, tiff_fill_region, tiff_seq_stop, + rtiff_seq_start, rtiff_fill_region, rtiff_seq_stop, rtiff, NULL ) ) return( -1 ); /* Copy to out, adding a cache. Enough tiles for two complete rows. */ if( vips_tilecache( t[0], &t[1], - "tile_width", rtiff->twidth, - "tile_height", rtiff->theight, - "max_tiles", 2 * (1 + t[0]->Xsize / rtiff->twidth), + "tile_width", tile_width, + "tile_height", tile_height, + "max_tiles", 2 * (1 + t[0]->Xsize / tile_width), NULL ) ) return( -1 ); - if( vips_tiff_autorotate( rtiff, t[1], &t[2] ) ) + if( rtiff_autorotate( rtiff, t[1], &t[2] ) ) return( -1 ); if( vips_image_write( t[2], out ) ) return( -1 ); @@ -1688,7 +1555,7 @@ read_tilewise( ReadTiff *rtiff, VipsImage *out ) } static int -tiff2vips_strip_read( TIFF *tiff, int strip, tdata_t buf ) +rtiff_strip_read( TIFF *tiff, int strip, tdata_t buf ) { tsize_t length; @@ -1705,25 +1572,28 @@ tiff2vips_strip_read( TIFF *tiff, int strip, tdata_t buf ) * interleave to the output. */ static int -tiff2vips_strip_read_interleaved( ReadTiff *rtiff, int y, tdata_t buf ) +rtiff_strip_read_interleaved( Rtiff *rtiff, int y, tdata_t buf ) { - tstrip_t strip = y / rtiff->rows_per_strip; + int samples_per_pixel = rtiff->header.samples_per_pixel; + int rows_per_strip = rtiff->header.rows_per_strip; + int bits_per_sample = rtiff->header.bits_per_sample; + tstrip_t strip = y / rtiff->header.rows_per_strip; - if( rtiff->separate ) { + if( rtiff->header.separate ) { int strips_per_plane = 1 + (rtiff->out->Ysize - 1) / - rtiff->rows_per_strip; - int strip_height = VIPS_MIN( rtiff->rows_per_strip, + rows_per_strip; + int strip_height = VIPS_MIN( rows_per_strip, rtiff->out->Ysize - y ); int pels_per_strip = rtiff->out->Xsize * strip_height; - int bytes_per_sample = rtiff->bits_per_sample >> 3; + int bytes_per_sample = bits_per_sample >> 3; int i, j, k; - for( i = 0; i < rtiff->samples_per_pixel; i++ ) { + for( i = 0; i < samples_per_pixel; i++ ) { VipsPel *p; VipsPel *q; - if( tiff2vips_strip_read( rtiff->tiff, + if( rtiff_strip_read( rtiff->tiff, strips_per_plane * i + strip, rtiff->plane_buf ) ) return( -1 ); @@ -1735,13 +1605,12 @@ tiff2vips_strip_read_interleaved( ReadTiff *rtiff, int y, tdata_t buf ) q[k] = p[k]; p += bytes_per_sample; - q += bytes_per_sample * - rtiff->samples_per_pixel; + q += bytes_per_sample * samples_per_pixel; } } } else { - if( tiff2vips_strip_read( rtiff->tiff, strip, buf ) ) + if( rtiff_strip_read( rtiff->tiff, strip, buf ) ) return( -1 ); } @@ -1749,10 +1618,11 @@ tiff2vips_strip_read_interleaved( ReadTiff *rtiff, int y, tdata_t buf ) } static int -tiff2vips_stripwise_generate( VipsRegion *or, +rtiff_stripwise_generate( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) { - ReadTiff *rtiff = (ReadTiff *) a; + Rtiff *rtiff = (Rtiff *) a; + int rows_per_strip = rtiff->header.rows_per_strip; VipsRect *r = &or->valid; int y; @@ -1771,17 +1641,17 @@ tiff2vips_stripwise_generate( VipsRegion *or, /* Tiles should always be on a strip boundary. */ - g_assert( r->top % rtiff->rows_per_strip == 0 ); + g_assert( r->top % rtiff->header.rows_per_strip == 0 ); /* Tiles should always be a strip in height, unless it's the final * strip. */ g_assert( r->height == - VIPS_MIN( rtiff->rows_per_strip, or->im->Ysize - r->top ) ); + VIPS_MIN( rows_per_strip, or->im->Ysize - r->top ) ); - VIPS_GATE_START( "tiff2vips_stripwise_generate: work" ); + VIPS_GATE_START( "rtiff_stripwise_generate: work" ); - for( y = 0; y < r->height; y += rtiff->rows_per_strip ) { + for( y = 0; y < r->height; y += rows_per_strip ) { tdata_t dst; /* Read directly into the image if we can. Otherwise, we must @@ -1792,16 +1662,16 @@ tiff2vips_stripwise_generate( VipsRegion *or, else dst = rtiff->contig_buf; - if( tiff2vips_strip_read_interleaved( rtiff, + if( rtiff_strip_read_interleaved( rtiff, r->top + y, dst ) ) { - VIPS_GATE_STOP( "tiff2vips_stripwise_generate: work" ); + VIPS_GATE_STOP( "rtiff_stripwise_generate: work" ); return( -1 ); } /* If necessary, unpack to destination. */ if( !rtiff->memcpy ) { - int height = VIPS_MIN( VIPS_MIN( rtiff->rows_per_strip, + int height = VIPS_MIN( VIPS_MIN( rows_per_strip, or->im->Ysize - (r->top + y) ), r->height ); VipsPel *p; @@ -1814,13 +1684,13 @@ tiff2vips_stripwise_generate( VipsRegion *or, rtiff->sfn( rtiff, q, p, or->im->Xsize, rtiff->client ); - p += rtiff->scanline_size; + p += rtiff->header.scanline_size; q += VIPS_REGION_LSKIP( or ); } } } - VIPS_GATE_STOP( "tiff2vips_stripwise_generate: work" ); + VIPS_GATE_STOP( "rtiff_stripwise_generate: work" ); return( 0 ); } @@ -1832,45 +1702,30 @@ tiff2vips_stripwise_generate( VipsRegion *or, * large image. Only offer sequential read. */ static int -read_stripwise( ReadTiff *rtiff, VipsImage *out ) +rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out ) { VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( out ), 3 ); #ifdef DEBUG - printf( "tiff2vips: read_stripwise\n" ); + printf( "tiff2vips: rtiff_read_stripwise\n" ); #endif /*DEBUG*/ t[0] = vips_image_new(); - if( parse_header( rtiff, t[0] ) ) + if( rtiff_set_header( rtiff, t[0] ) ) return( -1 ); vips_image_pipelinev( t[0], VIPS_DEMAND_STYLE_THINSTRIP, NULL ); - if( !tfget32( rtiff->tiff, - TIFFTAG_ROWSPERSTRIP, &rtiff->rows_per_strip ) ) - return( -1 ); - rtiff->scanline_size = TIFFScanlineSize( rtiff->tiff ); - rtiff->strip_size = TIFFStripSize( rtiff->tiff ); - rtiff->number_of_strips = TIFFNumberOfStrips( rtiff->tiff ); - - /* rows_per_strip can be 2 ** 32 - 1, meaning the whole image. Clip - * this down to ysize to avoid confusing vips. - * - * And it musn't be zero. - */ - rtiff->rows_per_strip = - VIPS_CLIP( 1, rtiff->rows_per_strip, t[0]->Ysize ); - #ifdef DEBUG - printf( "read_stripwise: rows_per_strip = %u\n", - rtiff->rows_per_strip ); - printf( "read_stripwise: scanline_size = %zd\n", - rtiff->scanline_size ); - printf( "read_stripwise: strip_size = %zd\n", - rtiff->strip_size ); - printf( "read_stripwise: number_of_strips = %d\n", - rtiff->number_of_strips ); + printf( "rtiff_read_stripwise: header.rows_per_strip = %u\n", + rtiff->header.rows_per_strip ); + printf( "rtiff_read_stripwise: header.scanline_size = %zd\n", + rtiff->header.scanline_size ); + printf( "rtiff_read_stripwise: header.strip_size = %zd\n", + rtiff->header.strip_size ); + printf( "rtiff_read_stripwise: header.number_of_strips = %d\n", + rtiff->header.number_of_strips ); #endif /*DEBUG*/ /* Double check: in memcpy mode, the vips linesize should exactly @@ -1881,13 +1736,13 @@ read_stripwise( ReadTiff *rtiff, VipsImage *out ) /* Lines are smaller in plane-separated mode. */ - if( rtiff->separate ) + if( rtiff->header.separate ) vips_line_size = VIPS_IMAGE_SIZEOF_ELEMENT( t[0] ) * t[0]->Xsize; else vips_line_size = VIPS_IMAGE_SIZEOF_LINE( t[0] ); - if( rtiff->scanline_size != vips_line_size ) { + if( rtiff->header.scanline_size != vips_line_size ) { vips_error( "tiff2vips", "%s", _( "unsupported tiff image type" ) ); return( -1 ); @@ -1900,9 +1755,9 @@ read_stripwise( ReadTiff *rtiff, VipsImage *out ) * We don't need a separate buffer per thread since the _generate() * function runs inside the cache lock. */ - if( rtiff->separate ) { - if( !(rtiff->plane_buf = - vips_malloc( VIPS_OBJECT( out ), rtiff->strip_size )) ) + if( rtiff->header.separate ) { + if( !(rtiff->plane_buf = vips_malloc( VIPS_OBJECT( out ), + rtiff->header.strip_size )) ) return( -1 ); } @@ -1915,9 +1770,9 @@ read_stripwise( ReadTiff *rtiff, VipsImage *out ) if( !rtiff->memcpy ) { tsize_t size; - size = rtiff->strip_size; - if( rtiff->separate ) - size *= rtiff->samples_per_pixel; + size = rtiff->header.strip_size; + if( rtiff->header.separate ) + size *= rtiff->header.samples_per_pixel; if( !(rtiff->contig_buf = vips_malloc( VIPS_OBJECT( out ), size )) ) @@ -1926,15 +1781,15 @@ read_stripwise( ReadTiff *rtiff, VipsImage *out ) if( vips_image_generate( t[0], - NULL, tiff2vips_stripwise_generate, NULL, + NULL, rtiff_stripwise_generate, NULL, rtiff, NULL ) || vips_sequential( t[0], &t[1], - "tile_height", rtiff->rows_per_strip, + "tile_height", rtiff->header.rows_per_strip, "access", rtiff->readbehind ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_SEQUENTIAL_UNBUFFERED, NULL ) || - vips_tiff_autorotate( rtiff, t[1], &t[2] ) || + rtiff_autorotate( rtiff, t[1], &t[2] ) || vips_image_write( t[2], out ) ) return( -1 ); @@ -1944,29 +1799,30 @@ read_stripwise( ReadTiff *rtiff, VipsImage *out ) /* Can be called many times. */ static void -readtiff_free( ReadTiff *rtiff ) +rtiff_free( Rtiff *rtiff ) { VIPS_FREEF( TIFFClose, rtiff->tiff ); } static void -readtiff_close( VipsObject *object, ReadTiff *rtiff ) +rtiff_close( VipsObject *object, Rtiff *rtiff ) { - readtiff_free( rtiff ); + rtiff_free( rtiff ); } -static ReadTiff * -readtiff_new( VipsImage *out, +static Rtiff * +rtiff_new( VipsImage *out, int page, int n, gboolean autorotate, gboolean readbehind ) { - ReadTiff *rtiff; + Rtiff *rtiff; - if( !(rtiff = VIPS_NEW( out, ReadTiff )) ) + if( !(rtiff = VIPS_NEW( out, Rtiff )) ) return( NULL ); rtiff->filename = NULL; rtiff->out = out; rtiff->page = page; + rtiff->n = n; rtiff->autorotate = autorotate; rtiff->readbehind = readbehind; rtiff->tiff = NULL; @@ -1974,14 +1830,11 @@ readtiff_new( VipsImage *out, rtiff->client = NULL; rtiff->memcpy = FALSE; rtiff->pos = 0; - rtiff->twidth = 0; - rtiff->theight = 0; - rtiff->separate = FALSE; rtiff->plane_buf = NULL; rtiff->contig_buf = NULL; g_signal_connect( out, "close", - G_CALLBACK( readtiff_close ), rtiff ); + G_CALLBACK( rtiff_close ), rtiff ); if( rtiff->page < 0 || rtiff->page > 1000000 ) { vips_error( "tiff2vips", _( "bad page number %d" ), @@ -1995,10 +1848,8 @@ readtiff_new( VipsImage *out, /* Load from a tiff dir into one of our tiff header structs. */ static int -rtiff_header_read( ReadTiff *rtiff, RtiffHeader *header ) +rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) { - /* We always need dimensions. - */ if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, &header->width ) || !tfget32( rtiff->tiff, TIFFTAG_IMAGELENGTH, &header->height ) || !tfget16( rtiff->tiff, @@ -2010,11 +1861,30 @@ rtiff_header_read( ReadTiff *rtiff, RtiffHeader *header ) &header->photometric_interpretation ) ) return( -1 ); - /* Some optional fields. + /* Arbitrary sanity-checking limits. */ - header->sample_format = rtiff_get_sample_format( rtiff->tiff ); - header->orientation = rtiff_get_orientation( rtiff->tiff ); + if( header->width <= 0 || + header->width > VIPS_MAX_COORD || + header->height <= 0 || + header->height > VIPS_MAX_COORD ) { + vips_error( "tiff2vips", + "%s", _( "width/height out of range" ) ); + return( -1 ); + } + if( header->samples_per_pixel <= 0 || + header->samples_per_pixel > 10000 || + header->bits_per_sample <= 0 || + header->bits_per_sample > 32 ) { + vips_error( "tiff2vips", + "%s", _( "samples out of range" ) ); + return( -1 ); + } + + header->sample_format = get_sample_format( rtiff->tiff ); + header->orientation = get_orientation( rtiff->tiff ); + + header->separate = FALSE; if( tfexists( rtiff->tiff, TIFFTAG_PLANARCONFIG ) ) { int v; @@ -2086,16 +1956,47 @@ rtiff_header_equal( RtiffHeader *h1, RtiffHeader *h2 ) return( 1 ); } -static ReadTiff * -readtiff_new_filename( const char *filename, VipsImage *out, +static int +rtiff_header_read_all( Rtiff *rtiff ) +{ + if( set_directory( rtiff->tiff, rtiff->page ) || + rtiff_header_read( rtiff, &rtiff->header ) ) + return( -1 ); + + /* If we're to read many pages, verify that they are all identical. + */ + if( rtiff->n > 1 ) { + + int i; + + for( i = 1; i < rtiff->n; i++ ) { + RtiffHeader header; + + if( set_directory( rtiff->tiff, rtiff->page + i ) || + rtiff_header_read( rtiff, &header ) ) + return( -1 ); + + if( !rtiff_header_equal( &rtiff->header, &header ) ) { + vips_error( "tiff2vips", + _( "page %d differs from page %d" ), + rtiff->page + i, rtiff->page ); + return( -1 ); + } + } + } + + return( 0 ); +} + +static Rtiff * +rtiff_new_filename( const char *filename, VipsImage *out, int page, int n, gboolean autorotate, gboolean readbehind ) { - ReadTiff *rtiff; + Rtiff *rtiff; - if( !(rtiff = readtiff_new( out, page, n, autorotate, readbehind )) || + if( !(rtiff = rtiff_new( out, page, n, autorotate, readbehind )) || !(rtiff->tiff = vips__tiff_openin( filename )) || - readtiff_set_directory( rtiff, page ) || - rtiff_header_read( rtiff, &rtiff->header ) ) + rtiff_header_read_all( rtiff ) ) return( NULL ); rtiff->filename = vips_strdup( VIPS_OBJECT( out ), filename ); @@ -2103,16 +2004,15 @@ readtiff_new_filename( const char *filename, VipsImage *out, return( rtiff ); } -static ReadTiff * -readtiff_new_buffer( const void *buf, size_t len, VipsImage *out, +static Rtiff * +rtiff_new_buffer( const void *buf, size_t len, VipsImage *out, int page, int n, gboolean autorotate, gboolean readbehind ) { - ReadTiff *rtiff; + Rtiff *rtiff; - if( !(rtiff = readtiff_new( out, page, n, autorotate, readbehind )) || + if( !(rtiff = rtiff_new( out, page, n, autorotate, readbehind )) || !(rtiff->tiff = vips__tiff_openin_buffer( out, buf, len )) || - readtiff_set_directory( rtiff, page ) || - rtiff_header_read( rtiff, &rtiff->header ) ) + rtiff_header_read_all( rtiff ) ) return( NULL ); return( rtiff ); @@ -2143,7 +2043,7 @@ int vips__tiff_read( const char *filename, VipsImage *out, int page, int n, gboolean autorotate, gboolean readbehind ) { - ReadTiff *rtiff; + Rtiff *rtiff; #ifdef DEBUG printf( "tiff2vips: libtiff version is \"%s\"\n", TIFFGetVersion() ); @@ -2152,16 +2052,16 @@ vips__tiff_read( const char *filename, VipsImage *out, vips__tiff_init(); - if( !(rtiff = readtiff_new_filename( filename, + if( !(rtiff = rtiff_new_filename( filename, out, page, n, autorotate, readbehind )) ) return( -1 ); - if( TIFFIsTiled( rtiff->tiff ) ) { - if( read_tilewise( rtiff, out ) ) + if( rtiff->header.tiled ) { + if( rtiff_read_tilewise( rtiff, out ) ) return( -1 ); } else { - if( read_stripwise( rtiff, out ) ) + if( rtiff_read_stripwise( rtiff, out ) ) return( -1 ); } @@ -2172,7 +2072,7 @@ vips__tiff_read( const char *filename, VipsImage *out, * 8. */ static void -vips__tiff_read_header_orientation( ReadTiff *rtiff, VipsImage *out ) +vips__tiff_read_header_orientation( Rtiff *rtiff, VipsImage *out ) { int orientation; @@ -2195,22 +2095,22 @@ int vips__tiff_read_header( const char *filename, VipsImage *out, int page, int n, gboolean autorotate ) { - ReadTiff *rtiff; + Rtiff *rtiff; vips__tiff_init(); - if( !(rtiff = readtiff_new_filename( filename, out, + if( !(rtiff = rtiff_new_filename( filename, out, page, n, autorotate, FALSE )) ) return( -1 ); - if( parse_header( rtiff, out ) ) + if( rtiff_set_header( rtiff, out ) ) return( -1 ); vips__tiff_read_header_orientation( rtiff, out ); /* Just a header read: we can free the tiff read early and save an fd. */ - readtiff_free( rtiff ); + rtiff_free( rtiff ); return( 0 ); } @@ -2264,15 +2164,15 @@ int vips__tiff_read_header_buffer( const void *buf, size_t len, VipsImage *out, int page, int n, gboolean autorotate ) { - ReadTiff *rtiff; + Rtiff *rtiff; vips__tiff_init(); - if( !(rtiff = readtiff_new_buffer( buf, len, out, + if( !(rtiff = rtiff_new_buffer( buf, len, out, page, n, autorotate, FALSE )) ) return( -1 ); - if( parse_header( rtiff, out ) ) + if( rtiff_set_header( rtiff, out ) ) return( -1 ); vips__tiff_read_header_orientation( rtiff, out ); @@ -2285,7 +2185,7 @@ vips__tiff_read_buffer( const void *buf, size_t len, VipsImage *out, int page, int n, gboolean autorotate, gboolean readbehind ) { - ReadTiff *rtiff; + Rtiff *rtiff; #ifdef DEBUG printf( "tiff2vips: libtiff version is \"%s\"\n", TIFFGetVersion() ); @@ -2294,16 +2194,16 @@ vips__tiff_read_buffer( const void *buf, size_t len, vips__tiff_init(); - if( !(rtiff = readtiff_new_buffer( buf, len, out, + if( !(rtiff = rtiff_new_buffer( buf, len, out, page, n, autorotate, readbehind )) ) return( -1 ); - if( TIFFIsTiled( rtiff->tiff ) ) { - if( read_tilewise( rtiff, out ) ) + if( rtiff->header.tiled ) { + if( rtiff_read_tilewise( rtiff, out ) ) return( -1 ); } else { - if( read_stripwise( rtiff, out ) ) + if( rtiff_read_stripwise( rtiff, out ) ) return( -1 ); } From 2475611b0da269d993b23b57ed49f5c6a7291e53 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 17 Nov 2016 12:30:32 +0000 Subject: [PATCH 11/46] started expanding strip read --- TODO | 4 ++ libvips/foreign/tiff2vips.c | 91 ++++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 41 deletions(-) diff --git a/TODO b/TODO index aada39c7..c826ebef 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,7 @@ +- test data + + http://downloads.openmicroscopy.org/images/OME-TIFF/2016-06/bioformats-artificial/multi-channel-z-series.ome.tif + - all toilet roll loaders need to set "page-height" magick, pdf and tiff need to use the same page/n interface diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 54312727..fb6a3ac0 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -197,8 +197,8 @@ */ /* -#define DEBUG */ +#define DEBUG #ifdef HAVE_CONFIG_H #include @@ -271,6 +271,10 @@ typedef struct _Rtiff { */ TIFF *tiff; + /* The current page we have set. + */ + int current_page; + /* Process for this image type. */ scanline_process_fn sfn; @@ -280,10 +284,6 @@ typedef struct _Rtiff { */ gboolean memcpy; - /* The current 'file pointer' for memory buffers. - */ - size_t pos; - /* Geometry as read from the TIFF header. This is read for the first * page, and equal for all other pages. */ @@ -436,11 +436,13 @@ get_orientation( TIFF *tiff ) } static int -set_directory( TIFF *tiff, int page ) +strip_read( TIFF *tiff, int strip, tdata_t buf ) { - if( !TIFFSetDirectory( tiff, page ) ) { - vips_error( "tiff2vips", - _( "TIFF does not contain page %d" ), page ); + tsize_t length; + + length = TIFFReadEncodedStrip( tiff, strip, buf, (tsize_t) -1 ); + if( length == -1 ) { + vips_error( "tiff2vips", "%s", _( "read error" ) ); return( -1 ); } @@ -448,17 +450,19 @@ set_directory( TIFF *tiff, int page ) } static int -n_directories( TIFF *tiff ) +rtiff_set_page( Rtiff *rtiff, int page ) { - int n; + if( rtiff->current_page != page ) { + if( !TIFFSetDirectory( rtiff->tiff, page ) ) { + vips_error( "tiff2vips", + _( "TIFF does not contain page %d" ), page ); + return( -1 ); + } - /* Faster than doing set_directory() repeatedly. - */ - set_directory( tiff, 0 ); - for( n = 0; TIFFReadDirectory( tiff ); n++ ) - ; + rtiff->current_page = page; + } - return( n ); + return( 0 ); } static int @@ -1174,7 +1178,10 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out ) void *data; out->Xsize = rtiff->header.width; - out->Ysize = rtiff->header.height; + out->Ysize = rtiff->header.height * rtiff->n; + + if( rtiff->n > 1 ) + vips_image_set_int( out, "page-height", rtiff->header.height ); /* Even though we could end up serving tiled data, always hint * THINSTRIP, since we're quite happy doing that too, and it could need @@ -1554,20 +1561,6 @@ rtiff_read_tilewise( Rtiff *rtiff, VipsImage *out ) return( 0 ); } -static int -rtiff_strip_read( TIFF *tiff, int strip, tdata_t buf ) -{ - tsize_t length; - - length = TIFFReadEncodedStrip( tiff, strip, buf, (tsize_t) -1 ); - if( length == -1 ) { - vips_error( "tiff2vips", "%s", _( "read error" ) ); - return( -1 ); - } - - return( 0 ); -} - /* Read a strip. If the image is in separate planes, read each plane and * interleave to the output. */ @@ -1593,7 +1586,7 @@ rtiff_strip_read_interleaved( Rtiff *rtiff, int y, tdata_t buf ) VipsPel *p; VipsPel *q; - if( rtiff_strip_read( rtiff->tiff, + if( strip_read( rtiff->tiff, strips_per_plane * i + strip, rtiff->plane_buf ) ) return( -1 ); @@ -1610,7 +1603,7 @@ rtiff_strip_read_interleaved( Rtiff *rtiff, int y, tdata_t buf ) } } else { - if( rtiff_strip_read( rtiff->tiff, strip, buf ) ) + if( strip_read( rtiff->tiff, strip, buf ) ) return( -1 ); } @@ -1639,12 +1632,12 @@ rtiff_stripwise_generate( VipsRegion *or, g_assert( r->width == or->im->Xsize ); g_assert( VIPS_RECT_BOTTOM( r ) <= or->im->Ysize ); - /* Tiles should always be on a strip boundary. + /* We can read many pages, so tiles won't always be on a strip + * boundary. */ - g_assert( r->top % rtiff->header.rows_per_strip == 0 ); /* Tiles should always be a strip in height, unless it's the final - * strip. + * strip in the image. */ g_assert( r->height == VIPS_MIN( rows_per_strip, or->im->Ysize - r->top ) ); @@ -1654,6 +1647,8 @@ rtiff_stripwise_generate( VipsRegion *or, for( y = 0; y < r->height; y += rows_per_strip ) { tdata_t dst; + what page is this y, set that page + /* Read directly into the image if we can. Otherwise, we must * read to a temp buffer then unpack into the image. */ @@ -1826,10 +1821,10 @@ rtiff_new( VipsImage *out, rtiff->autorotate = autorotate; rtiff->readbehind = readbehind; rtiff->tiff = NULL; + rtiff->current_page = -1; rtiff->sfn = NULL; rtiff->client = NULL; rtiff->memcpy = FALSE; - rtiff->pos = 0; rtiff->plane_buf = NULL; rtiff->contig_buf = NULL; @@ -1842,6 +1837,12 @@ rtiff_new( VipsImage *out, return( NULL ); } + if( rtiff->n < 1 || rtiff->n > 1000000 ) { + vips_error( "tiff2vips", _( "bad number of pages %d" ), + rtiff->n ); + return( NULL ); + } + return( rtiff ); } @@ -1959,20 +1960,28 @@ rtiff_header_equal( RtiffHeader *h1, RtiffHeader *h2 ) static int rtiff_header_read_all( Rtiff *rtiff ) { - if( set_directory( rtiff->tiff, rtiff->page ) || +#ifdef DEBUG + printf( "tiff2vips: reading header for page %d ...\n", rtiff->page ); +#endif /*DEBUG*/ + + if( rtiff_set_page( rtiff, rtiff->page ) || rtiff_header_read( rtiff, &rtiff->header ) ) return( -1 ); /* If we're to read many pages, verify that they are all identical. */ if( rtiff->n > 1 ) { - int i; for( i = 1; i < rtiff->n; i++ ) { RtiffHeader header; - if( set_directory( rtiff->tiff, rtiff->page + i ) || +#ifdef DEBUG + printf( "tiff2vips: verifying header for page %d ...\n", + rtiff->page + i ); +#endif /*DEBUG*/ + + if( rtiff_set_page( rtiff, rtiff->page + i ) || rtiff_header_read( rtiff, &header ) ) return( -1 ); From 64d02a7a156c3e963605b7155937bc03b4b8c1c5 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 17 Nov 2016 14:50:21 +0000 Subject: [PATCH 12/46] strip read works tile next --- ChangeLog | 1 + libvips/foreign/tiff2vips.c | 31 ++++++++++++++++++++----------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8e6be44d..8444f777 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,6 +10,7 @@ - all loaders have a @fail option, meaning fail on first warning, though it only does anything for jpg, csv, openslide - add vips_image_get_fields() to help bindings +- add multi-page read to tiffload 11/11/16 started 8.4.4 - fix crash in vips.exe arg parsing on Windows, thanks Yury diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index fb6a3ac0..c0578bc9 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -167,6 +167,8 @@ * convention * 26/5/16 * - add autorotate support + * 17/11/16 + * - add multi-page read */ /* @@ -197,8 +199,8 @@ */ /* - */ #define DEBUG + */ #ifdef HAVE_CONFIG_H #include @@ -1632,8 +1634,8 @@ rtiff_stripwise_generate( VipsRegion *or, g_assert( r->width == or->im->Xsize ); g_assert( VIPS_RECT_BOTTOM( r ) <= or->im->Ysize ); - /* We can read many pages, so tiles won't always be on a strip - * boundary. + /* If we're reading more than one page, tiles won't fall on strip + * boundaries. */ /* Tiles should always be a strip in height, unless it's the final @@ -1644,10 +1646,19 @@ rtiff_stripwise_generate( VipsRegion *or, VIPS_GATE_START( "rtiff_stripwise_generate: work" ); - for( y = 0; y < r->height; y += rows_per_strip ) { + y = 0; + while( y < r->height ) { + int page = rtiff->page + (r->top + y) / rtiff->header.height; + int page_y = (r->top + y) % rtiff->header.height; + int strip_height = VIPS_MIN( rows_per_strip, + rtiff->header.height - page_y ); + tdata_t dst; - what page is this y, set that page + if( rtiff_set_page( rtiff, page ) ) { + VIPS_GATE_STOP( "rtiff_stripwise_generate: work" ); + return( -1 ); + } /* Read directly into the image if we can. Otherwise, we must * read to a temp buffer then unpack into the image. @@ -1657,8 +1668,7 @@ rtiff_stripwise_generate( VipsRegion *or, else dst = rtiff->contig_buf; - if( rtiff_strip_read_interleaved( rtiff, - r->top + y, dst ) ) { + if( rtiff_strip_read_interleaved( rtiff, page_y, dst ) ) { VIPS_GATE_STOP( "rtiff_stripwise_generate: work" ); return( -1 ); } @@ -1666,16 +1676,13 @@ rtiff_stripwise_generate( VipsRegion *or, /* If necessary, unpack to destination. */ if( !rtiff->memcpy ) { - int height = VIPS_MIN( VIPS_MIN( rows_per_strip, - or->im->Ysize - (r->top + y) ), r->height ); - VipsPel *p; VipsPel *q; int z; p = rtiff->contig_buf; q = VIPS_REGION_ADDR( or, 0, r->top + y ); - for( z = 0; z < height; z++ ) { + for( z = 0; z < strip_height; z++ ) { rtiff->sfn( rtiff, q, p, or->im->Xsize, rtiff->client ); @@ -1683,6 +1690,8 @@ rtiff_stripwise_generate( VipsRegion *or, q += VIPS_REGION_LSKIP( or ); } } + + y += strip_height; } VIPS_GATE_STOP( "rtiff_stripwise_generate: work" ); From 80e8c3f496dc1379b22bad7f6fec4c6f2fdf9391 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 17 Nov 2016 15:00:24 +0000 Subject: [PATCH 13/46] half-way through adding tiled read --- libvips/foreign/tiff2vips.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index c0578bc9..fa91e2b0 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -1366,11 +1366,6 @@ rtiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop int tile_height = rtiff->header.tile_height; VipsRect *r = &out->valid; - /* Find top left of tiles we need. - */ - int xs = (r->left / tile_width) * tile_width; - int ys = (r->top / tile_height) * tile_height; - /* Sizeof a line of bytes in the TIFF tile. */ int tls = rtiff_tile_size( rtiff ) / tile_height; @@ -1387,8 +1382,12 @@ rtiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop /* Special case: we are filling a single tile exactly sized to match * the tiff tile and we have no repacking to do for this format. + * + * This will also only work for single-page read. If we are reading + * from a many-page TIFF, all our tile alignment will break. */ if( rtiff->memcpy && + rtiff->n == 1 && r->left % tile_width == 0 && r->top % tile_height == 0 && r->width == tile_width && @@ -1398,8 +1397,10 @@ rtiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop VIPS_GATE_START( "rtiff_fill_region: work" ); - for( y = ys; y < VIPS_RECT_BOTTOM( r ); y += tile_height ) - for( x = xs; x < VIPS_RECT_RIGHT( r ); x += tile_width ) { + y = 0; + while( y < r->height ) { + x = 0; + while( x < r->width ) { VipsRect tile; VipsRect hit; @@ -1434,8 +1435,13 @@ rtiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop rtiff->sfn( rtiff, q, p, hit.width, rtiff->client ); } + + x += tile_width; } + y += tile_height; + } + VIPS_GATE_STOP( "rtiff_fill_region: work" ); return( 0 ); From 75bc46c8b8c9d7b654e44eec89fac8565e6aeb46 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 17 Nov 2016 19:50:17 +0000 Subject: [PATCH 14/46] multi-page tiled read compiles need some test data though ... save next --- libvips/foreign/tiff2vips.c | 44 +++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index fa91e2b0..2b5615fe 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -1382,12 +1382,8 @@ rtiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop /* Special case: we are filling a single tile exactly sized to match * the tiff tile and we have no repacking to do for this format. - * - * This will also only work for single-page read. If we are reading - * from a many-page TIFF, all our tile alignment will break. */ if( rtiff->memcpy && - rtiff->n == 1 && r->left % tile_width == 0 && r->top % tile_height == 0 && r->width == tile_width && @@ -1399,26 +1395,46 @@ rtiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop y = 0; while( y < r->height ) { + VipsRect tile, page, hit; + x = 0; while( x < r->width ) { - VipsRect tile; - VipsRect hit; + int page_no = rtiff->page + (r->top + y) / + rtiff->header.height; + int page_y = (r->top + y) % rtiff->header.height; - /* Read that tile. + /* Coordinate of the tile on this page that xy falls in. */ - if( TIFFReadTile( rtiff->tiff, buf, x, y, 0, 0 ) < 0 ) { + int xs = ((r->left + x) / tile_width) * tile_width; + int ys = (page_y / tile_height) * tile_height; + + if( rtiff_set_page( rtiff, page_no ) || + TIFFReadTile( rtiff->tiff, + buf, xs, ys, 0, 0 ) < 0 ) { VIPS_GATE_STOP( "rtiff_fill_region: work" ); return( -1 ); } - /* The tile we read. + /* Position of tile on the page. */ - tile.left = x; - tile.top = y; + tile.left = xs; + tile.top = ys; tile.width = tile_width; tile.height = tile_height; - /* The section that hits the region we are building. + /* It'll be clipped by this page. + */ + page.left = 0; + page.top = 0; + page.width = rtiff->header.width; + page.height = rtiff->header.height; + vips_rect_intersectrect( &tile, &page, &tile ); + + /* To image coordinates. + */ + tile.top += page_no * rtiff->header.height; + + /* And clip again by this region. */ vips_rect_intersectrect( &tile, r, &hit ); @@ -1436,10 +1452,10 @@ rtiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop q, p, hit.width, rtiff->client ); } - x += tile_width; + x += tile.width; } - y += tile_height; + y += tile.height; } VIPS_GATE_STOP( "rtiff_fill_region: work" ); From 503225b777441cfef8da9bc6a820844da312497a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 18 Nov 2016 11:57:01 +0000 Subject: [PATCH 15/46] more hackery found a problem with non-aligned strips --- README.md | 4 ++++ TODO | 4 ++++ libvips/foreign/tiff2vips.c | 28 ++++++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 48b2e40e..93bb69cd 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,10 @@ Leak check: --leak-check=yes \ vips ... > vips-vg.log 2>&1 +Memory error debug: + + $ valgrind --vgdb=yes --vgdb-error=0 vips ... + valgrind threading check: $ valgrind --tool=helgrind vips ... > vips-vg.log 2>&1 diff --git a/TODO b/TODO index c826ebef..74df7898 100644 --- a/TODO +++ b/TODO @@ -2,6 +2,10 @@ http://downloads.openmicroscopy.org/images/OME-TIFF/2016-06/bioformats-artificial/multi-channel-z-series.ome.tif +- tiff load needs to support n=-1, meaning all pages to end + + is that what pdfload does? + - all toilet roll loaders need to set "page-height" magick, pdf and tiff need to use the same page/n interface diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 2b5615fe..9edc93ac 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -1587,6 +1587,10 @@ rtiff_read_tilewise( Rtiff *rtiff, VipsImage *out ) /* Read a strip. If the image is in separate planes, read each plane and * interleave to the output. + * + * y may not be on a strip boundary. We might need to read the whole strip, + * then skip down a few lines in the decompressed data to get the start point + * where we */ static int rtiff_strip_read_interleaved( Rtiff *rtiff, int y, tdata_t buf ) @@ -1672,8 +1676,22 @@ rtiff_stripwise_generate( VipsRegion *or, while( y < r->height ) { int page = rtiff->page + (r->top + y) / rtiff->header.height; int page_y = (r->top + y) % rtiff->header.height; - int strip_height = VIPS_MIN( rows_per_strip, - rtiff->header.height - page_y ); + + /* strips are normally rows_per_strip in height, but they will + * be smaller for the last strip on the page. + * + * We may not use the whole strip. If we have 16 lines to fill + * in this generate, we might take the first 10 from the end + * of the first page and then just 6 from the first strip at + * the top of page 2. + * + * In this case, we'll end up reading the top strip of page 2 + * again in the next generate call, and only using the bottom + * half. + */ + int strip_height = VIPS_MIN( VIPS_MIN( rows_per_strip, + rtiff->header.height - page_y ), + r->height - y ); tdata_t dst; @@ -1682,6 +1700,12 @@ rtiff_stripwise_generate( VipsRegion *or, return( -1 ); } + We must always have a strip-sized buffer to read to in case this + y is not aligned on a strip boundary ... we can then copy from + that buffer into the dest + + is contif_buffer always strip sized? + /* Read directly into the image if we can. Otherwise, we must * read to a temp buffer then unpack into the image. */ From fd1c8d2732520e863e77a7c62c939468699c770a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 18 Nov 2016 14:57:11 +0000 Subject: [PATCH 16/46] possible strip read fix untested though --- libvips/foreign/tiff2vips.c | 110 +++++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 34 deletions(-) diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 9edc93ac..0435e165 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -467,6 +467,25 @@ rtiff_set_page( Rtiff *rtiff, int page ) return( 0 ); } +static int +rtiff_n_pages( Rtiff *rtiff ) +{ + int n; + + (void) TIFFSetDirectory( rtiff->tiff, 0 ); + + for( n = 1; TIFFReadDirectory( rtiff->tiff ); n++ ) + ; + + (void) TIFFSetDirectory( rtiff->tiff, rtiff->current_page ); + +#ifdef DEBUG + printf( "rtiff_n_pages: found %d pages\n", n ); +#endif /*DEBUG*/ + + return( n ); +} + static int rtiff_check_samples( Rtiff *rtiff, int samples_per_pixel ) { @@ -1588,24 +1607,23 @@ rtiff_read_tilewise( Rtiff *rtiff, VipsImage *out ) /* Read a strip. If the image is in separate planes, read each plane and * interleave to the output. * - * y may not be on a strip boundary. We might need to read the whole strip, - * then skip down a few lines in the decompressed data to get the start point - * where we + * strip is the number of this strip in this page. */ static int -rtiff_strip_read_interleaved( Rtiff *rtiff, int y, tdata_t buf ) +rtiff_strip_read_interleaved( Rtiff *rtiff, tstrip_t strip, tdata_t buf ) { int samples_per_pixel = rtiff->header.samples_per_pixel; int rows_per_strip = rtiff->header.rows_per_strip; int bits_per_sample = rtiff->header.bits_per_sample; - tstrip_t strip = y / rtiff->header.rows_per_strip; + int strip_y = strip * rows_per_strip; if( rtiff->header.separate ) { - int strips_per_plane = 1 + (rtiff->out->Ysize - 1) / - rows_per_strip; - int strip_height = VIPS_MIN( rows_per_strip, - rtiff->out->Ysize - y ); - int pels_per_strip = rtiff->out->Xsize * strip_height; + int page_width = rtiff->header.width; + int page_height = rtiff->header.height; + int strips_per_plane = 1 + (page_height - 1) / rows_per_strip; + int strip_height = VIPS_MIN( rows_per_strip, + page_height - strip_y ); + int pels_per_strip = page_width * strip_height; int bytes_per_sample = bits_per_sample >> 3; int i, j, k; @@ -1644,6 +1662,7 @@ rtiff_stripwise_generate( VipsRegion *or, { Rtiff *rtiff = (Rtiff *) a; int rows_per_strip = rtiff->header.rows_per_strip; + int page_height = rtiff->header.height; VipsRect *r = &or->valid; int y; @@ -1674,13 +1693,20 @@ rtiff_stripwise_generate( VipsRegion *or, y = 0; while( y < r->height ) { - int page = rtiff->page + (r->top + y) / rtiff->header.height; - int page_y = (r->top + y) % rtiff->header.height; + /* Page number, position within this page. + */ + int page = rtiff->page + (r->top + y) / page_height; + int y_page = (r->top + y) % page_height; + + /* Strip number, position within this strip. + */ + tstrip_t strip = y_page / rows_per_strip; + int y_strip = y_page % rows_per_strip; /* strips are normally rows_per_strip in height, but they will * be smaller for the last strip on the page. * - * We may not use the whole strip. If we have 16 lines to fill + * We may not be tile-aligned. If we have 16 lines to fill * in this generate, we might take the first 10 from the end * of the first page and then just 6 from the first strip at * the top of page 2. @@ -1690,7 +1716,7 @@ rtiff_stripwise_generate( VipsRegion *or, * half. */ int strip_height = VIPS_MIN( VIPS_MIN( rows_per_strip, - rtiff->header.height - page_y ), + page_height - y_page ), r->height - y ); tdata_t dst; @@ -1700,33 +1726,39 @@ rtiff_stripwise_generate( VipsRegion *or, return( -1 ); } - We must always have a strip-sized buffer to read to in case this - y is not aligned on a strip boundary ... we can then copy from - that buffer into the dest - - is contif_buffer always strip sized? - /* Read directly into the image if we can. Otherwise, we must * read to a temp buffer then unpack into the image. + * + * We need to read via a buffer if we need to reformat pixels, + * or if this strip is not aligned on a tile boundary. */ - if( rtiff->memcpy ) - dst = VIPS_REGION_ADDR( or, 0, r->top + y ); - else - dst = rtiff->contig_buf; - - if( rtiff_strip_read_interleaved( rtiff, page_y, dst ) ) { - VIPS_GATE_STOP( "rtiff_stripwise_generate: work" ); - return( -1 ); + if( rtiff->memcpy || + y_page % rows_per_strip != 0 ) { + if( rtiff_strip_read_interleaved( rtiff, strip, + VIPS_REGION_ADDR( or, 0, r->top + y ) ) ) { + VIPS_GATE_STOP( + "rtiff_stripwise_generate: work" ); + return( -1 ); + } } - - /* If necessary, unpack to destination. - */ - if( !rtiff->memcpy ) { + else { VipsPel *p; VipsPel *q; int z; - p = rtiff->contig_buf; + /* Read and interleave the entire strip. + */ + if( rtiff_strip_read_interleaved( rtiff, strip, + rtiff->contig_buf ) ) { + VIPS_GATE_STOP( + "rtiff_stripwise_generate: work" ); + return( -1 ); + } + + /* Do any repacking to generate pixels in vips layout. + */ + p = rtiff->contig_buf + + y_strip * rtiff->header.scanline_size; q = VIPS_REGION_ADDR( or, 0, r->top + y ); for( z = 0; z < strip_height; z++ ) { rtiff->sfn( rtiff, @@ -1814,10 +1846,14 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out ) /* If we need to manipulate pixels, we must read to an interleaved * plane buffer before repacking to the output. * + * If we are doing a multi-page read, we need a strip buffer, since + * strips may not be aligned on tile boundaries. + * * We don't need a separate buffer per thread since the _generate() * function runs inside the cache lock. */ - if( !rtiff->memcpy ) { + if( !rtiff->memcpy || + rtiff->n > 1 ) { tsize_t size; size = rtiff->header.strip_size; @@ -1827,6 +1863,7 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out ) if( !(rtiff->contig_buf = vips_malloc( VIPS_OBJECT( out ), size )) ) return( -1 ); + } if( @@ -2023,6 +2060,11 @@ rtiff_header_read_all( Rtiff *rtiff ) rtiff_header_read( rtiff, &rtiff->header ) ) return( -1 ); + /* -1 means "to the end". + */ + if( rtiff->n == -1 ) + rtiff->n = rtiff->page - rtiff_n_pages( rtiff ); + /* If we're to read many pages, verify that they are all identical. */ if( rtiff->n > 1 ) { From 23e3fecdb03df3f11b4d57f2da96ad534fe4c3b9 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 19 Nov 2016 10:41:22 +0000 Subject: [PATCH 17/46] fix multi-page.tif load --- libvips/foreign/tiff2vips.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 0435e165..4f2e25d8 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -442,6 +442,10 @@ strip_read( TIFF *tiff, int strip, tdata_t buf ) { tsize_t length; +#ifdef DEBUG + printf( "strip_read: reading strip %d\n", strip ); +#endif /*DEBUG*/ + length = TIFFReadEncodedStrip( tiff, strip, buf, (tsize_t) -1 ); if( length == -1 ) { vips_error( "tiff2vips", "%s", _( "read error" ) ); @@ -455,6 +459,10 @@ static int rtiff_set_page( Rtiff *rtiff, int page ) { if( rtiff->current_page != page ) { +#ifdef DEBUG + printf( "rtiff_set_page: selecting page %d\n", page ); +#endif /*DEBUG*/ + if( !TIFFSetDirectory( rtiff->tiff, page ) ) { vips_error( "tiff2vips", _( "TIFF does not contain page %d" ), page ); @@ -1703,6 +1711,10 @@ rtiff_stripwise_generate( VipsRegion *or, tstrip_t strip = y_page / rows_per_strip; int y_strip = y_page % rows_per_strip; + /* Position of top of strip. + */ + int strip_y = strip * rows_per_strip; + /* strips are normally rows_per_strip in height, but they will * be smaller for the last strip on the page. * @@ -1715,12 +1727,12 @@ rtiff_stripwise_generate( VipsRegion *or, * again in the next generate call, and only using the bottom * half. */ - int strip_height = VIPS_MIN( VIPS_MIN( rows_per_strip, + int strip_height = VIPS_MIN( VIPS_MIN( VIPS_MIN( + rows_per_strip, page_height - y_page ), + strip_y + rows_per_strip - y_page ), r->height - y ); - tdata_t dst; - if( rtiff_set_page( rtiff, page ) ) { VIPS_GATE_STOP( "rtiff_stripwise_generate: work" ); return( -1 ); @@ -1732,8 +1744,9 @@ rtiff_stripwise_generate( VipsRegion *or, * We need to read via a buffer if we need to reformat pixels, * or if this strip is not aligned on a tile boundary. */ - if( rtiff->memcpy || - y_page % rows_per_strip != 0 ) { + if( rtiff->memcpy && + y_page % rows_per_strip == 0 && + strip_height == rows_per_strip ) { if( rtiff_strip_read_interleaved( rtiff, strip, VIPS_REGION_ADDR( or, 0, r->top + y ) ) ) { VIPS_GATE_STOP( @@ -1929,7 +1942,11 @@ rtiff_new( VipsImage *out, return( NULL ); } - if( rtiff->n < 1 || rtiff->n > 1000000 ) { + /* We allow n == -1, meaning all pages. It gets swapped for a real n + * value when we open the TIFF. + */ + if( rtiff->n != -1 && + (rtiff->n < 1 || rtiff->n > 1000000) ) { vips_error( "tiff2vips", _( "bad number of pages %d" ), rtiff->n ); return( NULL ); @@ -2063,7 +2080,7 @@ rtiff_header_read_all( Rtiff *rtiff ) /* -1 means "to the end". */ if( rtiff->n == -1 ) - rtiff->n = rtiff->page - rtiff_n_pages( rtiff ); + rtiff->n = rtiff_n_pages( rtiff ) - rtiff->page; /* If we're to read many pages, verify that they are all identical. */ From f1e4416d6a6aab5997d7ebe3828fe8461e580098 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 21 Nov 2016 13:02:08 +0000 Subject: [PATCH 18/46] more hacking --- libvips/foreign/vips2tiff.c | 59 ++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index ce4162d4..d855dade 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -302,6 +302,17 @@ struct _Write { int rgbjpeg; /* True for RGB not YCbCr */ int properties; /* Set to save XML props */ int strip; /* Don't write metadata */ + + /* True if we've detected a toilet-roll image, plus the page height, + * which has been checked to be a factor of im->Ysize. + */ + gboolean toilet_roll; + int page_height; + + /* The height of the TIFF we write. Equal to page_height in toilet + * roll mode. + */ + int image_height; }; static Layer * @@ -894,15 +905,54 @@ write_new( VipsImage *im, const char *filename, write->pyramid = pyramid; write->onebit = squash; write->miniswhite = miniswhite; + write->resunit = get_resunit( resunit ); + write->xres = xres; + write->yres = yres; write->icc_profile = vips_strdup( NULL, profile ); write->bigtiff = bigtiff; write->rgbjpeg = rgbjpeg; write->properties = properties; write->strip = strip; + write->toilet_roll = FALSE; + write->page_height = -1; - write->resunit = get_resunit( resunit ); - write->xres = xres; - write->yres = yres; + /* Updated below if we discover toilet roll mode. + */ + write->image_height = im->Ysize; + + /* Check for a toilet roll image. + */ + if( vips_image_get_typeof( im, "page-height" ) && + vips_image_get_int( im, "page-height", &write->page_height ) ) { + write_free( write ); + return( NULL ); + } + + /* If page_height <= Ysize, treat as a single-page image. + */ + if( write->page_height > 0 && + write->page_height < im->Ysize ) { + write->toilet_roll = TRUE; + write->image_height = write->page_height; + + if( im->Ysize % write->page_height != 0 ) { + vips_error( "vips2tiff", + _( "image height %d is not a factor of " + "page-height %d" ), + im->Ysize, write->page_height ); + write_free( write ); + return( NULL ); + } + + /* We can't pyramid toilet roll images. + */ + if( write->pyramid ) { + vips_warn( "vips2tiff", + "%s", _( "can't pyramid multi page images --- " + "disabling pyramid" ) ); + write->pyramid = FALSE; + } + } /* In strip mode we use tileh to set rowsperstrip, and that does not * have the multiple-of-16 restriction. @@ -970,7 +1020,8 @@ write_new( VipsImage *im, const char *filename, /* Build the pyramid framework. */ - write->layer = pyramid_new( write, NULL, im->Xsize, im->Ysize ); + write->layer = pyramid_new( write, NULL, + im->Xsize, write->image_height ); /* Fill all the layers. */ From 5644296b1c7446ba0cb8c9fe351db5f898684b7d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 21 Nov 2016 13:10:06 +0000 Subject: [PATCH 19/46] expand intro to resample --- libvips/resample/resample.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libvips/resample/resample.c b/libvips/resample/resample.c index 42fe3481..b2da6639 100644 --- a/libvips/resample/resample.c +++ b/libvips/resample/resample.c @@ -64,7 +64,15 @@ * sort of 2D transform which preserves straight lines; so any combination of * stretch, sheer, rotate and translate. You supply an interpolator for it to * use to generate pixels, see vips_interpolate_new(). It will not produce - * good results for very large shrinks. + * good results for very large shrinks: you'll see aliasing. + * + * vips_reduce() is like vips_affine(), but it can only shrink images, it can't + * enlarge, rotate, or skew. It's very fast and uses an adaptive kernel for + * interpolation. Again, it will give poor results for large size reductions. + * + * vips_shrink() is a fast block shrinker. It can quickly reduce images by + * large integer factors. It will give poor results for small size reductions: + * again, you'll see aliasing. * * Next, vips_resize() specialises in the common task of image reduce and * enlarge. It strings together combinations of vips_shrink(), vips_reduce(), From 14dd8b32b1b5f422ea8c8140f6c6542bf739a210 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 22 Nov 2016 14:59:11 +0000 Subject: [PATCH 20/46] better clipping in tiff2vips --- libvips/foreign/tiff2vips.c | 66 +++++++++++++++-------------- libvips/foreign/vips2tiff.c | 84 ++++++++++++++++++------------------- 2 files changed, 76 insertions(+), 74 deletions(-) diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 4f2e25d8..b1e46681 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -1703,37 +1703,40 @@ rtiff_stripwise_generate( VipsRegion *or, while( y < r->height ) { /* Page number, position within this page. */ - int page = rtiff->page + (r->top + y) / page_height; + int page_no = rtiff->page + (r->top + y) / page_height; int y_page = (r->top + y) % page_height; - /* Strip number, position within this strip. + /* Strip number. */ - tstrip_t strip = y_page / rows_per_strip; - int y_strip = y_page % rows_per_strip; + tstrip_t strip_no = y_page / rows_per_strip; - /* Position of top of strip. + VipsRect image, page, strip, hit; + + /* Our four (including the output region) rects, all in + * output image coordinates. */ - int strip_y = strip * rows_per_strip; + image.left = 0; + image.top = 0; + image.width = rtiff->out->Xsize; + image.height = rtiff->out->Ysize; - /* strips are normally rows_per_strip in height, but they will - * be smaller for the last strip on the page. - * - * We may not be tile-aligned. If we have 16 lines to fill - * in this generate, we might take the first 10 from the end - * of the first page and then just 6 from the first strip at - * the top of page 2. - * - * In this case, we'll end up reading the top strip of page 2 - * again in the next generate call, and only using the bottom - * half. - */ - int strip_height = VIPS_MIN( VIPS_MIN( VIPS_MIN( - rows_per_strip, - page_height - y_page ), - strip_y + rows_per_strip - y_page ), - r->height - y ); + page.left = 0; + page.top = page_height * ((r->top + y) / page_height); + page.width = rtiff->out->Xsize; + page.height = page_height; - if( rtiff_set_page( rtiff, page ) ) { + strip.left = 0; + strip.top = page.top + strip_no * rows_per_strip; + strip.width = rtiff->out->Xsize; + strip.height = rows_per_strip; + + vips_rect_intersectrect( &strip, &page, &hit ); + vips_rect_intersectrect( &hit, &image, &hit ); + vips_rect_intersectrect( &hit, r, &hit ); + + g_assert( hit.height > 0 ); + + if( rtiff_set_page( rtiff, page_no ) ) { VIPS_GATE_STOP( "rtiff_stripwise_generate: work" ); return( -1 ); } @@ -1745,9 +1748,9 @@ rtiff_stripwise_generate( VipsRegion *or, * or if this strip is not aligned on a tile boundary. */ if( rtiff->memcpy && - y_page % rows_per_strip == 0 && - strip_height == rows_per_strip ) { - if( rtiff_strip_read_interleaved( rtiff, strip, + hit.top % rows_per_strip == 0 && + hit.height == rows_per_strip ) { + if( rtiff_strip_read_interleaved( rtiff, strip_no, VIPS_REGION_ADDR( or, 0, r->top + y ) ) ) { VIPS_GATE_STOP( "rtiff_stripwise_generate: work" ); @@ -1761,7 +1764,7 @@ rtiff_stripwise_generate( VipsRegion *or, /* Read and interleave the entire strip. */ - if( rtiff_strip_read_interleaved( rtiff, strip, + if( rtiff_strip_read_interleaved( rtiff, strip_no, rtiff->contig_buf ) ) { VIPS_GATE_STOP( "rtiff_stripwise_generate: work" ); @@ -1771,9 +1774,10 @@ rtiff_stripwise_generate( VipsRegion *or, /* Do any repacking to generate pixels in vips layout. */ p = rtiff->contig_buf + - y_strip * rtiff->header.scanline_size; + (hit.top - strip.top) * + rtiff->header.scanline_size; q = VIPS_REGION_ADDR( or, 0, r->top + y ); - for( z = 0; z < strip_height; z++ ) { + for( z = 0; z < hit.height; z++ ) { rtiff->sfn( rtiff, q, p, or->im->Xsize, rtiff->client ); @@ -1782,7 +1786,7 @@ rtiff_stripwise_generate( VipsRegion *or, } } - y += strip_height; + y += hit.height; } VIPS_GATE_STOP( "rtiff_stripwise_generate: work" ); diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index d855dade..8bb23d88 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -198,9 +198,9 @@ */ /* + */ #define DEBUG_VERBOSE #define DEBUG - */ #ifdef HAVE_CONFIG_H #include @@ -932,6 +932,12 @@ write_new( VipsImage *im, const char *filename, */ if( write->page_height > 0 && write->page_height < im->Ysize ) { +#ifdef DEBUG + printf( "write_new: detected toilet roll image, " + "page-height=%d\n", + write->page_height ); +#endif/*DEBUG*/ + write->toilet_roll = TRUE; write->image_height = write->page_height; @@ -944,6 +950,11 @@ write_new( VipsImage *im, const char *filename, return( NULL ); } +#ifdef DEBUG + printf( "write_new: pages=%d\n", + im->Ysize / write->page_height ); +#endif/*DEBUG*/ + /* We can't pyramid toilet roll images. */ if( write->pyramid ) { @@ -1654,6 +1665,32 @@ write_gather( Write *write ) return( 0 ); } +static int +write_image( Write *write ) +{ + if( vips_sink_disc( write->im, write_strip, write ) ) + return( -1 ); + + if( !TIFFWriteDirectory( write->layer->tif ) ) + return( -1 ); + + if( write->pyramid ) { + /* Free lower pyramid resources ... this will TIFFClose() (but + * not delete) the smaller layers ready for us to read from + * them again. + */ + if( write->layer->below ) + pyramid_free( write->layer->below ); + + /* Append smaller layers to the main file. + */ + if( write_gather( write ) ) + return( -1 ); + } + + return( 0 ); +} + int vips__tiff_write( VipsImage *in, const char *filename, VipsForeignTiffCompression compression, int Q, @@ -1688,30 +1725,11 @@ vips__tiff_write( VipsImage *in, const char *filename, properties, strip )) ) return( -1 ); - if( vips_sink_disc( write->im, write_strip, write ) ) { + if( write_image( write ) ) { write_free( write ); return( -1 ); } - if( !TIFFWriteDirectory( write->layer->tif ) ) - return( -1 ); - - if( write->pyramid ) { - /* Free lower pyramid resources ... this will TIFFClose() (but - * not delete) the smaller layers ready for us to read from - * them again. - */ - if( write->layer->below ) - pyramid_free( write->layer->below ); - - /* Append smaller layers to the main file. - */ - if( write_gather( write ) ) { - write_free( write ); - return( -1 ); - } - } - write_free( write ); return( 0 ); @@ -1751,35 +1769,15 @@ vips__tiff_write_buf( VipsImage *in, write->obuf = obuf; write->olen = olen; - if( vips_sink_disc( write->im, write_strip, write ) ) { + if( write_image( write ) ) { write_free( write ); return( -1 ); } - if( !TIFFWriteDirectory( write->layer->tif ) ) - return( -1 ); - - if( write->pyramid ) { - /* Free lower pyramid resources ... this will TIFFClose() (but - * not delete) the smaller layers ready for us to read from - * them again. - */ - if( write->layer->below ) - pyramid_free( write->layer->below ); - - /* Append smaller layers to the main file. - */ - if( write_gather( write ) ) { - write_free( write ); - return( -1 ); - } - } - /* Now close the top layer, and we'll get a pointer we can return * to our caller. */ - TIFFClose( write->layer->tif ); - write->layer->tif = NULL; + VIPS_FREEF( TIFFClose, write->layer->tif ); *obuf = write->layer->buf; *olen = write->layer->len; From 32d049a1dfc89b4b3a44f3f4520ef71052ffc2d3 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 22 Nov 2016 16:42:34 +0000 Subject: [PATCH 21/46] tweak clipping again --- libvips/foreign/tiff2vips.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index b1e46681..caf4e85c 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -1730,9 +1730,15 @@ rtiff_stripwise_generate( VipsRegion *or, strip.width = rtiff->out->Xsize; strip.height = rows_per_strip; - vips_rect_intersectrect( &strip, &page, &hit ); - vips_rect_intersectrect( &hit, &image, &hit ); - vips_rect_intersectrect( &hit, r, &hit ); + /* Clip strip against page and image ... the final strip will + * be smaller. + */ + vips_rect_intersectrect( &strip, &image, &strip ); + vips_rect_intersectrect( &strip, &page, &strip ); + + /* Now the bit that overlaps with the region we are filling. + */ + vips_rect_intersectrect( &strip, r, &hit ); g_assert( hit.height > 0 ); @@ -1748,8 +1754,8 @@ rtiff_stripwise_generate( VipsRegion *or, * or if this strip is not aligned on a tile boundary. */ if( rtiff->memcpy && - hit.top % rows_per_strip == 0 && - hit.height == rows_per_strip ) { + hit.top == strip.top && + hit.height == strip.height ) { if( rtiff_strip_read_interleaved( rtiff, strip_no, VIPS_REGION_ADDR( or, 0, r->top + y ) ) ) { VIPS_GATE_STOP( From 582512ddc4011b3225266b64cbc20424d5845e8c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 22 Nov 2016 17:22:10 +0000 Subject: [PATCH 22/46] fix jpeg-compressed tiff read --- libvips/foreign/tiff2vips.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index caf4e85c..b6534836 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -246,7 +246,6 @@ typedef struct _RtiffHeader { /* Fields for strip images. */ uint32 rows_per_strip; - tsize_t scanline_size; tsize_t strip_size; int number_of_strips; } RtiffHeader; @@ -1197,7 +1196,7 @@ rtiff_pick_reader( Rtiff *rtiff ) return( rtiff_parse_copy ); } -/* Set the header on @out from our rtiff. rtiff_header_read() has alreay been +/* Set the header on @out from our rtiff. rtiff_header_read() has already been * called. */ static int @@ -1671,6 +1670,7 @@ rtiff_stripwise_generate( VipsRegion *or, Rtiff *rtiff = (Rtiff *) a; int rows_per_strip = rtiff->header.rows_per_strip; int page_height = rtiff->header.height; + tsize_t scanline_size = TIFFScanlineSize( rtiff->tiff ); VipsRect *r = &or->valid; int y; @@ -1780,14 +1780,13 @@ rtiff_stripwise_generate( VipsRegion *or, /* Do any repacking to generate pixels in vips layout. */ p = rtiff->contig_buf + - (hit.top - strip.top) * - rtiff->header.scanline_size; + (hit.top - strip.top) * scanline_size; q = VIPS_REGION_ADDR( or, 0, r->top + y ); for( z = 0; z < hit.height; z++ ) { rtiff->sfn( rtiff, q, p, or->im->Xsize, rtiff->client ); - p += rtiff->header.scanline_size; + p += scanline_size; q += VIPS_REGION_LSKIP( or ); } } @@ -1825,8 +1824,6 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out ) #ifdef DEBUG printf( "rtiff_read_stripwise: header.rows_per_strip = %u\n", rtiff->header.rows_per_strip ); - printf( "rtiff_read_stripwise: header.scanline_size = %zd\n", - rtiff->header.scanline_size ); printf( "rtiff_read_stripwise: header.strip_size = %zd\n", rtiff->header.strip_size ); printf( "rtiff_read_stripwise: header.number_of_strips = %d\n", @@ -1847,7 +1844,7 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out ) else vips_line_size = VIPS_IMAGE_SIZEOF_LINE( t[0] ); - if( rtiff->header.scanline_size != vips_line_size ) { + if( vips_line_size != TIFFScanlineSize( rtiff->tiff ) ) { vips_error( "tiff2vips", "%s", _( "unsupported tiff image type" ) ); return( -1 ); @@ -2029,7 +2026,6 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) if( !tfget32( rtiff->tiff, TIFFTAG_ROWSPERSTRIP, &header->rows_per_strip ) ) return( -1 ); - header->scanline_size = TIFFScanlineSize( rtiff->tiff ); header->strip_size = TIFFStripSize( rtiff->tiff ); header->number_of_strips = TIFFNumberOfStrips( rtiff->tiff ); @@ -2067,7 +2063,6 @@ rtiff_header_equal( RtiffHeader *h1, RtiffHeader *h2 ) } else { if( h1->rows_per_strip != h2->rows_per_strip || - h1->scanline_size != h2->scanline_size || h1->strip_size != h2->strip_size || h1->number_of_strips != h2->number_of_strips ) return( 0 ); From 423b6b4a044e0968f2071570ea5d6262bbe2ea5a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 22 Nov 2016 17:37:50 +0000 Subject: [PATCH 23/46] split saver off --- libvips/foreign/vips2tiff.c | 45 ++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 8bb23d88..4d44ad3e 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -1665,27 +1665,34 @@ write_gather( Write *write ) return( 0 ); } +/* Two basic write patterns: multipage and pyramid. We do single image as a + * special case of pyramid. + */ static int write_image( Write *write ) { - if( vips_sink_disc( write->im, write_strip, write ) ) - return( -1 ); - - if( !TIFFWriteDirectory( write->layer->tif ) ) - return( -1 ); - - if( write->pyramid ) { - /* Free lower pyramid resources ... this will TIFFClose() (but - * not delete) the smaller layers ready for us to read from - * them again. - */ - if( write->layer->below ) - pyramid_free( write->layer->below ); - - /* Append smaller layers to the main file. - */ - if( write_gather( write ) ) + if( write->toilet_roll ) { + } + else { + if( vips_sink_disc( write->im, write_strip, write ) ) return( -1 ); + + if( !TIFFWriteDirectory( write->layer->tif ) ) + return( -1 ); + + if( write->pyramid ) { + /* Free lower pyramid resources ... this will + * TIFFClose() (but not delete) the smaller layers + * ready for us to read from them again. + */ + if( write->layer->below ) + pyramid_free( write->layer->below ); + + /* Append smaller layers to the main file. + */ + if( write_gather( write ) ) + return( -1 ); + } } return( 0 ); @@ -1716,8 +1723,6 @@ vips__tiff_write( VipsImage *in, const char *filename, if( vips_check_coding_known( "vips2tiff", in ) ) return( -1 ); - /* Make output image. - */ if( !(write = write_new( in, filename, compression, Q, predictor, profile, tile, tile_width, tile_height, pyramid, squash, @@ -1757,8 +1762,6 @@ vips__tiff_write_buf( VipsImage *in, if( vips_check_coding_known( "vips2tiff", in ) ) return( -1 ); - /* Make output image. - */ if( !(write = write_new( in, NULL, compression, Q, predictor, profile, tile, tile_width, tile_height, pyramid, squash, From bba1aba4b668d2f0dbb1650c31fb985582c5badf Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 22 Nov 2016 18:04:58 +0000 Subject: [PATCH 24/46] don't clear meta before creating a pipeline vips__image_copy_fields_array() used to wipe meta on the output before merging the input images. This broke foreign.c in some cases, since it likes setting some meta on the output before calling the subclass loaders, and they sometimes call write(). see https://github.com/jcupitt/ruby-vips/issues/93 --- libvips/include/vips/header.h | 1 + libvips/iofuncs/header.c | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/libvips/include/vips/header.h b/libvips/include/vips/header.h index 691a407f..5affb207 100644 --- a/libvips/include/vips/header.h +++ b/libvips/include/vips/header.h @@ -190,6 +190,7 @@ int vips_image_get_string( const VipsImage *image, const char *name, const char **out ); void vips_image_set_string( VipsImage *image, const char *name, const char *str ); +void vips_image_print_field( const VipsImage *image, const char *field ); int vips_image_history_printf( VipsImage *image, const char *format, ... ) __attribute__((format(printf, 2, 3))); diff --git a/libvips/iofuncs/header.c b/libvips/iofuncs/header.c index 4a4f6965..bd361235 100644 --- a/libvips/iofuncs/header.c +++ b/libvips/iofuncs/header.c @@ -893,8 +893,11 @@ vips__image_copy_fields_array( VipsImage *out, VipsImage *in[] ) /* Need to copy last-to-first so that in0 meta will override any * earlier meta. + * + * Don't destroy the meta on out. Things like foreign.c like setting + * image properties before calling a subclass loader, and those + * subclass loaders will sometimes write to an image. */ - vips__meta_destroy( out ); for( i = ni - 1; i >= 0; i-- ) if( meta_cp( out, in[i] ) ) return( -1 ); @@ -1573,6 +1576,28 @@ vips_image_get_as_string( const VipsImage *image, return( 0 ); } +/** + * vips_image_print_field: + * @image: image to get the header field from + * @field: field name + * + * Prints a field to stdout as ASCII. Handy for debugging. + */ +void +vips_image_print_field( const VipsImage *image, const char *field ) +{ + char *str; + + if( vips_image_get_as_string( image, field, &str ) ) { + printf( "vips_image_print_field: unable to read field\n" ); + return; + } + + printf( ".%s: %s\n", field, str ); + + g_free( str ); +} + /** * vips_image_history_printf: * @image: add history line to this image From e195a4d51692e6d98b6da90826a550d48b42513e Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 23 Nov 2016 11:21:35 +0000 Subject: [PATCH 25/46] multi-page write seems to work refactor and cleanup next --- libvips/foreign/vips2tiff.c | 93 ++++++++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 18 deletions(-) diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 4d44ad3e..13ed5df3 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -710,6 +710,31 @@ write_tiff_header( Write *write, Layer *layer ) return( 0 ); } +static int +layer_rewind( Write *write, Layer *layer ) +{ + VipsRect strip_size; + + /* Build a line of tiles here. + * + * Expand the strip if necessary to make sure we have an even + * number of lines. + */ + strip_size.left = 0; + strip_size.top = 0; + strip_size.width = layer->image->Xsize; + strip_size.height = write->tileh; + if( (strip_size.height & 1) == 1 ) + strip_size.height += 1; + if( vips_region_buffer( layer->strip, &strip_size ) ) + return( -1 ); + + layer->y = 0; + layer->write_y = 0; + + return( 0 ); +} + /* Walk the pyramid allocating resources. */ static int @@ -718,8 +743,6 @@ pyramid_fill( Write *write ) Layer *layer; for( layer = write->layer; layer; layer = layer->below ) { - VipsRect strip_size; - layer->image = vips_image_new(); if( vips_image_pipelinev( layer->image, VIPS_DEMAND_STYLE_ANY, write->im, NULL ) ) @@ -736,19 +759,8 @@ pyramid_fill( Write *write ) vips__region_no_ownership( layer->strip ); vips__region_no_ownership( layer->copy ); - /* Build a line of tiles here. - * - * Expand the strip if necessary to make sure we have an even - * number of lines. - */ - strip_size.left = 0; - strip_size.top = 0; - strip_size.width = layer->image->Xsize; - strip_size.height = write->tileh; - if( (strip_size.height & 1) == 1 ) - strip_size.height += 1; - if( vips_region_buffer( layer->strip, &strip_size ) ) - return( -1 ); + if( layer_rewind( write, layer ) ) + return( -1 ); if( layer->lname ) layer->tif = vips__tiff_openout( @@ -1665,15 +1677,52 @@ write_gather( Write *write ) return( 0 ); } -/* Two basic write patterns: multipage and pyramid. We do single image as a - * special case of pyramid. +/* Three types of write: single image, multipage and pyramid. */ static int write_image( Write *write ) { if( write->toilet_roll ) { + int n_pages = write->im->Ysize / write->page_height; + + int i; + +#ifdef DEBUG + printf( "write_image: toilet-roll mode\n" ); +#endif /*DEBUG*/ + + for( i = 0; i < n_pages; i++ ) { + VipsImage *page; + + if( vips_crop( write->im, &page, + 0, i * write->page_height, + write->im->Xsize, write->page_height, + NULL ) ) + return( -1 ); + if( vips_sink_disc( page, write_strip, write ) ) { + g_object_unref( page ); + return( -1 ); + } + g_object_unref( page ); + + /* If there will be another page after this one, we + * need to flush this directory and set the directory + * for the next write. + */ + if( i < n_pages - 1 ) { + if( !TIFFWriteDirectory( write->layer->tif ) || + layer_rewind( write, write->layer ) || + write_tiff_header( write, + write->layer ) ) + return( -1 ); + } + } } - else { + else if( write->pyramid ) { +#ifdef DEBUG + printf( "write_image: pyramid mode\n" ); +#endif /*DEBUG*/ + if( vips_sink_disc( write->im, write_strip, write ) ) return( -1 ); @@ -1694,6 +1743,14 @@ write_image( Write *write ) return( -1 ); } } + else { +#ifdef DEBUG + printf( "write_image: single-image mode\n" ); +#endif /*DEBUG*/ + + if( vips_sink_disc( write->im, write_strip, write ) ) + return( -1 ); + } return( 0 ); } From c0da8e31576e6e1c2d9ed94e97f48fda1ccc682e Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 23 Nov 2016 15:00:32 +0000 Subject: [PATCH 26/46] fixed up naming conventions a bit --- TODO | 9 +- libvips/foreign/vips2tiff.c | 680 ++++++++++++++++++------------------ 2 files changed, 337 insertions(+), 352 deletions(-) diff --git a/TODO b/TODO index 74df7898..7715e425 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,12 @@ +- add VIPS_META_PAGE_HEIGHT and probably some others too .. "orientation"? + +- in wtiff_layer_rewind() we size strip height by tileh, but is this correct + for strip images? + - test data http://downloads.openmicroscopy.org/images/OME-TIFF/2016-06/bioformats-artificial/multi-channel-z-series.ome.tif -- tiff load needs to support n=-1, meaning all pages to end - - is that what pdfload does? - - all toilet roll loaders need to set "page-height" magick, pdf and tiff need to use the same page/n interface diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 13ed5df3..2e7396c4 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -143,7 +143,7 @@ * 3/12/14 * - embed XMP in output * 10/12/14 - * - zero out edge tile buffers before jpeg write, thanks iwbh15 + * - zero out edge tile buffers before jpeg wtiff, thanks iwbh15 * 19/1/15 * - disable chroma subsample if Q >= 90 * 13/2/15 @@ -228,12 +228,12 @@ #define MAX_ALPHA (64) typedef struct _Layer Layer; -typedef struct _Write Write; +typedef struct _Wtiff Wtiff; /* A layer in the pyramid. */ struct _Layer { - Write *write; /* Main write struct */ + Wtiff *wtiff; /* Main wtiff struct */ /* The filename for this layer, for file output. */ @@ -270,7 +270,7 @@ struct _Layer { /* A TIFF image in the process of being written. */ -struct _Write { +struct _Wtiff { VipsImage *im; /* Original input image */ /* File to write to, or NULL. @@ -291,9 +291,9 @@ struct _Write { int predictor; /* Predictor value */ int tile; /* Tile or not */ int tilew, tileh; /* Tile size */ - int pyramid; /* Write pyramid */ - int onebit; /* Write as 1-bit TIFF */ - int miniswhite; /* Write as 0 == white */ + int pyramid; /* Wtiff pyramid */ + int onebit; /* Wtiff as 1-bit TIFF */ + int miniswhite; /* Wtiff as 0 == white */ int resunit; /* Resolution unit (inches or cm) */ double xres; /* Resolution in X */ double yres; /* Resolution in Y */ @@ -315,64 +315,6 @@ struct _Write { int image_height; }; -static Layer * -pyramid_new( Write *write, Layer *above, int width, int height ) -{ - Layer *layer; - - layer = VIPS_NEW( write->im, Layer ); - layer->write = write; - layer->width = width; - layer->height = height; - - if( !above ) - /* Top of pyramid. - */ - layer->sub = 1; - else - layer->sub = above->sub * 2; - - layer->lname = NULL; - layer->buf = NULL; - layer->len = 0; - layer->tif = NULL; - layer->image = NULL; - layer->write_y = 0; - layer->y = 0; - layer->strip = NULL; - layer->copy = NULL; - - layer->below = NULL; - layer->above = above; - - if( write->pyramid ) - if( layer->width > write->tilew || - layer->height > write->tileh ) - layer->below = pyramid_new( write, layer, - width / 2, height / 2 ); - - /* The name for the top layer is the output filename. - * - * We need lname to be freed automatically: it has to stay - * alive until after write_gather(). - */ - if( write->filename ) { - if( !above ) - layer->lname = vips_strdup( VIPS_OBJECT( write->im ), - write->filename ); - else { - char *lname; - - lname = vips__temp_name( "%s.tif" ); - layer->lname = - vips_strdup( VIPS_OBJECT( write->im ), lname ); - g_free( lname ); - } - } - - return( layer ); -} - /* Embed an ICC profile from a file. */ static int @@ -412,33 +354,89 @@ embed_profile_meta( TIFF *tif, VipsImage *im ) return( 0 ); } -static int -write_embed_profile( Write *write, TIFF *tif ) +static Layer * +wtiff_layer_new( Wtiff *wtiff, Layer *above, int width, int height ) { - if( write->icc_profile && - strcmp( write->icc_profile, "none" ) != 0 && - embed_profile_file( tif, write->icc_profile ) ) + Layer *layer; + + layer = VIPS_NEW( wtiff->im, Layer ); + layer->wtiff = wtiff; + layer->width = width; + layer->height = height; + + if( !above ) + /* Top of pyramid. + */ + layer->sub = 1; + else + layer->sub = above->sub * 2; + + layer->lname = NULL; + layer->buf = NULL; + layer->len = 0; + layer->tif = NULL; + layer->image = NULL; + layer->write_y = 0; + layer->y = 0; + layer->strip = NULL; + layer->copy = NULL; + + layer->below = NULL; + layer->above = above; + + if( wtiff->pyramid ) + if( layer->width > wtiff->tilew || + layer->height > wtiff->tileh ) + layer->below = wtiff_layer_new( wtiff, layer, + width / 2, height / 2 ); + + /* The name for the top layer is the output filename. + * + * We need lname to be freed automatically: it has to stay + * alive until after wtiff_gather(). + */ + if( wtiff->filename ) { + if( !above ) + layer->lname = vips_strdup( VIPS_OBJECT( wtiff->im ), + wtiff->filename ); + else { + char *lname; + + lname = vips__temp_name( "%s.tif" ); + layer->lname = + vips_strdup( VIPS_OBJECT( wtiff->im ), lname ); + g_free( lname ); + } + } + + return( layer ); +} + +static int +wtiff_embed_profile( Wtiff *wtiff, TIFF *tif ) +{ + if( wtiff->icc_profile && + strcmp( wtiff->icc_profile, "none" ) != 0 && + embed_profile_file( tif, wtiff->icc_profile ) ) return( -1 ); - if( !write->icc_profile && - vips_image_get_typeof( write->im, VIPS_META_ICC_NAME ) && - embed_profile_meta( tif, write->im ) ) + if( !wtiff->icc_profile && + vips_image_get_typeof( wtiff->im, VIPS_META_ICC_NAME ) && + embed_profile_meta( tif, wtiff->im ) ) return( -1 ); return( 0 ); } -/* Embed any XMP metadata. - */ static int -write_embed_xmp( Write *write, TIFF *tif ) +wtiff_embed_xmp( Wtiff *wtiff, TIFF *tif ) { void *data; size_t data_length; - if( !vips_image_get_typeof( write->im, VIPS_META_XMP_NAME ) ) + if( !vips_image_get_typeof( wtiff->im, VIPS_META_XMP_NAME ) ) return( 0 ); - if( vips_image_get_blob( write->im, VIPS_META_XMP_NAME, + if( vips_image_get_blob( wtiff->im, VIPS_META_XMP_NAME, &data, &data_length ) ) return( -1 ); TIFFSetField( tif, TIFFTAG_XMLPACKET, data_length, data ); @@ -450,17 +448,15 @@ write_embed_xmp( Write *write, TIFF *tif ) return( 0 ); } -/* Embed any IPCT metadata. - */ static int -write_embed_ipct( Write *write, TIFF *tif ) +wtiff_embed_ipct( Wtiff *wtiff, TIFF *tif ) { void *data; size_t data_length; - if( !vips_image_get_typeof( write->im, VIPS_META_IPCT_NAME ) ) + if( !vips_image_get_typeof( wtiff->im, VIPS_META_IPCT_NAME ) ) return( 0 ); - if( vips_image_get_blob( write->im, VIPS_META_IPCT_NAME, + if( vips_image_get_blob( wtiff->im, VIPS_META_IPCT_NAME, &data, &data_length ) ) return( -1 ); @@ -485,17 +481,15 @@ write_embed_ipct( Write *write, TIFF *tif ) return( 0 ); } -/* Embed any XMP metadata. - */ static int -write_embed_photoshop( Write *write, TIFF *tif ) +wtiff_embed_photoshop( Wtiff *wtiff, TIFF *tif ) { void *data; size_t data_length; - if( !vips_image_get_typeof( write->im, VIPS_META_PHOTOSHOP_NAME ) ) + if( !vips_image_get_typeof( wtiff->im, VIPS_META_PHOTOSHOP_NAME ) ) return( 0 ); - if( vips_image_get_blob( write->im, + if( vips_image_get_blob( wtiff->im, VIPS_META_PHOTOSHOP_NAME, &data, &data_length ) ) return( -1 ); TIFFSetField( tif, TIFFTAG_PHOTOSHOP, data_length, data ); @@ -511,12 +505,12 @@ write_embed_photoshop( Write *write, TIFF *tif ) * vips' metadata. */ static int -write_embed_imagedescription( Write *write, TIFF *tif ) +wtiff_embed_imagedescription( Wtiff *wtiff, TIFF *tif ) { - if( write->properties ) { + if( wtiff->properties ) { char *doc; - if( !(doc = vips__make_xml_metadata( "vips2tiff", write->im )) ) + if( !(doc = vips__make_xml_metadata( "vips2tiff", wtiff->im )) ) return( -1 ); TIFFSetField( tif, TIFFTAG_IMAGEDESCRIPTION, doc ); xmlFree( doc ); @@ -524,10 +518,10 @@ write_embed_imagedescription( Write *write, TIFF *tif ) else { const char *imagedescription; - if( !vips_image_get_typeof( write->im, + if( !vips_image_get_typeof( wtiff->im, VIPS_META_IMAGEDESCRIPTION ) ) return( 0 ); - if( vips_image_get_string( write->im, + if( vips_image_get_string( wtiff->im, VIPS_META_IMAGEDESCRIPTION, &imagedescription ) ) return( -1 ); TIFFSetField( tif, TIFFTAG_IMAGEDESCRIPTION, imagedescription ); @@ -540,11 +534,10 @@ write_embed_imagedescription( Write *write, TIFF *tif ) return( 0 ); } -/* Write a TIFF header. width and height are the size of the VipsImage we are - * writing (it may have been shrunk). +/* Write a TIFF header for this layer. */ static int -write_tiff_header( Write *write, Layer *layer ) +wtiff_write_header( Wtiff *wtiff, Layer *layer ) { TIFF *tif = layer->tif; @@ -557,47 +550,47 @@ write_tiff_header( Write *write, Layer *layer ) TIFFSetField( tif, TIFFTAG_IMAGELENGTH, layer->height ); TIFFSetField( tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG ); TIFFSetField( tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT ); - TIFFSetField( tif, TIFFTAG_COMPRESSION, write->compression ); + TIFFSetField( tif, TIFFTAG_COMPRESSION, wtiff->compression ); - if( write->compression == COMPRESSION_JPEG ) - TIFFSetField( tif, TIFFTAG_JPEGQUALITY, write->jpqual ); + if( wtiff->compression == COMPRESSION_JPEG ) + TIFFSetField( tif, TIFFTAG_JPEGQUALITY, wtiff->jpqual ); - if( write->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE ) - TIFFSetField( tif, TIFFTAG_PREDICTOR, write->predictor ); + if( wtiff->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE ) + TIFFSetField( tif, TIFFTAG_PREDICTOR, wtiff->predictor ); /* Don't write mad resolutions (eg. zero), it confuses some programs. */ - TIFFSetField( tif, TIFFTAG_RESOLUTIONUNIT, write->resunit ); + TIFFSetField( tif, TIFFTAG_RESOLUTIONUNIT, wtiff->resunit ); TIFFSetField( tif, TIFFTAG_XRESOLUTION, - VIPS_FCLIP( 0.01, write->xres, 1000000 ) ); + VIPS_FCLIP( 0.01, wtiff->xres, 1000000 ) ); TIFFSetField( tif, TIFFTAG_YRESOLUTION, - VIPS_FCLIP( 0.01, write->yres, 1000000 ) ); + VIPS_FCLIP( 0.01, wtiff->yres, 1000000 ) ); - if( !write->strip ) - if( write_embed_profile( write, tif ) || - write_embed_xmp( write, tif ) || - write_embed_ipct( write, tif ) || - write_embed_photoshop( write, tif ) || - write_embed_imagedescription( write, tif ) ) + if( !wtiff->strip ) + if( wtiff_embed_profile( wtiff, tif ) || + wtiff_embed_xmp( wtiff, tif ) || + wtiff_embed_ipct( wtiff, tif ) || + wtiff_embed_photoshop( wtiff, tif ) || + wtiff_embed_imagedescription( wtiff, tif ) ) return( -1 ); - if( vips_image_get_typeof( write->im, VIPS_META_ORIENTATION ) && - !vips_image_get_int( write->im, + if( vips_image_get_typeof( wtiff->im, VIPS_META_ORIENTATION ) && + !vips_image_get_int( wtiff->im, VIPS_META_ORIENTATION, &orientation ) ) TIFFSetField( tif, TIFFTAG_ORIENTATION, orientation ); /* And colour fields. */ - if( write->im->Coding == VIPS_CODING_LABQ ) { + if( wtiff->im->Coding == VIPS_CODING_LABQ ) { TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 3 ); TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 8 ); TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CIELAB ); } - else if( write->onebit ) { + else if( wtiff->onebit ) { TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 1 ); TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 1 ); TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, - write->miniswhite ? + wtiff->miniswhite ? PHOTOMETRIC_MINISWHITE : PHOTOMETRIC_MINISBLACK ); } @@ -611,14 +604,14 @@ write_tiff_header( Write *write, Layer *layer ) int alpha_bands; - TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, write->im->Bands ); + TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, wtiff->im->Bands ); TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, - vips_format_sizeof( write->im->BandFmt ) << 3 ); + vips_format_sizeof( wtiff->im->BandFmt ) << 3 ); - if( write->im->Bands < 3 ) { + if( wtiff->im->Bands < 3 ) { /* Mono or mono + alpha. */ - photometric = write->miniswhite ? + photometric = wtiff->miniswhite ? PHOTOMETRIC_MINISWHITE : PHOTOMETRIC_MINISBLACK; colour_bands = 1; @@ -626,22 +619,22 @@ write_tiff_header( Write *write, Layer *layer ) else { /* Could be: RGB, CMYK, LAB, perhaps with extra alpha. */ - if( write->im->Type == VIPS_INTERPRETATION_LAB || - write->im->Type == VIPS_INTERPRETATION_LABS ) { + if( wtiff->im->Type == VIPS_INTERPRETATION_LAB || + wtiff->im->Type == VIPS_INTERPRETATION_LABS ) { photometric = PHOTOMETRIC_CIELAB; colour_bands = 3; } - else if( write->im->Type == VIPS_INTERPRETATION_CMYK && - write->im->Bands >= 4 ) { + else if( wtiff->im->Type == VIPS_INTERPRETATION_CMYK && + wtiff->im->Bands >= 4 ) { photometric = PHOTOMETRIC_SEPARATED; TIFFSetField( tif, TIFFTAG_INKSET, INKSET_CMYK ); colour_bands = 4; } - else if( write->compression == COMPRESSION_JPEG && - write->im->Bands == 3 && - write->im->BandFmt == VIPS_FORMAT_UCHAR && - (!write->rgbjpeg && write->jpqual < 90) ) { + else if( wtiff->compression == COMPRESSION_JPEG && + wtiff->im->Bands == 3 && + wtiff->im->BandFmt == VIPS_FORMAT_UCHAR && + (!wtiff->rgbjpeg && wtiff->jpqual < 90) ) { /* This signals to libjpeg that it can do * YCbCr chrominance subsampling from RGB, not * that we will supply the image as YCbCr. @@ -662,7 +655,7 @@ write_tiff_header( Write *write, Layer *layer ) } alpha_bands = VIPS_CLIP( 0, - write->im->Bands - colour_bands, MAX_ALPHA ); + wtiff->im->Bands - colour_bands, MAX_ALPHA ); if( alpha_bands > 0 ) { uint16 v[MAX_ALPHA]; int i; @@ -682,12 +675,12 @@ write_tiff_header( Write *write, Layer *layer ) /* Layout. */ - if( write->tile ) { - TIFFSetField( tif, TIFFTAG_TILEWIDTH, write->tilew ); - TIFFSetField( tif, TIFFTAG_TILELENGTH, write->tileh ); + if( wtiff->tile ) { + TIFFSetField( tif, TIFFTAG_TILEWIDTH, wtiff->tilew ); + TIFFSetField( tif, TIFFTAG_TILELENGTH, wtiff->tileh ); } else - TIFFSetField( tif, TIFFTAG_ROWSPERSTRIP, write->tileh ); + TIFFSetField( tif, TIFFTAG_ROWSPERSTRIP, wtiff->tileh ); if( layer->above ) /* Pyramid layer. @@ -697,13 +690,13 @@ write_tiff_header( Write *write, Layer *layer ) /* Sample format. */ format = SAMPLEFORMAT_UINT; - if( vips_band_format_isuint( write->im->BandFmt ) ) + if( vips_band_format_isuint( wtiff->im->BandFmt ) ) format = SAMPLEFORMAT_UINT; - else if( vips_band_format_isint( write->im->BandFmt ) ) + else if( vips_band_format_isint( wtiff->im->BandFmt ) ) format = SAMPLEFORMAT_INT; - else if( vips_band_format_isfloat( write->im->BandFmt ) ) + else if( vips_band_format_isfloat( wtiff->im->BandFmt ) ) format = SAMPLEFORMAT_IEEEFP; - else if( vips_band_format_iscomplex( write->im->BandFmt ) ) + else if( vips_band_format_iscomplex( wtiff->im->BandFmt ) ) format = SAMPLEFORMAT_COMPLEXIEEEFP; TIFFSetField( tif, TIFFTAG_SAMPLEFORMAT, format ); @@ -711,7 +704,7 @@ write_tiff_header( Write *write, Layer *layer ) } static int -layer_rewind( Write *write, Layer *layer ) +wtiff_layer_rewind( Wtiff *wtiff, Layer *layer ) { VipsRect strip_size; @@ -723,7 +716,7 @@ layer_rewind( Write *write, Layer *layer ) strip_size.left = 0; strip_size.top = 0; strip_size.width = layer->image->Xsize; - strip_size.height = write->tileh; + strip_size.height = wtiff->tileh; if( (strip_size.height & 1) == 1 ) strip_size.height += 1; if( vips_region_buffer( layer->strip, &strip_size ) ) @@ -735,17 +728,15 @@ layer_rewind( Write *write, Layer *layer ) return( 0 ); } -/* Walk the pyramid allocating resources. - */ static int -pyramid_fill( Write *write ) +wtiff_allocate_layers( Wtiff *wtiff ) { Layer *layer; - for( layer = write->layer; layer; layer = layer->below ) { + for( layer = wtiff->layer; layer; layer = layer->below ) { layer->image = vips_image_new(); if( vips_image_pipelinev( layer->image, - VIPS_DEMAND_STYLE_ANY, write->im, NULL ) ) + VIPS_DEMAND_STYLE_ANY, wtiff->im, NULL ) ) return( -1 ); layer->image->Xsize = layer->width; layer->image->Ysize = layer->height; @@ -759,20 +750,20 @@ pyramid_fill( Write *write ) vips__region_no_ownership( layer->strip ); vips__region_no_ownership( layer->copy ); - if( layer_rewind( write, layer ) ) + if( wtiff_layer_rewind( wtiff, layer ) ) return( -1 ); if( layer->lname ) layer->tif = vips__tiff_openout( - layer->lname, write->bigtiff ); + layer->lname, wtiff->bigtiff ); else { - layer->tif = vips__tiff_openout_buffer( write->im, - write->bigtiff, &layer->buf, &layer->len ); + layer->tif = vips__tiff_openout_buffer( wtiff->im, + wtiff->bigtiff, &layer->buf, &layer->len ); } if( !layer->tif ) return( -1 ); - if( write_tiff_header( write, layer ) ) + if( wtiff_write_header( wtiff, layer ) ) return( -1 ); } @@ -782,21 +773,21 @@ pyramid_fill( Write *write ) /* Delete any temp files we wrote. */ static void -write_delete_temps( Write *write ) +wtiff_delete_temps( Wtiff *wtiff ) { Layer *layer; /* Don't delete the top layer: that's the output file. */ - if( write->layer && - write->layer->below ) - for( layer = write->layer->below; layer; layer = layer->below ) + if( wtiff->layer && + wtiff->layer->below ) + for( layer = wtiff->layer->below; layer; layer = layer->below ) if( layer->lname ) { #ifndef DEBUG unlink( layer->lname ); VIPS_FREE( layer->buf ); #else - printf( "write_delete_temps: leaving %s\n", + printf( "wtiff_delete_temps: leaving %s\n", layer->lname ); #endif /*DEBUG*/ @@ -818,24 +809,22 @@ layer_free( Layer *layer ) /* Free an entire pyramid. */ static void -pyramid_free( Layer *layer ) +layer_free_all( Layer *layer ) { if( layer->below ) - pyramid_free( layer->below ); + layer_free_all( layer->below ); layer_free( layer ); } -/* Free a Write. - */ static void -write_free( Write *write ) +wtiff_free( Wtiff *wtiff ) { - write_delete_temps( write ); + wtiff_delete_temps( wtiff ); - VIPS_FREEF( vips_free, write->tbuf ); - VIPS_FREEF( pyramid_free, write->layer ); - VIPS_FREEF( vips_free, write->icc_profile ); + VIPS_FREEF( vips_free, wtiff->tbuf ); + VIPS_FREEF( layer_free_all, wtiff->layer ); + VIPS_FREEF( vips_free, wtiff->icc_profile ); } static int @@ -882,10 +871,8 @@ get_resunit( VipsForeignTiffResunit resunit ) return( -1 ); } -/* Make and init a Write. - */ -static Write * -write_new( VipsImage *im, const char *filename, +static Wtiff * +wtiff_new( VipsImage *im, const char *filename, VipsForeignTiffCompression compression, int Q, VipsForeignTiffPredictor predictor, char *profile, @@ -899,81 +886,81 @@ write_new( VipsImage *im, const char *filename, gboolean properties, gboolean strip ) { - Write *write; + Wtiff *wtiff; - if( !(write = VIPS_NEW( im, Write )) ) + if( !(wtiff = VIPS_NEW( im, Wtiff )) ) return( NULL ); - write->im = im; - write->filename = filename ? + wtiff->im = im; + wtiff->filename = filename ? vips_strdup( VIPS_OBJECT( im ), filename ) : NULL; - write->layer = NULL; - write->tbuf = NULL; - write->compression = get_compression( compression ); - write->jpqual = Q; - write->predictor = predictor; - write->tile = tile; - write->tilew = tile_width; - write->tileh = tile_height; - write->pyramid = pyramid; - write->onebit = squash; - write->miniswhite = miniswhite; - write->resunit = get_resunit( resunit ); - write->xres = xres; - write->yres = yres; - write->icc_profile = vips_strdup( NULL, profile ); - write->bigtiff = bigtiff; - write->rgbjpeg = rgbjpeg; - write->properties = properties; - write->strip = strip; - write->toilet_roll = FALSE; - write->page_height = -1; + wtiff->layer = NULL; + wtiff->tbuf = NULL; + wtiff->compression = get_compression( compression ); + wtiff->jpqual = Q; + wtiff->predictor = predictor; + wtiff->tile = tile; + wtiff->tilew = tile_width; + wtiff->tileh = tile_height; + wtiff->pyramid = pyramid; + wtiff->onebit = squash; + wtiff->miniswhite = miniswhite; + wtiff->resunit = get_resunit( resunit ); + wtiff->xres = xres; + wtiff->yres = yres; + wtiff->icc_profile = vips_strdup( NULL, profile ); + wtiff->bigtiff = bigtiff; + wtiff->rgbjpeg = rgbjpeg; + wtiff->properties = properties; + wtiff->strip = strip; + wtiff->toilet_roll = FALSE; + wtiff->page_height = -1; /* Updated below if we discover toilet roll mode. */ - write->image_height = im->Ysize; + wtiff->image_height = im->Ysize; /* Check for a toilet roll image. */ if( vips_image_get_typeof( im, "page-height" ) && - vips_image_get_int( im, "page-height", &write->page_height ) ) { - write_free( write ); + vips_image_get_int( im, "page-height", &wtiff->page_height ) ) { + wtiff_free( wtiff ); return( NULL ); } /* If page_height <= Ysize, treat as a single-page image. */ - if( write->page_height > 0 && - write->page_height < im->Ysize ) { + if( wtiff->page_height > 0 && + wtiff->page_height < im->Ysize ) { #ifdef DEBUG - printf( "write_new: detected toilet roll image, " + printf( "wtiff_new: detected toilet roll image, " "page-height=%d\n", - write->page_height ); + wtiff->page_height ); #endif/*DEBUG*/ - write->toilet_roll = TRUE; - write->image_height = write->page_height; + wtiff->toilet_roll = TRUE; + wtiff->image_height = wtiff->page_height; - if( im->Ysize % write->page_height != 0 ) { + if( im->Ysize % wtiff->page_height != 0 ) { vips_error( "vips2tiff", _( "image height %d is not a factor of " "page-height %d" ), - im->Ysize, write->page_height ); - write_free( write ); + im->Ysize, wtiff->page_height ); + wtiff_free( wtiff ); return( NULL ); } #ifdef DEBUG - printf( "write_new: pages=%d\n", - im->Ysize / write->page_height ); + printf( "wtiff_new: pages=%d\n", + im->Ysize / wtiff->page_height ); #endif/*DEBUG*/ /* We can't pyramid toilet roll images. */ - if( write->pyramid ) { + if( wtiff->pyramid ) { vips_warn( "vips2tiff", "%s", _( "can't pyramid multi page images --- " "disabling pyramid" ) ); - write->pyramid = FALSE; + wtiff->pyramid = FALSE; } } @@ -981,8 +968,8 @@ write_new( VipsImage *im, const char *filename, * have the multiple-of-16 restriction. */ if( tile ) { - if( (write->tilew & 0xf) != 0 || - (write->tileh & 0xf) != 0 ) { + if( (wtiff->tilew & 0xf) != 0 || + (wtiff->tileh & 0xf) != 0 ) { vips_error( "vips2tiff", "%s", _( "tile size not a multiple of 16" ) ); return( NULL ); @@ -991,7 +978,7 @@ write_new( VipsImage *im, const char *filename, /* We can only pyramid LABQ and non-complex images. */ - if( write->pyramid ) { + if( wtiff->pyramid ) { if( im->Coding == VIPS_CODING_NONE && vips_band_format_iscomplex( im->BandFmt ) ) { vips_error( "vips2tiff", @@ -1003,68 +990,68 @@ write_new( VipsImage *im, const char *filename, /* Only 1-bit-ize 8 bit mono images. */ - if( write->onebit && + if( wtiff->onebit && (im->Coding != VIPS_CODING_NONE || im->BandFmt != VIPS_FORMAT_UCHAR || im->Bands != 1) ) { vips_warn( "vips2tiff", "%s", _( "can only squash 1 band uchar images -- " "disabling squash" ) ); - write->onebit = 0; + wtiff->onebit = 0; } - if( write->onebit && - write->compression == COMPRESSION_JPEG ) { + if( wtiff->onebit && + wtiff->compression == COMPRESSION_JPEG ) { vips_warn( "vips2tiff", "%s", _( "can't have 1-bit JPEG -- disabling JPEG" ) ); - write->compression = COMPRESSION_NONE; + wtiff->compression = COMPRESSION_NONE; } /* We can only MINISWHITE non-complex images of 1 or 2 bands. */ - if( write->miniswhite && + if( wtiff->miniswhite && (im->Coding != VIPS_CODING_NONE || vips_band_format_iscomplex( im->BandFmt ) || im->Bands > 2) ) { vips_warn( "vips2tiff", "%s", _( "can only save non-complex greyscale images " "as miniswhite -- disabling miniswhite" ) ); - write->miniswhite = FALSE; + wtiff->miniswhite = FALSE; } /* Sizeof a line of bytes in the TIFF tile. */ if( im->Coding == VIPS_CODING_LABQ ) - write->tls = write->tilew * 3; - else if( write->onebit ) - write->tls = VIPS_ROUND_UP( write->tilew, 8 ) / 8; + wtiff->tls = wtiff->tilew * 3; + else if( wtiff->onebit ) + wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 8 ) / 8; else - write->tls = VIPS_IMAGE_SIZEOF_PEL( im ) * write->tilew; + wtiff->tls = VIPS_IMAGE_SIZEOF_PEL( im ) * wtiff->tilew; /* Build the pyramid framework. */ - write->layer = pyramid_new( write, NULL, - im->Xsize, write->image_height ); + wtiff->layer = wtiff_layer_new( wtiff, NULL, + im->Xsize, wtiff->image_height ); /* Fill all the layers. */ - if( pyramid_fill( write ) ) { - write_free( write ); + if( wtiff_allocate_layers( wtiff ) ) { + wtiff_free( wtiff ); return( NULL ); } if( tile ) - write->tbuf = vips_malloc( NULL, - TIFFTileSize( write->layer->tif ) ); + wtiff->tbuf = vips_malloc( NULL, + TIFFTileSize( wtiff->layer->tif ) ); else - write->tbuf = vips_malloc( NULL, - TIFFScanlineSize( write->layer->tif ) ); - if( !write->tbuf ) { - write_free( write ); + wtiff->tbuf = vips_malloc( NULL, + TIFFScanlineSize( wtiff->layer->tif ) ); + if( !wtiff->tbuf ) { + wtiff_free( wtiff ); return( NULL ); } - return( write ); + return( wtiff ); } /* Convert VIPS LabQ to TIFF LAB. Just take the first three bands. @@ -1089,14 +1076,14 @@ LabQ2LabC( VipsPel *q, VipsPel *p, int n ) /* Pack 8 bit VIPS to 1 bit TIFF. */ static void -eightbit2onebit( Write *write, VipsPel *q, VipsPel *p, int n ) +eightbit2onebit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) { int x; VipsPel bits; /* Invert in miniswhite mode. */ - int white = write->miniswhite ? 0 : 1; + int white = wtiff->miniswhite ? 0 : 1; int black = white ^ 1; bits = 0; @@ -1146,10 +1133,10 @@ eightbit2onebit( Write *write, VipsPel *q, VipsPel *p, int n ) * the opposite conversion. */ static void -invert_band0( Write *write, VipsPel *q, VipsPel *p, int n ) +invert_band0( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) { - VipsImage *im = write->im; - gboolean invert = write->miniswhite; + VipsImage *im = wtiff->im; + gboolean invert = wtiff->miniswhite; int x, i; @@ -1212,7 +1199,7 @@ LabS2Lab16( VipsPel *q, VipsPel *p, int n ) /* Pack the pixels in @area from @in into a TIFF tile buffer. */ static void -pack2tiff( Write *write, Layer *layer, +wtiff_pack2tiff( Wtiff *wtiff, Layer *layer, VipsRegion *in, VipsRect *area, VipsPel *q ) { int y; @@ -1224,37 +1211,37 @@ pack2tiff( Write *write, Layer *layer, * Black out the tile first to make sure these edge pixels are always * zero. */ - if( write->compression == COMPRESSION_JPEG && - (area->width < write->tilew || - area->height < write->tileh) ) + if( wtiff->compression == COMPRESSION_JPEG && + (area->width < wtiff->tilew || + area->height < wtiff->tileh) ) memset( q, 0, TIFFTileSize( layer->tif ) ); for( y = area->top; y < VIPS_RECT_BOTTOM( area ); y++ ) { VipsPel *p = (VipsPel *) VIPS_REGION_ADDR( in, area->left, y ); - if( write->im->Coding == VIPS_CODING_LABQ ) + if( wtiff->im->Coding == VIPS_CODING_LABQ ) LabQ2LabC( q, p, area->width ); - else if( write->onebit ) - eightbit2onebit( write, q, p, area->width ); + else if( wtiff->onebit ) + eightbit2onebit( wtiff, q, p, area->width ); else if( (in->im->Bands == 1 || in->im->Bands == 2) && - write->miniswhite ) - invert_band0( write, q, p, area->width ); - else if( write->im->BandFmt == VIPS_FORMAT_SHORT && - write->im->Type == VIPS_INTERPRETATION_LABS ) + wtiff->miniswhite ) + invert_band0( wtiff, q, p, area->width ); + else if( wtiff->im->BandFmt == VIPS_FORMAT_SHORT && + wtiff->im->Type == VIPS_INTERPRETATION_LABS ) LabS2Lab16( q, p, area->width ); else memcpy( q, p, area->width * - VIPS_IMAGE_SIZEOF_PEL( write->im ) ); + VIPS_IMAGE_SIZEOF_PEL( wtiff->im ) ); - q += write->tls; + q += wtiff->tls; } } /* Write a set of tiles across the strip. */ static int -layer_write_tile( Write *write, Layer *layer, VipsRegion *strip ) +wtiff_layer_write_tile( Wtiff *wtiff, Layer *layer, VipsRegion *strip ) { VipsImage *im = layer->image; VipsRect *area = &strip->valid; @@ -1267,18 +1254,18 @@ layer_write_tile( Write *write, Layer *layer, VipsRegion *strip ) image.width = im->Xsize; image.height = im->Ysize; - for( x = 0; x < im->Xsize; x += write->tilew ) { + for( x = 0; x < im->Xsize; x += wtiff->tilew ) { VipsRect tile; tile.left = x; tile.top = area->top; - tile.width = write->tilew; - tile.height = write->tileh; + tile.width = wtiff->tilew; + tile.height = wtiff->tileh; vips_rect_intersectrect( &tile, &image, &tile ); /* Have to repack pixels. */ - pack2tiff( write, layer, strip, &tile, write->tbuf ); + wtiff_pack2tiff( wtiff, layer, strip, &tile, wtiff->tbuf ); #ifdef DEBUG_VERBOSE printf( "Writing %dx%d tile at position %dx%d to image %s\n", @@ -1286,7 +1273,7 @@ layer_write_tile( Write *write, Layer *layer, VipsRegion *strip ) TIFFFileName( layer->tif ) ); #endif /*DEBUG_VERBOSE*/ - if( TIFFWriteTile( layer->tif, write->tbuf, + if( TIFFWriteTile( layer->tif, wtiff->tbuf, tile.left, tile.top, 0, 0 ) < 0 ) { vips_error( "vips2tiff", "%s", _( "TIFF write tile failed" ) ); @@ -1300,11 +1287,11 @@ layer_write_tile( Write *write, Layer *layer, VipsRegion *strip ) /* Write tileh scanlines, less for the last strip. */ static int -layer_write_strip( Write *write, Layer *layer, VipsRegion *strip ) +wtiff_layer_write_strip( Wtiff *wtiff, Layer *layer, VipsRegion *strip ) { VipsImage *im = layer->image; VipsRect *area = &strip->valid; - int height = VIPS_MIN( write->tileh, area->height ); + int height = VIPS_MIN( wtiff->tileh, area->height ); int y; @@ -1319,22 +1306,22 @@ layer_write_strip( Write *write, Layer *layer, VipsRegion *strip ) /* Any repacking necessary. */ if( im->Coding == VIPS_CODING_LABQ ) { - LabQ2LabC( write->tbuf, p, im->Xsize ); - p = write->tbuf; + LabQ2LabC( wtiff->tbuf, p, im->Xsize ); + p = wtiff->tbuf; } else if( im->BandFmt == VIPS_FORMAT_SHORT && im->Type == VIPS_INTERPRETATION_LABS ) { - LabS2Lab16( write->tbuf, p, im->Xsize ); - p = write->tbuf; + LabS2Lab16( wtiff->tbuf, p, im->Xsize ); + p = wtiff->tbuf; } - else if( write->onebit ) { - eightbit2onebit( write, write->tbuf, p, im->Xsize ); - p = write->tbuf; + else if( wtiff->onebit ) { + eightbit2onebit( wtiff, wtiff->tbuf, p, im->Xsize ); + p = wtiff->tbuf; } else if( (im->Bands == 1 || im->Bands == 2) && - write->miniswhite ) { - invert_band0( write, write->tbuf, p, im->Xsize ); - p = write->tbuf; + wtiff->miniswhite ) { + invert_band0( wtiff, wtiff->tbuf, p, im->Xsize ); + p = wtiff->tbuf; } if( TIFFWriteScanline( layer->tif, p, area->top + y, 0 ) < 0 ) @@ -1424,17 +1411,17 @@ layer_strip_shrink( Layer *layer ) static int layer_strip_arrived( Layer *layer ) { - Write *write = layer->write; + Wtiff *wtiff = layer->wtiff; int result; VipsRect new_strip; VipsRect overlap; VipsRect image_area; - if( write->tile ) - result = layer_write_tile( write, layer, layer->strip ); + if( wtiff->tile ) + result = wtiff_layer_write_tile( wtiff, layer, layer->strip ); else - result = layer_write_strip( write, layer, layer->strip ); + result = wtiff_layer_write_strip( wtiff, layer, layer->strip ); if( result ) return( -1 ); @@ -1447,11 +1434,11 @@ layer_strip_arrived( Layer *layer ) * Expand the strip if necessary to make sure we have an even * number of lines. */ - layer->y += write->tileh; + layer->y += wtiff->tileh; new_strip.left = 0; new_strip.top = layer->y; new_strip.width = layer->image->Xsize; - new_strip.height = write->tileh; + new_strip.height = wtiff->tileh; image_area.left = 0; image_area.top = 0; @@ -1493,8 +1480,8 @@ layer_strip_arrived( Layer *layer ) static int write_strip( VipsRegion *region, VipsRect *area, void *a ) { - Write *write = (Write *) a; - Layer *layer = write->layer; + Wtiff *wtiff = (Wtiff *) a; + Layer *layer = wtiff->layer; #ifdef DEBUG printf( "write_strip: strip at %d, height %d\n", @@ -1556,7 +1543,7 @@ write_strip( VipsRegion *region, VipsRect *area, void *a ) * we might have set. */ static int -write_copy_tiff( Write *write, TIFF *out, TIFF *in ) +wtiff_copy_tiff( Wtiff *wtiff, TIFF *out, TIFF *in ) { uint32 i32; uint16 i16; @@ -1584,23 +1571,23 @@ write_copy_tiff( Write *write, TIFF *out, TIFF *in ) CopyField( TIFFTAG_ROWSPERSTRIP, i32 ); CopyField( TIFFTAG_SUBFILETYPE, i32 ); - if( write->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE ) - TIFFSetField( out, TIFFTAG_PREDICTOR, write->predictor ); + if( wtiff->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE ) + TIFFSetField( out, TIFFTAG_PREDICTOR, wtiff->predictor ); /* TIFFTAG_JPEGQUALITY is a pesudo-tag, so we can't copy it. - * Set explicitly from Write. + * Set explicitly from Wtiff. */ - if( write->compression == COMPRESSION_JPEG ) { - TIFFSetField( out, TIFFTAG_JPEGQUALITY, write->jpqual ); + if( wtiff->compression == COMPRESSION_JPEG ) { + TIFFSetField( out, TIFFTAG_JPEGQUALITY, wtiff->jpqual ); /* Only for three-band, 8-bit images. */ - if( write->im->Bands == 3 && - write->im->BandFmt == VIPS_FORMAT_UCHAR ) { + if( wtiff->im->Bands == 3 && + wtiff->im->BandFmt == VIPS_FORMAT_UCHAR ) { /* Enable rgb->ycbcr conversion in the jpeg write. */ - if( !write->rgbjpeg && - write->jpqual < 90 ) + if( !wtiff->rgbjpeg && + wtiff->jpqual < 90 ) TIFFSetField( out, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB ); @@ -1614,14 +1601,14 @@ write_copy_tiff( Write *write, TIFF *out, TIFF *in ) } } - /* We can't copy profiles or xmp :( Set again from Write. + /* We can't copy profiles or xmp :( Set again from Wtiff. */ - if( !write->strip ) - if( write_embed_profile( write, out ) || - write_embed_xmp( write, out ) || - write_embed_ipct( write, out ) || - write_embed_photoshop( write, out ) || - write_embed_imagedescription( write, out ) ) + if( !wtiff->strip ) + if( wtiff_embed_profile( wtiff, out ) || + wtiff_embed_xmp( wtiff, out ) || + wtiff_embed_ipct( wtiff, out ) || + wtiff_embed_photoshop( wtiff, out ) || + wtiff_embed_imagedescription( wtiff, out ) ) return( -1 ); buf = vips_malloc( NULL, TIFFTileSize( in ) ); @@ -1629,7 +1616,7 @@ write_copy_tiff( Write *write, TIFF *out, TIFF *in ) for( tile = 0; tile < n; tile++ ) { tsize_t len; - /* It'd be good to use TIFFReadRawTile()/TIFFWriteRawTile() + /* It'd be good to use TIFFReadRawTile()/TIFFWtiffRawTile() * here to save compression/decompression, but sadly it seems * not to work :-( investigate at some point. */ @@ -1648,13 +1635,13 @@ write_copy_tiff( Write *write, TIFF *out, TIFF *in ) /* Append all of the lower layers we wrote to the output. */ static int -write_gather( Write *write ) +wtiff_gather( Wtiff *wtiff ) { Layer *layer; - if( write->layer && - write->layer->below ) - for( layer = write->layer->below; layer; + if( wtiff->layer && + wtiff->layer->below ) + for( layer = wtiff->layer->below; layer; layer = layer->below ) { TIFF *in; @@ -1664,13 +1651,13 @@ write_gather( Write *write ) if( !(in = vips__tiff_openin( layer->lname )) ) return( -1 ); - if( write_copy_tiff( write, write->layer->tif, in ) ) { + if( wtiff_copy_tiff( wtiff, wtiff->layer->tif, in ) ) { TIFFClose( in ); return( -1 ); } TIFFClose( in ); - if( !TIFFWriteDirectory( write->layer->tif ) ) + if( !TIFFWriteDirectory( wtiff->layer->tif ) ) return( -1 ); } @@ -1680,75 +1667,72 @@ write_gather( Write *write ) /* Three types of write: single image, multipage and pyramid. */ static int -write_image( Write *write ) +wtiff_write_image( Wtiff *wtiff ) { - if( write->toilet_roll ) { - int n_pages = write->im->Ysize / write->page_height; - - int i; + if( wtiff->toilet_roll ) { + int y; #ifdef DEBUG - printf( "write_image: toilet-roll mode\n" ); + printf( "wtiff_write_image: toilet-roll mode\n" ); #endif /*DEBUG*/ - for( i = 0; i < n_pages; i++ ) { + y = 0; + for(;;) { VipsImage *page; - if( vips_crop( write->im, &page, - 0, i * write->page_height, - write->im->Xsize, write->page_height, + if( vips_crop( wtiff->im, &page, + 0, y, wtiff->im->Xsize, wtiff->page_height, NULL ) ) return( -1 ); - if( vips_sink_disc( page, write_strip, write ) ) { + if( vips_sink_disc( page, write_strip, wtiff ) ) { g_object_unref( page ); return( -1 ); } g_object_unref( page ); - /* If there will be another page after this one, we - * need to flush this directory and set the directory - * for the next write. - */ - if( i < n_pages - 1 ) { - if( !TIFFWriteDirectory( write->layer->tif ) || - layer_rewind( write, write->layer ) || - write_tiff_header( write, - write->layer ) ) - return( -1 ); - } + y += wtiff->page_height; + if( y >= wtiff->im->Ysize ) + break; + + if( !TIFFWriteDirectory( wtiff->layer->tif ) || + wtiff_layer_rewind( wtiff, + wtiff->layer ) || + wtiff_write_header( wtiff, + wtiff->layer ) ) + return( -1 ); } } - else if( write->pyramid ) { + else if( wtiff->pyramid ) { #ifdef DEBUG - printf( "write_image: pyramid mode\n" ); + printf( "wtiff_write_image: pyramid mode\n" ); #endif /*DEBUG*/ - if( vips_sink_disc( write->im, write_strip, write ) ) + if( vips_sink_disc( wtiff->im, write_strip, wtiff ) ) return( -1 ); - if( !TIFFWriteDirectory( write->layer->tif ) ) + if( !TIFFWriteDirectory( wtiff->layer->tif ) ) return( -1 ); - if( write->pyramid ) { + if( wtiff->pyramid ) { /* Free lower pyramid resources ... this will * TIFFClose() (but not delete) the smaller layers * ready for us to read from them again. */ - if( write->layer->below ) - pyramid_free( write->layer->below ); + if( wtiff->layer->below ) + layer_free_all( wtiff->layer->below ); /* Append smaller layers to the main file. */ - if( write_gather( write ) ) + if( wtiff_gather( wtiff ) ) return( -1 ); } } else { #ifdef DEBUG - printf( "write_image: single-image mode\n" ); + printf( "wtiff_write_image: single-image mode\n" ); #endif /*DEBUG*/ - if( vips_sink_disc( write->im, write_strip, write ) ) + if( vips_sink_disc( wtiff->im, write_strip, wtiff ) ) return( -1 ); } @@ -1769,7 +1753,7 @@ vips__tiff_write( VipsImage *in, const char *filename, gboolean rgbjpeg, gboolean properties, gboolean strip ) { - Write *write; + Wtiff *wtiff; #ifdef DEBUG printf( "tiff2vips: libtiff version is \"%s\"\n", TIFFGetVersion() ); @@ -1780,19 +1764,19 @@ vips__tiff_write( VipsImage *in, const char *filename, if( vips_check_coding_known( "vips2tiff", in ) ) return( -1 ); - if( !(write = write_new( in, filename, + if( !(wtiff = wtiff_new( in, filename, compression, Q, predictor, profile, tile, tile_width, tile_height, pyramid, squash, miniswhite, resunit, xres, yres, bigtiff, rgbjpeg, properties, strip )) ) return( -1 ); - if( write_image( write ) ) { - write_free( write ); + if( wtiff_write_image( wtiff ) ) { + wtiff_free( wtiff ); return( -1 ); } - write_free( write ); + wtiff_free( wtiff ); return( 0 ); } @@ -1812,41 +1796,41 @@ vips__tiff_write_buf( VipsImage *in, gboolean rgbjpeg, gboolean properties, gboolean strip ) { - Write *write; + Wtiff *wtiff; vips__tiff_init(); if( vips_check_coding_known( "vips2tiff", in ) ) return( -1 ); - if( !(write = write_new( in, NULL, + if( !(wtiff = wtiff_new( in, NULL, compression, Q, predictor, profile, tile, tile_width, tile_height, pyramid, squash, miniswhite, resunit, xres, yres, bigtiff, rgbjpeg, properties, strip )) ) return( -1 ); - write->obuf = obuf; - write->olen = olen; + wtiff->obuf = obuf; + wtiff->olen = olen; - if( write_image( write ) ) { - write_free( write ); + if( wtiff_write_image( wtiff ) ) { + wtiff_free( wtiff ); return( -1 ); } /* Now close the top layer, and we'll get a pointer we can return * to our caller. */ - VIPS_FREEF( TIFFClose, write->layer->tif ); + VIPS_FREEF( TIFFClose, wtiff->layer->tif ); - *obuf = write->layer->buf; - *olen = write->layer->len; + *obuf = wtiff->layer->buf; + *olen = wtiff->layer->len; /* Now our caller owns it, we must not free it. */ - write->layer->buf = NULL; + wtiff->layer->buf = NULL; - write_free( write ); + wtiff_free( wtiff ); return( 0 ); } From 25dd60c7817772abf673e01ee0939efbc7e9dc28 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 23 Nov 2016 17:20:04 +0000 Subject: [PATCH 27/46] add tests all done, I think --- ChangeLog | 3 +- TODO | 14 +++------ libvips/foreign/tiff2vips.c | 3 +- libvips/foreign/tiffload.c | 4 +-- libvips/foreign/tiffsave.c | 4 +++ libvips/foreign/vips2tiff.c | 7 +++-- libvips/include/vips/header.h | 9 ++++++ test/images/multi-channel-z-series.ome.tif | Bin 0 -> 1128003 bytes test/test_foreign.py | 34 +++++++++++++++++++++ 9 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 test/images/multi-channel-z-series.ome.tif diff --git a/ChangeLog b/ChangeLog index 68fbbcb3..dd81b5e7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,7 +10,8 @@ - all loaders have a @fail option, meaning fail on first warning, though it only does anything for jpg and csv - add vips_image_get_fields() to help bindings -- add multi-page read to tiffload +- add tiff multi-page read/write +- add VIPS_META_PAGE_HEIGHT metadata 11/11/16 started 8.4.4 - fix crash in vips.exe arg parsing on Windows, thanks Yury diff --git a/TODO b/TODO index 7715e425..f99c9889 100644 --- a/TODO +++ b/TODO @@ -1,16 +1,12 @@ -- add VIPS_META_PAGE_HEIGHT and probably some others too .. "orientation"? - -- in wtiff_layer_rewind() we size strip height by tileh, but is this correct - for strip images? - -- test data - - http://downloads.openmicroscopy.org/images/OME-TIFF/2016-06/bioformats-artificial/multi-channel-z-series.ome.tif - - all toilet roll loaders need to set "page-height" magick, pdf and tiff need to use the same page/n interface + + + + + - not sure about utf8 error messages on win - strange: diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index b6534836..0ec28d7f 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -1209,7 +1209,8 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out ) out->Ysize = rtiff->header.height * rtiff->n; if( rtiff->n > 1 ) - vips_image_set_int( out, "page-height", rtiff->header.height ); + vips_image_set_int( out, + VIPS_META_PAGE_HEIGHT, rtiff->header.height ); /* Even though we could end up serving tiled data, always hint * THINSTRIP, since we're quite happy doing that too, and it could need diff --git a/libvips/foreign/tiffload.c b/libvips/foreign/tiffload.c index ccbd9501..29fd58c4 100644 --- a/libvips/foreign/tiffload.c +++ b/libvips/foreign/tiffload.c @@ -328,8 +328,8 @@ vips_foreign_load_tiff_buffer_init( VipsForeignLoadTiffBuffer *buffer ) * * @n means load this many pages. By default a single page is read. All the * pages must have the same dimensions, and they are loaded as a tall, thin - * "toilet roll" image. The "page-height" metadata tag gives the height in - * pixels of each page. Use -1 to load all pages. + * "toilet roll" image. The #VIPS_META_PAGE_HEIGHT metadata + * tag gives the height in pixels of each page. Use -1 to load all pages. * * Setting @autorotate to %TRUE will make the loader interpret the * orientation tag and automatically rotate the image appropriately during diff --git a/libvips/foreign/tiffsave.c b/libvips/foreign/tiffsave.c index bfbc9f26..7e2db098 100644 --- a/libvips/foreign/tiffsave.c +++ b/libvips/foreign/tiffsave.c @@ -475,6 +475,10 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * * Write a VIPS image to a file as TIFF. * + * If @in has the #VIPS_META_PAGE_HEIGHT metadata item, this is assumed to be a + * "toilet roll" image. It will be + * written as series of pages, each #VIPS_META_PAGE_HEIGHT pixels high. + * * Use @compression to set the tiff compression. Currently jpeg, packbits, * fax4, lzw, none and deflate are supported. The default is no compression. * JPEG compression is a good lossy compressor for photographs, packbits is diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 2e7396c4..8cd9f744 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -198,9 +198,9 @@ */ /* - */ #define DEBUG_VERBOSE #define DEBUG + */ #ifdef HAVE_CONFIG_H #include @@ -921,8 +921,9 @@ wtiff_new( VipsImage *im, const char *filename, /* Check for a toilet roll image. */ - if( vips_image_get_typeof( im, "page-height" ) && - vips_image_get_int( im, "page-height", &wtiff->page_height ) ) { + if( vips_image_get_typeof( im, VIPS_META_PAGE_HEIGHT ) && + vips_image_get_int( im, + VIPS_META_PAGE_HEIGHT, &wtiff->page_height ) ) { wtiff_free( wtiff ); return( NULL ); } diff --git a/libvips/include/vips/header.h b/libvips/include/vips/header.h index 5affb207..1ae81011 100644 --- a/libvips/include/vips/header.h +++ b/libvips/include/vips/header.h @@ -133,6 +133,15 @@ extern "C" { */ #define VIPS_META_ORIENTATION "orientation" +/** + * VIPS_META_PAGE_HEIGHT: + * + * If set, the height of each page when this image was loaded. If you save an + * image with "page-height" set to a format that supports multiple pages, such + * as tiff, the image will be saved as a series of pages. + */ +#define VIPS_META_PAGE_HEIGHT "page-height" + guint64 vips_format_sizeof( VipsBandFormat format ); guint64 vips_format_sizeof_unsafe( VipsBandFormat format ); diff --git a/test/images/multi-channel-z-series.ome.tif b/test/images/multi-channel-z-series.ome.tif new file mode 100644 index 0000000000000000000000000000000000000000..1aa66b0fb368316aeb4f2fffdbab9682aaa2e2b3 GIT binary patch literal 1128003 zcmeEPb(|c>(XClJ`Gh8!WP#Zh*pe*sS!R}HW>OT{Qf$eNBqwp0nb~1>m=lMYnVHdH zrY|sGS9euS^-k~Y-bp+1>Z*R)=~u5_RrkEUy`LlfkrgZ85CE72GctgNLvG3L%reGs zM^=Nn=O?*JoiM6e3QvO?z_bs-jt4$6+tK&l;S^J|U6p30%*?XgSW|yZwkI^hzO7TU zy#%kwW~ttT2)mPHi*YZQ3DCMYyO4Nfvi&}H=K57PtT}6Bczd(p$Zr~1yli;&+Ep7iths5^$kO3WH?3Rk$YsOVZ(Ow=t8PZ?M(4#o06TmeVDeJ| zrk?Iz>wf?<-T|=FO#r*>3$W{V0CsyCz#eA+?3n}1dMCi_^#F7C1DN++fPJ41u>Y9= z2fBF&-3f5W27m?o11$U=z@g6oSbP@1QfJ`d?*cgDW`Ls(066;l0LMNPVB~Co~ zc;znu?sy@F9G=IRDh3t4B!(F0Ql4rfKUGl;Il6Q`23{+Uz`T;rH=!A<$(ZSTMF>? zUjuyer2yZ)4B)%d0lxPMfFC>v;75l8{P;HjKYba%&o2k~}v$UdZc)P0BNm~%k zlF%(+48z9LGluajt!NwGuBvqh3io;rxu;)a7%~!iQNX12P2qvOOppc!Bn)Yg6>TIU z4IZXo9BLDG=q~9}r{OKyM)H2N75m`nc4@IamP1DU?NUBcG>^1glaxmJ8G%$#Z@?Y6 zb=c9*SI{f4%psTnYm1bXdTmqr0}97NA!ruz2!&U|GRZEWS_>J|D#JWzD|VPcJuJoz z0)-4_6r@v3aZC}hI8nkzo)ISdOggwYOyR4D5x9rIP)CwOa2Ze+E7j_>G=WwmI&Z@@ zH^J)?=P7rGv&Uy3l{$%nY8PRpfzR7vs_6U)sa*yZRY5XOZZUGA^0Qo54CPx+nJgJzXR%jF#K^mCuF7}Wf0elj*sv*Lb^@`cNy7S+j<1(yMeg{5(RDU*)j4U;WWP6|7byr=GFuC$6sRmz-V z1UevxS8uFXhP2~AIsTBdQSLl*@L&>PAd zD^d%2G_A=}ne0AQdJ5 z!ZD*kRh}xi3~+?7JTJ{@SsQk#k1)VNnIu?%8t!}4!*^++j6s0Hh@gZ9NQrV;d?D`T zOf&>oimA*K5Rshl>Qo8nC<>n9($SzQ7xRP^fFn3@s}Ifb7D^VExk);Ud6Cf7WftS8 zSCL-G#Zs3eq8cYWUvdIGO$HSw>?94mYADZel6I0Q$1@Cr@Hvg53smK4g3ADhG3!Ww2U#Y(U_h|EtI{JB%V=T zSEMmuNm3b;obehDqRbWJEu)+hUR_fHDyo>uyQ!i;0eYU00&ql>BxVw&WsG@^#`H`& zVQfq^hEGCdS*Zo8vxlj?Z&E5_S;d6TrBjg!D6;7@C>%2yjLReyQUH#KlEh4+w2U#Y zVM_ZhdbUy)8)~xi#(E5^QY%m6Rcm5SG67o4xKvbxKmn1D!V1`t!V#mwqV5n$%K%3R z%kzko`GMCm#=QC=rCs#wLa8=%WZNdHs7{lRsVYNOb)U*geD%@+c%1kmnnE2RZV;V% zi9$%Klt}62MA*n@6Cg*zVFk+vAXYBh^I9g@0k6Wfyo;JG)o8;+lNQo>5)@VRutL>! z>S$7_l|kfb`E1glURzprqHvh!GTUR*t=2-0xHrA1NocZJy1Rm1|?!==(CAdr-5 zR+~LUd>iRf(JMnd*CyrH7Jbwk#8NA~ zjiyE|e&Eg)=72d+kpsI}459^8b(*-V9-8pa;Y8S2@CEAcc_-TRImyWLIdkyi z0xuz~#S;}=sTbKjpKt&E5U||lJ0~I-Npv53MaFB&L zU=CE|z`+*VqXT)4Z{;2YJPLmW=?e(47-D;L;BNU&hjfeF33C5HB?2FwqJk#_!DU;w zu>$T8KR-}h@MWoW9cY3rcVI!+$a9jo`xE2= zr{#onwrn#8N|&`@0bifKHs-e3IVaY}3%f*~FU(2epbuW9fUJ-~W6y2t0A3YeS2#&= zL42RcYaDd;?4tvVMvolJ{=rL58S<)Hu%i#OY6T9&7v?t%|GS!Q%qM}a;54>{IB=-N zAUbfEg*jjjROG;73v<96sK|jO7K7-(QVVmy9H_{F!^a+Z+|riDMawF5!2jh($Nk#w zsV(i;h6Oh`)N_Pm=^S{mL6tolIp6epq|py zj@;#IJUAsZZZrpuN<|(>zqm)&R0|8e9}=!Grx7%ZGhS0?FmB8akez5Yf7}dYeH!L* z1x2btJwa1`q7?PqQdSt#g9AsWA}12RDhO%C%;i*d*jF)C6-q-R&5;EMg$abDVxS@i z==R33+xR=jvDx<7a?H3R-v*uI-g0|uJa*iXkGsuhf&MmqDiK z*@2OC;D4XH3VymqQWW}KZ$3h^y3R4dmO9{X!ab5V(muR{i0H6%(6o=TyhjI)PesmT zeteD?zb}5JG%TLRDy!o)&{XW8i90Oo*@5M$$eGL?3V)V+NU2(=y4>j~9Q2XP(-l0n zM}Hj(1NhZ(Gv(tlzJZ8E6^csXzk9w$sH8Ar3miD9uaWn4`UD<+a$h6w>vS7#4h)t9 zr&yQ+=0HUboN8eXm;)6#aGJ#+I&iv$IbaS{;9LuHz#OQ^ft42KfH_c+1Ls+o1Lihz?v} zVGfuB6*+LBg*jjjROG-#7UqCCP>}-{TMVKDmspqs=0HUbTxwwsm;)6#aG8ZUU=CE| zz~vT$=)e^g=72d+kpov+m;>fOMGjnLVGfuB6*+LV#UMIxjfFX24pij8wHD@pIZ%-U z*I5jr127{4XaIm)vOBYkG2D^Wpzireu2LtAs+Pjjpyo8oA>04y*^a*V4u`*-?W#00 zWoDM`#+rlHXL~}^&(G%XknJUS$847BJ&3S7Nw(PUH50P=dt?_9kGt2-eCnC&SKYAY ztdZfhx81U42XhSwp#X=L%T;nizbZP>8prcEPDhd14{ZnYzq4PU=; z)q1SD+4B#DY{4%eTl7N64!a1lC6ggr`Vq*M-3Hl_he3AKFCjbTMUWkLF=WS2fo%Cl zAv^K@ke$33vQzGY?6em{cE%-;oi!D*vp)veiU&ZpatUPT{R*-RUIN)gmqK>QG{`Rf zIAoVU5V9+mLUz@!A-m?KkX?5fWcQd3+3HU~w&p>Q-EcT$YkvdTy(v?1!VW%5wZt-3bF@11hR)50og-;2ie140ofz3gzV8f zLH3wWL-x3bLiU6sA$#KQA$#&GA$#gokUf28$e!^T$e#5u$ewc)WY7HrWY2pQWG}cH zvKQ?F*^565*-IY|*~^cH>=l26>{aesUwsW^uiX{0*L@DMH#`EeHys1noBssaTVD;? z+pmS}o$hMx{5)juek5e?Jr=U}{TZ?kyauulT?g4mc7yDrUx4i6kAm!z$3ga~zd-hx z*FyHWRgisQcgVi@MaaJVXvn@g0@>I83fVVa2idpo0oixdhP@8@d z)Moq(YCF9dYP;M3wVAV_w%gaCw#O5pw%5r}oArNCoBbB3&0Pz%eda)I-)}%||0hB1 zz*C@h(7&N}$XlVdU>(#J&4t>b--O!YCqr%NsZcxoKTtd3ZBRSvUQjz`9@LKg7Su+b z0=4C*LG6V9LhYotL+z9sp?2CnP&@tG;7qlTd2sjq8F0UTp?()Wab3CM)VU|EyLsVh zH*Z|OYV)SK%N8wJv?LVmIcN@K7K7+O&B7co2P$%)Zeb3X0~I;Yuoy%Knidny0r!7L zn0VzhTN3stEA@?$sA8Kc_Go+U))s_2Zs-<(X_R!H-lMGZfZi%qY*WP^y}fNuDmktI zY>Sf4b9$6@9?)B*ifyXcqwQ^nwjkVbL$`o23>#0+7{;@-qHTD)L0H8S0b@AzczVV# zo~0FS!`oH0PTGQSmV|BrV;DA`o-vGPX+_)cc2%uAP`KA~$UXfU!;q2CivlLCZwe3O zWr8#?AYn*@tY{+R~Z%5GZ6YqadAPierk1#fcI&@{BOqXVSsNVG3VGjKDnvhB}fQg3Exq zSgBU0r3thm(RmxLxd~pEI8V7doIO4RsnkgnRJ#Z(4Se1XQ$^=bNbNGPs0xyKdaLAn zp}``2;h^bRSglg ztXGV{xKqGZ8s#4|@BhUdkyn18BGUQcl!BCY1I>3Wt7@CSI4N*i2Dy}eDNC7x3N@#k5 z)H01r9l9u3fZkBvSdm)DqiIc+%4GMc(vy%%t!;Df6W z2rdI0AuP{Jb6Tb`cL^sz&q1u<#}mN%i6F@eS`rH$|S)8)NtRU9==NpWefro zMg%1^KuVO$;tO#vXQCm%QcPu@fQaOTSEouqM^W$;myQNixtJ%U035-ITYYGbw@|XU z%uUi+%!`DsF0&X%y^8cgE|$6+5!E>1`H~ahX)>rdVJB(eRYQ4(leCjeIi6t{gwJUd zU7#vY6I=#3j9It(&>U^nPB~quU;}9c32+6$0u-q^8d7w)q7N>(gdtA#K?J8k;4%>k zh)K?P6y0xK&<7pa4BPMQVQ%93ZtqdYh%V(1Y_1e<16NM9Xg-(Mq{P+-nlnie4Gw zxi%@kw&>ZF#eV*vS`w29!sVvcG)K+yM4>v~UyQR3m^geuD@x?Nm z?lE?p4$STl`Rv*54@HhyNQLw{kzUp=&?FDCwZ9IyJ5X1GcNNltL{wxb2q(hEf-g{i z&pXkk&q+p}&zXZC7kCL_EuM(5o_j!#kOVdLqQ;^h4$MtOo;%h*d^I3^x=Rtg#)Z7l zqOed)n}NPL;N4c12Z;Bit}ViH%h9!Ib>9vis_N#G1cPr^s_x!OuP(d0Bx1>C3)CQdCQaQj^A( zzB>@E&Hu9Wdm$<|4Okd2qfB&CE!qriB?tDk7(@s5voHtDfr=d1-@+U)2P$&l01I=# z9H_{F11$#8frBi}0dt@t2M)H_9v#SYd@J`L;8FN1NMAsR#Sq)019!`JI;30VPLTTt zDiQeb6cs!f2rk>YjU8~;QCv~`t*Ns~NpS}I<-q)|;eP!EO+KH%{P}_6f-g(0>p&B1 zxdRKjMxK+z-Jc*2I4vinvt^q(P`a!I3;6o7tG1;gO99k+eIAyS#<=l1 zz&C+k4xABIe2VXAzobP)>pSdDBY2MvDx*hr;D}V@fy~{?AIBi;)3&OrU;~Lz?ZV%v zdxXq0$c92e8XB7eN2UV3c07LY;aAcXX?v9_EbwJ&(hIlY=uR4~H{7!WWG6e!?)qLWUm!~^u+FC3vJ8)Dg@<95)``_SSgpxFa8Tr!|@b6Vrj?EVe%f z@?Ku4Cs8khOw+RiBk92ZK6e%Tbd97a^t;}Cgl2V}V}dPpz~6*>ByXgBcn1;DVdaZs0x5lX&gNX(!^$6O)l6>mIoB!fU}| zH3$NTpT5dXB#jl8ZEXketK(+M$7Os25sNAmmBN4be2q{^VZ;_Va8h3*@9XplJpAOo zM&8%yHryN-EC)`pFbB+miX1rA!W=LMDstd7i$QeYbPIF99H_{FGc3#jbD$yz&a@ar z2hOrE2h4$r95~y;954qea^M^bbHE&^$bl6WgXqAy7UqCCP>};GEzALPpdts(voHtD zfr=bB-(nCQxWK|3Fb67f;6e*?z#OQ^fr~860dt@t2QIc4L_Xyk z_xgs5&RoChhBar646nWImNgqUZC85q79l31y`i-mBV-?)Uzd-H$*Fx>WRZzQlcc@+RMW|i&XsBH=0<|mu3bm_W2eoVO z0ku_oKYPUQVY8#y;oBj^9Ti*b+ z``#03x9tVB`+o&$4}2Wd9()4S9`X;UJ?xE8d&C;3J!%%z9{p9QJ@)ZXd;Ez|d%{1V z_M|sK?J3to?P+^M?de~G+B2U3wP&9Mwdec`YPY``YR|s`YA>7(wHJLIYA<;r)LwQn z)L#C7Pr$X&3r$gd@c^1^Codfk5?gw_5Gd$^#jg@`aw;oAN)S3 z&))>~g@-_W(T|~i*mI%2WF^!OcR%RM-VgO7H$(mC`A|RRCs04`cBmhJ9@I}5g8GRc zfcnX|LjBYQP(SUbP(R~&P(SN@sGlMK47^_BO5`uPi?e!|8 z?)fv|e*HrIE_~v;a>c20Pg-~L!qaZvxPH~>tmTC!+KDB5$-9LOvN(Se$UIbaS{ zL@CYl58|Bf*6%4xPF>`_+g8zWK0HdXA=_S&s22zT7jEdbLf z={&thS?2-0RjSygiamOJ+n!W%Tmje?C7tK=DC<0+w@MY;RIx|f+YW6(xZ{Ry0b>|8 zo}Mv`XK6*-@OFc+iX{TZaO&~&jA1-WE82#)t7@IJ1>r0S-2%oiY&<<<7|+s*w&Crn zT6dsuuji0^`Zb0jBcT@sOj_R*9>~iCX<$IYkOo=NMk3PSVG72fHerYEk}h=`-lA;kH_kTIi*n6cLLPC2Zsw zVY1JpgNwryzKR%udk745Bsm0^0d=ubtxii5Xhov)He7QPye@H`a(6g;dB&YzIlWnfViB=huE$@fBmpH%XvI7|D6z9J42KvM4(u#f_fj}mTjWHG># ziYaP^MsX3Of$8pI59txWCsC^!B5YZ&7=dx8rg;!^yfUxWVn`=Mb`&!NO{T!kBn)Ix zojh4^8K78L8t0cX=@{NH*&^knuoKC9>Tc#rtB6#k%qd2o19Euv#)@UgtJ;F0DhYIe z2gfip6;&Fdh!j*@VX}|{a9EVk^aQD88kahBQLq5Lp}esowU9^Cnk<#c?o*{FA(dL& z=H4g1A+#r8Uez0DC>uU}}WO13Bq_db830+-gF^+l_>4jV@bvYucal-Q@C&1HW zP;tUe(!i^R@(d?wCz*0Q!!QV+(|ykGH!9Jyox{pdUlG`9PbisBV_uJaoJ7tXX#-oOL$d-2)YVJRETm;cy-DI zEU4)eju{QA@^ryvfFp$Ed1+3|81ov9>6z3***i(%8Rd0F8UvOjl`+W~ukj$tTru7< z$~ocHH6@^;imAMtDhd>!=LsnQM?^_tCQ(|(nAd1b&!iK^#zbTIBt({#TA(_6n9BPm zr81UPOz2!X6`6n{n?8fWF{8n_Oj02Q;D{(m%p^+781ovYwC|#4D`l~vCOdDe$FM53 z@-$wxCgvm)ptX!kMMVe{5cw#qfE_6uF)A$T4w1ABaD=ctk4Tvxcr9bhs~=L@Mb9pj zYC}i1ZK8_mGzpoiGGtZvsjS3TFCBo#i7%ol)Dhwa(W#dxgrrJ|lwMARjeIr%awHs9 zuzUbw<+44mWr7{>DqPFEsM%7DHcT{WA)O~dQAH0cR9&ZzCY4$lM4pzOdZDjLdgSae?W5_`2eYWj5Vo>^L2m-68VXv)vzx9J7!L>2o5ztX-f<9%O5O z9dLJ`t_1HYqy>qn$WRbYgpCDXp#GkBqD`NZj69z+2R|}xTI z4(w-P4wwTKIk3NlIbaS{459-ES(pRnKt&E5Y_UB$kmvYT?m@t# z@K=z&fDnrzwnqoLhQucRx|_9|6a z;LFscmtyjf9_pImEpmV?_X}Tx8;G}Aq`ImqD4;ZbhGIhW^Nj&h8iAA&C^qb9&n_oHq6Z{To7|j*B2A>8M!ek&%I)+J|k=-0PIu$vQ z_*FqjD`qaIs>8mDsj5&K8fgwMILFt+NE*adTdQzj{HBk;vx@f`h&_%OcjVikbKF~Q zZ;i)}JMwXNTBA8IF&#M0V*7I-@8y+x67@33G(9^ok`DYYbyvYp*GP&&zw6CMXja!b zCfHI3{7tw=@UxkKU4at|q03ssjp9fgBFa(TLf$M)#21L86QPTWeVT9Cpt5xM8+85Ymj zV=FjtLMn155%>>&lL!`z|9XX#8`SED_y-M9HG}WycR50gCKzT>8spC(pX{H)^-5DI&P+XT*fyLv8X~(Dg1ZO z*9esqMr?rtC-pV*zD}RO!%yyOh7(@q7 zw=f6Hfr=bB!@?Xe2P$&lOp8Hu;4BMsz#OQ^fwL{l0dt@t2hOoD2h4$r99Us7hz^`< zVGfuB6*;id!W=LMDstdF3v<96sK|lyEe6qn3oOh5bD$yzF0?QQ%z=s=xX8jBFb67f z;9`qGbl?&TbHE&^$bm~O%mH(tA_p$BFbB+miX6DyVh|m;!onOd2P$&lN(*zq9H_{F zt1Qd`bD$yzuC^FN2d=R&2h4$r9JtoP954qea^O0PL399SWB?5Sa7%V)mNAAqvKrJq zKgm_~<=L)EGgD?}*>0@4_usNTq3P$>H$N@gOYrU4 zEY*7uVRw>jvEOSZ)HlC0yO4O?z446Q&s@LihBar646nWImNgqUZC85q79l31y`i-mBV-?)UJE4A`4N$+|{!qW|dr*JC zGob#Uv!MQv4C)Vk7t|kqGt?h>0MsA#eW*X?nNWY+*-(FiGw6x$hWe9lf%;Pqg!Tfs~>Tha7{mt)#`dc?a{q2W9{T)As`kl{(`ny*`{k_ik_q`wLAJ`1_ z56y@AhkpX~kKPXTkDmwiPYyx-Qy+l(XKsc1=N3Ty^FM|97oP|9FP{(fuTFyc*FFgK zZ`=p!-&zRuZ~qMH-+eyRzkdPLf4BqGfAk@!|Kz?<|Jfp_|NQ4r|K$sy{wrtjZ-$}% z+Ydwi_xFSP9}k84pMC-Lzq}CYf4d0k|CkK*e|`k&|92bI|8p4B|NBd*=P!arb}=;S zQ=rlKC^TC4hsLDE(AeQFXiR=FG^Snxjp&<*y#b#*kuVccKsDJc6$jl_P7)p zdrgDJtdB!u_5-0YcPTXH{Tdqkz7!h!Uj~f>r$ghQPe9|42SH=O;m}z48)zK*GH5Km z92!e!K;!UFLgR=BL*uAr&^Y?H&^Y$x&=|P_8q3`e#wkZY^QO7W z7A;w{BoysAXbxl+gXlob!W=LMDsrH1VGfuB6*tDAdd4uGr4?<%+f}tr z+JbPFgl++27&e}sF^p$vMceRpRjoTvxYu*YJ^dQPkde@f0w%3*3J>IEf;2E7VMv3l zXd@A6@Gu4AP@AwrcS)Bz4R6sllJ}#n*auIyON;HX95U)}m-3OKd8FluYD7+GuNp=C%TF97I8RkJ-vBM1N zVKHtHC}c3BAe~~0V~U8yi4r#Qj4;_}(!s@H3SUKxz&!+pI+7fM%YeFAsaB_@3A7^7 zc^j^|30{{tPq{msJw5}e)JYUny9g@{eBKUIMdwdQ?J}^a3X*wxtK@s3z)vdqQ=FxJ zLthbx2_UI=3s^`2$VUk`IkFhwNyQYkLZi3{(!g|gv4`{s;FG9T4H34iSB${8Q`0<% zIbNApYcZq~B0Gv1f+ka7XA%aos7{_NxC~G%ERFL^nREu+qHS}@DE04$3afw~1;)VXfDqce;*O}#Y z%9N=Tju{QA@>IcPfFp$Ed1+3|+OSJ~gaHo9B*6mIaNnaIzDo;b3<4BJ1SK>;N|eju z3vn-Jq9MRiOl6*ch~$Jaa0S5v6sb8HQgpbY4=%WbAx`x{1gAmZG7$=hNzQl`Spbd> z5%Z2K2u^r)l>}52wJIhB6<5p?QUDH%lJaz7A{n>1RbEA)06jZJYL0h_wh=OY$hhpL z`Lpyel_k8YK?Gd|BPv8WC%igk0v6PC3df8FRe8GLGQbhS^1L*sWsG@^#`H{Tq3oR` z@r?4iB8>q{lFFFmjMsP&Wv&=+8ReYt>Y5TzQN>i=O%(+S(DQ^8fFq(LF_S1QW6Wzb zrf1R#V`HK*d=etdN-a>GJxt|&lTsPWDkgL;or+9AkxidL;h522TqdcI0&ql>BxVw& zWsG?ZQ`&dYvz4;gP?McE)?-+eT6r3;S`%}U3D8=`rJ^DP3W$6ZR=|!Fju;gdb%#h= z1~@`ko=2q254@H!=G6}=?V@KFO0}UQ+cr@}b((}sRT;9X`&3rqtCtSI}<>TMVKD zdsvtQ=0HUb>}g>Rm;)6#u$RRkIxx#(`*Ohj+Z?`-|A)NrW(YgIqplIg1*21o@#A-3 z?~dUNcQyR%9gt6bp6J2*2{f;%EYWDxR&^i`H%8{WrMSTKK73vA#WI`jF?O5|%pFHnEaJJF`kNk*Q} znS&n}cnM)Go`|rXdq9tn1U2=d#-bk%%uPj}JJvsZH6VPtOA)=sg}l(Buuw~zfxbE5 z-By+di1(zfEy8lk(Y0xH-wrz=2hU4{yPptT%Nfio&^#v*+5yVKqMur1xZe(_ODSN1 z@S0TSt}PrWa3P86{XXx+n!it{$a7c0&psY`S%CuS%f3QVR7;0alg5_5I}onT|FZOZ zAu2WvSQsy(OmtE$+6-+a2lllXLFbB+miX7PA!W=LMDstce3v<96sK|i>Ee6qn zgDlJebD$yz4z}1H9msQhEB7GaQTQuJUqFb(5Zj{zcguG=q+8@pkoyNJ5%};F6+9UT zF59||9dOrCTv7Y2sk2E*aR&S4!2GV^e*FbaKA*t+`GMksFH5cKKoe}a0}HxFo|DAg zpCAu7EhnV2Wt%xrx~v5Y`1Bi+uJ_&RMr?D-> zfkQ0@(SgG(%mH(tA_o>*m;>fOMGh>n7(@q_T9^aoKt&E5KK97tmbNr5T2`S0{x3f| z?$>rtZE43gD&#?*AC8-J6I;E51Itp8r%2)2s%@#rI)FUr^JQb~>oCsf!U1*^zYxfV z6`z<=)fvxeQPKKhoZ~w|14pMKClbFK2x-O4fQxyXri4+>wvF(;CfziRr*`7Tcc# zc`vWjlc<+Lrs>&%k#yjH^1BLtx<*nI`dx26LbJNgF~OEP;BUe`k~h*myn~47uyoM0 zkFvZ+2aZog&SZXkju^i$ex)=lp2jMx<2BG!?4XG|EbG~U<*CS-%pD4UmU~F4TBy3* z=_nlZk;~H+Jhn%F9T1lhaN<@<)q)hJiO4-i&#-vL9$UeI6H<{giNJsGn?$f!{MRd_ z+@MxJ#6M_=su}Eu18jDfOMGl;5VGfuB6*+L4#UMIxx`jDl4pij885ZV%IZ%-UXIc!R z17}&71Li}=YS(pRn zKt&FmZ!w4tTwq}im;)6#aG`}cU=CE|z(p43fH_c+0~cEiq63#$m;>fOMGjnQVGfuB z6*+L3g*jjjROG z`rbRB@yxxmU6p30%*?XgSo7?6XL~}^&u`p*akiJ>tFu|E_aMUVB-vuWmz#F`n(RX2 zarfq(-#c^tsvFjvH8Q;Rwp-S0+_ZVs>KjKkZ@hKQ@H*r-jVxX^yn5}b4I9?nv}t7N z@TQyAt#;(H;p;c9T8~v{!0iu!#`Bjz;|0Hh#*1D8jh9>sjh9V>#>+nrjaNPp8h0#( z#;bn~jn}>u8n3?$8gHBqjW>M)8gF?JG~RYNG~WIjXuR`f(0JG7(0I=bXuS86(0KoY zq4B|G(D=}Aq4ANIL*rvtK;sk6q)&bd8lQd$G(LL-G(Pt`Xnf%n(D>4o(D=$u(D>@7 zq4D*HLgSl9LgQP%hsJka361Yv1&tr<42>Ut1{yzp7&Lx*6f}PJ2Wb4_RnWNWYH0j= z7ij$Ev(Wh6!=dqqqoMJ~KSJZr?i&7j4K)6~D>VM`IcWUr5zzSeG0^zWpP=!-S39$> zg=TFgH0z&-X7iEI96A=7ll~0N;nzTO%5~73wi`63e*v01J_?#U9|z4{{sPUJuZ8CB ztDw2(?$F%pi_qNr(a@YT0?oO9h2}o5gXVtsfaU>vK=Z&aLG$3pKy&`_&|L60XfAp^ zG!I)1%_Z)_mwp+V%N`5OBbP(-sJ}z=m^VQ4xO+nLc=rRa{43Br@o~^R`2=X5@(*a9 z_C{!)u?CuF&4T9HUxnt1$3t`FiO@XnpU}MEP0+mPdT3sMhUV(8Lvzg&p?Sl}&|Ld}(7e}Mpn20;Xl|GT&6~dg&3iuynww66 z=H`Dx^FD8d=Ka<|^Zs+8`G9Xi^FdFB=0i?}=0pDj&4<4Ynvc8}G#@<=nveMwG#~dA zXg=XIXg=}3(0uaSq50Gsq51TEp!tk%L-Scrh30ckhvswt2hHcb1DY?m37Rk37n(2r z4m4l-G-$s33~0V0hvuu^3C&lphvsYdgXZhL3(Yq?9hz@C6Pj;vKhkf#6Pj<|0L^#q z?|$&_w2ygk_xu@fzkZ>97d~-ax#HBhC#}1A;b}K-T)%4brn$=&Em^cA6zw@^4rCUC z=s?ZF954qea-eQu4wwTKInb~eLj}v$UdZc)LMZ#S#HyIQ4jX#xS0x6>Y=YRkcpqf^e3EZUJK$HlCg_ zjAv;@+wgW(tvgV-*K^1{{TjoNkq~)5VG|JBiq=I?_?!c|Xj()y^UV&u} z!30=aq^#6yo5~+hI2H;)vyew9yb_j4b^+B|$e30c=0RJr!wl+SF>VkjWH6&3onne( ziipLD5;pRTFxh9)!Np+;Uqy_-Jp_h2k{p7|fVxGlc-e<5w@&XjKH{4(>#bdUYS>GF{BeBJBk^C zCR1Q%5(cuUPM$2d3{WgAjq^*HbPR8pY>{$O*oovlbvJXRRYa;%<`g5)0Xe*SW5qJ$ zRc*mgl>|D#gJT$)iYg6JL<%adFj+_eI4nwNdVp8kV>s>bMF)15ZV(kuj&mnlntg(IQ?i)m8S?U0~{eN&r5S!rZIO3CqU0hk(!fJ zL?qAltE7ljfVsW{;^^l`>3kH^(ndNoLl&KVs84arPRKaC{ zBZTF7X->=9uuFY}0S?L}!2;B9-=iMBOABQT0u)9BB{V=vl*{4^aW7}0A;3~hWuAbD zjPkl7jR8xN%9!Mg z*LVHicCO}O`k#Gn9*QdCaI7Da72_OW)h`kjCl=H+IP{jm9p4S zlbtu#V_211c^a=;6LXRY&|1c&q9OzehXA-7!?+Ehe%omI6_#SN2JUTyp}QM z)ekA{qGuOMwV@;1Hc>@&nuJVM8M3PTR951vmkz+=#23*N>IiXz=+sLTLQQ>N!P_hAS>DimJaW#ICL)7SJ9pl`a8+q*SxgjKCFq1kypN zDl8J=1cVWzb_WU0;Moc)3KbO0%9`ZcNSBIU8REG%DZjSpquwBvTH$RpHEQt#ceXGG z%z=s=*u`QH9oW^v954qea$u%~IbaS{)ykWYP{ z=)wC5G_R>F(P-3Gbs!HnM&`SvxWM#2d|mOyGMnx(cAO5(?hyIx+3pWTj#)^B^f{4U z)-KQ_53;qt4!Ao|SAusH(t<=(WGDzH!p4FxP=C)m(WcKyMxM`^gC7@o31Kash_Ie} zK#!0FHT9y#q8|>-O+}tN)<1kTAbh$@5xvHRywIYsP)nPEzB%CCR+a~d_oS{Z!g9;e zwP|(V4m%+S&r5{6pAcNj8O$rtJSP#_0m{OnpIT(N-wvotDPV!{npEblEgUFtA&Kh! zKJUbuzfY&gb63I7J|1~lfdc8vzCuz|ONUaE#+JT25U$Ptvh;f)DmD#R7%!ttbW$za z3~eO`_O%#92llft2h4$r9N6E&954qea^L_9bHE&^$bkbb2GN0oEX)COpdtqjw%8sW z$a8!v_aNX=_$x?XK#0W<+oJ<_%Xd1YTjWlV`v)o!`0x}JJQ)Zs+q#V%aMw{>QTwf_ zvq?#D2K(i}{I21C{RK@vpTPY2f#QNMOReib6KuHy3%W+0lf>PhAP+b#C#17wn>kRr ztOX1B`t-Fix6RHuu{K`VCGvb>P7()w@G1plg$x>dZd(WNs`$FXNs0^N`$S&jptENm z9auDafOMGhQ3_Q>OwwlpqUR-ps_FF!i&*LF{BX~#A!;9xJv z3pi?n71ItZOGTa{g=?#}r6NlK(dNqprgX-Q#{sq!zZ}>fR(xVgRp&gXMMdiqJIi31 zk`Niy-GL)gkq0t&D}Nk=tWVpjs)7w9LbVHjqwY~;o(+XS+H>p<9GMFA+VS|ohhIro zr0rFzu)vq8NiW6ZBR$kL!&~G4S?(9U1~<@ubL19vRaH<=Y5EL$U|L~pR|k$tMIK1M zxJTDi3k$s;60R_(5lAV4V#AL1+!6=KPBfc8!S9fU(OjWx@M%yXOa}6#W0=$#+0B8Y zQ;`#iUloM3V&-zHI_#^MstTo{k>>D%b9_CFq(NM@wF(EuZ~FKA?R|cNP3}jie~_yWV_+ zW_6uof-QBx--LT4Z=`*A2NBU>>7Z#JWqFSd9G{Au$^7^nF@9hCN@-X;ja631YoMvv zK@)da*0Tf4Q;{>7I~4vb_mEPxP<6S}Q8?%$m!~UuY>)mrATA@|#I2O71u0Auk$aAw zVeyPTwt@pEq#|b$f&bt)iD0q#uUAO9L9Kp>f6x$BGuRIY*z9m+bTQ>?qpeBpFVWv_ z;62Tgciz*bA z!hiRCjZjHp#1=SkQePwQ>+}gc{N%ny-q-0i+#DDz2Trjt2h4$r95~g&954qea^N(J zL3H4B3v<96sK|jcEX)COpdts(v=~GO&ayBE%z=s=INQP;Fb67f;2aBcz#OQ^ffW{m z=)k!a=72d+kpn9&%mH(tA_va1FbB+miX1rKVh|m;z``6b2P$&lLJM=i9H_{Fi!96m zbD$yzF18p%2QINN2h4$r9JtiN954qea^NxxbHE&^$bri(2GM~lEX)COpdtsZv@i$E zfr=cs%EBBl2P$&lYKuX1;2H~az#OQ^fom~eL<7`)@nJF`~Y&X`t_d(g7(Dd`0AJ`+? zOYppGmg+r-uscb%*zYwHnjbhgyO4O?z4g79oVkA04QtLC8D4waEo(Mz+PrG@jU$^k z-nwRZ9rBw-7B3rKy>``x4Qp=NG_rJf(@pDEJ963Z^&3~M$11py?iD`xcW8e24bc4P zJ)!w=cc4#v1)86F95g?30yIDS4`_b=jnMq!8fbob7Bs)|RcL^?mH5;10{yH>&`$TB|{$yzW z;s2odr?)`!FKeOsw>i-K`!}HZ&nH3i|4xDCzyA%*|GpKP`8sH2bD>rHCbSw)hF0rT zXbt@bT06WAT9fYut*P^%HSJr_n(-89?Q|NncK$E4c6~dvcDoT;d+YLz=R)hjE1~t!7PKDrerP>nGqfHxA6k$83A7%2JG35u9<-h~1g$5109sGE6fedNH)VatXA) zHWgZ5{}{Bs`2c8rdkM6@^DFm*f2V!SgS+R?fcy0e^}Fzi>&g|U&OK?}%?nSvdE@$3 zn>Wo}wrI(sC821~L31Fp7(@qZ7UqCCP>}<53v<96sK|ka#UMJ+w3uiPxc@uC#4D%S zlCVcvsc(!#728y?N84++wjkVbL$?4-qonin9%Y>e^j4{2n=1C`?QMHf$#Df>Tauexx&@43*m!!zFrK9qZNu9Q!YY;s7{jT@(=&$gEUjo8 z-ma>3(iViXBy6fkLhQ+OaR6QqFw z2}2rWMH`7ogNG>?huVZ4x=Xs$X?Tmak-Q&m#XfktU0Q69<&aT-yOfU<%_A+>B&AV) zMj#c`8*m419d`8d74!-$a|kBD+9GA8UfWdufWom*2%3dFLgAILOtK58)!(@w;lfq6U@2R_) zE3G0@l`^Lofey&w)f+38A+Kr+hN>jc0UjK~&{R}uh$2!@afQi33cz7eLemqZmT6q- z&_%%l^oH`riqt|LO>44LCc96So`h6tZJT?a_=eD)fO%DKprLFqg~I7agQ`45a2enT zVR>Gf(=v^@OE>{~PKwl=oFXE5wqIREUb!fg*hs|*uc41KUU@vOj!W!96)*G;R`D7- zxy~%FQ>IL%aLi~>m8S|W0~{eN&r5S!)`nf`BMfj*CJ7dxhWj4%@LgIcV-TP)A}FB& zQleZIUx<4-6Ab~DVk+|lL?kD?I#mKXih`%ObTp{S#XKPe;0R9K>O*t9g_6Z(Zj#Po zUL=sKyD;mz)4klR?D^J4pkt8p<=Aq@85S@eIQtd`_e20#$jM z;4;8r%(~Tw=4i8a%IQJ{8%QHafGY?Vph(TpkfOsCeQ?1g3~{OtA~+2Kmx)k7OmfDn z$O3S5h?sX=L2$yWt0bVJs8um3sJLRDkOFX6l$56v6Un&6t@0`Y1?br+QgggZw2hGI zL&jw{&7Y-*sVw1D4I=0&7*Qe0IpNhQ6R@DBQ#fWcsLIm?mjR9tmgl88Eo01UG^S@# z3uW&liD#786=@7ul2pbdXS~LPD09Vl%P8lBSJ#w)iYlh^ZmK9yfSxC$02~n|iJ3%c z8Dn0fF+Gz`7#kCf;gb+qR%(Ij>|rYJo0Q5}RxzP-=~QF_ifsA}3df8F<1$Hw6o4b5 zBr%gHEo01Un9{zBo~@L{hMMfWu^z*!)XLL%)tZ=-On}xhE)^9aP(b9PumX0ZaKxyv zs5?Z`GQbhS@;oACe&Dr?F|U3|X%{`aP^t|b*|v!)s?#K7s>+a6-KVk=U%hky9w)wt zrcg(S8$_pGq7afQB~p4h5jOJK1jvzaSi$lEh?UFsyp{=ez^iaA@1kZ)HQF%Iq=j^z z1Vt4+tWb5GI+|2!We|ByQz2qz$n7_~b{a0bs-P*JF$U{=;7-$uGr^vV#=wMqH4 zMIZGBvD6B0qp4AgAGouHIbaS{};OEzALPpdtr$voHtDfr=d1 z-C__O*u%mcFb67fU{4Ekz#OQ^fxRpS(ScbO+m{3G-{$az{6FM{H$&Lz9d(T`E*PC! zj32)Pdv^?HxU1o3?|^*j^F$BcPoQ~CWr;?kwyFbpxG^%{EyV?<_u=b`FP7PKkFn!) zV0MSdXU}$jD00j~Dx}Ye^s;t=CV7yp{dK_Ifw~gBtB@8Xq9Q{z)J{g@kE66+yi=qB&ewuH5UDFU~Ve%+_C=Qs{!HDU5e;6F64z4g@sz$ z4D`(b@3yi$K)fe)Z4s7Rj;>9s`*zp~Ie1}-%SeOImKt&E5 zXfcQm9Ase*m;)6#aInSp=s=$1Te$}TkHTL;`T{~MhS(k*xLdx{A>AT(g4{n)iNJ@a zsNl&!aM{*v?0~zD;)>dDO`T0jiZj?R2j+JT_vA8V8*{`{=-;(IdyQfAEr1hPwO0p@Oa*%Fc>LhQucRx|_9|6a;LFscmO#_9gGR{>j==%4(=YD9?)qnO_}}NQf}gID6wv@2A+YNt6Kt6S{wCZbc_ZzklOiO2Ayku^_Cc2S z?7;D<$eGNK&k^JI#jlix#nV`2b^97xJlj#z-eY-B4lGYa&SdUT__N$YO4UNu~LjtG39Hctx4@K(ciA;@tTV(m5J12%a%ECVlr}M-2+!%cr940 z20;M&nTH}bkyI-TZ+!>wtK+81$7Os25sNAmmBN4be2q{^VZ;_Va8h3*@9XplJpAOo zM&8%yHryN-EC)`pFbB+miX1rA!W=LMDstd7i$QeYbPIF99H_{FGc3#jbD$yz&a@ar z2hOrE2h4$r95~y;954qea^M^bbHE&^$bl6WgXqAy7UqCCP>};GEzALPpdts(voHtD zfr=bB-(nCQxWK|3Fb67f;6e*?z#OQ^fr~860dt@t2QIc4LKCbWL?yX->Z zara5tJV`FEjSR27?UpqgH*H?E`o@vX8*g1Rybk$IBa4>}uU@-q!-h3CZ5mlR zyy>QOs~x#)`1*~j)?*c*_0u}Ee)b+{{o>xxy6Yfl{pyF%`pvVU^}7|&`a=U+e|#^r z{=5-de?1slfBO-%{_z}W{p(z4{ksXR|GW=c|JwvZa0m=#KZc?Db782t5{8CaFf{4? zFf_awhNjGip{YNCq3O57(2nQ9(9T0Jw95xzXy&ajwEF@W+T*7%wAb@sXz%l3XwD=U zn)^W*+UGtnwBJG)+W%)Tbl~%0=->-rX#Nf`wBSQ9wCKJtbl4&oTKsbuTKWPQT6Q4} z9XSj`M|~KEj=3KU9d{@Ujr;j*8duYZhqg91 zL;W$IhWg_k1@)U3L;VTAf%=nP1NEm|2=%9J0rjVU2I|jzG}NDc0MwuJTc|(pwNQV- zMNogymQa83T~L3?W1#-B1EK!%-$DJAuY>w67eoCuTS5J`pN0DC9}D%{4ubj{eh>9G zy&mefUjp^FZVmOfeGcmHcpTKGIRxq- z{Ug*rejC(3c^T9{Jp<~W`8?D=do$GUJ{0Po{}a@|_y(wd>2j!lWgDn}^$Sq{`V*l3 z&BLJntv^HkJ8y*g_m)Hb2irpZhhK#HkDmzjpDuy=&;A1SU%UzGzkC4Hf4v>le{&Di zfA=J)|KV_`|M9O-|MQ!n{?`>y|NHh(|Hqf0{;wxP{Xa{g{@=erJ-;1)E%iq|xPSgV zVE*`psrTX&^J%9XGke*p>*gPO-I~?Q*R7qsc)_9ti(FHmqvk+OV-y{jqM;6`0~I+i zRYM(62P$%46OB=HU{j5a%>nbDBTRg9nk@+jl$GYjNmOx66=xJad$UP|d0jWn0L!T8 zy?sDg?*)T(syL>KGY0$GfmHmu3~($edhZ!f)_cKVohpv0;*7%A)+Z6>b=@?BaV(q2 z%sAGQw4!6^xKYS#34?LG^+aaIv7V$A9Ye=el^c@?V~Lw)FpgytnHk4=l2&vK9amLu z`Y@koka^l1LsxmB6*=s+cE~-D=M6kSzzbdJWMRyUyarz;Fa{NbJ*G>#)Z5Szg_U%k zKZ<;?bWs|P$F{5dd{IiT6z%g`>T&5;*&U7)P#!=Nc(gmw?ibK1uuZ|(1JZ_)lrk4o z_63>MB+pX2Wf}f zY%89iX6D0o5hE}UhOUn!1>-XCQ=}BxX#EDCk?4I4^~?sVOT15c+P!=H4qheRL_l#0 zvphiW+hZ&1>>K)VUKqNJc%M8f`CTZmn@awKcPSodJM6FrNXpY3W|9H&Q9(n73=1qN zn9vIv!9~z7Oizb1c!mMJiO6CJvt_+t7{-&5rbWb2CSK&Qq!;pbgdL1pK5#M#9T~c< z7UMENF|&T1O=Z$BbYOCX+eu+BlFpRV#Fa9OM5oLNhM@uc^5TmHOOUcSf}m(IG=LVz z&<%yIUm}PU6kITwNd~wqD*W~YDP2Su^^?8R{NZ6mC62Fc_ty{Ug2=- z6WtKn<1l6M1rqWBZ68KI78LC^<1)Y%%+kIzCuI`T6mtyZo)jrLev62t-8P#Je{yJ) z*hobMWj99^Wm=EfaEViV#|rI(RFvH$^@*k2Zg+ebW-KV$9mZvVE10EyX->)t*pxQh z0t0yyV+LZmoe>W^C55~O0SY6+7k&Y^e9g%=;#sO#;{i#r}lJiT# zglXEKhA9Z*RvVt6(g{q(LIyEO6=jw=U}zUHZ@55ELD_T?P*8qUFexawu#ZUwxGXBl z+dUJ>XvAdx6ow4s?i49GIwd-W$@ELcZL`h3%K%$RLRl>G&~z}OMpRQl*(eh*P}6)E zW-KV$(-@Zlu3(n-r8y}hOgUOJFe!z6c9O(*8= zN)_6 zA#0u#WCF5m+U7nCGZu{7Udki`ToDzCot~01!j#>X;wf^sP=*5~+52F*hE%Cc+bBy- z%yGp)Y6+K`f?&uX@{w5vTlg@CFJ--nS(z`L>SHfk1r5Erl zU$*6?jIjY$g}Ho+*v;MOz{HRg`u9mtz9W|fRo|^+Nx4@Bk*DQ%;{oy6JhG(^BkBu{ z24(nTg8@=DkT$@z8MLamlSl zPgB8jaY6TWP2R>Oe&fY^^bh4oufj z2h@Rz9GIb@4yXeaIk1g}I-m|z}=MYp4V2Kt&GhpfQRL?5MF} zIbi5cc{;+4C3^Mz0a&C+@&bJ;NAodib-GL$;xT{*(36qGtFNXOC3(FnEdlJ_cVZP<)+N64DFS}7Lo|6c3 zKQ5@36POdw93x@c0AINzpHifF*bazG2{0hMCQ+Gdb1&pLS490`zwgGif9GD2=ca={ zJ6mLB0XfpPZO2Pd%@acHHJXO*fV(#PhowCWQ8?6LW~|IttmAGNQ*<9Wu#3hhIVP^>kpsJHr~~RiMGowtF^UfCsi6+20~I;2m&S(ZK%V1QnTH1!xqk#{ z8wj!KVncM`e)&y%_lw*Ja{E9f0vnd{4NE$L+M3t11Liuy6%}tyyc@R!r*K#f%Z=*-B1N&%~PzTh3iX2#^ zF^UfCr=bq00~I;2|M(-1M+#-!)T%-U>_7bId0ye33T4kRY~W%m$O9a6K$~d?7N;Ul zk<1mkg_30{fLg21T}o+`o2UbH6Zp%4J6y&m`;PW=89Gwm<8;4*&gh|1dQ1lnNJZ|* z++_MO46?Q?bVUaTya?4M?2Vd-t1N@8YXnl!_#8Me73j6$@dqC^dtE4tUEd*rZTlga zXbe~P(n!7HfgSL6lEdV#{mtQ9lwHv=w5@2Ua41b#x`(DPqGj2EgHn+@(kAohTS{Tj z+a>M_bHCys2e?WduG~Z&@V27I^kY^bYfHD5Iw%qyKjSpTC(f7lD6TxNlkH))&o0mcq(!t^TTt*`1@kBFI}=Uc1c~#Zp5-ZHN_dN z2XbI(Dsm!ogWO+BE%c>GzPr@ud>H5>H`)H+tj5qC$ms+GRG92bk&wbN6*-djVecF( zjr+)fBT|tQiNOBgZ<2>0*$Q(G5f_s!&u4 z`@3g*9+ebIOu~U>Lydf>+c)CnM-4Udp>EgZ>cD6@aI}Uxpbk{zz%d%?fI3i-1IKEN zq65cir~~RiMGhRVp$@166*+K%#wa>)qJ}!44pij8NgC>aI#7`VCu^t!>Oe&foT4#` z4xFl?4yXeaIdGbWI-m|zVP^>kppLFjG_Z)YN!M1Kt&FmrJ)X}0~I-NwuU;O z4pij8IU1wrz_}XgfI3i-1LtX|1L{CU4xF!{4yXeaIdFl-C^~SVhB}}QROG-#8tQ;L zP>}-{Yp4V2Kt&E*qA`jNT&kfCr~?%_aG8cWpbk{zz~vgF=m2b91K0!rOsd^cs}Y7< zYg1sVOxe4(g`K_C;8nHhLNkMR)@s|3X46e;+ry@AfBlx) z4vgPW+fmH!>9Ki}+CqEQ3~1EfUOUV4czSd9Jx^G@{K}OlF3GOC@%oi()~;K=;_4;q z*4(f%TZR1EB?}j4E3R68?X@efS-WJvZ0$9xRv2<|cEy_ItFa4a<)@+1d=xZVi=ol} z4K%v1fyU++LSxG@N>{O_;JuU z^k8Tl_6KMjZfrZ^QfMrj4vnMkhQ=|EhsJS-K;!s7LgU2SpmFkL&^Xn6`e~ns#u+z5 zXyd4@(ejqfSx&t(x_GM^1<0;U1))CNn_TQoL+_ymE`75FE z!X2UUqOU;X|DFnsmmUd?m;D18uXrmoUUdaDUcD1EUh`FGyzXhxxOEvcZu=)R-uO0X zy!lFKyk#ad-ug9Yy#49Wc;`{jc-Oz6@t(Is6fX06T8vnZkns6;Nr|bsJso#a> zrq6?B{X}RsYtYQz3(fX*(CqFG&1v6*<`&O~=2j;`bNUo$&bSkr+g=aN?e~D@4&R67 zPA`DwtdpTRXDT#zejhYW_MG|NMKv{P7D@@5LwP(@r^N_Oeyi%|G_KHLI7e zTRVI4f<+4!xu!lx&4HT6C^|4jLmf~DDso_|hB}}QROG-W8l&jIrWzZY1Li+RnE2#0 zTM`Z^E6t6QsN$F^&M18LW|Ii>x^9{QmQm4r`+&0E3kK^{aZD9w4ED7HsrYpn;8;}j z-ZP-A_kzJXRUA{r8HKN{Pa@3gx@iXEST>QFajYk4MaR%_qmbDW2IF|^iOh^+JxMD% zhK{Q$HzpCr5;x6Y9Lpv$GmiBnt>_p!uBzPhVLs0w^RzjJuJS@Fa@cF_kb5A{8+d?# z7rN5P!k8C%4ZciZ3@QkFOqX=2x1l2nE9pFc6!~E3qBI@ z`VBrK(fb(cnGIH#c%SmLd-wPqyh^-@fZ`Npd4S%x$5z(aH}vDYFmxI5K6zB~yHH>^ zmHY|sQasRh*kKQll&3k&Bm?B5f`$wk7Fbd+p%*lQi=bbao(^a53&L=Y(`xL`7q3~*Ug`0WW&$|NpL z$R=Y3@<9G#K}sR5_Bq)qll`~yOhU@N!r|5@x*@d3VannQB;*6yK8$`WDB5ktWq>P~ zrG05m$|R;K<`~F5DN=I$77vnJ5~ui%71{@>D7#7O z6HB?>?)WgwSWvV(jLQI5FiZQ=oRk%?DQ&m~2J$Ax48(FfBOZ223V96z6h?$E`~q(I znv-qBvsAIh1CnCN`xrzd6_kxC0S)DYwwQ7(DB58klMHYL6(-w|933HKahryCGVF_l zrq8Zo-TZGT9k3LYeyNt^K zml12S4axb#ij>j04i0z?BL=3yn1LWA=a+;D)3iYiQxL?hHatP46PSvH3}TWh$}Drh z&@N)$aDkwLvgstCp!}#{Qc!SVACnAlSyYs_dnS_6h{^mZ3>nDXDN=HDN^}g9>6eV# zW}AJN0k)EavRLGy>0m^SsHTFlQ6^xZrui_;SWvX5F)jmK!7S}db5cf_aF*(3iIyWihwWK0K);uZ51Z3H?&3zbVEEu=Flt~7-A}SI) zJtbv?DZ4GjQ{--;3gv$a;FW_0eY|Bd-V*{)TbNLjpo4e70i6JTU?~|Z> zM=lGhzFWtVa<2>`Ps{Jd1LCuJWJ@1L)E62J%J9bq1Eg#qZGdaXPp`ma8ob-|6l3R_Qv^0y^a)R(0>Xgl}Kpm*afo(NL z(ShwW)B$y%A_unDPzTh3iX7NMV-y|OQDeh$!2G*8{2==;^4ymp?DdVZ=P@RXUL(p+ z+<~2XhB4gq@MkB7Y-#Jn4Bn5UDW|H$pi=j#1G&2~Z@x+40@LTvb;TD;Ym?5IphaO&^O%C6 zIbhuumOF^|B(5#Oe9O_bN%hcPcB5Q8ClTg;Tu?73FejinM#8iKzH&)ErAYCx9T1li zU_f|HqB7U!UdVB-i2B2R-;Hbk&b=bfO$UE=w#do?a-?nBj+dgECxqH-G!5MWcWw3$ zOM4cgaHzw~SedU_$K5cd=st2_7mZPLU{?)wKpm*af!#FJ0d=4v2X@y`2h@Rz9N0r+ z6dl-8Lmf~DDso^ijSbO(Jjbsx4-YJI{|M4H5MtBChUmck@|*VV7r7DS_JK+SHZ0{E zmUINQHLqs}%yon-D&CrSH*N_|;jkQ-+c(S~e@>In^T)z(B zRnc|1HwhO+_ldm3PN&ZyIypd!cqKa3H=gd!YOO)&9n8;pht9 zMvq_z_R$zc2lmxa2h@Rz99XEK4yXeaIj~4$6dl-4Lmf~DDso`|@kbty6w0`%RfP`N zfB4b!yuv*d%ARA`z{OUO2RP<{Hq#C)PDP#~nJaV)CChpNtIrpYcdXYs<=FwU6n{C8 zbQzym`mS-F%FvN|UKazu!f%OtS`FyH0jbCxnVU>MhC$Yrg|6t}fES_KguPMoa21x8 zbq!By8n*)nrUJb-JpSOrX0HonvFke|ux&piQ`mfDGYkM#9d+TS0E(>f^{eAb21KiJJD?Vaelkf zt)`B@23tB6!ek)tbp(?-CHpyWa4K?7;;#xqpRqEv?;32oSc;A>-Jst?3HR8(Tj>{3 zS928(Ox*N|Pge0=BXPzd6OMd6OiuX74Q=qy2}eHRNgY%NHckf))7bDF$OrkPfkcBW zQq8~)EJ+9cFLl$wpT3b4`31OwVc$(En4AOlCd?ywBgNH85t6pyx=BrOk=6q{aCjoX)1CebA#MpOD*)JNWQz&=zJLHBRARp z;H<{b9mwef1XP&pOOcSmG8H+J_F?ZFD~VP^>kpstSjG_a_X{ZD0Kt&E5ub~d80~I-Ng2pI1aH57f zpbk{zz)2eFfI3i-11D>!1L{CU4xFMfiVmErp$@166*+L4hB}}QROG};? zXpEu*XKJVe>Oe&foTZ@-r~?%_aJGgzpbk{zz&RSD=)k!e>VP^>kpt&xr~~RiMGl;= zp$@166*+K$#wa>)p@ur34pij8MH=dWI#7`V7i*{k>Oe&fT%s|G4qU3C4yXeaIdGYV zI-m|zI+7@>9 zR%q@qzcyWHX7J8hZ5z_;bysbB*wpTC&RbdAf${3vj$(FCkIkFZ7TU9BKy%&=wX-~r zr)MYr=7iPDuUvWJlI*G*uV1-l?YiYFu3oZk%?&HFRmiVhvT$*>;;QA>Uc2&|wM+KP z)?Tw}g&`MbSFBmS8oOXtJ`I`+mO*o$e?oKN+n~AMmC#%~6PgEn4Vni%9h!$61?7;G><(Rn#cVcnkT#inkTJ-<|(tGdFnTydHOSefkUo&(K?91qQh=Fq(HJNVV9cVu0 zxzK#v3DCURe9sf^faa5~h2~RsgXUAe3(coL51P+B5t`4gLGwB9h350FgXRl%hvo~v z2hA5hADS;Y37RjP0?n7-3C&kt56xTlfaa^e56#!U0Gh8q8Jf3Eh2|UH2hBG<7@D{5 z3C*|s0Ge-mAvE7{3N+uf2{hmRerVpY2AX&71BWQl+#nAlhY0$iTGiZMPgV6lqI%s}rE;PUVV`zT$|DgHx)1mpzIyAra zA!vT*255e79yGuI6KMYMCD8ow8PNP`1DZekFf@Ph5NQ5#J~V&zQ)vF?rO^D{nb7=0 z6Pkbg2sHovP-y;j0W|;iGid(fWzhWBSI*_59{w#myO+w5$} z8ZF40AA_uQBV?U@A?yAEvdv!s*_P)(wsjk_=^uw|n}Z=aG;t+z+xvzk+Q4TOd2&e8>)( z2HC-%g6zOy?w+|@m zyz&`dM_BPQ^he=oKg7N`Xs`>8OM5( zR&)#54?61|V1p4nh^iT5c_yLXS@!K=iZ2q;crmIvs4 zdu(N$eM3La3qzL??~_L*zY7I+Q^}w3F2w_FhaL6+NqL&XOfo<|Drm@%VSyzD6M8`- zxCr`%>FIC=&oH1j5m_u@wyYNn!+28Cw1_y$#ETr3^g`Z_u!B*{2TmrTBSW{sZ1J%4or@4J1Oi%(wTCaxKd`3=#)9ZFf@Q)UVO1&2~rkE5ELzj2GHUdx}nhZ zO9YXEf(s@y$pDu{h2NearA*?|glsZqAP?j(7Niu?YM+y>GTDDC&m^SWD;#cpq8mbc z9HuP3KtevC?ZfEDf}-7KTn4y;S=yK8q)cL(Vvd2_lOiR@ZxNBS+h)_@PY#U|8>y(E z?B=MVOzSZlE^&(QSfPE8in5!eKCzVB?T!z_j0Huz!?+A^1+%m-%}H4So6?3`U?6W| z%s?!+GvZ;Vq>$GjKw(7q!Y|;KuQ}O9JWCa8JRm8SypKUdQbF0M63|dSXp1Swf}$Pv zG06Z|P+_tS$)?RbFk)aDj2Q@0a(+peFijiO zFa<%}YQqy$I)SNJ$RH-EqRcV}4DBN34HpP1D4R|K3d)ZPCItl-_A$u-mqkT+yJsR9 zjhM`z!jOU7ogyVir$omvnSRN*ZMNBW8DJ|(D2qiNnhr+Ph-xY*8)X6pYMKwjj0Ht| z8sjp+70lAUG$&<*DMxDtCZ&+iPLlYJ{JAWR0ZEd|n52qwyvSFk86O$dR8TfU2`H$r zl}-~)h79CBCK=$0s7UPel#~&s9IY9cG{V@JXbruIC(BAH5R(IJrE`-~UP~$>WX+R; zOhA@R+uVm?#)5I%OPOSVE21K?(^FDLn6leaJVovn%5b10dmk*7~m|2_%IcjU65>brF;Dfh}C^0fSJJRm-sN4E4~M17&rpbUR( zFhI%%(gwJ8{PYS;wm}(-fU#{OaHixZ9W^#A2h6{l z!w<6mBF}vp!d~AfdmdxL=ryAJ#2whFXBfjx4}W%Y$dI*_{? z^X8irE--x#T~~au#HMGIouC6Vdqh5SruoZ9j+HB!w6!O#tSFEqEz&$(2h1IaE5W;R zWgroS3I*|5!;g1Qt1ee1T5%zNn{KFMa?PpP=F$@Q0ry|b{ zYah1j5I)VNuwLRI4_XukHIFG6ngiBtVY!2NPvY7l%(om}n^X_&WjD&ja}r_h#|8Cr z0&@bIV&=1EWzCyM*C(%yxb^m)8`r_} z`$V44&%MMz8~#7Zq5})YjvU+eK`HkY@}e8q zu@`E$0tey?vj@8WU+r(q7LKmqZS)9sU>}W9bYNc%bwC}c$bp3#>VP^>kpqh~M$v)& zG}HlgpdttMAAjWWNTH0IT2<(P{f8et&nw(hq3k*48@bcx`{N<~#MYzHXaXN6A#)juWKFB8xBpPIqY6f;-NjmU<@|zC+^o^v*FTfQH z`)*Rfw=j!&8wHnIE1b#@`p4ed&^=u}kV=b|aSU zsVUBAJ&*%SQ;`#y8|404YN0Ph^4+CI=fglBxykkiXElcIKu#whpu%Kdii8xFsmPJE z4}0fWY1~H+9FdBgNCfr=f0H~6$^Lq|k_JS!OYFlh5j};&aDdEqSLQFKd~N<{ueO(H zZ`bm8&EZOUqo2_4Rt^rsK|j6G)B>Z6E)NUb)X^#PSQ{Z)Paf|I9Wp-PzNe< z;1rEfbl_ACbwC}c$br)|)B$y%A_q>_PzTh3iX1pYV-y`YQ$rn42P$&lEDd!)9jM5G zvo+KKb)X^#&e0e}2hP<{2h@Rz95_!y9Z&}8o(w1U{dXl zT8%K=TAKn>E#EMmsUD+hGl9D>#b}m7cG5d)TiDrK4gOU&Vy{_PawPUC6KK;1G1|d zkX`d($gX_|WY^D!?7=^UZ0$=SyWvd89@>QLVIP6);SYuEkqaQZ>1U8V`el$k_AJOA zpFwu>MK^-P(cdwogFz#z#Q*=0%X*{!7T-`YOoY zeja4+>_YagPeS&dM?&`A{UE#ZSCGB`7RWw$K4c%B2H8hG1=+`Lg6tFfL-xsEL-y%c zLw45%kbQ1*$nO3$WM6m`WcMtF>`T9a>?^N<>}wZ7_Khtd`{rjL`}U(D`|bgdeebuB z{ou8b{pcddezGNGKfMdGpFak&dk=){m%oGT*RO-@w--b9`>i1R!)GD;(_Oj^AgDZy)|V2`5a{bdmOajU})9;0IjLFLTl4Yp;ey_t;XHZ${r7`_94*f z{1IByZiChqmqBZ*8PMAL^U#`cGqkon6k6N;30gb60a`m<4y{?+Kx_6FptbW8ptb8^ z(Aw?K(AwjT(AsM`wB~LLt$AOB)`BNOYu_c%TKE@e?e`{VEq(yB4%`k}2i*g$L!Jb! z!w!emlD|T0>6@W-4}{ifJ3#C7FGK6h zr$FoMBcOH8-=THhTcCBpN@!iQBeX953bZbLDzq*?5?agu0j(8ph1SX|pmpU=(7NiY z(0b6*pmohMXs!My{#xpfdT{^zd%*nh3sdjKC+5>mIcD~KS!AOr`<}6=w|gwF9a6bs6ATRP^36pse?T z!8%nOQ^gsDudPoa%{abt134p z5ylcX&0rkMCNeXQ^(3w67&@-1-1K2S&mi-(IfkzCLMw9EYweJGAkQ0kfPfde(#gV@ z7kLf7OkfNu2zyMIbg8$YBMK|&Jbx7VVCkYX9FJ{R`T3%hUMbq=wbbL%ud+KFDWE)n zCh%x?qTMf`RbZQfu?M6LB`IYtsO$?eM?xn2F60q1FN9^1TtKxH684#wSx{*9*x_eL zj0ZRh8LWJGonXRivWUcq77o%5x7k)aLCwsE?IK2C9t>R{Neaeg;HO9_veEhtJ|ofl z80wh~R+o66^0a&R_#M1TyorF~6lQsV-nYkA*4a1og+!QZS(xG=htuUznZ_XYdRIdJ~bw5@ySK!7z*`B~6Qn zqfETWVM#CK?Fc&6_N7Ku)o z6AVKG_~peH3zi^daRfopVrT#@j-eY0UB5&SDJZyLGLsB&SycG#2~x@=E=|ZLV+Qg- z{$fE&A+7c~*(#I$xAIIv%Duwj)+f3lw8vq};tM3?1KK`}ek>^3ZN_DQE10EyX->){ zrYYta$UP}ia{Lw%NxN+}9scCdD6x@>3d(MdD$2ATv*8k__>L9Y2dOB#N$L|zx!vyg zFw9s`v^$K;09P4rootjASLIQgbCBMK@C$7#H}_wL8TLziiHefk}Aq9bHLCpV%~6p zpn|gLB%q-Ds9;i1aA6;l3~*Ugl(%~(lF^9C{3#3>$lWPYa&$^`43p`XjN4|LeU|~Y zl7zBYZkDjh?hLi`jC5@EYovGBm|}Ux-tEMI`WNn3AU$IMSDr zil>%GmD_o)N9yD@LRN#O$1=g@V<7fWn8(#nbgNzx+C!*#&ifw&U9D^~^*QK(Sh-UtT+AJA~$cVpW=D;aq{YZm^P zz)NsBJP~0(x4=JK;naQ>H5$WkV0J3<+_3gxyAI*gTng(Y4)UNyVNmm!f}uHJ-4>QR zi1#F}Ey8@u(X~nS&|Y?_4w?vIfL zRF(^`)3n|kC|%aPd31g9+K5|k@40auJikxm`TX2V475R+8^|*0H2Pe>4&YVMb-6bQ z7ex1oyu?nY&mlUnVC=}TZ6B0!Um-8LfgO9Hb}MiozA$^B`~TJc#%$r}3f@MKUGjVumT5auacYEX7|AoOc83A!rrKPgvzq6;YfXs-+=>D zfnFOPfAC?m*M+j!^&Jw}wjYuyY(CIJ*;70z2fXDr(KVQb_BV%bQFcWK^}bBMgIt(a zDBIV8gHn+@(kAohTS{Tj+a>M_bH4&9ArP!PQJ<4>z}tyt(~tApm2Ne4{59CpsSqXu zd9Ndw)G67|frC?#dlG+D5c-UjseRXA+r?6JeCY=L9!j{!_T5Uqh`O4qaA4x5Pkge9 z?;43S4w-P|>tS-jM{a0?hfX;12~X;vISA^ymhGu2&S*W514~np6PX+2{#t6GFGcd*rAFt&Kp(lu_6KJ*hVDR4Cm^80 zWM7Je6qc#Tk+ctc=U8dnM-Cj3ikwIU_6L8HJPgVHdbyGYM7B%p!!Hp%g~M=w%yw7i zFQ$BL{%EhZmuPR-@_5bRN_nH7(KI;+j!Z_5ta)IjL^-h7bpi+EGj~lI@=~c#eBV2O zzdB|s|CowyAYxI4qEgu3J=^oBq)=iK4lEmL=PzTh3 ziX1puLmf~DDstcyjZt*qR1I}N9jM5G(=^lpb)X^#PS;Qe)Paf|I74F;9XL}%9Z&}< za^NftbwC}c$bqvp)B$y%A_vaV7)1xp)ldi2fr=bBPeUD02P$&ld<}I#9jM5G3p7U2 zfeSU%0d=4v2QJc32h@Rz9Jp9R9Z&}x?(Xg%fPtTlt(V^ntydlj ztyldCTCaWsv|f8Tv|hgrv~K+ZwBGOpXuat$XubK*(0a=oq4l=q(0a$V(0b<=q4n-3 zLhFtt(0cD*p!L2tLF)q#fYyh$gVu-df!0T#1g(!B4y{l86XBcXNgKcMxiw?gYTS3v7`J3;IBUxn5mp9ZZzFN4-!{t2zWy$xFb zxDs0bnhC9ce+^pyeLA%Aqo57{g7%cRLwl2}puO2FXxG0E?dCI}-8vfD?SDhN`wnPt zz6#n~&W83@-+=b?XF_|MW1zk5f1thnJE6VfgP=Wg4zy=|6WVj01?^pqh4!xhh4$|6 zg7%(QLwoO?p*{Co(4PNnXzz0zwD-6^ zhrb8fN34eSvR$El)OVnL%yXfA+zHS=!Tc?A;vLXF`C4e7x*N1l`!2N4cpkLRIuY9E z)S!Ltd!c>)bG61103f%XILg!TikhxQeFK>Nz?LwnT=pndhp z&|W`*A z_Tx{5_7gUR_7gtmIcD~KS!AOr`<}6=w|gwF9a6bs6ATRP^36pse?T!8%nOQ^gsDudPoa%{abt134p5ylcX&0rkMCNeXQ^(3w67&@-1-1K2S z&mi-(IfkzCLMw9EYweJGAkQ0kfPfde(#gV@7kLf7OkfNu2zyMIbg8$YBMK|&Jbx7V zVCkYX9FJ{R`T3%hUMbq=wbbL%ud+KFDWE)nCh%x?qTMf`RbZQfu?M6LB`IYtsO$?e zM?xn2F60q1FN9^1TtKxH684#wSx{*9*x_eLj0ZRh8LWJGonXRivWUcq77o%5x7k)a zLCwsE?IK2C9t>R{Neaeg;HO9_veEhtJ|ofl80wh~R+o66^0a&R_#M1TyorF~6lQsV z-nYkA*4a1og+!QZS(x zG=htuUznZ_XYdRIdJ~bw5@ySK!7z*`B~6QnqfETWVM#CK?Fc&6_N7Ku)o6AVKG_~peH3zi^daRfopVrT#@j-eY0 zUB5&SDJZyLGLsB&SycG#2~x@=E=|ZLV+Qg-{$fE&A+7c~*(#I$xAIIv%Duwj)+f3l zw8vq};tM3?1KK`}ek>^3ZN_DQE10EyX->){rYYta$UP}ia{Lw%NxN+}9scCdD6x@> z3d(MdD$2ATv*8k__>L9Y2dOB#N$L|zx!vygFw9s`v^$K;09P4rootjASLIQgbCBM zK@C$7#H}_wL8TLziiHefk}Aq9bHLCpV%~6ppn|gLB%q-Ds9;i1aA6;l3~*Ugl(%~( zlF^9C{3#3>$lWPYa&$^`43p`XjN4|LeU|~Yl7zBYZkDjh?hL zi`jC5@EYovGBm|}Ux-tEMI`WNn3AU$IMSDril>%GmD_o)N9 zyD@LRN#O$1=g@V<7fWn8(#nbgNzx+C!*#&ifw&U9 zD^~^*QK(Sh-UtT+AJA~$cVpW=D;aq{YZm^Pz)NsBJP~0(x4=JK;naQ>H5$WkV0J3< z+_3gxyAI*gTng(Y4)UNyVNmm!f}uHJ-4>QRi1#F}Ey8@u(X~nS&|Y?_4w?vIfLRF(^`)3n|kC|%aPd31g9+K5|k@40au zJikxm`TX2V475R+8^|*0H2Pe>4&YVMb-6bQ7ex1oyu?nY&mlUnVC=}TZ6B0!Um-8L zfgO9Hb}MiozA$^B`~TJc#%$r}3f@MKU~SDR#M@H&C_ooUh}vdI3N|d zBXg7K$1up+vd|SB9PlDko3J-(9M_bHBpxqKb0r9h4il1Kv(Fn|{m=WNqp8Q3pk$<7b?v{6s10X{4-BW&j5cPDSoX z{8d5dGghYdU4v~GOVRPA8}xfP;Uc%eBkm}u$N_(Q6F6<+lM^^>!yGwe!jZ3s$q661 zp$#57;m9XEse|gk#_7Oe8XKMi`5>P(kZ6!asu|dUCF#KbeQrAV(>IbLzW`S-?7K+? zlXJk{gn1-yq`11Lh_;x4o|@t^tp{@8@KoeP=7;Bq@%P1MU%F&z?2@{e-H2s-YKk*j z59GkoROCeF2D!hMTIfrWe0QnQ`7jW}R5k^OrN$5)$Z0nMDopmJNJwFsiaa+g{+Opr z7VG0aaNvkkXEOwp10r9khZi1lbDjbpn_?u%^^N*?AC5UvS{q=I9;%oSpRr_5< ziPn>FVA)V3AL{mvc==I7jeMxvb-6k)S`Hkop$@166*+K>hB}}QROG<18l&jIaT@A? zI#7`V$7`qq>Oe&foS-p^4xFf=4yXeaIdGDOI-m|zVP^>kprh_jG_alYN!M1 zKt&FmrlAg~0~I-Nx`sNS4pij885*PLz?mBAfI3i-17~Td1L{CU4xFu_4yXeaIdG1~ zC^~SihB}}QROG;U8tQ;LP>}=YYp4V2Kt&E*pfQRLT&STAr~?%_aFK>Opbk{zz{MKs zfI3i-1D9xwq63#|r~~RiMGjo1p$@166*+LZ#wa=fo7Vs~0RWR~chqWx;nvy|m}>ck z=}h$)RhtRig(*g}6xuI1q_%~fz183wYtw~h2JfuZwjs@nr`NWJP3`{nOWt1Ff$^QS z9mVXP9-B9*EwpFNfc8s1T06`0czWl~gHBkz{K}OlF3GOC@%oi()~;K=;_4;q*4(f% zTZR1EB?}j4E3R68?X@efS-WJvZ0$9xRv2<|cEy_ItFa4a%R%@+nxpOHy#V^H~kmdx4#S8Z@n7YZ{Hc(@Aww9-}P*0zvnn; z-|;_a-}!E6zyBI&e{dIQf9TuL{>XEn{juYr{qY>ypL`FrKfM~-ckK%8&wdBmcRv@} zUpN8U_n7m)bO*G*axJvKwi~p+{#|H)^Lfzz_KDE`ZVlSsdoQ$qa2>RNv^%ta{5@#@ z^!d>K`AN{ecM7zBc_*}geLb{)y9cy?_kC#p;RVqC)5*~O%T#Fp^?lI(`-7qV&po02 zuOC4BKQDy#|4xC9xq?pZ{m_}Z20EMW1)a@)2%W}@pp%^no%W{C>3je>)7C;~i@l+< z<&U7V^^2i1<22}OyBT!0`yh07SO=Y*=0a!YkD)XB|Ddz;>CoA=4xQaT1f4x@fX-g? zptJW+pfm3!&{=Q>boOmPXW@sTv)@CYvv@vq4)`f_4tgnc4mlG#hc%(IVRUk06H&w|eJ8FWtgD0EJG7<5kA2Rf(z96G1J96D#74V|-F&^hO0&^hl$ z=v=TbbT0e_bS{1cbS^yyI+wShv;5=GS@Ce_tXv44EAEBPRj-83gU*G{H67@z{seTc zdjxbIya+mLehHm*uY%4)&V$awy3o1tlhAp@BcXHCe$aW;ub}goTcGo}^PzL|H0V6x zQ_y+RP0)GD{?K{quc7nwS3~ES7eMFPn?vV0pN7u!9tE8jEQZbte*>KtzXm!lxez)p z+X6Z-{|t0q`Dp0easYH*{afg~_O;M?{YB8ZZA<99;V$UB=`ql`{Xpov<#*6|+v}k7 zj*Fr5uC1W+?$1K!j>qD!rT(Y~_s_ov%pbon^Oy?w+|@myz&` zdM_BPQ^he=oKg7N`Xs`>8OM5(R&)#54? z61|V1p4nh^iT5c_yLXS@!K=iZ2q;crmIvs4du(N$eM3La3qzL??~_L*zY7I+Q^}w3 zF2w_FhaL6+NqL&XOfo<|Drm@%VSyzD6M8`-xCr`%>FIC=&oH1j5m_u@wyYNn!+28C zw1_y$#ETr3^g`Z_u!B*{2TmrTBSW{sZ1J%4or@4J1Oi%(wTCaxKd`3 z=#)9ZFf@Q)UVO1&2~rkE5ELzj2GHUdx}nhZO9YXEf(s@y$pDu{h2NearA*?|glsZq zAP?j(7Niu?YM+y>GTDDC&m^SWD;#cpq8mbc9HuP3KtevC?ZfEDf}-7KTn4y;S=yK8 zq)cL(Vvd2_lOiR@ZxNBS+h)_@PY#U|8>y(E?B=MVOzSZlE^&(QSfPE8in5!eKCzVB z?T!z_j0Huz!?+A^1+%m-%}H4So6?3`U?6W|%s?!+GvZ;Vq>$GjKw(7q!Y|;KuQ}O9 zJWCa8JRm8SypKUdQbF0M63|dSXp1Swf}$PvG06Z|P+_tS$)?RbFk)aDj2Q@0a(+peFijiOFa<%}YQqy$I)SNJ$RH-EqRcV}4DBN3 z4HpP1D4R|K3d)ZPCItl-_A$u-mqkT+yJsR9jhM`z!jOU7ogyVir$omvnSRN*ZMNBW z8DJ|(D2qiNnhr+Ph-xY*8)X6pYMKwjj0Ht|8sjp+70lAUG$&<*DMxDtCZ&+iPLlYJ z{JAWR0ZEd|n52qwyvSFk86O$dR8TfU2`H$rl}-~)h79CBCK=$0s7UPel#~&s9IY9c zG{V@JXbruIC(BAH5R(IJrE`-~UP~$>WX+R;OhA@R+uVm?#)5I%OPOSVE21K?(^FDL zn6leaJVovn%5b10dmk*7~m|2_%IcjU65 z>brF;Dfh}C^0fSJJRm-sN4E4~M17&rpbUR(FhI%%(gwJ8{PYS;wm}(-fU#{OaHix< zj2T4J{2sW%4Kd&yk%(m6#YrD2F1gj{NlUYsEhh-Cp^hm-Q_S~;IMr7~0)K`nd5VD} zeJNQUMqnB?9BCkT1!f6z4BUz_r=0|M&~5<*g$f_Ua!&FE(v++hhFGp3rO#%4H0#9N z3*JUsqY=AsD-Cr(9jM5Gtu;o`f$19RfI3i-12Z(#0d=4v2e#2r2h@Rz9N1Q46dl-3 zLmf~DDso_Z4Rt^rsK|jGG)B>Z9W^#A2h6{l!w<6mBF}vp!d~AfdmdxL=ryAJ#2whF zXBfjx4}W%Y$dI*_{?^X8irE--x#T~~au#HMGIouC6Vdqh5S zruoZ9j+HB!w6!O#tSFEqEz&$(2h1IaE5W;RWgroS3I*|5! z;g1Qt1ee1T5%zNn{KFMa?PpP=F$@Q0ry|b{Yah1j5I)VNuwLRI4_XukHIFG6ngiBt zVY!2NPvY7l%(om}n^X_&WjD&ja}r_h#|8Cr0&@bIV&=1EWzCyM*C(%yxb^m)8`r_}`$V44&%MMz8~#7Zq5})YjvU+eK`HkY@}e8qu@`E$0tey?vj@8WU+r(q7LKmqZS)9s zU>}W9bYNc%bwC}c$bp3#>VP^>kpqh~M$v)&G}HlgpdttMAAjWWNTH0IT2<(P{f8et z&nw(hq3k(^4P0ymd4OXMXfy4=;#A}*lDR^+P_irqP;2$MODTpLW{Z9gOvjp6EE8mU)2umj#sa+ut;zd3x1vMV}>A+zc8=eFCAfGglXplv! z8Q6g(>A?SeZaVnWHVyGaF;bHLt&c_eS7xVoo^wwQsQn&L982Xf%>ROCeF zhv$g#_r+#kx@2kWlDe4Ph-G_fiZfad*GFf;D}V@L?W<1_?zTmNJx5?2BIu_xI|;f z4zS4x$b6sUgfD_3Og>y?8SarL^b}3Xfg_WVBWoU*DNznAcAdZh@w9_(f}rRs9Fhb0 zn`2hU)(YxtH``&~te){}5x*-#@N>h_Iz`B6iSe5l)XxjHaf4jiqa z4yXeaIdF`II-m|z}=2Yp4V2Kt&FmpfQRLoT#A=r~?%_aFT{P zpbk{zz{wiwfI3i-1E*+=q64RDr~~RiMGl;%p$@166*+LahB}}QROG-J8l&jInHuVV zI#7`VXKAPd>Oe&foUNe_r~?%_aE`_(I&iLrI-m|zVP^>kpt&zr~~RiMGjn` zF^UdcsG$z10~I-Nk%l^;4pij8#Tx2>I#7`VmuQTl1D9&31L{CU4qT?84yXeaIdHkg zC^`U}*8nyF0F!EW)M|v`*4h-9YWargO!XL5n+e>7DMqstI(PoBwuPO&6*})*QkyO` zGk9mMwhd`M@U7bRu&LeO`S1<39T?wK+fmH!>9Ki}+CqEQ4Cs9L3AM94kEeIP_n#A1 zFTZl-iA%DpZoGcwnzieeuef^2x-~bf%vK@4cFDrU*@~-{UwiG!Yt}B=FI#)fsuhM@ zoL#YI`D*NfS@}}veDqA{e7p&rPkaPApL!^CKC=Kicl``HpL-c}K7ST;zL-Jho{vK3 z%MXLjSNDO=*M1J2Z@e5j-#QyQ-)TYTyB~wj_iu#G5BG)6kA4B2pS%J(KRX9Hzi30} z-j74)R}Y8IZx%x5xA#Kl_pgM`AJ2u(pF7a`%O{}ow?{zdAB&*#&tF34->-tsf6s$X z-i0oF61r0!3EfThgYKrkf^Pj5=r+%XZfhEJ+n<7N_a^9WzCU!g_%(F5dNp*XUjW^0 zHiz!EpN8)CkAm)wi=n&IZ=gHtHPD@NA#``y0=m0?2D-aH8oGNP0NuTQ3*EV|h3@=| zpu5kO(B1bg=q`E;boW0Hx{H4Y-2-0--GeWN?x9;j_pr}G_wdI;_lSd_d*tt-d(`Wp zd(0)!J#K609{)M$p7=QEo_sKLPx%9MPrDVmXIu*1v!+A$?7N|R?&G0*{vpu4;E&L~ z=r-tHav5|ln*rU+KM&mp+zj0Z9tz!+e}e9nZ-DNq%b|PqHqgE13(&px3DCX%Fz7z` z&(K}_M(EzK9J&wP7P=4nB6J`AMCd+p33PAz3v?g-Cg?u)0nmN?cF?`~9_T*tNzi@r z;n01`U!nW7H$(RsE1>(V?VImrG@^|RI<}J{D-Ad@*x+8RN`wDd5_*Ce=`AF#A{txKB^{vo-`xVfA=T6Xl z*H@wYo~J?gz006`=RcwQ{*gPO-I~?Q*R7qsc)_9ti(FHmqvk+OV-y{jqM;6`0~I+i zRYM(62P$%46OB=HU{j5a%>naYBTRg9nk@+jl$GYjNmOx66=xJad$UP|d0jWn0L!T8 zy?sDg?*)T(syL>KGY0$GfmHwB-gU>>byVkB%Qm(o@4f3Hcj;-}Ev>pNTWBR$VY$k- zTmhT)>e*VX-4(klY!lK*LP#T_w~&MsoJI;s=q)6qkdTBFlF(Zyfe->A5CYtsGc)JR znS1B$d%Np*$;nef#nZtOJ*0c?6SJ!&n0s<^?-3;b1Y(716 z7*EogwxR9nT95YuuLlU8c8#ISywHjq_FCKI9?0_qZXn==E_Jfd=0#qEhY1XzlCVX0 zMVC4aZBZIY`}wWN2TPZwVS6mQ%=ee2bfjpW*HVpBKg!NcFktj&{C)R)J*- z#vYJ1q@jV>y z$s!UbO4vv<++>?^2Nf6luvNqe;K9(+A`AI}Z$1Mm$e$m3%K0*hv+C!dc22+6p`D0g`ezhnZx6 ze3VcVkYRu&1rusPBe)3qf$8qB2lp_blZaIeVYaFl48yon(lm%TT8S5H7}5%PJHiY` zOMGBw5;`(emn>mi1t@0LkF!ga1co+Dws1KqY(>(ZvYWWd$|6yza)Mz9fDbR;Sg-_X z60fCfFqctd1+3{B&IIr7|1y(QgVEX zh@{zeg$loNC{)-;MG390k1|?mJg&eccJUP}v=36z>N=@TEUinIp5OzR*`O$&z_1DM&MC})hT07o!O^U|D@5vDa7(=jQ9ymyksGxF=QGzKI|Dr1r|TH`@J zgJQg8lv6@0G$o**!c^K#6d5v*^O$6SBcdcR(^FDLnAT`a$0P`2W1=y15>Hl@QXo1z zm`eL5r975YM97>w1(|>>%W@yU%m(AKmomu!M?^_trl+KgFs*J%c^5fbD8q)5Y`w7@ zL#otD(`c2NnB$Cr)Do@~1;LO(f?1m9NuKX`DI-j)9a7mv z&JIekA&{j_L{Xe3Arn=Cq-sBvl=$kE1JF43g*8DPA!=Zqc!?nNs+36R`9#=AXJf!e z!eN1>19+A%+wxMz7=TsbTHQs==4!NIqDczc!P^g76xuKpKjozb(YBwjvVvJs{;S29ETpWVsmu6l^#WAXf!u33CkG zh*`Ux1ZU7}0R@E$AH?#S-W^F$BckE3Z# zWr;?m9#;nncVpgsSi%LS_o3^GFP7MJkFxW0V0nuumM@2Yd=!|ulu4U=(#pyLNzx!) zyXyelfw&U9E0+cmQOHo>PK1qt52(B6J<_JHNJdetSb-l1yacy~CnBsD7WjutoZ9!I zMxz@JG*VF%z}kncI)qQS6xK@|mhakt|MGg`PRhQxFk4*-Ev@6 z+kjvGoTgai!Kzh`qJk}Rt?fV+EZl+BZKEi>1n!TK1(a43uhZp_IZ(N*)vM|HnEIf-n}#;+ke1I&euU z3P%>O(gzr1ZCa{|3O0BVsv+!+!oy{lTGkajsmq)l*pdqL8hHHR!>(Qz(sI>TNMOsp zNiJdX77Jxf@q!%imfMA{0S4l2hE!HX1qD7$pCL?$ejd_dWz!B^nu@}ac7aFRR0@OM z4slml_z_46fneQ^`do+u-cB@|KF)8Ky3the*I-kpLYNHXy^df~r(`<^E=xt>N&KoH z^ebkN`zl~t#Z**$>KgqVT5yi7yODknRdubyf%%(0|IRwzt0(r@I`1eBLFc@;Jlq(>|HjTs2fufUF>PXbdAk}p2z;HV7-_)UkpSF<{`2jeBVcSV6SeOI$ zCg730k@D!4BANmXEj8s~ns?;D_EZ!^7TXKN_-uvm2h2gK7hx(vp2h@R@9JoP49Z&~qa^OadUUc9l4Rt^rsL6qw zHPiuhpe6@)Yp4V2Kur$ZqS1>E+^V4tr~@@QaGQoYpbpgJz#a{CKpm*bf!j5D(SbWO z)B$y%CI{}+PzTh3njE-GLmf~DYI5Lijb3!%9u0Ls9jM8Hr)sDJ>Of5nJWWF#PzP#q z;OQE@=)k=i>VP^>lLPl@r~~RiO%6OmqZb`8d7m*y8)IPUdvjkOVe?quQD%|lkAuo0 zk5P53!2RYZ(Cje%AAfJ(fbD(E^naqS?-Zd~#xwi+P9@E!UfXwuImXWK|LkRbXEMH` z?<~=Kp~rBNzV&vmWv2hLyZUbPJno(!^`2ehBM&yO8(zHc(F4t?>6wwyhlXdS4mKB$ zAwNC5e)HndeIpYS&HdBE8y8RS9~%X7^Wq1lM#iyfVB~$K|8p?<^B*w%U;I(i|D`9J z{x5&c^nc|yO#fG(Vfw#zs_Fmw-aX;m1vuZ8ur|4U?Vt+a^1C)MU%fFxjae zG}-AdFxiRxG}-7iCfoZRlRfYUCfoN+lRa~#$sYO_la2qZ$tHK2?7(+THuZ-l zn|a7&&mJ_{!~bfsM}N*_-}e-gJ@AkmWWRO2$=-03$$sapCi}euCVS%rCi{bro9vHX zX0kWmV6wL^GTGbSX0mrY%Vh7m&}8ragvtK+=`^Ir5`=584 z>|4(^*|&#G_J5x>*>_)Kvf@_wavqOgOZ}(^Pt2bI_~q9u`T{<2z4^v#8dr`@4(*(r z8XuXNZfst&Va*0t)Mu|b(5KOh4jiSS4yXe)Ij~4W9Z&~qa^PrO0RwF0s@Zfx*1>^C9S7-C~G~Svq~M?)UiirZ`+ZIkIMkt zqNMel4rQ$ebXKWjn>zL=z3r105O7@A&0r40=F>BW@g%Kj8``cHRX&c(EuJw2y@OpsYY1bIK%nPl^VXw7K?twf{ z;06L-=u#&OZC>Oxc$mNdDhXS3S9Gb<&=#eUw4dLKe6Vy`8n(x>%Y1)XN=J(3c`emA z^`qo1B*qOKg$!msyiPFTm@Fc3qJ)h!!%emscTjP$4_ify03Hlo9Z3qtRp7fw zDOS+>34TSQ^)^(4309XlPqo{fJwAh1iIWH@c43wq=)5havd*5+xAVYIWyJI3R>}85 zft^(GC!D3cp{=mP9v~@qbC^j6$VUk^0T~8ZQZS(wG=htuADHeAdvFf}I*C}t5N4}- z!7z+FB~62fqm_8Eh9RwxwU zt*(>m#L~KS=?Ol7nGK5a35=@%M=(qC(wvl)Fr+rz0DwG+F#|E&_K1h=l0qJX0EH3Z z6F-1UKIg?2;$F&F;{i!A<#`Muk`h`$m4HC`pec~f21Pl{W0C=mpafPMlA|qzEG|RVtMhu|9n1LWA=ZAz2DB7R`Bm{A)4Np+&1dy?iK}=Fc zE6W@J+C|I*7YIsdg-QYn%C`z81qB!8G06ajMM-tKXCfK3z{;<}kb#_?A|*$=MB6Z# zK4e^mY4$7~OeG1eVvvVW!H5b`P6@4`Ou#^8K7g4GigL!d3UCCoG%w9b8DUzZF&&dq z$a^PAJR`p@OJhKiq%tNcqctAnGbqMeMmZ(4LQ?_?Domx_M3Er_Igd#OI3h|CGd(3` zglUb&bWDOUHYOTFC-G!eDFvdlgQ>J{Qp#gVMTE?`Q;-SBvMl!j%xo|&dnuC)a72_O zW_n7>2-E7Oly{M{g)(d?$<`aoF{DbZG>ulNi8;;~NG;(?Q4kCnL_RXhV890uv%*jZ zSvMn!Vi~(2`uGL+{Y_3KdCYq$spC>{2 ziX0YHZKuvAtGs4dS)A()iPzTh3njAP;qZb`GMME7>2WoO)nT9%`4%FnpsT%5lI#81Xr)l(} z1E*`K1L{Cc4xFK(4yXe)IdG;%FFJ6R#^L1v{M{VBko}81_htxNy`!voj0;Aq7Uk#f zz}YPW42K$i&UVPAHc#~6{WzM|RF-H|>Tz|Ta5v`7hb3HKdLO#3_+p7o_b5A02bQ;p zV)=6T$47ygOPRE}C#|e3kR%P#wYv_$9f&KzyK-qD5rqr|?nKxa_<*{5-Xm@LiewbU ziWT^Qz)Nszcp}1jVS#_R#HoERYBajxKqD1J0jz!4szdmMOJTjlK_0Xy3~Fvu&@~6F z+rn}O@t(xBMVN0nx;Clq+F_5#!7CF1_v3bE{W>hKJSq=f3Q^)1yt}eXpxl# z4Rt^rsL6qgG!91xiUQvX9v)ca{s__*5Mt5A;po5<@|`Yj7X=6k`#>cE8!s0McSfm*G>f%wAghVH+s{f*hg(KVb#w_pd> zYV@K5>on8>b)Y5()@!H(>Of5nY|!XM2R3S`1L{Cc4s4ox6md%_jf5EOcNjtIDTR5;e6-A0O(gzr1ZCa{|3O0BVsv+!+!oy|QZ=qE& z>x;T{?7)^(px40T2OoCzx{#KuzCr?9_Dx}lt9F-ARJ1S$yybQwqhX@`=I|}bs;Hpe zr`1DX4}Gv|XH;d%v~%FnR1}W13q0DUQW*4hh`YjqM%a`m(&W?`l$)~yY%&;(*Whw| z+SJXX3JOF8Q_Ne26^EXdIxWoNz-6f@Jc-{8gnq>ga$f~(tC)(4PhF#*6D?NYX8K7{ zS(ENMumH~&(AwR1>Y3?V=N-koJGU;Nb^eh{c)G=6eLM~hT%L-8NCftS-y{#if~04uAzDQZ zhiG)!0X7){neW$l!GoX(7auOO4A)2#YKj)*z!k|Tkc9`Xw9pz@tU7@M;%OUQ2SHI) z*d+(>n**cy1DU%7v0P!lUQSd#hA&yQpH-G}nKUoqhxlzpATIbalE8R|k5_ zfvYvt0d=4z2d>dj2h@R@9N4MRiw-Of5n+@PTjr~@@QaHB>qI&hPQI-m~JVP^>lLNao)B$y%CI@cO=tT!^ z)ldi2ftnn+O+y_}2WoO)kA^y+4%Fnp?Hax4z#SUufI3i<19xhu1L{Cc4&0@o4yXe) zIdHc|FFJ6KhB}}Q)a1ZZHPiuhpe6^NrlAg~12sADbd6qg;9d=NKpm*bf%`Pn0d=4z z2cDtPiw>B)&zPf)F|hQ#xv!6~d93d!v&iztL1mH0s5(~QesdIPc9?w8^ZN#D?_+@9 z*LRB0EaRDdeW#M<*pa?7%rSO;e%yEa&Ln*NF@0x=-U~g3lk}~(do45h@h9}%=6T#5 zV%PY{gU#!P7w>!YKyzw(W@PlC;hCv}&BbHLPY#mCco${CSNsa@}cuhzUE^lU-wdzZ@Av%n~pO1 z=C_)B%K?*Lc7e&ae%$2SUS{&`H<T=^80T# z`QBqq-h8LYADl7y*ea7h^V25Z|0^Av<|H`8#|Fv}{ zfBol8{_DSB^54AOO#b`pP5#C&nEVfa(d2Ks!{l!{!Q^lKQht0q-mzsfN|I-W{_j)t1_~~X~>B(l`gugNa+4Ic6z-4CO z#IKrxlOHn!%kDJ;r=4O3PXB8&aORJgfwQ-of#v^Y1{%L^1_tjl1LrO?1Lu7Jzn1z@ z51yDm1Ne*YqA%bR*PCy=rg7!iBi~1AQ93=)h4L>VP^> zlLL!1)B$y%CI^nz=tT#P(Kyl^fd3m|;+4~EMcARN(l0V__z$PElOI?=}^{sKxdUYwy9%}(%U|10RhK#-3;b1 zY(7167*EogwxR8MVP#7g%;D7Y>6ycLlGd~hZCBTN+yVkDaor5&Fl;_Oa~Mz3nzo_s z>RONY0j~!Lo_39)%e>Hv9QIn<1Q$NbiaHN281M0x7-Hvv?fL4KJ3dSCgHl(DK zYe{7f$Q%io@UxId$h;6%Npb#etCra2z zGu&jGaR(I_`><8S2;jlc)sduNTm`<1lwt*~pWs&{T5m%&m|%5@^HjUt+2b>Kl{krj zVi#t)fzI1vD(mbCeLD{fRYp8dZk2p56xc}>f5KVH8`=sx>;aN;H;0*IfP9ot6Odtm zB?S{|K_j>b`hn^0um|@rpp%GI3}Lpa7YxI=Q_?huI9iDpYZ%fBc{{=kMoWBPXA(Lx zRF^DaTm>j*){nDGl>~-1Otx@2DQrd3p0b;`%E}^9sd9p02!IbS-dL~%X%$-#6eWfL zXmAW&Q>gkOf=EHZ1(TU%fWxB1Pfw6iCUK=h78x^;8}b_qQVMCbUz4RO*?ubbB&1v` zZEk&{8$x>=rd7OwguKC0AE2KNitPAsiUm!9AQnAxBxpTM{Za0Ih7FU?6= z2}5eb4FJfK7&8#VZI5`^E-BDT8N?)Iw6e?rpk2f~aDkwNR;VPPpnR)fQc!SV9+M1k zSd>(!dnS@m3#|Mq3>nDTDN=H@OSBD>=|je4m}bw?!BmpaDh7E76^y75<&@A0$^;Bl z<^!17peSdIs{ltZOY_p4lo6&i8q+Z;g}ir?#53~ivNQ%HNh)KKGFsz7K7(SsWt3Aw zD>Nmbpu$wzO%xe2kn@;ifFq(LG1F60Mwr%UOvfY$V`HK*bP`Wil~N!&JD5uQCZ#-< zR7A*}I|Z45EX#5qz|02YvX?T+07pbgVy360j4-WkN_iJKTPVYZl5D-P97C$qO4DeS znwaB^fz%SN6a~SMLF6N|3^W?_JuV;9U*F9op^~L^s1Cd==nt0NM~cfN5Wx&r2}}D zFWd4`#u$KA;ac5A%;sveVWLS2{dp3UugGCR)pqJ^Qm$1&%?3O-bPcS7CZ1H4Rt^rsL6qoHG0v3Q#8~8 zb)Y5(mT9O1>Of5noT{M?r~@@QaGFLhI&iv%I-m~JVP^>lLKdJ^r8c2X&hb- zz~9Z`3)#QOb8m*Q)jP_X$GBj$YEgdv4xHUGz;LMH=WK^;YV$-7-jAbcO=XEjr5;xY z3U_1Pd|1K-ruU)iiZ7PfbdR$0bYOXlD3&jWe|!{}xs*wpd(z6v0!h*!UAyZ5+<~|f zyepRm5>d!d;7){%fe)y==RMM9K13Sa6c}n zRuEVj&`OXn4Zvq^kxwa7+-(QMr34rdUX#e++T4L0=aQ)2?eiX4^9Nf+Q9uPhgBDp? zK#sI!Tk%pFi=tT!E)KCZ1ftnn+NaJvHpeXRI;NgKq?vEgC0U;J$ z9F7h=A>ZlZc2R(!un$xsuwf}*u%siXtm`3m0InlkQTf)y*|;P)h23&sRoj4H{+y;* z<-w{|j-rAsbFJ+_6)fC=)or6Fyaeu#kp+}i6R*?dkU3DftktXO`sB3{cgW6pWNkdu zCW>OH@DhMFXypR3OgfD|53K`uRdikMB;kVSK9QH$>Gat}2iD9U1(xlDmcnPqi)vtJ zAE?zD9EdN>Zs`8I+TWN>99_d{bPIN1twt|8uuelAPzP#qV7-PqpbpgJzy^(8bYP=~ zI-m~J7Ghy%Qh_FU@IsBoV7uVX$Ll^qDYavTu@0n7qY8SyQ|q z2fXEWp=*GF_M5}ED667^dY`7xAP1%u%C>dj(o__Vv)QzT!zXqE+6~bg7?{x%|IwjjVa9Jt}PvTbvpA)&4;K1dnD2PO0Kln}Z zFf2%VmKvf})NqJKmmOe}5s>+QjTbx!ig5AaGRts{G@+(wK@MDzi~?DB;7SXvfyJs5 zI3S+3(RC0MRfS!00KYjfnm>@aOAyNy_Uq+D(befk@bIg; z8bw#9>u`0Tw;Z@yLmf~DYI5Kj4Rt^rsL6qy8olViQ#8~8b)Y5(uGLTn)Pb5D*rm~n z4qT_94yXe)IdHv(I-m~JVP^>lLI$u^r8bdX{ZD0Kur$Ztf3C512s9YTSFaC z2WoQQ7L8tX;8qQFKpm*bf!j3H0d=4z2li;F1L{Cc4&1KMiw@kOp$@16H92snhB}}Q z)a1Zj8tQ;LP?H09YxJT6_h_gC>Of5nJXJ#-PzP#q;AtA_fI3i<15elJMF;NHPzTh3 znjE-KLmf~DYI5Ki8olViwjGa*?{7T2IW;{tIWauAe$BeUMss3xa_`v0gTsTj@7XzY z>EMp-+g1(@HSQdlngDidV`ksjbYpaKe7rd^(*QU!(YX1>YliN+@rK5Db7o}k$jnIN z{{54q53O!Iyl-rDA803LM#d(fY3ktU*vS6I-ZALK9RVL0nHqsMH~|dW+nAi$*PLpM zjUSktni+xlG;vL1_x|R{bhFXiJ2ul89XU8NHhFM*|D%ly#~x^m%rr*!@4slZopAri z=tGTz2jG~^#^lu4gJZahJ5=+5$*E>z?0j5t(4-d{h?zYi=&GC`xwa~D3 z!@BjC46VCl&~et4rjz=n0})~|(oaGgV7Ie0kQ9Bf=Q)f}0boEjd)&95Ar z9D+B8*PU)`UbA7%hQY?|x9_@oc<|uV#MXlc$M$Z$e{{no%`IEj53SoUvUh0HrY#$X zE_-0h=An`1rp=o!y=49UmtD3Vjxs&Ab=scshRIRuU38{|L5VkNV`-uT{ao|N^xnbk z+jfnQJlJgD(Jk5vKfE`=57Y4A_`&@%V?(3+MkXej`-h%0G~JvUYfi7hPPhr^+&%V4 zbN@70@!&NRd&frLM}KDOAQ*Ku{bb%ewYLd=-F5dpSM3?JTeue7KqG_M*S-009A z_}Vyh*YMz`jh8umH?Ws%armC$!Oeq>J&zuMlZ{QxTnhVN<&I_td%Sg>ojo#sV1ILZ zcXP__hqJeDyJ76XeKWg9X7;V!zHP0Sx0-=xS?CP-iI~HTJ@B)AHGb)Lj!jL^fH7c^ zh2RW9umIq$op4pK?%qE#(Y$K%;KYo3Km757mi^7nZf{OMr`y)zbnuOixqTaZ}8{=l$4(GCnhrL%nL;MbqtI52(b;J3QoI52(b z;Me-zI52(bF71^A)0Yl@XMLhM43|I4mu~$N@VC13iRoLn{t5VdUHZiIwObFMH-0MU md$;}x_`6-&WBTH)KlH!dt#$wQ4gVTv|4koO_%E65=Klfizm+@y literal 0 HcmV?d00001 diff --git a/test/test_foreign.py b/test/test_foreign.py index c6d0eba0..98b5ab7e 100755 --- a/test/test_foreign.py +++ b/test/test_foreign.py @@ -41,6 +41,7 @@ class TestForeign(unittest.TestCase): self.jpeg_file = "images/йцук.jpg" self.png_file = "images/sample.png" self.tiff_file = "images/sample.tif" + self.ome_file = "images/multi-channel-z-series.ome.tif" self.profile_file = "images/sRGB.icm" self.analyze_file = "images/t00740_tr1_segm.hdr" self.gif_file = "images/cramps.gif" @@ -315,6 +316,39 @@ class TestForeign(unittest.TestCase): self.assertEqual(x1.height, x2.width) os.unlink("test-14.tif") + x = Vips.Image.new_from_file(self.ome_file) + self.assertEqual(x.width, 439) + self.assertEqual(x.height, 167) + page_height = x.height + + x = Vips.Image.new_from_file(self.ome_file, n = -1) + self.assertEqual(x.width, 439) + self.assertEqual(x.height, page_height * 15) + + x = Vips.Image.new_from_file(self.ome_file, page = 1, n = -1) + self.assertEqual(x.width, 439) + self.assertEqual(x.height, page_height * 14) + + x = Vips.Image.new_from_file(self.ome_file, page = 1, n = 2) + self.assertEqual(x.width, 439) + self.assertEqual(x.height, page_height * 2) + + x = Vips.Image.new_from_file(self.ome_file, n = -1) + self.assertEqual(x(0,166)[0], 96) + self.assertEqual(x(0,167)[0], 0) + self.assertEqual(x(0,168)[0], 1) + + x.write_to_file("test-15.tif") + + x = Vips.Image.new_from_file("test-15.tif", n = -1) + self.assertEqual(x.width, 439) + self.assertEqual(x.height, page_height * 15) + self.assertEqual(x(0,166)[0], 96) + self.assertEqual(x(0,167)[0], 0) + self.assertEqual(x(0,168)[0], 1) + + os.unlink("test-15.tif") + def test_magickload(self): x = Vips.type_find("VipsForeign", "magickload") if not x.is_instantiatable(): From 8c84e6cc343ab10921e871e218160b91ed6f16c7 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 23 Nov 2016 22:11:26 +0000 Subject: [PATCH 28/46] add page-height to pdfload --- TODO | 38 +++++++++++++++++++++++++++++++++++- libvips/foreign/magickload.c | 2 ++ libvips/foreign/pdfload.c | 13 ++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index f99c9889..e2e3d930 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,42 @@ - all toilet roll loaders need to set "page-height" - magick, pdf and tiff need to use the same page/n interface + magick, pdf, gif and tiff need to use the same page/n interface + + magick: has page and all_frames + + deprecate all_frames, add @n, make all_frames set n == -1 + + do we allow page + all_frames? + + need to check magick2vips.c and magick7load.c + + add page-height + + magick6 + + john@kiwi:~/pics$ vips magickload nipguide.pdf x.v --all-frames + john@kiwi:~/pics$ vipsheader x.v + x.v: 595x842 ushort, 2 bands, grey16, magickload + + argh + + magick7 + + $ vips magickload nipguide.pdf x.v --all-frames + $ vipsheader x.v + x.v: 595x48836 ushort, 4 bands, rgb16, magickload + + phew + + gifload has page, but no n + + add an n param + + add page-height + + pdfload has page, n, allows n == -1, sets page-height + + diff --git a/libvips/foreign/magickload.c b/libvips/foreign/magickload.c index 9b5eb0db..ec94ec24 100644 --- a/libvips/foreign/magickload.c +++ b/libvips/foreign/magickload.c @@ -308,6 +308,7 @@ vips_foreign_load_magick_buffer_init( VipsForeignLoadMagickBuffer *buffer ) * Optional arguments: * * * @all_frames: %gboolean, load all frames in sequence + * * @page: %gint, load from this page * * @density: string, canvas resolution for rendering vector formats like SVG * * Read in an image using libMagick, the ImageMagick library. This library can @@ -355,6 +356,7 @@ vips_magickload( const char *filename, VipsImage **out, ... ) * Optional arguments: * * * @all_frames: %gboolean, load all frames in sequence + * * @page: %gint, load from this page * * @density: string, canvas resolution for rendering vector formats like SVG * * Read an image memory block using libMagick into a VIPS image. Exactly as diff --git a/libvips/foreign/pdfload.c b/libvips/foreign/pdfload.c index 7ab3907a..a32591c2 100644 --- a/libvips/foreign/pdfload.c +++ b/libvips/foreign/pdfload.c @@ -4,6 +4,8 @@ * - from openslideload.c * 12/5/16 * - add @n ... number of pages to load + * 23/11/16 + * - set page-height, if we can */ /* @@ -309,6 +311,17 @@ vips_foreign_load_pdf_header( VipsForeignLoad *load ) top += pdf->pages[i].height; } + /* If all pages are the same size, we can tag this as a toilet roll + * image and tiffsave will be able to save it as a multipage tiff. + */ + for( i = 1; i < pdf->n; i++ ) + if( pdf->pages[i].width != pdf->pages[0].width || + pdf->pages[i].height != pdf->pages[0].height ) + break; + if( i == pdf->n ) + vips_image_set_int( load->out, + VIPS_META_PAGE_HEIGHT, pdf->pages[0].height ); + vips_foreign_load_pdf_set_image( pdf, load->out ); return( 0 ); From 6e26e317e00777673f27b2b1b272d5ff0ad11e61 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 25 Nov 2016 14:46:46 +0000 Subject: [PATCH 29/46] update magick6 loader now supports page/n/page-height --- ChangeLog | 1 + TODO | 6 +-- libvips/deprecated/im_magick2vips.c | 2 +- libvips/foreign/magick2vips.c | 79 ++++++++++++++++++----------- libvips/foreign/magickload.c | 39 ++++++++++---- libvips/foreign/pforeign.h | 8 +-- test/test_foreign.py | 11 ++++ 7 files changed, 97 insertions(+), 49 deletions(-) diff --git a/ChangeLog b/ChangeLog index dd81b5e7..13772b33 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,7 @@ - add vips_image_get_fields() to help bindings - add tiff multi-page read/write - add VIPS_META_PAGE_HEIGHT metadata +- IM6 magickload supports page/n, all_frames deprecated 11/11/16 started 8.4.4 - fix crash in vips.exe arg parsing on Windows, thanks Yury diff --git a/TODO b/TODO index e2e3d930..071a054a 100644 --- a/TODO +++ b/TODO @@ -14,11 +14,7 @@ magick6 - john@kiwi:~/pics$ vips magickload nipguide.pdf x.v --all-frames - john@kiwi:~/pics$ vipsheader x.v - x.v: 595x842 ushort, 2 bands, grey16, magickload - - argh + deprecated all_frames, added n, added tests magick7 diff --git a/libvips/deprecated/im_magick2vips.c b/libvips/deprecated/im_magick2vips.c index 06b6d56f..2858cc7e 100644 --- a/libvips/deprecated/im_magick2vips.c +++ b/libvips/deprecated/im_magick2vips.c @@ -50,7 +50,7 @@ im_magick2vips( const char *filename, IMAGE *out ) #ifdef HAVE_MAGICK /* Old behaviour was always to read all frames. */ - return( vips__magick_read( filename, out, TRUE, NULL, 0 ) ); + return( vips__magick_read( filename, out, NULL, 0, -1 ) ); #else vips_error( "im_magick2vips", "%s", _( "no libMagick support in your libvips" ) ); diff --git a/libvips/foreign/magick2vips.c b/libvips/foreign/magick2vips.c index 98730280..f6469f5c 100644 --- a/libvips/foreign/magick2vips.c +++ b/libvips/foreign/magick2vips.c @@ -51,6 +51,8 @@ * - add @page option, 0 by default * 18/4/16 * - fix @page with graphicsmagick + * 25/11/16 + * - remove @all_frames, add @n */ /* @@ -119,8 +121,8 @@ typedef struct _Read { char *filename; VipsImage *im; - gboolean all_frames; int page; + int n; Image *image; ImageInfo *image_info; @@ -166,7 +168,7 @@ read_close( VipsImage *im, Read *read ) static Read * read_new( const char *filename, VipsImage *im, - gboolean all_frames, const char *density, int page ) + const char *density, int page, int n ) { Read *read; static int inited = 0; @@ -180,11 +182,17 @@ read_new( const char *filename, VipsImage *im, inited = 1; } + /* IM doesn't use the -1 means end-of-file convention, change it to a + * very large number. + */ + if( n == -1 ) + n = 100000; + if( !(read = VIPS_NEW( im, Read )) ) return( NULL ); read->filename = filename ? g_strdup( filename ) : NULL; - read->all_frames = all_frames; read->page = page; + read->n = n; read->im = im; read->image = NULL; read->image_info = CloneImageInfo( NULL ); @@ -218,24 +226,24 @@ read_new( const char *filename, VipsImage *im, SetImageOption( read->image_info, "dcm:display-range", "reset" ); #endif /*HAVE_SETIMAGEOPTION*/ - if( !all_frames ) { + if( read->page > 0 ) { #ifdef HAVE_NUMBER_SCENES - /* I can't find docs for these fields, but this seems to work. - */ + /* I can't find docs for these fields, but this seems to work. + */ char page[256]; read->image_info->scene = read->page; - read->image_info->number_scenes = 1; + read->image_info->number_scenes = read->n; /* Some IMs must have the string version set as well. */ - vips_snprintf( page, 256, "%d", read->page ); + vips_snprintf( page, 256, "%d-%d", read->page, read->page + n ); read->image_info->scenes = strdup( page ); #else /*!HAVE_NUMBER_SCENES*/ /* This works with GM 1.2.31 and probably others. */ read->image_info->subimage = read->page; - read->image_info->subrange = 1; + read->image_info->subrange = read->n; #endif } @@ -465,8 +473,17 @@ parse_header( Read *read ) for( p = image; p; (p = GetNextImageInList( p )) ) { if( p->columns != (unsigned int) im->Xsize || p->rows != (unsigned int) im->Ysize || - get_bands( p ) != im->Bands ) + get_bands( p ) != im->Bands ) { +#ifdef DEBUG + printf( "frame %d differs\n", read->n_frames ); + printf( "%zdx%zd, %d bands\n", + p->columns, p->rows, get_bands( p ) ); + printf( "first frame is %dx%d, %d bands\n", + im->Xsize, im->Ysize, im->Bands ); +#endif /*DEBUG*/ + break; + } read->n_frames += 1; } @@ -479,14 +496,11 @@ parse_header( Read *read ) printf( "image has %d frames\n", read->n_frames ); #endif /*DEBUG*/ - /* If all_frames is off, just get the first one. - */ - if( !read->all_frames ) - read->n_frames = 1; + if( read->n != -1 ) + read->n_frames = VIPS_MIN( read->n_frames, read->n ); /* Record frame pointers. */ - im->Ysize *= read->n_frames; if( !(read->frames = VIPS_ARRAY( NULL, read->n_frames, Image * )) ) return( -1 ); p = image; @@ -495,6 +509,11 @@ parse_header( Read *read ) p = GetNextImageInList( p ); } + if( read->n_frames > 1 ) { + vips_image_set_int( im, VIPS_META_PAGE_HEIGHT, im->Ysize ); + im->Ysize *= read->n_frames; + } + return( 0 ); } @@ -711,8 +730,8 @@ magick_fill_region( VipsRegion *out, } int -vips__magick_read( const char *filename, VipsImage *out, - gboolean all_frames, const char *density, int page ) +vips__magick_read( const char *filename, + VipsImage *out, const char *density, int page, int n ) { Read *read; @@ -720,7 +739,7 @@ vips__magick_read( const char *filename, VipsImage *out, printf( "magick2vips: vips__magick_read: %s\n", filename ); #endif /*DEBUG*/ - if( !(read = read_new( filename, out, all_frames, density, page )) ) + if( !(read = read_new( filename, out, density, page, n )) ) return( -1 ); #ifdef DEBUG @@ -750,8 +769,8 @@ vips__magick_read( const char *filename, VipsImage *out, * http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=20017 */ int -vips__magick_read_header( const char *filename, VipsImage *im, - gboolean all_frames, const char *density, int page ) +vips__magick_read_header( const char *filename, + VipsImage *out, const char *density, int page, int n ) { Read *read; @@ -759,7 +778,7 @@ vips__magick_read_header( const char *filename, VipsImage *im, printf( "vips__magick_read_header: %s\n", filename ); #endif /*DEBUG*/ - if( !(read = read_new( filename, im, all_frames, density, page )) ) + if( !(read = read_new( filename, out, density, page, n )) ) return( -1 ); #ifdef DEBUG @@ -778,7 +797,8 @@ vips__magick_read_header( const char *filename, VipsImage *im, if( parse_header( read ) ) return( -1 ); - if( im->Xsize <= 0 || im->Ysize <= 0 ) { + if( out->Xsize <= 0 || + out->Ysize <= 0 ) { vips_error( "magick2vips", "%s", _( "bad image size" ) ); return( -1 ); } @@ -791,8 +811,8 @@ vips__magick_read_header( const char *filename, VipsImage *im, } int -vips__magick_read_buffer( const void *buf, const size_t len, VipsImage *out, - gboolean all_frames, const char *density, int page ) +vips__magick_read_buffer( const void *buf, const size_t len, + VipsImage *out, const char *density, int page, int n ) { Read *read; @@ -800,7 +820,7 @@ vips__magick_read_buffer( const void *buf, const size_t len, VipsImage *out, printf( "magick2vips: vips__magick_read_buffer: %p %zu\n", buf, len ); #endif /*DEBUG*/ - if( !(read = read_new( NULL, out, all_frames, density, page )) ) + if( !(read = read_new( NULL, out, density, page, n )) ) return( -1 ); #ifdef DEBUG @@ -827,8 +847,7 @@ vips__magick_read_buffer( const void *buf, const size_t len, VipsImage *out, int vips__magick_read_buffer_header( const void *buf, const size_t len, - VipsImage *im, - gboolean all_frames, const char *density, int page ) + VipsImage *out, const char *density, int page, int n ) { Read *read; @@ -836,7 +855,7 @@ vips__magick_read_buffer_header( const void *buf, const size_t len, printf( "vips__magick_read_buffer_header: %p %zu\n", buf, len ); #endif /*DEBUG*/ - if( !(read = read_new( NULL, im, all_frames, density, page )) ) + if( !(read = read_new( NULL, out, density, page, n )) ) return( -1 ); #ifdef DEBUG @@ -854,8 +873,8 @@ vips__magick_read_buffer_header( const void *buf, const size_t len, if( parse_header( read ) ) return( -1 ); - if( im->Xsize <= 0 || - im->Ysize <= 0 ) { + if( out->Xsize <= 0 || + out->Ysize <= 0 ) { vips_error( "magick2vips", "%s", _( "bad image size" ) ); return( -1 ); } diff --git a/libvips/foreign/magickload.c b/libvips/foreign/magickload.c index ec94ec24..7cbc1518 100644 --- a/libvips/foreign/magickload.c +++ b/libvips/foreign/magickload.c @@ -8,6 +8,8 @@ * - add @all_frames option, off by default * 14/2/16 * - add @page option, 0 by default + * 25/11/16 + * - add @n, deprecate @all_frames (just sets n = -1) */ /* @@ -61,9 +63,13 @@ typedef struct _VipsForeignLoadMagick { VipsForeignLoad parent_object; - gboolean all_frames; /* Load all frames */ + /* Deprecated. Just sets n = -1. + */ + gboolean all_frames; + char *density; /* Load at this resolution */ int page; /* Load this page (frame) */ + int n; /* Load this many pages */ } VipsForeignLoadMagick; @@ -110,7 +116,7 @@ vips_foreign_load_magick_class_init( VipsForeignLoadMagickClass *class ) VIPS_ARG_BOOL( class, "all_frames", 3, _( "all_frames" ), _( "Read all frames from an image" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, G_STRUCT_OFFSET( VipsForeignLoadMagick, all_frames ), FALSE ); @@ -127,11 +133,19 @@ vips_foreign_load_magick_class_init( VipsForeignLoadMagickClass *class ) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignLoadMagick, page ), 0, 100000, 0 ); + + VIPS_ARG_INT( class, "n", 6, + _( "n" ), + _( "Load this many pages" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadMagick, n ), + -1, 100000, 1 ); } static void vips_foreign_load_magick_init( VipsForeignLoadMagick *magick ) { + magick->n = 1; } typedef struct _VipsForeignLoadMagickFile { @@ -154,7 +168,7 @@ ismagick( const char *filename ) t = vips_image_new(); vips_error_freeze(); - result = vips__magick_read_header( filename, t, FALSE, NULL, 0 ); + result = vips__magick_read_header( filename, t, NULL, 0, 1 ); g_object_unref( t ); vips_error_thaw(); @@ -175,8 +189,11 @@ vips_foreign_load_magick_file_header( VipsForeignLoad *load ) VipsForeignLoadMagickFile *magick_file = (VipsForeignLoadMagickFile *) load; + if( magick->all_frames ) + magick->n = -1; + if( vips__magick_read( magick_file->filename, - load->out, magick->all_frames, magick->density, magick->page ) ) + load->out, magick->density, magick->page, magick->n ) ) return( -1 ); VIPS_SETSTR( load->out->filename, magick_file->filename ); @@ -236,7 +253,7 @@ vips_foreign_load_magick_buffer_is_a_buffer( const void *buf, size_t len ) t = vips_image_new(); vips_error_freeze(); - result = vips__magick_read_buffer_header( buf, len, t, FALSE, NULL, 0 ); + result = vips__magick_read_buffer_header( buf, len, t, NULL, 0, 1 ); g_object_unref( t ); vips_error_thaw(); @@ -257,9 +274,12 @@ vips_foreign_load_magick_buffer_header( VipsForeignLoad *load ) VipsForeignLoadMagickBuffer *magick_buffer = (VipsForeignLoadMagickBuffer *) load; + if( magick->all_frames ) + magick->n = -1; + if( vips__magick_read_buffer( magick_buffer->buf->data, magick_buffer->buf->length, - load->out, magick->all_frames, magick->density, magick->page ) ) + load->out, magick->density, magick->page, magick->n ) ) return( -1 ); return( 0 ); @@ -307,8 +327,8 @@ vips_foreign_load_magick_buffer_init( VipsForeignLoadMagickBuffer *buffer ) * * Optional arguments: * - * * @all_frames: %gboolean, load all frames in sequence * * @page: %gint, load from this page + * * @n: %gint, load this many pages * * @density: string, canvas resolution for rendering vector formats like SVG * * Read in an image using libMagick, the ImageMagick library. This library can @@ -322,7 +342,8 @@ vips_foreign_load_magick_buffer_init( VipsForeignLoadMagickBuffer *buffer ) * "--with-magickpackage" configure option. * * Normally it will only load the first image in a many-image sequence (such - * as a GIF). Set @all_frames to true to read the whole image sequence. + * as a GIF or a PDF). Use @page and @n to set the start page and number of + * pages to load. Set @n to -1 to load all pages from @page onwards. * * @density is "WxH" in DPI, e.g. "600x300" or "600" (default is "72x72"). See * the [density @@ -355,8 +376,8 @@ vips_magickload( const char *filename, VipsImage **out, ... ) * * Optional arguments: * - * * @all_frames: %gboolean, load all frames in sequence * * @page: %gint, load from this page + * * @n: %gint, load this many pages * * @density: string, canvas resolution for rendering vector formats like SVG * * Read an image memory block using libMagick into a VIPS image. Exactly as diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 4459f66d..6f1ecb72 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -120,14 +120,14 @@ int vips__fits_read( const char *filename, VipsImage *out ); int vips__fits_write( VipsImage *in, const char *filename ); int vips__magick_read( const char *filename, - VipsImage *out, gboolean all_frames, const char *density, int page ); + VipsImage *out, const char *density, int page, int n ); int vips__magick_read_header( const char *filename, - VipsImage *out, gboolean all_frames, const char *density, int page ); + VipsImage *out, const char *density, int page, int n ); int vips__magick_read_buffer( const void *buf, const size_t len, - VipsImage *out, gboolean all_frames, const char *density, int page ); + VipsImage *out, const char *density, int page, int n ); int vips__magick_read_buffer_header( const void *buf, const size_t len, - VipsImage *out, gboolean all_frames, const char *density, int page ); + VipsImage *out, const char *density, int page, int n ); extern const char *vips__mat_suffs[]; diff --git a/test/test_foreign.py b/test/test_foreign.py index 98b5ab7e..c17ead16 100755 --- a/test/test_foreign.py +++ b/test/test_foreign.py @@ -383,6 +383,7 @@ class TestForeign(unittest.TestCase): #self.assertEqual(im.height, height * 2) # all-frames should load every frame of the animation + # (though all-frames is deprecated) im = Vips.Image.magickload(self.gif_anim_file) width = im.width height = im.height @@ -390,6 +391,16 @@ class TestForeign(unittest.TestCase): self.assertEqual(im.width, width) self.assertEqual(im.height, height * 5) + # page/n let you pick a range of pages + im = Vips.Image.magickload(self.gif_anim_file) + width = im.width + height = im.height + im = Vips.Image.magickload(self.gif_anim_file, page = 1, n = 2) + self.assertEqual(im.width, width) + self.assertEqual(im.height, height * 2) + page_height = im.get_value("page-height") + self.assertEqual(page_height, height) + # should work for dicom im = Vips.Image.magickload(self.dicom_file) self.assertEqual(im.width, 128) From 6e968d46f2d5a6005344bd9c5c88e40ca1243e55 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 25 Nov 2016 18:19:36 +0000 Subject: [PATCH 30/46] magick7 now supports page/n --- ChangeLog | 2 +- TODO | 18 ++-------------- libvips/foreign/magick2vips.c | 3 ++- libvips/foreign/magick7load.c | 40 ++++++++++++++++++++++++++--------- 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/ChangeLog b/ChangeLog index 13772b33..da70784d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,7 +12,7 @@ - add vips_image_get_fields() to help bindings - add tiff multi-page read/write - add VIPS_META_PAGE_HEIGHT metadata -- IM6 magickload supports page/n, all_frames deprecated +- IM6/IM7 magickload supports page/n/page-height, all_frames deprecated 11/11/16 started 8.4.4 - fix crash in vips.exe arg parsing on Windows, thanks Yury diff --git a/TODO b/TODO index 071a054a..2f4c6dd3 100644 --- a/TODO +++ b/TODO @@ -2,27 +2,13 @@ magick, pdf, gif and tiff need to use the same page/n interface - magick: has page and all_frames - - deprecate all_frames, add @n, make all_frames set n == -1 - - do we allow page + all_frames? - - need to check magick2vips.c and magick7load.c - - add page-height - magick6 - deprecated all_frames, added n, added tests + deprecated all_frames, added n, added tests, added page-height magick7 - $ vips magickload nipguide.pdf x.v --all-frames - $ vipsheader x.v - x.v: 595x48836 ushort, 4 bands, rgb16, magickload - - phew + same gifload has page, but no n diff --git a/libvips/foreign/magick2vips.c b/libvips/foreign/magick2vips.c index f6469f5c..c9416819 100644 --- a/libvips/foreign/magick2vips.c +++ b/libvips/foreign/magick2vips.c @@ -237,7 +237,8 @@ read_new( const char *filename, VipsImage *im, /* Some IMs must have the string version set as well. */ - vips_snprintf( page, 256, "%d-%d", read->page, read->page + n ); + vips_snprintf( page, 256, "%d-%d", + read->page, read->page + read->n ); read->image_info->scenes = strdup( page ); #else /*!HAVE_NUMBER_SCENES*/ /* This works with GM 1.2.31 and probably others. diff --git a/libvips/foreign/magick7load.c b/libvips/foreign/magick7load.c index c654f16c..1d483483 100644 --- a/libvips/foreign/magick7load.c +++ b/libvips/foreign/magick7load.c @@ -2,6 +2,8 @@ * * 8/7/16 * - from magickload + * 25/11/16 + * - add @n, deprecate @all_frames (just sets n = -1) */ /* @@ -55,9 +57,13 @@ typedef struct _VipsForeignLoadMagick7 { VipsForeignLoad parent_object; - gboolean all_frames; /* Load all frames */ + /* Deprecated. Just sets n = -1. + */ + gboolean all_frames; + char *density; /* Load at this resolution */ int page; /* Load this page (frame) */ + int n; /* Load this many pages */ Image *image; ImageInfo *image_info; @@ -308,6 +314,9 @@ vips_foreign_load_magick7_build( VipsObject *object ) if( !magick7->image_info ) return( -1 ); + if( magick7->all_frames ) + magick7->n = -1; + /* Canvas resolution for rendering vector formats like SVG. */ VIPS_SETSTR( magick7->image_info->density, magick7->density ); @@ -321,15 +330,16 @@ vips_foreign_load_magick7_build( VipsObject *object ) */ SetImageOption( magick7->image_info, "dcm:display-range", "reset" ); - if( !magick7->all_frames ) { + if( magick7->page > 0 ) { /* I can't find docs for these fields, but this seems to work. */ char page[256]; magick7->image_info->scene = magick7->page; - magick7->image_info->number_scenes = 1; + magick7->image_info->number_scenes = magick7->n; - vips_snprintf( page, 256, "%d", magick7->page ); + vips_snprintf( page, 256, "%d-%d", + magick7->page, magick7->page + magick7->n ); magick7->image_info->scenes = strdup( page ); } @@ -368,7 +378,7 @@ vips_foreign_load_magick7_class_init( VipsForeignLoadMagick7Class *class ) VIPS_ARG_BOOL( class, "all_frames", 3, _( "all_frames" ), _( "Read all frames from an image" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, G_STRUCT_OFFSET( VipsForeignLoadMagick7, all_frames ), FALSE ); @@ -385,11 +395,20 @@ vips_foreign_load_magick7_class_init( VipsForeignLoadMagick7Class *class ) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignLoadMagick7, page ), 0, 100000, 0 ); + + VIPS_ARG_INT( class, "n", 6, + _( "n" ), + _( "Load this many pages" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadMagick7, n ), + -1, 100000, 1 ); + } static void vips_foreign_load_magick7_init( VipsForeignLoadMagick7 *magick7 ) { + magick7->n = 1; } static void @@ -545,14 +564,15 @@ vips_foreign_load_magick7_parse( VipsForeignLoadMagick7 *magick7, printf( "image has %d frames\n", magick7->n_frames ); #endif /*DEBUG*/ - /* If all_frames is off, just get the first one. - */ - if( !magick7->all_frames ) - magick7->n_frames = 1; + if( magick7->n != -1 ) + magick7->n_frames = VIPS_MIN( magick7->n_frames, magick7->n ); /* So we can finally set the height. */ - out->Ysize *= magick7->n_frames; + if( magick7->n_frames > 1 ) { + vips_image_set_int( out, VIPS_META_PAGE_HEIGHT, out->Ysize ); + out->Ysize *= magick7->n_frames; + } return( 0 ); } From 577588c2d1608f5c0f040160222e332ab04fbdfa Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 25 Nov 2016 18:19:55 +0000 Subject: [PATCH 31/46] start adding many page read to gifload --- libvips/foreign/gifload.c | 119 ++++++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 11 deletions(-) diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index f641623f..3516a81c 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -11,6 +11,8 @@ * - support unicode on win * 19/8/16 * - better transparency detection, thanks diegocsandrim + * 25/11/16 + * - support @n */ /* @@ -82,8 +84,20 @@ typedef struct _VipsForeignLoadGif { */ int page; + /* Load this many pages. + */ + int n; + GifFileType *file; + /* The current read position, in pages. + */ + int current_page; + + /* Set for EOF detected. + */ + gboolean eof; + /* As we scan the file, the index of the transparent pixel for this * frame. */ @@ -469,11 +483,11 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, VipsImage *out ) } static int -vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, VipsImage *out ) +vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, + VipsImage *previous, VipsImage *out ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); - int frame_n; GifRecordType record; vips_image_init_fields( out, @@ -496,10 +510,16 @@ vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, VipsImage *out ) if( vips_image_write_prepare( out ) ) return( -1 ); - /* Scan the GIF until we have enough to have completely rendered the - * frame we need. + /* And init with the previous frame, if any. + */ + if( previous ) + memcpy( VIPS_IMAGE_ADDR( out, 0, 0 ), + VIPS_IMAGE_ADDR( previous, 0, 0 ), + VIPS_IMAGE_SIZEOF_IMAGE( out ) ); + + /* Scan the GIF until we have enough to have completely rendered the + * next frame. */ - frame_n = 0; do { GifByteType *extension; int ext_code; @@ -521,9 +541,10 @@ vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, VipsImage *out ) if( vips_foreign_load_gif_render( gif, out ) ) return( -1 ); - frame_n += 1; + gif->current_page += 1; - VIPS_DEBUG_MSG( "gifload: start frame %d:\n", frame_n ); + VIPS_DEBUG_MSG( "gifload: frame %d:\n", + gif->current_page ); break; @@ -533,7 +554,7 @@ vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, VipsImage *out ) gif->transparency = -1; if( DGifGetExtension( gif->file, - &ext_code, &extension) == GIF_ERROR ) { + &ext_code, &extension ) == GIF_ERROR ) { vips_foreign_load_gif_error( gif ); return( -1 ); } @@ -572,6 +593,7 @@ vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, VipsImage *out ) case TERMINATE_RECORD_TYPE: VIPS_DEBUG_MSG( "gifload: TERMINATE_RECORD_TYPE:\n" ); + gif->eof = TRUE; break; case SCREEN_DESC_RECORD_TYPE: @@ -585,8 +607,8 @@ vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, VipsImage *out ) default: break; } - } while( frame_n <= gif->page && - record != TERMINATE_RECORD_TYPE ); + } while( gif->current_page + frame_n <= gif->page && + !gif->eof ); if( frame_n <= gif->page ) { vips_error( class->nickname, @@ -602,6 +624,66 @@ vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, VipsImage *out ) return( 0 ); } +static void +unref_array( GSList *list ) +{ + g_slist_free_full( list, (GDestroyNotify) g_object_unref ); +} + +/* We render each frame to a separate memory image held in a linked + * list, then assemble to out. We don't know the number of frames in advance, + * so we can't just allocate a large area. + */ +static int +vips_foreign_load_gif_frames( VipsForeignLoadGif *gif, VipsImage **out ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); + + GSList *frames; + VipsImage *previous; + VipsImage **t; + GifRecordType record; + int n_frames; + int i; + + frames = NULL; + previous = NULL; + + do { + VipsImage *frame; + + if( vips_foreign_load_gif_to_memory( gif, previous, &frame ) ) { + unref_array( frames ); + return( -1 ); + } + frames = g_slist_append( frames, frame ); + previous = frame; + } while( (gif->n == -1 && !gif->eof) || + (gif->current_page < gif->page + gif->n) ); + + /* We've rendered to a set of memory images ... we can shut down the GIF + * reader now. + */ + vips_foreign_load_gif_close( gif ); + + n_frames = gif->current_page - gif->page; + if( !(t = VIPS_ARRAY( gif, n_frames, VipsImage * )) ) { + unref_array( frames ); + return( -1 ); + } + + for( i = 0; i < n_frames; i++ ) + t[i] = g_slist_nth( frames, i ); + + if( vips_arrayjoin( t, out, n_frames, NULL ) ) { + unref_array( frames ); + return( -1 ); + } + unref_array( frames ); + + return( 0 ); +} + static int vips_foreign_load_gif_load( VipsForeignLoad *load ) { @@ -683,11 +765,19 @@ vips_foreign_load_gif_class_init( VipsForeignLoadGifClass *class ) G_STRUCT_OFFSET( VipsForeignLoadGif, page ), 0, 100000, 0 ); + VIPS_ARG_INT( class, "n", 6, + _( "n" ), + _( "Load this many pages" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadGif, n ), + -1, 100000, 1 ); + } static void vips_foreign_load_gif_init( VipsForeignLoadGif *gif ) { + gif->n = 1; gif->transparency = -1; } @@ -854,10 +944,16 @@ vips_foreign_load_gif_buffer_init( VipsForeignLoadGifBuffer *buffer ) * Optional arguments: * * * @page: %gint, page (frame) to read + * * @n: %gint, load this many pages * * Read a GIF file into a VIPS image. Rendering uses the giflib library. * - * Use @page to set page number (frame number) to read. + * Use @page to select a page to render, numbering from zero. + * + * Use @n to select the number of pages to render. The default is 1. Pages are + * rendered in a vertical column, with each individual page aligned to the + * left. Set to -1 to mean "until the end of the document". Use vips_grid() + * to change page layout. * * The whole GIF is rendered into memory on header access. The output image * will be 1, 2, 3 or 4 bands depending on what the reader finds in the file. @@ -889,6 +985,7 @@ vips_gifload( const char *filename, VipsImage **out, ... ) * Optional arguments: * * * @page: %gint, page (frame) to read + * * @n: %gint, load this many pages * * Read a GIF-formatted memory block into a VIPS image. Exactly as * vips_gifload(), but read from a memory buffer. From 0d9bf6a81ec82e06fb0cf0bea51f18d0dc56497a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 26 Nov 2016 15:07:12 +0000 Subject: [PATCH 32/46] gifload supports n and page-height --- ChangeLog | 1 + TODO | 27 ------ libvips/foreign/gifload.c | 184 +++++++++++++++++++++++++------------- test/test_foreign.py | 12 +++ 4 files changed, 133 insertions(+), 91 deletions(-) diff --git a/ChangeLog b/ChangeLog index da70784d..c70f14cb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,7 @@ - add tiff multi-page read/write - add VIPS_META_PAGE_HEIGHT metadata - IM6/IM7 magickload supports page/n/page-height, all_frames deprecated +- gifload supports n/page-height 11/11/16 started 8.4.4 - fix crash in vips.exe arg parsing on Windows, thanks Yury diff --git a/TODO b/TODO index 2f4c6dd3..715f4c8f 100644 --- a/TODO +++ b/TODO @@ -1,30 +1,3 @@ -- all toilet roll loaders need to set "page-height" - - magick, pdf, gif and tiff need to use the same page/n interface - - magick6 - - deprecated all_frames, added n, added tests, added page-height - - magick7 - - same - - gifload has page, but no n - - add an n param - - add page-height - - pdfload has page, n, allows n == -1, sets page-height - - - - - - - - - not sure about utf8 error messages on win - strange: diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index 3516a81c..789598f1 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -12,7 +12,7 @@ * 19/8/16 * - better transparency detection, thanks diegocsandrim * 25/11/16 - * - support @n + * - support @n, page-height */ /* @@ -428,6 +428,13 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, VipsImage *out ) } } + /* We need a line buffer to decompress to. + */ + if( !gif->line ) + if( !(gif->line = VIPS_ARRAY( gif, + gif->file->SWidth, GifPixelType )) ) + return( -1 ); + if( file->Image.Interlace ) { int i; @@ -482,44 +489,17 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, VipsImage *out ) return( 0 ); } +/* Write the next page, if there is one, to @page. Set EOF if we hit the end of + * the file. @page must be a memory image of the right size. + */ static int -vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, - VipsImage *previous, VipsImage *out ) +vips_foreign_load_gif_page( VipsForeignLoadGif *gif, VipsImage *out ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); - GifRecordType record; + int n_pages; - vips_image_init_fields( out, - gif->file->SWidth, gif->file->SHeight, - 4, VIPS_FORMAT_UCHAR, - VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); + n_pages = 0; - /* We will have the whole GIF frame in memory, so we can render any - * area. - */ - vips_image_pipelinev( out, VIPS_DEMAND_STYLE_ANY, NULL ); - - /* We need a line buffer to decompress to. - */ - gif->line = VIPS_ARRAY( gif, gif->file->SWidth, GifPixelType ); - - /* Turn out into a memory image which we then render the GIF frames - * into. - */ - if( vips_image_write_prepare( out ) ) - return( -1 ); - - /* And init with the previous frame, if any. - */ - if( previous ) - memcpy( VIPS_IMAGE_ADDR( out, 0, 0 ), - VIPS_IMAGE_ADDR( previous, 0, 0 ), - VIPS_IMAGE_SIZEOF_IMAGE( out ) ); - - /* Scan the GIF until we have enough to have completely rendered the - * next frame. - */ do { GifByteType *extension; int ext_code; @@ -541,10 +521,10 @@ vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, if( vips_foreign_load_gif_render( gif, out ) ) return( -1 ); - gif->current_page += 1; + n_pages += 1; - VIPS_DEBUG_MSG( "gifload: frame %d:\n", - gif->current_page ); + VIPS_DEBUG_MSG( "gifload: page %d:\n", + gif->current_page + n_pages ); break; @@ -607,23 +587,41 @@ vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, default: break; } - } while( gif->current_page + frame_n <= gif->page && + } while( n_pages < 1 && !gif->eof ); - if( frame_n <= gif->page ) { - vips_error( class->nickname, - "%s", _( "too few frames in GIF file" ) ); - return( -1 ); - } - - /* We've rendered to a memory image ... we can shut down the GIF - * reader now. - */ - vips_foreign_load_gif_close( gif ); + gif->current_page += n_pages; return( 0 ); } +static VipsImage * +vips_foreign_load_gif_new_page( VipsForeignLoadGif *gif ) +{ + VipsImage *out; + + out = vips_image_new_memory(); + + vips_image_init_fields( out, + gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR, + VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); + + /* We will have the whole GIF frame in memory, so we can render any + * area. + */ + vips_image_pipelinev( out, VIPS_DEMAND_STYLE_ANY, NULL ); + + /* Turn out into a memory image which we then render the GIF frames + * into. + */ + if( vips_image_write_prepare( out ) ) { + g_object_unref( out ); + return( NULL ); + } + + return( out ); +} + static void unref_array( GSList *list ) { @@ -635,52 +633,112 @@ unref_array( GSList *list ) * so we can't just allocate a large area. */ static int -vips_foreign_load_gif_frames( VipsForeignLoadGif *gif, VipsImage **out ) +vips_foreign_load_gif_pages( VipsForeignLoadGif *gif, VipsImage **out ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); GSList *frames; + VipsImage *frame; VipsImage *previous; VipsImage **t; - GifRecordType record; int n_frames; int i; frames = NULL; previous = NULL; - do { - VipsImage *frame; + /* Accumulate any start stuff up to the first frame we need. + */ + if( !(frame = vips_foreign_load_gif_new_page( gif )) ) + return( -1 ); + do { + if( vips_foreign_load_gif_page( gif, frame ) ) { + g_object_unref( frame ); + return( -1 ); + } + } while( !gif->eof && + gif->current_page <= gif->page ); - if( vips_foreign_load_gif_to_memory( gif, previous, &frame ) ) { + if( gif->eof ) { + vips_error( class->nickname, + "%s", _( "too few frames in GIF file" ) ); + g_object_unref( frame ); + return( -1 ); + } + + frames = g_slist_append( frames, frame ); + previous = frame; + + while( gif->n == -1 || + gif->current_page < gif->page + gif->n ) { + /* We might need a frame for this read to render to. + */ + if( !(frame = vips_foreign_load_gif_new_page( gif )) ) { unref_array( frames ); return( -1 ); } - frames = g_slist_append( frames, frame ); - previous = frame; - } while( (gif->n == -1 && !gif->eof) || - (gif->current_page < gif->page + gif->n) ); + + /* And init with the previous frame, if any. + */ + if( previous ) + memcpy( VIPS_IMAGE_ADDR( frame, 0, 0 ), + VIPS_IMAGE_ADDR( previous, 0, 0 ), + VIPS_IMAGE_SIZEOF_IMAGE( frame ) ); + + if( vips_foreign_load_gif_page( gif, frame ) ) { + g_object_unref( frame ); + unref_array( frames ); + return( -1 ); + } + + if( gif->eof ) { + /* Nope, didn't need the new frame. + */ + g_object_unref( frame ); + break; + } + else { + frames = g_slist_append( frames, frame ); + previous = frame; + } + } + + n_frames = g_slist_length( frames ); + + if( gif->eof && + gif->n != -1 && + n_frames < gif->n ) { + unref_array( frames ); + vips_error( class->nickname, + "%s", _( "too few frames in GIF file" ) ); + return( -1 ); + } /* We've rendered to a set of memory images ... we can shut down the GIF * reader now. */ vips_foreign_load_gif_close( gif ); - n_frames = gif->current_page - gif->page; if( !(t = VIPS_ARRAY( gif, n_frames, VipsImage * )) ) { unref_array( frames ); return( -1 ); } for( i = 0; i < n_frames; i++ ) - t[i] = g_slist_nth( frames, i ); + t[i] = (VipsImage *) g_slist_nth_data( frames, i ); - if( vips_arrayjoin( t, out, n_frames, NULL ) ) { + if( vips_arrayjoin( t, out, n_frames, + "across", 1, + NULL ) ) { unref_array( frames ); return( -1 ); } + unref_array( frames ); + if( n_frames > 1 ) + vips_image_set_int( *out, VIPS_META_PAGE_HEIGHT, frame->Ysize ); + return( 0 ); } @@ -693,11 +751,9 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) VipsImage *im; - /* Render to a memory image. - */ - im = t[0] = vips_image_new_memory(); - if( vips_foreign_load_gif_to_memory( gif, im ) ) + if( vips_foreign_load_gif_pages( gif, &t[0] ) ) return( -1 ); + im = t[0]; /* Depending on what we found, transform and write to load->real. */ diff --git a/test/test_foreign.py b/test/test_foreign.py index c17ead16..fb928840 100755 --- a/test/test_foreign.py +++ b/test/test_foreign.py @@ -560,6 +560,18 @@ class TestForeign(unittest.TestCase): self.file_loader("gifload", self.gif_file, gif_valid) self.buffer_loader("gifload_buffer", self.gif_file, gif_valid) + x1 = Vips.Image.new_from_file(self.gif_anim_file ) + x2 = Vips.Image.new_from_file(self.gif_anim_file, n = 2 ) + self.assertEqual(x2.height, 2 * x1.height) + page_height = x2.get_value("page-height") + self.assertEqual(page_height, x1.height) + + x2 = Vips.Image.new_from_file(self.gif_anim_file, n = -1 ) + self.assertEqual(x2.height, 5 * x1.height) + + x2 = Vips.Image.new_from_file(self.gif_anim_file, page = 1, n = -1 ) + self.assertEqual(x2.height, 4 * x1.height) + def test_svgload(self): x = Vips.type_find("VipsForeign", "svgload") if not x.is_instantiatable(): From 4f85a5d7cde209e089d4ffa1c7620c2bf0d0d69f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 8 Dec 2016 13:54:05 +0000 Subject: [PATCH 33/46] lower libgsf requirement to 1.14.26 1.14.16 seems to more or less work, and is the most recent version on centos see https://github.com/jcupitt/libvips/issues/528 --- ChangeLog | 3 +++ configure.ac | 15 +++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5cf8f631..1a4f3e91 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +8/12/16 started 8.4.5 +- allow libgsf-1.14.26 to help centos, thanks tdiprima + 11/11/16 started 8.4.4 - fix crash in vips.exe arg parsing on Windows, thanks Yury diff --git a/configure.ac b/configure.ac index c8192ddc..4861cd27 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # also update the version number in the m4 macros below -AC_INIT([vips], [8.4.4], [vipsip@jiscmail.ac.uk]) +AC_INIT([vips], [8.4.5], [vipsip@jiscmail.ac.uk]) # required for gobject-introspection AC_PREREQ(2.62) @@ -18,7 +18,7 @@ AC_CONFIG_MACRO_DIR([m4]) # user-visible library versioning m4_define([vips_major_version], [8]) m4_define([vips_minor_version], [4]) -m4_define([vips_micro_version], [4]) +m4_define([vips_micro_version], [5]) m4_define([vips_version], [vips_major_version.vips_minor_version.vips_micro_version]) @@ -38,7 +38,7 @@ VIPS_VERSION_STRING=$VIPS_VERSION-`date` # binary interface changes not backwards compatible?: reset age to 0 LIBRARY_CURRENT=48 -LIBRARY_REVISION=4 +LIBRARY_REVISION=5 LIBRARY_AGE=6 # patched into include/vips/version.h @@ -404,9 +404,12 @@ GTK_DOC_CHECK([1.14],[--flavour no-tmpl]) AC_ARG_WITH([gsf], AS_HELP_STRING([--without-gsf], [build without libgsf-1 (default: test)])) -# libgsf-1 1.14.21 crashes, .27 is known-good, not sure about 22-26 +# libgsf-1 1.14.21 crashes +# .27 is known to work well +# .26 seems OK but has not been tested much +# not sure about 22-25 if test x"$with_gsf" != "xno"; then - PKG_CHECK_MODULES(GSF, libgsf-1 >= 1.14.27, + PKG_CHECK_MODULES(GSF, libgsf-1 >= 1.14.26, [AC_DEFINE(HAVE_GSF,1,[define if you have libgsf-1 installed.]) with_gsf=yes PACKAGES_USED="$PACKAGES_USED libgsf-1" @@ -1095,7 +1098,7 @@ file import/export with libtiff: $with_tiff file import/export with giflib: $with_giflib file import/export with libjpeg: $with_jpeg image pyramid export: $with_gsf - (requires libgsf-1 1.14.27 or later) + (requires libgsf-1 1.14.26 or later) use libexif to load/save JPEG metadata: $with_libexif ]) From c31f4ec2d78e2433756da14bfe582fc244d4d0fa Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 31 Dec 2016 17:58:33 +0000 Subject: [PATCH 34/46] add VIPS_SONAME etc. helps check ABI and php-vips-ext lock the right library --- ChangeLog | 2 ++ libvips/Makefile.am | 4 +++ libvips/include/vips/Makefile.am | 2 ++ libvips/include/vips/private.h | 10 ++++---- libvips/include/vips/soname.h | 2 ++ libvips/include/vips/version.h.in | 15 +++++++++++ libvips/include/vips/vips.h | 11 +++++---- libvips/iofuncs/Makefile.am | 1 + libvips/iofuncs/init.c | 41 ++++++++++++++++++------------- 9 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 libvips/include/vips/soname.h diff --git a/ChangeLog b/ChangeLog index 9f19380f..ab5930cb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,6 +14,8 @@ - add VIPS_META_PAGE_HEIGHT metadata - IM6/IM7 magickload supports page/n/page-height, all_frames deprecated - gifload supports n/page-height +- added #defines for VIPS_SONAME, VIPS_LIBRARY_CURRENT, VIPS_LIBRARY_REVISION, + VIPS_LIBRARY_AGE 8/12/16 started 8.4.5 - allow libgsf-1.14.26 to help centos, thanks tdiprima diff --git a/libvips/Makefile.am b/libvips/Makefile.am index 015ac722..fa678ff6 100644 --- a/libvips/Makefile.am +++ b/libvips/Makefile.am @@ -64,6 +64,10 @@ EXTRA_DIST = \ CLEANFILES = +all-local: + echo '/* This file is autogenerated, do not edit. */' > include/vips/soname.h; \ + source libvips.la; echo "#define VIPS_SONAME \"$$dlname\"" >> include/vips/soname.h; + -include $(INTROSPECTION_MAKEFILE) INTROSPECTION_GIRS = INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) diff --git a/libvips/include/vips/Makefile.am b/libvips/include/vips/Makefile.am index 33630384..69f51b28 100644 --- a/libvips/include/vips/Makefile.am +++ b/libvips/include/vips/Makefile.am @@ -37,6 +37,7 @@ pkginclude_HEADERS = \ region.h \ resample.h \ semaphore.h \ + soname.h \ threadpool.h \ thread.h \ transform.h \ @@ -70,6 +71,7 @@ vips_scan_headers = \ ${top_srcdir}/libvips/include/vips/morphology.h \ ${top_srcdir}/libvips/include/vips/draw.h \ ${top_srcdir}/libvips/include/vips/basic.h \ + ${top_srcdir}/libvips/include/vips/version.h \ ${top_srcdir}/libvips/include/vips/object.h enumtypes.h: $(vips_scan_headers) Makefile diff --git a/libvips/include/vips/private.h b/libvips/include/vips/private.h index d9648190..6e3bd497 100644 --- a/libvips/include/vips/private.h +++ b/libvips/include/vips/private.h @@ -56,11 +56,6 @@ extern "C" { */ #define VIPS_SIZEOF_HEADER (64) -/* Startup ABI check. - */ -int vips__init( const char *argv0 ); -size_t vips__get_sizeof_vipsobject( void ); - /* What we track for each mmap window. Have a list of these on an openin * VipsImage. */ @@ -183,6 +178,11 @@ void vips__demand_hint_array( struct _VipsImage *image, int vips__image_copy_fields_array( struct _VipsImage *out, struct _VipsImage *in[] ); +/* Deprecated. + */ +int vips__init( const char *argv0 ); +size_t vips__get_sizeof_vipsobject( void ); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/include/vips/soname.h b/libvips/include/vips/soname.h new file mode 100644 index 00000000..7fd6c91d --- /dev/null +++ b/libvips/include/vips/soname.h @@ -0,0 +1,2 @@ +/* This file is autogenerated, do not edit. */ +#define VIPS_SONAME "libvips.so.42" diff --git a/libvips/include/vips/version.h.in b/libvips/include/vips/version.h.in index 85a2a5ae..465f3f8e 100644 --- a/libvips/include/vips/version.h.in +++ b/libvips/include/vips/version.h.in @@ -10,6 +10,21 @@ #define VIPS_MINOR_VERSION (@VIPS_MINOR_VERSION@) #define VIPS_MICRO_VERSION (@VIPS_MICRO_VERSION@) +/* The ABI version, as used for library versioning. + */ +#define VIPS_LIBRARY_CURRENT (@LIBRARY_CURRENT@) +#define VIPS_LIBRARY_REVISION (@LIBRARY_REVISION@) +#define VIPS_LIBRARY_AGE (@LIBRARY_AGE@) + +/** + * VIPS_SONAME: + * + * The name of the shared object containing the vips library, for example + * "libvips.so.42", or "libvips-42.dll". + */ + +#include "soname.h" + /* Not really anything to do with versions, but this is a handy place to put * it. */ diff --git a/libvips/include/vips/vips.h b/libvips/include/vips/vips.h index 174eeb3e..cb21d52d 100644 --- a/libvips/include/vips/vips.h +++ b/libvips/include/vips/vips.h @@ -164,14 +164,15 @@ extern "C" { * not have _(). */ #define VIPS_INIT( ARGV0 ) \ - (sizeof( VipsObject ) != vips__get_sizeof_vipsobject() ? ( \ + (vips_version( 3 ) - vips_version( 5 ) != \ + VIPS_LIBRARY_CURRENT - VIPS_LIBRARY_AGE ? ( \ vips_info( "vips_init", "ABI mismatch" ), \ vips_info( "vips_init", \ - "library has sizeof(VipsObject) == %zd", \ - vips__get_sizeof_vipsobject() ), \ + "library has ABI version %d", \ + vips_version( 3 ) - vips_version( 5 ) ), \ vips_info( "vips_init", \ - "application has sizeof(VipsObject) == %zd", \ - sizeof( VipsObject ) ), \ + "application needs ABI version %d", \ + VIPS_LIBRARY_CURRENT - VIPS_LIBRARY_AGE ), \ vips_error( "vips_init", "ABI mismatch" ), \ -1 ) : \ vips_init( ARGV0 )) diff --git a/libvips/iofuncs/Makefile.am b/libvips/iofuncs/Makefile.am index eff85d1b..0a7807dc 100644 --- a/libvips/iofuncs/Makefile.am +++ b/libvips/iofuncs/Makefile.am @@ -68,6 +68,7 @@ vips_scan_headers = \ ${top_srcdir}/libvips/include/vips/morphology.h \ ${top_srcdir}/libvips/include/vips/draw.h \ ${top_srcdir}/libvips/include/vips/basic.h \ + ${top_srcdir}/libvips/include/vips/version.h \ ${top_srcdir}/libvips/include/vips/object.h enumtypes.c: $(vips_scan_headers) Makefile diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c index 5650be42..3aa7a4c4 100644 --- a/libvips/iofuncs/init.c +++ b/libvips/iofuncs/init.c @@ -432,20 +432,6 @@ vips_init( const char *argv0 ) return( 0 ); } -/* Return the sizeof() various important data structures. These are checked - * against the headers used to build our caller by vips_init(). - * - * We allow direct access to members of VipsImage and VipsRegion (mostly for - * reasons of history), so any change to a superclass of either of these - * objects will break our ABI. - */ - -size_t -vips__get_sizeof_vipsobject( void ) -{ - return( sizeof( VipsObject ) ); -} - /* Call this before vips stuff that uses stuff we need to have inited. */ void @@ -1029,6 +1015,9 @@ vips_version_string( void ) * Get the major, minor or micro library version, with @flag values 0, 1 and * 2. * + * Get the ABI current, revision and age (as used by libtool) with @flag + * values 3, 4, 5. + * * Returns: library version number */ int @@ -1037,15 +1026,24 @@ vips_version( int flag ) switch( flag ) { case 0: return( VIPS_MAJOR_VERSION ); - + case 1: return( VIPS_MINOR_VERSION ); - + case 2: return( VIPS_MICRO_VERSION ); + case 3: + return( VIPS_LIBRARY_CURRENT ); + + case 4: + return( VIPS_LIBRARY_REVISION ); + + case 5: + return( VIPS_LIBRARY_AGE ); + default: - vips_error( "vips_version", "%s", _( "flag not 0, 1, 2" ) ); + vips_error( "vips_version", "%s", _( "flag not in [0, 5]" ) ); return( -1 ); } } @@ -1064,3 +1062,12 @@ vips_leak_set( gboolean leak ) { vips__leak = leak; } + +/* Deprecated. + */ +size_t +vips__get_sizeof_vipsobject( void ) +{ + return( sizeof( VipsObject ) ); +} + From 2be0b97dce1eeb62a90c7dbb8947c50d05983e81 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 3 Jan 2017 15:52:27 +0000 Subject: [PATCH 35/46] switch to g_warning()_/g_info() we had vips_warn() and vips_info(), but they are a bit crappy ... switch to g_warning() and g_info() instead see https://github.com/jcupitt/libvips/issues/544 --- ChangeLog | 2 + TODO | 20 ++++ cplusplus/VImage.cpp | 4 +- libvips/arithmetic/measure.c | 8 +- libvips/colour/icc_transform.c | 25 +++-- libvips/conversion/cast.c | 8 +- libvips/conversion/copy.c | 2 +- libvips/conversion/sequential.c | 4 +- libvips/conversion/tilecache.c | 4 +- libvips/convolution/convi.c | 9 +- libvips/convolution/gaussblur.c | 3 +- libvips/deprecated/rename.c | 64 ++++++++++++ libvips/foreign/csv.c | 3 +- libvips/foreign/dzsave.c | 3 +- libvips/foreign/foreign.c | 14 ++- libvips/foreign/gifload.c | 4 +- libvips/foreign/jpeg2vips.c | 14 +-- libvips/foreign/ppm.c | 9 +- libvips/foreign/tiff.c | 8 +- libvips/foreign/tiff2vips.c | 4 +- libvips/foreign/vips2jpeg.c | 39 ++++---- libvips/foreign/vips2tiff.c | 19 ++-- libvips/foreign/vips2webp.c | 12 +-- libvips/histogram/maplut.c | 5 +- libvips/include/vips/almostdeprecated.h | 8 ++ libvips/include/vips/error.h | 7 -- libvips/include/vips/vips.h | 8 +- libvips/iofuncs/error.c | 123 ------------------------ libvips/iofuncs/gate.c | 6 +- libvips/iofuncs/generate.c | 2 +- libvips/iofuncs/image.c | 11 +-- libvips/iofuncs/init.c | 15 +-- libvips/iofuncs/mapfile.c | 2 +- libvips/iofuncs/memory.c | 9 +- libvips/iofuncs/operation.c | 8 +- libvips/iofuncs/region.c | 3 +- libvips/iofuncs/system.c | 3 +- libvips/iofuncs/threadpool.c | 3 +- libvips/iofuncs/vector.c | 2 +- libvips/iofuncs/vips.c | 6 +- libvips/resample/reduceh.cpp | 2 +- libvips/resample/reducev.cpp | 4 +- libvips/resample/resize.c | 19 ++-- libvips/resample/thumbnail.c | 54 ++++------- tools/vipsthumbnail.c | 5 +- 45 files changed, 240 insertions(+), 347 deletions(-) diff --git a/ChangeLog b/ChangeLog index ab5930cb..2cdfb374 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,6 +16,8 @@ - gifload supports n/page-height - added #defines for VIPS_SONAME, VIPS_LIBRARY_CURRENT, VIPS_LIBRARY_REVISION, VIPS_LIBRARY_AGE +- deprecate vips_warn(), use g_warning() instead +- deprecate vips_info(), use g_info() instead 8/12/16 started 8.4.5 - allow libgsf-1.14.26 to help centos, thanks tdiprima diff --git a/TODO b/TODO index 715f4c8f..285d5ee1 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,23 @@ +- need some simple way to turn on info output from CLI or env var + +- switch to glib logging + + vips_error() stuff must stay + + vips_info() and vips_warn() can change + + - switch to g_warning() and g_info(), have some compat stuff + + just calls + + g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, format, args); + + also see + + vips_info_set(), --vips-info, VIPS_INFO + + + - not sure about utf8 error messages on win - strange: diff --git a/cplusplus/VImage.cpp b/cplusplus/VImage.cpp index d9b55133..a1905441 100644 --- a/cplusplus/VImage.cpp +++ b/cplusplus/VImage.cpp @@ -350,7 +350,7 @@ set_property( VipsObject *object, const char *name, const GValue *value ) if( vips_object_get_argument( object, name, &pspec, &argument_class, &argument_instance ) ) { - vips_warn( NULL, "%s", vips_error_buffer() ); + g_warning( "%s", vips_error_buffer() ); vips_error_clear(); return; } @@ -364,7 +364,7 @@ set_property( VipsObject *object, const char *name, const GValue *value ) if( (enum_value = vips_enum_from_nick( object_class->nickname, pspec_type, g_value_get_string( value ) )) < 0 ) { - vips_warn( NULL, "%s", vips_error_buffer() ); + g_warning( "%s", vips_error_buffer() ); vips_error_clear(); return; } diff --git a/libvips/arithmetic/measure.c b/libvips/arithmetic/measure.c index 9e7c881e..5bbfcfb5 100644 --- a/libvips/arithmetic/measure.c +++ b/libvips/arithmetic/measure.c @@ -165,9 +165,11 @@ vips_measure_build( VipsObject *object ) */ if( dev * 5 > VIPS_FABS( avg ) && VIPS_FABS( avg ) > 3 ) - vips_warn( class->nickname, - _( "patch %d x %d, band %d: " - "avg = %g, sdev = %g" ), + g_warning( _( "%s: " + "patch %d x %d, " + "band %d: " + "avg = %g, sdev = %g" ), + class->nickname, i, j, b, avg, dev ); *VIPS_MATRIX( measure->out, diff --git a/libvips/colour/icc_transform.c b/libvips/colour/icc_transform.c index 43c4c891..f41c12c9 100644 --- a/libvips/colour/icc_transform.c +++ b/libvips/colour/icc_transform.c @@ -193,9 +193,9 @@ static int icc_error( int code, const char *text ) { if( code == LCMS_ERRC_WARNING ) - vips_warn( "VipsIcc", "%s", text ); + g_warning( "%s", text ); else - vips_error( "VipsIcc", "%s", text ); + vips_error( "VipsIcc", text ); return( 0 ); } @@ -452,9 +452,9 @@ vips_check_intent( const char *domain, { if( profile && !cmsIsIntentSupported( profile, intent, direction ) ) - vips_warn( domain, - _( "intent %d (%s) not supported by " + g_warning( _( "%s: intent %d (%s) not supported by " "%s profile; falling back to default intent" ), + domain, intent, vips_enum_nick( VIPS_TYPE_INTENT, intent ), direction == LCMS_USED_AS_INPUT ? _( "input" ) : _( "output" ) ); @@ -542,7 +542,7 @@ vips_image_expected_bands( VipsImage *image ) } static cmsHPROFILE -vips_icc_load_profile_image( const char *domain, VipsImage *image ) +vips_icc_load_profile_image( VipsImage *image ) { void *data; size_t data_length; @@ -554,15 +554,15 @@ vips_icc_load_profile_image( const char *domain, VipsImage *image ) if( vips_image_get_blob( image, VIPS_META_ICC_NAME, &data, &data_length ) || !(profile = cmsOpenProfileFromMem( data, data_length )) ) { - vips_warn( domain, "%s", _( "corrupt embedded profile" ) ); + g_warning( "%s", _( "corrupt embedded profile" ) ); return( NULL ); } if( vips_image_expected_bands( image ) != vips_icc_profile_needs_bands( profile ) ) { VIPS_FREEF( cmsCloseProfile, profile ); - vips_warn( domain, - "%s", _( "embedded profile incompatible with image" ) ); + g_warning( "%s", + _( "embedded profile incompatible with image" ) ); return( NULL ); } @@ -584,8 +584,7 @@ vips_icc_load_profile_file( const char *domain, if( vips_image_expected_bands( image ) != vips_icc_profile_needs_bands( profile ) ) { VIPS_FREEF( cmsCloseProfile, profile ); - vips_warn( domain, - _( "profile \"%s\" incompatible with image" ), + g_warning( _( "profile \"%s\" incompatible with image" ), filename ); return( NULL ); } @@ -615,8 +614,7 @@ vips_icc_import_build( VipsObject *object ) if( code->in && (import->embedded || !import->input_profile_filename) ) - icc->in_profile = vips_icc_load_profile_image( class->nickname, - code->in ); + icc->in_profile = vips_icc_load_profile_image( code->in ); if( !icc->in_profile && code->in && @@ -1027,8 +1025,7 @@ vips_icc_transform_build( VipsObject *object ) if( code->in && (transform->embedded || !transform->input_profile_filename) ) - icc->in_profile = vips_icc_load_profile_image( class->nickname, - code->in ); + icc->in_profile = vips_icc_load_profile_image( code->in ); if( !icc->in_profile && code->in && diff --git a/libvips/conversion/cast.c b/libvips/conversion/cast.c index 3e4bb0fb..6e658ee5 100644 --- a/libvips/conversion/cast.c +++ b/libvips/conversion/cast.c @@ -126,11 +126,9 @@ vips_cast_preeval( VipsImage *image, VipsProgress *progress, VipsCast *cast ) static void vips_cast_posteval( VipsImage *image, VipsProgress *progress, VipsCast *cast ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( cast ); - - if( cast->overflow || cast->underflow ) - vips_warn( class->nickname, - _( "%d underflows and %d overflows detected" ), + if( cast->overflow || + cast->underflow ) + g_warning( _( "%d underflows and %d overflows detected" ), cast->underflow, cast->overflow ); } diff --git a/libvips/conversion/copy.c b/libvips/conversion/copy.c index ff3ee88e..168a0281 100644 --- a/libvips/conversion/copy.c +++ b/libvips/conversion/copy.c @@ -177,7 +177,7 @@ vips_copy_build( VipsObject *object ) return( -1 ); if( copy->swap ) - vips_warn( class->nickname, "%s", + g_warning( "%s", _( "copy swap is deprecated, use byteswap instead" ) ); if( vips_image_pipelinev( conversion->out, diff --git a/libvips/conversion/sequential.c b/libvips/conversion/sequential.c index 62b19c35..0942c2a1 100644 --- a/libvips/conversion/sequential.c +++ b/libvips/conversion/sequential.c @@ -124,7 +124,6 @@ vips_sequential_generate( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) { VipsSequential *sequential = (VipsSequential *) b; - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( sequential ); VipsRect *r = &or->valid; VipsRegion *ir = (VipsRegion *) seq; @@ -132,8 +131,7 @@ vips_sequential_generate( VipsRegion *or, g_thread_self(), r->top, r->height ); if( sequential->trace ) - vips_info( class->nickname, - "request for line %d, height %d", + g_info( "request for line %d, height %d", r->top, r->height ); VIPS_GATE_START( "vips_sequential_generate: wait" ); diff --git a/libvips/conversion/tilecache.c b/libvips/conversion/tilecache.c index d7299d0e..789e73a6 100644 --- a/libvips/conversion/tilecache.c +++ b/libvips/conversion/tilecache.c @@ -605,7 +605,6 @@ vips_tile_cache_gen( VipsRegion *or, { VipsRegion *in = (VipsRegion *) seq; VipsBlockCache *cache = (VipsBlockCache *) b; - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( cache ); VipsRect *r = &or->valid; VipsTile *tile; @@ -702,8 +701,7 @@ vips_tile_cache_gen( VipsRegion *or, "vips_tile_cache_gen: " "error on tile %p\n", tile ); - vips_warn( class->nickname, - _( "error in tile %d x %d" ), + g_warning( _( "error in tile %d x %d" ), tile->pos.left, tile->pos.top ); vips_region_black( tile->region ); diff --git a/libvips/convolution/convi.c b/libvips/convolution/convi.c index d11e3606..b7c6845c 100644 --- a/libvips/convolution/convi.c +++ b/libvips/convolution/convi.c @@ -860,7 +860,7 @@ intize_to_fixed_point( VipsImage *in, int *out ) for( i = 0; i < ne; i++ ) if( scaled[i] >= 4.0 || scaled[i] < -4 ) { - vips_info( "intize_to_fixed_point", + g_info( "intize_to_fixed_point: " "out of range for vector path" ); return( -1 ); } @@ -880,7 +880,7 @@ intize_to_fixed_point( VipsImage *in, int *out ) /* 0.1 is a 10% error. */ if( total_error > 0.1 ) { - vips_info( "intize_to_fixed_point", "too many underflows" ); + g_info( "intize_to_fixed_point: too many underflows" ); return( -1 ); } @@ -906,7 +906,6 @@ intize_to_fixed_point( VipsImage *in, int *out ) static int vips_convi_build( VipsObject *object ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsConvolution *convolution = (VipsConvolution *) object; VipsConvi *convi = (VipsConvi *) object; VipsImage **t = (VipsImage **) vips_object_local_array( object, 4 ); @@ -941,7 +940,7 @@ vips_convi_build( VipsObject *object ) !intize_to_fixed_point( M, convi->fixed ) && !vips_convi_compile( convi, in ) ) { generate = vips_convi_gen_vector; - vips_info( class->nickname, "using vector path" ); + g_info( "using vector path" ); } else vips_convi_compile_free( convi ); @@ -980,7 +979,7 @@ vips_convi_build( VipsObject *object ) } generate = vips_convi_gen; - vips_info( class->nickname, "using C path" ); + g_info( "using C path" ); } g_object_set( convi, "out", vips_image_new(), NULL ); diff --git a/libvips/convolution/gaussblur.c b/libvips/convolution/gaussblur.c index 41800097..d5bfc29a 100644 --- a/libvips/convolution/gaussblur.c +++ b/libvips/convolution/gaussblur.c @@ -67,7 +67,6 @@ G_DEFINE_TYPE( VipsGaussblur, vips_gaussblur, VIPS_TYPE_OPERATION ); static int vips_gaussblur_build( VipsObject *object ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsGaussblur *gaussblur = (VipsGaussblur *) object; VipsImage **t = (VipsImage **) vips_object_local_array( object, 2 ); @@ -85,7 +84,7 @@ vips_gaussblur_build( VipsObject *object ) vips_matrixprint( t[0], NULL ); #endif /*DEBUG*/ - vips_info( class->nickname, "gaussblur mask width %d", t[0]->Xsize ); + g_info( "gaussblur mask width %d", t[0]->Xsize ); if( vips_convsep( gaussblur->in, &t[1], t[0], "precision", gaussblur->precision, diff --git a/libvips/deprecated/rename.c b/libvips/deprecated/rename.c index 7285ffa9..9244cc33 100644 --- a/libvips/deprecated/rename.c +++ b/libvips/deprecated/rename.c @@ -724,3 +724,67 @@ vips_check_bands_3ormore( const char *domain, VipsImage *im ) { return( vips_check_bands_atleast( domain, im, 3 ) ); } + +/* The old vips_info() stuff, now replaced by g_warning() / g_info(). + */ + +int vips__info = 0; + +void +vips_info_set( gboolean info ) +{ + vips__info = info; +} + +void +vips_vinfo( const char *domain, const char *fmt, va_list ap ) +{ + if( vips__info ) { + g_mutex_lock( vips__global_lock ); + (void) fprintf( stderr, _( "%s: " ), _( "info" ) ); + if( domain ) + (void) fprintf( stderr, _( "%s: " ), domain ); + (void) vfprintf( stderr, fmt, ap ); + (void) fprintf( stderr, "\n" ); + g_mutex_unlock( vips__global_lock ); + } +} + +void +vips_info( const char *domain, const char *fmt, ... ) +{ + va_list ap; + + va_start( ap, fmt ); + vips_vinfo( domain, fmt, ap ); + va_end( ap ); +} + +void +vips_vwarn( const char *domain, const char *fmt, va_list ap ) +{ + if( !g_getenv( "IM_WARNING" ) && + !g_getenv( "VIPS_WARNING" ) ) { + g_mutex_lock( vips__global_lock ); + (void) fprintf( stderr, _( "%s: " ), _( "vips warning" ) ); + if( domain ) + (void) fprintf( stderr, _( "%s: " ), domain ); + (void) vfprintf( stderr, fmt, ap ); + (void) fprintf( stderr, "\n" ); + g_mutex_unlock( vips__global_lock ); + } + + if( vips__fatal ) + vips_error_exit( "vips__fatal" ); +} + +void +vips_warn( const char *domain, const char *fmt, ... ) +{ + va_list ap; + + va_start( ap, fmt ); + vips_vwarn( domain, fmt, ap ); + va_end( ap ); +} + diff --git a/libvips/foreign/csv.c b/libvips/foreign/csv.c index 95f71f18..70aae08c 100644 --- a/libvips/foreign/csv.c +++ b/libvips/foreign/csv.c @@ -195,8 +195,7 @@ read_double( FILE *fp, const char whitemap[256], const char sepmap[256], /* Only a warning, since (for example) exported spreadsheets * will often have text or date fields. */ - vips_warn( "csv2vips", - _( "error parsing number, line %d, column %d" ), + g_warning( _( "error parsing number, line %d, column %d" ), lineno, colno ); if( fail ) return( EOF ); diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index f9897e8e..d35e55cb 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -1997,8 +1997,7 @@ vips_foreign_save_dz_build( VipsObject *object ) #ifndef HAVE_GSF_DEFLATE_LEVEL if( dz->compression > 0 ) { - vips_warn( class->nickname, "%s", - _( "deflate-level not supported by libgsf, " + g_warning( _( "deflate-level not supported by libgsf, " "using default compression" ) ); dz->compression = -1; } diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index ea7d03e3..65322c72 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -846,9 +846,9 @@ vips_foreign_load_build( VipsObject *object ) if( (flags & VIPS_FOREIGN_PARTIAL) && (flags & VIPS_FOREIGN_SEQUENTIAL) ) { - vips_warn( class->nickname, "%s", + g_warning( "%s", _( "VIPS_FOREIGN_PARTIAL and VIPS_FOREIGN_SEQUENTIAL " - "both set -- using SEQUENTIAL" ) ); + "both set -- using SEQUENTIAL" ) ); flags ^= VIPS_FOREIGN_PARTIAL; } @@ -865,12 +865,10 @@ vips_foreign_load_build( VipsObject *object ) build( object ) ) return( -1 ); - if( load->sequential ) { - vips_warn( class->nickname, "%s", - _( "ignoring deprecated \"sequential\" mode" ) ); - vips_warn( class->nickname, "%s", - _( "please use \"access\" instead" ) ); - } + if( load->sequential ) + g_warning( "%s", + _( "ignoring deprecated \"sequential\" mode -- " + "please use \"access\" instead" ) ); g_object_set( object, "out", vips_image_new(), NULL ); diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index 789598f1..5b347ac6 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -359,7 +359,6 @@ static void vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif, int width, VipsPel * restrict q, VipsPel * restrict p ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); ColorMapObject *map = gif->file->Image.ColorMap ? gif->file->Image.ColorMap : gif->file->SColorMap; @@ -369,8 +368,7 @@ vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif, VipsPel v = p[x]; if( v >= map->ColorCount ) { - vips_warn( class->nickname, - "%s", _( "pixel value out of range" ) ); + g_warning( "%s", _( "pixel value out of range" ) ); continue; } diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c index 676ccd7e..dce2ed28 100644 --- a/libvips/foreign/jpeg2vips.c +++ b/libvips/foreign/jpeg2vips.c @@ -192,10 +192,9 @@ readjpeg_free( ReadJpeg *jpeg ) result = 0; if( jpeg->eman.pub.num_warnings != 0 ) { - vips_warn( "VipsJpeg", - _( "read gave %ld warnings" ), + g_warning( _( "read gave %ld warnings" ), jpeg->eman.pub.num_warnings ); - vips_warn( NULL, "%s", vips_error_buffer() ); + g_warning( "%s", vips_error_buffer() ); /* Make the message only appear once. */ @@ -575,8 +574,7 @@ res_from_exif( VipsImage *im, ExifData *ed ) if( get_entry_double( ed, 0, EXIF_TAG_X_RESOLUTION, &xres ) || get_entry_double( ed, 0, EXIF_TAG_Y_RESOLUTION, &yres ) || get_entry_int( ed, 0, EXIF_TAG_RESOLUTION_UNIT, &unit ) ) { - vips_warn( "VipsJpeg", - "%s", _( "error reading resolution" ) ); + g_warning( "%s", _( "error reading resolution" ) ); return( -1 ); } @@ -612,8 +610,7 @@ res_from_exif( VipsImage *im, ExifData *ed ) break; default: - vips_warn( "VipsJpeg", - "%s", _( "unknown EXIF resolution unit" ) ); + g_warning( "%s", _( "unknown EXIF resolution unit" ) ); return( -1 ); } @@ -827,8 +824,7 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out ) break; default: - vips_warn( "VipsJpeg", - "%s", _( "unknown JFIF resolution unit" ) ); + g_warning( "%s", _( "unknown JFIF resolution unit" ) ); break; } diff --git a/libvips/foreign/ppm.c b/libvips/foreign/ppm.c index 22315526..9a7677f5 100644 --- a/libvips/foreign/ppm.c +++ b/libvips/foreign/ppm.c @@ -799,9 +799,8 @@ vips__ppm_save( VipsImage *in, const char *filename, if( ascii && in->BandFmt == VIPS_FORMAT_FLOAT ) { - vips_warn( "vips2ppm", - "%s", _( "float images must be binary -- " - "disabling ascii" ) ); + g_warning( "%s", + _( "float images must be binary -- disabling ascii" ) ); ascii = FALSE; } @@ -810,8 +809,8 @@ vips__ppm_save( VipsImage *in, const char *filename, if( squash && (in->Bands != 1 || in->BandFmt != VIPS_FORMAT_UCHAR) ) { - vips_warn( "vips2ppm", - "%s", _( "can only squash 1 band uchar images -- " + g_warning( "%s", + _( "can only squash 1 band uchar images -- " "disabling squash" ) ); squash = FALSE; } diff --git a/libvips/foreign/tiff.c b/libvips/foreign/tiff.c index 07bf926d..dd9bdcd2 100644 --- a/libvips/foreign/tiff.c +++ b/libvips/foreign/tiff.c @@ -58,8 +58,7 @@ #include "tiff.h" /* Handle TIFF errors here. Shared with vips2tiff.c. These can be called from - * more than one thread, but vips_error and vips_warn have mutexes in, so that's - * OK. + * more than one thread. */ static void vips__thandler_error( const char *module, const char *fmt, va_list ap ) @@ -73,10 +72,7 @@ vips__thandler_error( const char *module, const char *fmt, va_list ap ) static void vips__thandler_warning( const char *module, const char *fmt, va_list ap ) { - char buf[256]; - - vips_vsnprintf( buf, 256, fmt, ap ); - vips_warn( module, "%s", buf ); + g_logv( G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, ap ); } /* Call this during startup. Other libraries may be using libtiff and we want diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 0ec28d7f..5fae35ce 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -387,7 +387,7 @@ get_resolution( TIFF *tiff, VipsImage *out ) } } else { - vips_warn( "tiff2vips", _( "no resolution information for " + g_warning( _( "no resolution information for " "TIFF image \"%s\" -- defaulting to 1 pixel per mm" ), TIFFFileName( tiff ) ); x = 1.0; @@ -1041,7 +1041,7 @@ rtiff_parse_palette( Rtiff *rtiff, VipsImage *out ) read->blue8[i] = read->blue16[i] >> 8; } else { - vips_warn( "tiff2vips", "%s", _( "assuming 8-bit palette" ) ); + g_warning( "%s", _( "assuming 8-bit palette" ) ); for( i = 0; i < len; i++ ) { read->red8[i] = read->red16[i] & 0xff; diff --git a/libvips/foreign/vips2jpeg.c b/libvips/foreign/vips2jpeg.c index 9a6834b3..83ba17bc 100644 --- a/libvips/foreign/vips2jpeg.c +++ b/libvips/foreign/vips2jpeg.c @@ -495,8 +495,7 @@ vips__set_exif_resolution( ExifData *ed, VipsImage *im ) break; default: - vips_warn( "VipsJpeg", - "%s", _( "unknown EXIF resolution unit" ) ); + g_warning( "%s", _( "unknown EXIF resolution unit" ) ); return( 0 ); } @@ -626,7 +625,7 @@ vips_exif_image_field( VipsImage *image, /* value must be a string. */ if( vips_image_get_string( image, field, &string ) ) { - vips_warn( "VipsJpeg", _( "bad exif meta \"%s\"" ), field ); + g_warning( _( "bad exif meta \"%s\"" ), field ); return( NULL ); } @@ -636,12 +635,12 @@ vips_exif_image_field( VipsImage *image, for( ; isdigit( *p ); p++ ) ; if( *p != '-' ) { - vips_warn( "VipsJpeg", _( "bad exif meta \"%s\"" ), field ); + g_warning( _( "bad exif meta \"%s\"" ), field ); return( NULL ); } if( !(tag = exif_tag_from_name( p + 1 )) ) { - vips_warn( "VipsJpeg", _( "bad exif meta \"%s\"" ), field ); + g_warning( _( "bad exif meta \"%s\"" ), field ); return( NULL ); } @@ -764,7 +763,7 @@ write_blob( Write *write, const char *field, int app ) * For now, just ignore oversize objects and warn. */ if( data_length > 65530 ) - vips_warn( "VipsJpeg", _( "field \"%s\" is too large " + g_warning( _( "field \"%s\" is too large " "for a single JPEG marker, ignoring" ), field ); else { @@ -1082,8 +1081,7 @@ write_vips( Write *write, int qfac, const char *profile, write->cinfo.optimize_coding = TRUE; } else - vips_warn( "vips2jpeg", - "%s", _( "trellis_quant unsupported" ) ); + g_warning( "%s", _( "trellis_quant unsupported" ) ); } /* Apply overshooting to samples with extreme values e.g. 0 & 255 @@ -1095,8 +1093,8 @@ write_vips( Write *write, int qfac, const char *profile, jpeg_c_set_bool_param( &write->cinfo, JBOOLEAN_OVERSHOOT_DERINGING, TRUE ); else - vips_warn( "vips2jpeg", - "%s", _( "overshoot_deringing unsupported" ) ); + g_warning( "%s", + _( "overshoot_deringing unsupported" ) ); } /* Split the spectrum of DCT coefficients into separate scans. * Requires progressive output. Must be set before @@ -1109,12 +1107,12 @@ write_vips( Write *write, int qfac, const char *profile, jpeg_c_set_bool_param( &write->cinfo, JBOOLEAN_OPTIMIZE_SCANS, TRUE ); else - vips_warn( "vips2jpeg", - "%s", _( "Ignoring optimize_scans" ) ); + g_warning( "%s", + _( "ignoring optimize_scans" ) ); } else - vips_warn( "vips2jpeg", "%s", - _( "Ignoring optimize_scans for baseline" ) ); + g_warning( "%s", + _( "ignoring optimize_scans for baseline" ) ); } /* Use predefined quantization table. @@ -1125,22 +1123,21 @@ write_vips( Write *write, int qfac, const char *profile, jpeg_c_set_int_param( &write->cinfo, JINT_BASE_QUANT_TBL_IDX, quant_table ); else - vips_warn( "vips2jpeg", - "%s", _( "Setting quant_table unsupported" ) ); + g_warning( "%s", + _( "setting quant_table unsupported" ) ); } #else /* Using jpeglib.h without extension parameters, warn of ignored * options. */ if( trellis_quant ) - vips_warn( "vips2jpeg", "%s", _( "Ignoring trellis_quant" ) ); + g_warning( "%s", _( "ignoring trellis_quant" ) ); if( overshoot_deringing ) - vips_warn( "vips2jpeg", - "%s", _( "Ignoring overshoot_deringing" ) ); + g_warning( "%s", _( "ignoring overshoot_deringing" ) ); if( optimize_scans ) - vips_warn( "vips2jpeg", "%s", _( "Ignoring optimize_scans" ) ); + g_warning( "%s", _( "ignoring optimize_scans" ) ); if( quant_table > 0 ) - vips_warn( "vips2jpeg", "%s", _( "Ignoring quant_table" ) ); + g_warning( "%s", _( "ignoring quant_table" ) ); #endif /* Set compression quality. Must be called after setting params above. diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 8cd9f744..3f3858cf 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -464,8 +464,7 @@ wtiff_embed_ipct( Wtiff *wtiff, TIFF *tif ) * long, not byte. */ if( data_length & 3 ) { - vips_warn( "vips2tiff", - "%s", _( "rounding up IPCT data length" ) ); + g_warning( "%s", _( "rounding up IPCT data length" ) ); data_length /= 4; data_length += 1; } @@ -958,8 +957,8 @@ wtiff_new( VipsImage *im, const char *filename, /* We can't pyramid toilet roll images. */ if( wtiff->pyramid ) { - vips_warn( "vips2tiff", - "%s", _( "can't pyramid multi page images --- " + g_warning( "%s", + _( "can't pyramid multi page images --- " "disabling pyramid" ) ); wtiff->pyramid = FALSE; } @@ -995,16 +994,16 @@ wtiff_new( VipsImage *im, const char *filename, (im->Coding != VIPS_CODING_NONE || im->BandFmt != VIPS_FORMAT_UCHAR || im->Bands != 1) ) { - vips_warn( "vips2tiff", - "%s", _( "can only squash 1 band uchar images -- " + g_warning( "%s", + _( "can only squash 1 band uchar images -- " "disabling squash" ) ); wtiff->onebit = 0; } if( wtiff->onebit && wtiff->compression == COMPRESSION_JPEG ) { - vips_warn( "vips2tiff", - "%s", _( "can't have 1-bit JPEG -- disabling JPEG" ) ); + g_warning( "%s", + _( "can't have 1-bit JPEG -- disabling JPEG" ) ); wtiff->compression = COMPRESSION_NONE; } @@ -1014,8 +1013,8 @@ wtiff_new( VipsImage *im, const char *filename, (im->Coding != VIPS_CODING_NONE || vips_band_format_iscomplex( im->BandFmt ) || im->Bands > 2) ) { - vips_warn( "vips2tiff", - "%s", _( "can only save non-complex greyscale images " + g_warning( "%s", + _( "can only save non-complex greyscale images " "as miniswhite -- disabling miniswhite" ) ); wtiff->miniswhite = FALSE; } diff --git a/libvips/foreign/vips2webp.c b/libvips/foreign/vips2webp.c index 662aae5b..7d3f49e0 100644 --- a/libvips/foreign/vips2webp.c +++ b/libvips/foreign/vips2webp.c @@ -161,11 +161,9 @@ write_webp( WebPPicture *pic, VipsImage *in, pic->use_argb = lossless || near_lossless || smart_subsample; #else if( lossless || near_lossless ) - vips_warn( "vips2webp", - "%s", _( "lossless unsupported" ) ); + g_warning( "%s", _( "lossless unsupported" ) ); if( alpha_q != 100 ) - vips_warn( "vips2webp", - "%s", _( "alpha_q unsupported" ) ); + g_warning( "%s", _( "alpha_q unsupported" ) ); #endif #if WEBP_ENCODER_ABI_VERSION >= 0x0209 @@ -175,11 +173,9 @@ write_webp( WebPPicture *pic, VipsImage *in, config.preprocessing |= 4; #else if( near_lossless ) - vips_warn( "vips2webp", - "%s", _( "near_lossless unsupported" ) ); + g_warning( "%s", _( "near_lossless unsupported" ) ); if( smart_subsample ) - vips_warn( "vips2webp", - "%s", _( "smart_subsample unsupported" ) ); + g_warning( "%s", _( "smart_subsample unsupported" ) ); #endif if( !WebPValidateConfig( &config ) ) { diff --git a/libvips/histogram/maplut.c b/libvips/histogram/maplut.c index 09631b95..95cb7054 100644 --- a/libvips/histogram/maplut.c +++ b/libvips/histogram/maplut.c @@ -106,11 +106,8 @@ static void vips_maplut_posteval( VipsImage *image, VipsProgress *progress, VipsMaplut *maplut ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( maplut ); - if( maplut->overflow ) - vips_warn( class->nickname, - _( "%d overflows detected" ), maplut->overflow ); + g_warning( _( "%d overflows detected" ), maplut->overflow ); } /* Our sequence value: the region this sequence is using, and local stats. diff --git a/libvips/include/vips/almostdeprecated.h b/libvips/include/vips/almostdeprecated.h index c16bf350..26d90937 100644 --- a/libvips/include/vips/almostdeprecated.h +++ b/libvips/include/vips/almostdeprecated.h @@ -284,6 +284,14 @@ int im_plotpoint( IMAGE *im, int x, int y, PEL *pel ); int im_smudge( IMAGE *image, int ix, int iy, VipsRect *r ); int im_smear( IMAGE *im, int ix, int iy, VipsRect *r ); +void vips_warn( const char *domain, const char *fmt, ... ) + __attribute__((format(printf, 2, 3))); +void vips_vwarn( const char *domain, const char *fmt, va_list ap ); +void vips_info_set( gboolean info ); +void vips_info( const char *domain, const char *fmt, ... ) + __attribute__((format(printf, 2, 3))); +void vips_vinfo( const char *domain, const char *fmt, va_list ap ); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/include/vips/error.h b/libvips/include/vips/error.h index 6afa66cf..1b697afa 100644 --- a/libvips/include/vips/error.h +++ b/libvips/include/vips/error.h @@ -50,13 +50,6 @@ void vips_verror_system( int err, const char *domain, const char *fmt, va_list ap ); void vips_error_g( GError **error ); void vips_g_error( GError **error ); -void vips_warn( const char *domain, const char *fmt, ... ) - __attribute__((format(printf, 2, 3))); -void vips_vwarn( const char *domain, const char *fmt, va_list ap ); -void vips_info_set( gboolean info ); -void vips_info( const char *domain, const char *fmt, ... ) - __attribute__((format(printf, 2, 3))); -void vips_vinfo( const char *domain, const char *fmt, va_list ap ); void vips_error_exit( const char *fmt, ... ) __attribute__((noreturn, format(printf, 1, 2))); diff --git a/libvips/include/vips/vips.h b/libvips/include/vips/vips.h index cb21d52d..30be4706 100644 --- a/libvips/include/vips/vips.h +++ b/libvips/include/vips/vips.h @@ -166,12 +166,10 @@ extern "C" { #define VIPS_INIT( ARGV0 ) \ (vips_version( 3 ) - vips_version( 5 ) != \ VIPS_LIBRARY_CURRENT - VIPS_LIBRARY_AGE ? ( \ - vips_info( "vips_init", "ABI mismatch" ), \ - vips_info( "vips_init", \ - "library has ABI version %d", \ + g_warning( "ABI mismatch" ), \ + g_warning( "library has ABI version %d", \ vips_version( 3 ) - vips_version( 5 ) ), \ - vips_info( "vips_init", \ - "application needs ABI version %d", \ + g_warning( "application needs ABI version %d", \ VIPS_LIBRARY_CURRENT - VIPS_LIBRARY_AGE ), \ vips_error( "vips_init", "ABI mismatch" ), \ -1 ) : \ diff --git a/libvips/iofuncs/error.c b/libvips/iofuncs/error.c index d1f53d8b..546a4e2c 100644 --- a/libvips/iofuncs/error.c +++ b/libvips/iofuncs/error.c @@ -105,10 +105,6 @@ * supposed to indicate the component which failed. */ -/* Show info messages. Handy for debugging. - */ -int vips__info = 0; - /* Make global array to keep the error message buffer. */ #define VIPS_MAX_ERROR (10240) @@ -371,125 +367,6 @@ vips_error_clear( void ) g_mutex_unlock( vips__global_lock ); } -/** - * vips_info_set: - * @info: %TRUE to enable info messages - * - * If set, vips will output various informative messages to stderr as it works. - * - * See also: vips_info(). - */ -void -vips_info_set( gboolean info ) -{ - vips__info = info; -} - -/** - * vips_vinfo: - * @domain: the source of the message - * @fmt: printf()-style format string for the message - * @ap: arguments to the format string - * - * Sends a formatted informational message to stderr if the --vips-info flag - * has been given to the program, or the environment variable VIPS_INFO has been - * defined, or if vips_info_set() has been called. - * - * Informational messages are used to report details about the operation of - * functions. - * - * See also: vips_info(), vips_info_set(), vips_warn(). - */ -void -vips_vinfo( const char *domain, const char *fmt, va_list ap ) -{ - if( vips__info ) { - g_mutex_lock( vips__global_lock ); - (void) fprintf( stderr, _( "%s: " ), _( "info" ) ); - if( domain ) - (void) fprintf( stderr, _( "%s: " ), domain ); - (void) vfprintf( stderr, fmt, ap ); - (void) fprintf( stderr, "\n" ); - g_mutex_unlock( vips__global_lock ); - } -} - -/** - * vips_info: - * @domain: the source of the diagnostic message - * @fmt: printf()-style format string for the message - * @...: arguments to the format string - * - * Sends a formatted informational message to stderr if the --vips-info flag - * has been given to the program or the environment variable VIPS_INFO has been - * defined, or if vips_info_set() has been called. - * - * Informational messages are used to report details about the operation of - * functions. - * - * See also: vips_info_set(), vips_vinfo(), vips_warn(). - */ -void -vips_info( const char *domain, const char *fmt, ... ) -{ - va_list ap; - - va_start( ap, fmt ); - vips_vinfo( domain, fmt, ap ); - va_end( ap ); -} - -/** - * vips_vwarn: - * @domain: the source of the warning message - * @fmt: printf()-style format string for the message - * @ap: arguments to the format string - * - * Exactly as vips_warn(), but takes a va_list argument. - * - * See also: vips_warn(). - */ -void -vips_vwarn( const char *domain, const char *fmt, va_list ap ) -{ - if( !g_getenv( "IM_WARNING" ) && - !g_getenv( "VIPS_WARNING" ) ) { - g_mutex_lock( vips__global_lock ); - (void) fprintf( stderr, _( "%s: " ), _( "vips warning" ) ); - if( domain ) - (void) fprintf( stderr, _( "%s: " ), domain ); - (void) vfprintf( stderr, fmt, ap ); - (void) fprintf( stderr, "\n" ); - g_mutex_unlock( vips__global_lock ); - } - - if( vips__fatal ) - vips_error_exit( "vips__fatal" ); -} - -/** - * vips_warn: - * @domain: the source of the warning message - * @fmt: printf()-style format string for the message - * @...: arguments to the format string - * - * Sends a formatted warning message to stderr. If you define the - * environment variable VIPS_WARNING, these message are supressed. - * - * Warning messages are used to report things like overflow counts. - * - * See also: vips_info(), vips_vwarn(). - */ -void -vips_warn( const char *domain, const char *fmt, ... ) -{ - va_list ap; - - va_start( ap, fmt ); - vips_vwarn( domain, fmt, ap ); - va_end( ap ); -} - /** * vips_error_exit: * @fmt: printf()-style format string for the message diff --git a/libvips/iofuncs/gate.c b/libvips/iofuncs/gate.c index 41f72978..84dcbcc2 100644 --- a/libvips/iofuncs/gate.c +++ b/libvips/iofuncs/gate.c @@ -143,8 +143,7 @@ vips_thread_profile_save( VipsThreadProfile *profile ) vips__file_open_write( "vips-profile.txt", TRUE ); if( !vips__thread_fp ) { g_mutex_unlock( vips__global_lock ); - vips_warn( "VipsGate", - "%s", "unable to create profile log" ); + g_warning( "unable to create profile log" ); return; } @@ -204,8 +203,7 @@ vips__thread_profile_init_cb( VipsThreadProfile *profile ) * been called. */ if( vips__thread_profile ) - vips_warn( "VipsGate", - "discarding unsaved state for thread %p --- " + g_warning( "discarding unsaved state for thread %p --- " "call vips_thread_shutdown() for this thread", profile->thread ); diff --git a/libvips/iofuncs/generate.c b/libvips/iofuncs/generate.c index e24cd1d7..58a9c013 100644 --- a/libvips/iofuncs/generate.c +++ b/libvips/iofuncs/generate.c @@ -411,7 +411,7 @@ vips_image_pipelinev( VipsImage *image, VipsDemandStyle hint, ... ) ; va_end( ap ); if( i == MAX_IMAGES ) { - vips_warn( "vips_image_pipeline", "%s", _( "too many images" ) ); + g_warning( "%s", _( "too many images" ) ); /* Make sure we have a sentinel there. */ diff --git a/libvips/iofuncs/image.c b/libvips/iofuncs/image.c index 50627744..79475600 100644 --- a/libvips/iofuncs/image.c +++ b/libvips/iofuncs/image.c @@ -1006,8 +1006,7 @@ vips_image_build( VipsObject *object ) * still be able to process it without coredumps. */ if( image->file_length > sizeof_image ) - vips_warn( "VipsImage", - _( "%s is longer than expected" ), + g_warning( _( "%s is longer than expected" ), image->filename ); break; @@ -2590,9 +2589,8 @@ vips_image_write_to_memory( VipsImage *in, size_t *size_out ) vips_error( "vips_image_write_to_memory", _( "out of memory --- size == %dMB" ), (int) (size / (1024.0 * 1024.0)) ); - vips_warn( "vips_image_write_to_memory", - _( "out of memory --- size == %dMB" ), - (int) (size / (1024.0*1024.0)) ); + g_warning( _( "out of memory --- size == %dMB" ), + (int) (size / (1024.0 * 1024.0)) ); return( NULL ); } @@ -3143,8 +3141,7 @@ vips_image_wio_input( VipsImage *image ) * generate from this image. */ if( image->regions ) - vips_warn( "vips_image_wio_input", "%s", - "rewinding image with active regions" ); + g_warning( "rewinding image with active regions" ); break; diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c index 3aa7a4c4..a851f354 100644 --- a/libvips/iofuncs/init.c +++ b/libvips/iofuncs/init.c @@ -226,10 +226,8 @@ vips_load_plugins( const char *fmt, ... ) module = g_module_open( path, G_MODULE_BIND_LAZY ); if( !module ) { - vips_warn( "vips_init", - _( "unable to load \"%s\" -- %s" ), - path, - g_module_error() ); + g_warning( _( "unable to load \"%s\" -- %s" ), + path, g_module_error() ); result = -1; } } @@ -343,11 +341,14 @@ vips_init( const char *argv0 ) bindtextdomain( GETTEXT_PACKAGE, name ); bind_textdomain_codeset( GETTEXT_PACKAGE, "UTF-8" ); - /* Default various settings from env. + /* Deprecated, this is just for compat. */ if( g_getenv( "VIPS_INFO" ) || g_getenv( "IM_INFO" ) ) vips_info_set( TRUE ); + + /* Default various settings from env. + */ if( g_getenv( "VIPS_TRACE" ) ) vips_cache_set_trace( TRUE ); @@ -391,7 +392,7 @@ vips_init( const char *argv0 ) */ if( im_load_plugins( "%s/vips-%d.%d", libdir, VIPS_MAJOR_VERSION, VIPS_MINOR_VERSION ) ) { - vips_warn( "vips_init", "%s", vips_error_buffer() ); + g_warning( "%s", vips_error_buffer() ); vips_error_clear(); } @@ -399,7 +400,7 @@ vips_init( const char *argv0 ) * :-( kept for back compat convenience. */ if( im_load_plugins( "%s", libdir ) ) { - vips_warn( "vips_init", "%s", vips_error_buffer() ); + g_warning( "%s", vips_error_buffer() ); vips_error_clear(); } diff --git a/libvips/iofuncs/mapfile.c b/libvips/iofuncs/mapfile.c index 3c7c3e3d..37e1cf90 100644 --- a/libvips/iofuncs/mapfile.c +++ b/libvips/iofuncs/mapfile.c @@ -177,7 +177,7 @@ vips__mmap( int fd, int writeable, size_t length, gint64 offset ) if( baseaddr == MAP_FAILED ) { vips_error_system( errno, "vips_mapfile", "%s", _( "unable to mmap" ) ); - vips_warn( "vips_mapfile", _( "map failed (%s), " + g_warning( _( "map failed (%s), " "running very low on system resources, " "expect a crash soon" ), strerror( errno ) ); return( NULL ); diff --git a/libvips/iofuncs/memory.c b/libvips/iofuncs/memory.c index c45dbdc1..58686750 100644 --- a/libvips/iofuncs/memory.c +++ b/libvips/iofuncs/memory.c @@ -248,11 +248,9 @@ vips_tracked_free( void *s ) g_mutex_lock( vips_tracked_mutex ); if( vips_tracked_allocs <= 0 ) - vips_warn( "vips_tracked", - "%s", _( "vips_free: too many frees" ) ); + g_warning( "%s", _( "vips_free: too many frees" ) ); if( vips_tracked_mem < size ) - vips_warn( "vips_tracked", - "%s", _( "vips_free: too much free" ) ); + g_warning( "%s", _( "vips_free: too much free" ) ); vips_tracked_mem -= size; vips_tracked_allocs -= 1; @@ -310,8 +308,7 @@ vips_tracked_malloc( size_t size ) vips_error( "vips_tracked", _( "out of memory --- size == %dMB" ), (int) (size / (1024.0 * 1024.0)) ); - vips_warn( "vips_tracked", - _( "out of memory --- size == %dMB" ), + g_warning( _( "out of memory --- size == %dMB" ), (int) (size / (1024.0 * 1024.0)) ); return( NULL ); diff --git a/libvips/iofuncs/operation.c b/libvips/iofuncs/operation.c index 6bd7fb85..4dfd5905 100644 --- a/libvips/iofuncs/operation.c +++ b/libvips/iofuncs/operation.c @@ -221,12 +221,8 @@ vips_operation_finalize( GObject *gobject ) VIPS_DEBUG_MSG( "vips_operation_finalize: %p\n", gobject ); - if( operation->pixels ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gobject ); - - vips_info( class->nickname, - _( "%d pixels calculated" ), operation->pixels ); - } + if( operation->pixels ) + g_info( _( "%d pixels calculated" ), operation->pixels ); G_OBJECT_CLASS( vips_operation_parent_class )->finalize( gobject ); } diff --git a/libvips/iofuncs/region.c b/libvips/iofuncs/region.c index 766f2104..0ecc619e 100644 --- a/libvips/iofuncs/region.c +++ b/libvips/iofuncs/region.c @@ -269,8 +269,7 @@ vips__region_stop( VipsRegion *region ) * can really do with it, sadly. */ if( result ) - vips_warn( "VipsRegion", - "stop callback failed for image %s", + g_warning( "stop callback failed for image %s", image->filename ); region->seq = NULL; diff --git a/libvips/iofuncs/system.c b/libvips/iofuncs/system.c index 6357f869..4a7ea5ee 100644 --- a/libvips/iofuncs/system.c +++ b/libvips/iofuncs/system.c @@ -231,8 +231,7 @@ vips_system_build( VipsObject *object ) if( std_error ) { vips__chomp( std_error ); if( strcmp( std_error, "" ) != 0 ) - vips_warn( class->nickname, - _( "stderr output: %s" ), std_error ); + g_warning( _( "stderr output: %s" ), std_error ); } if( std_output ) { vips__chomp( std_output ); diff --git a/libvips/iofuncs/threadpool.c b/libvips/iofuncs/threadpool.c index 172f92cc..68640805 100644 --- a/libvips/iofuncs/threadpool.c +++ b/libvips/iofuncs/threadpool.c @@ -407,8 +407,7 @@ vips_concurrency_get( void ) if( nthr < 1 || nthr > MAX_THREADS ) { nthr = VIPS_CLIP( 1, nthr, MAX_THREADS ); - vips_warn( "vips_concurrency_get", - _( "threads clipped to %d" ), nthr ); + g_warning( _( "threads clipped to %d" ), nthr ); } /* Save for next time around. diff --git a/libvips/iofuncs/vector.c b/libvips/iofuncs/vector.c index f405c3ad..65b71889 100644 --- a/libvips/iofuncs/vector.c +++ b/libvips/iofuncs/vector.c @@ -73,7 +73,7 @@ vips_vector_error( VipsVector *vector ) { #ifdef HAVE_ORC_PROGRAM_GET_ERROR if( vector->program ) - vips_warn( "VipsVector", "orc error: %s", + g_warning( "orc error: %s", orc_program_get_error( vector->program ) ); #endif /*HAVE_ORC_PROGRAM_GET_ERROR*/ } diff --git a/libvips/iofuncs/vips.c b/libvips/iofuncs/vips.c index ef44ad6a..749007ff 100644 --- a/libvips/iofuncs/vips.c +++ b/libvips/iofuncs/vips.c @@ -915,8 +915,7 @@ vips_image_open_input( VipsImage *image ) return( -1 ); image->file_length = rsize; if( psize > rsize ) - vips_warn( "VipsImage", - _( "unable to read data for \"%s\", %s" ), + g_warning( _( "unable to read data for \"%s\", %s" ), image->filename, _( "file has been truncated" ) ); /* Set demand style. This suits a disc file we read sequentially. @@ -928,8 +927,7 @@ vips_image_open_input( VipsImage *image ) * harmless. */ if( readhist( image ) ) { - vips_warn( "VipsImage", _( "error reading XML: %s" ), - vips_error_buffer() ); + g_warning( _( "error reading XML: %s" ), vips_error_buffer() ); vips_error_clear(); } diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 0fda98eb..52ec7d3d 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -465,7 +465,7 @@ vips_reduceh_build( VipsObject *object ) */ reduceh->n_point = vips_reduce_get_points( reduceh->kernel, reduceh->hshrink ); - vips_info( object_class->nickname, "%d point mask", reduceh->n_point ); + g_info( "%d point mask", reduceh->n_point ); if( reduceh->n_point > MAX_POINT ) { vips_error( object_class->nickname, "%s", _( "reduce factor too large" ) ); diff --git a/libvips/resample/reducev.cpp b/libvips/resample/reducev.cpp index b8017ebc..63a03c43 100644 --- a/libvips/resample/reducev.cpp +++ b/libvips/resample/reducev.cpp @@ -779,7 +779,7 @@ vips_reducev_raw( VipsReducev *reducev, VipsImage *in ) if( in->BandFmt == VIPS_FORMAT_UCHAR && vips_vector_isenabled() && !vips_reducev_compile( reducev ) ) { - vips_info( object_class->nickname, "using vector path" ); + g_info( "using vector path" ); generate = vips_reducev_vector_gen; } @@ -848,7 +848,7 @@ vips_reducev_build( VipsObject *object ) "%s", _( "reduce factor too large" ) ); return( -1 ); } - vips_info( object_class->nickname, "%d point mask", reducev->n_point ); + g_info( "%d point mask", reducev->n_point ); /* Unpack for processing. */ diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index 59d963c8..419717e5 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -155,7 +155,6 @@ vips_resize_interpolate( VipsKernel kernel ) static int vips_resize_build( VipsObject *object ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsResample *resample = VIPS_RESAMPLE( object ); VipsResize *resize = (VipsResize *) object; @@ -186,7 +185,7 @@ vips_resize_build( VipsObject *object ) int_vshrink = vips_resize_int_shrink( resize, vscale ); if( int_vshrink > 1 ) { - vips_info( class->nickname, "shrinkv by %d", int_vshrink ); + g_info( "shrinkv by %d", int_vshrink ); if( vips_shrinkv( in, &t[0], int_vshrink, NULL ) ) return( -1 ); in = t[0]; @@ -195,7 +194,7 @@ vips_resize_build( VipsObject *object ) } if( int_hshrink > 1 ) { - vips_info( class->nickname, "shrinkh by %d", int_hshrink ); + g_info( "shrinkh by %d", int_hshrink ); if( vips_shrinkh( in, &t[1], int_hshrink, NULL ) ) return( -1 ); in = t[1]; @@ -251,8 +250,7 @@ vips_resize_build( VipsObject *object ) /* Any residual downsizing. */ if( vscale < 1.0 ) { - vips_info( class->nickname, "residual reducev by %g", - vscale ); + g_info( "residual reducev by %g", vscale ); if( vips_reducev( in, &t[2], 1.0 / vscale, "kernel", resize->kernel, "centre", resize->centre, @@ -262,7 +260,7 @@ vips_resize_build( VipsObject *object ) } if( hscale < 1.0 ) { - vips_info( class->nickname, "residual reduceh by %g", + g_info( "residual reduceh by %g", hscale ); if( vips_reduceh( in, &t[3], 1.0 / hscale, "kernel", resize->kernel, @@ -285,8 +283,7 @@ vips_resize_build( VipsObject *object ) if( hscale > 1.0 && vscale > 1.0 ) { - vips_info( class->nickname, - "residual scale %g x %g", hscale, vscale ); + g_info( "residual scale %g x %g", hscale, vscale ); if( vips_affine( in, &t[4], hscale, 0.0, 0.0, vscale, "interpolate", interpolate, @@ -295,8 +292,7 @@ vips_resize_build( VipsObject *object ) in = t[4]; } else if( hscale > 1.0 ) { - vips_info( class->nickname, - "residual scale %g", hscale ); + g_info( "residual scale %g", hscale ); if( vips_affine( in, &t[4], hscale, 0.0, 0.0, 1.0, "interpolate", interpolate, NULL ) ) @@ -304,8 +300,7 @@ vips_resize_build( VipsObject *object ) in = t[4]; } else { - vips_info( class->nickname, - "residual scale %g", vscale ); + g_info( "residual scale %g", vscale ); if( vips_affine( in, &t[4], 1.0, 0.0, 0.0, vscale, "interpolate", interpolate, NULL ) ) diff --git a/libvips/resample/thumbnail.c b/libvips/resample/thumbnail.c index dcf24259..c90c5610 100644 --- a/libvips/resample/thumbnail.c +++ b/libvips/resample/thumbnail.c @@ -195,9 +195,8 @@ vips_thumbnail_open( VipsThumbnail *thumbnail ) if( class->get_info( thumbnail ) ) return( NULL ); - vips_info( "thumbnail", "selected loader is %s", - thumbnail->loader ); - vips_info( "thumbnail", "input size is %d x %d", + g_info( "selected loader is %s", thumbnail->loader ); + g_info( "input size is %d x %d", thumbnail->input_width, thumbnail->input_height ); shrink = 1; @@ -206,21 +205,18 @@ vips_thumbnail_open( VipsThumbnail *thumbnail ) if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) { shrink = vips_thumbnail_find_jpegshrink( thumbnail, thumbnail->input_width, thumbnail->input_height ); - vips_info( "thumbnail", - "loading jpeg with factor %d pre-shrink", shrink ); + g_info( "loading jpeg with factor %d pre-shrink", shrink ); } else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) || vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) { scale = 1.0 / vips_thumbnail_calculate_shrink( thumbnail, thumbnail->input_width, thumbnail->input_height ); - vips_info( "thumbnail", - "loading PDF/SVG with factor %g pre-scale", scale ); + g_info( "loading PDF/SVG with factor %g pre-scale", scale ); } else if( vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) { shrink = vips_thumbnail_calculate_shrink( thumbnail, thumbnail->input_width, thumbnail->input_height ); - vips_info( "thumbnail", - "loading webp with factor %d pre-shrink", shrink ); + g_info( "loading webp with factor %d pre-shrink", shrink ); } if( !(im = class->open( thumbnail, shrink, scale )) ) @@ -269,7 +265,7 @@ vips_thumbnail_build( VipsObject *object ) /* RAD needs special unpacking. */ if( in->Coding == VIPS_CODING_RAD ) { - vips_info( "thumbnail", "unpacking Rad to float" ); + g_info( "unpacking Rad to float" ); /* rad is scrgb. */ @@ -296,11 +292,9 @@ vips_thumbnail_build( VipsObject *object ) (vips_image_get_typeof( in, VIPS_META_ICC_NAME ) || thumbnail->import_profile) ) { if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) - vips_info( "thumbnail", - "importing with embedded profile" ); + g_info( "importing with embedded profile" ); else - vips_info( "thumbnail", - "importing with profile %s", + g_info( "importing with profile %s", thumbnail->import_profile ); if( vips_icc_import( in, &t[1], @@ -317,7 +311,7 @@ vips_thumbnail_build( VipsObject *object ) /* To the processing colourspace. This will unpack LABQ as well. */ - vips_info( "thumbnail", "converting to processing space %s", + g_info( "converting to processing space %s", vips_enum_nick( VIPS_TYPE_INTERPRETATION, interpretation ) ); if( vips_colourspace( in, &t[2], interpretation, NULL ) ) return( -1 ); @@ -328,7 +322,7 @@ vips_thumbnail_build( VipsObject *object ) */ have_premultiplied = FALSE; if( vips_image_hasalpha( in ) ) { - vips_info( "thumbnail", "premultiplying alpha" ); + g_info( "premultiplying alpha" ); if( vips_premultiply( in, &t[3], NULL ) ) return( -1 ); have_premultiplied = TRUE; @@ -353,7 +347,7 @@ vips_thumbnail_build( VipsObject *object ) in = t[4]; if( have_premultiplied ) { - vips_info( "thumbnail", "unpremultiplying alpha" ); + g_info( "unpremultiplying alpha" ); if( vips_unpremultiply( in, &t[5], NULL ) || vips_cast( t[5], &t[6], unpremultiplied_format, NULL ) ) return( -1 ); @@ -369,8 +363,7 @@ vips_thumbnail_build( VipsObject *object ) if( have_imported ) { if( thumbnail->export_profile || vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) { - vips_info( "thumbnail", - "exporting to device space with a profile" ); + g_info( "exporting to device space with a profile" ); if( vips_icc_export( in, &t[7], "output_profile", thumbnail->export_profile, NULL ) ) @@ -378,7 +371,7 @@ vips_thumbnail_build( VipsObject *object ) in = t[7]; } else { - vips_info( "thumbnail", "converting to sRGB" ); + g_info( "converting to sRGB" ); if( vips_colourspace( in, &t[7], VIPS_INTERPRETATION_sRGB, NULL ) ) return( -1 ); @@ -390,23 +383,20 @@ vips_thumbnail_build( VipsObject *object ) thumbnail->import_profile) ) { VipsImage *out; - vips_info( "thumbnail", - "exporting with profile %s", thumbnail->export_profile ); + g_info( "exporting with profile %s", thumbnail->export_profile ); /* We first try with the embedded profile, if any, then if * that fails try again with the supplied fallback profile. */ out = NULL; if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) { - vips_info( "thumbnail", - "importing with embedded profile" ); + g_info( "importing with embedded profile" ); if( vips_icc_transform( in, &t[7], thumbnail->export_profile, "embedded", TRUE, NULL ) ) { - vips_warn( "thumbnail", - _( "unable to import with " + g_warning( _( "unable to import with " "embedded profile: %s" ), vips_error_buffer() ); @@ -418,8 +408,7 @@ vips_thumbnail_build( VipsObject *object ) if( !out && thumbnail->import_profile ) { - vips_info( "thumbnail", - "importing with fallback profile" ); + g_info( "importing with fallback profile" ); if( vips_icc_transform( in, &t[7], thumbnail->export_profile, @@ -442,7 +431,7 @@ vips_thumbnail_build( VipsObject *object ) int left = (in->Xsize - thumbnail->width) / 2; int top = (in->Ysize - thumbnail->height) / 2; - vips_info( "thumbnail", "cropping to %dx%d", + g_info( "cropping to %dx%d", thumbnail->width, thumbnail->height ); if( vips_extract_area( in, &t[8], left, top, thumbnail->width, thumbnail->height, NULL ) ) @@ -454,7 +443,7 @@ vips_thumbnail_build( VipsObject *object ) thumbnail->angle != VIPS_ANGLE_D0 ) { VipsAngle angle = vips_autorot_get_angle( in ); - vips_info( "thumbnail", "rotating by %s", + g_info( "rotating by %s", vips_enum_nick( VIPS_TYPE_ANGLE, angle ) ); /* Need to copy to memory, we have to stay seq. @@ -573,7 +562,7 @@ vips_thumbnail_file_get_info( VipsThumbnail *thumbnail ) VipsImage *image; - vips_info( "thumbnail", "thumbnailing %s", file->filename ); + g_info( "thumbnailing %s", file->filename ); if( !(thumbnail->loader = vips_foreign_find_load( file->filename )) || !(image = vips_image_new_from_file( file->filename, NULL )) ) @@ -726,8 +715,7 @@ vips_thumbnail_buffer_get_info( VipsThumbnail *thumbnail ) VipsImage *image; - vips_info( "thumbnail", "thumbnailing %zd bytes of data", - buffer->buf->length ); + g_info( "thumbnailing %zd bytes of data", buffer->buf->length ); if( !(thumbnail->loader = vips_foreign_find_load_buffer( buffer->buf->data, buffer->buf->length )) || diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index e09c8421..e6a184e4 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -213,8 +213,7 @@ thumbnail_write( VipsObject *process, VipsImage *im, const char *filename ) g_free( dir ); } - vips_info( "vipsthumbnail", - "thumbnailing %s as %s", filename, output_name ); + g_info( "thumbnailing %s as %s", filename, output_name ); g_free( file ); @@ -308,7 +307,7 @@ main( int argc, char **argv ) if( rotate_image ) { #ifndef HAVE_EXIF - vips_warn( "vipsthumbnail", "%s", + g_warning( "%s", _( "auto-rotate disabled: " "libvips built without exif support" ) ); #endif /*!HAVE_EXIF*/ From 0d97c78f0288f84bed1869ab45fd19b2279cf25a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 3 Jan 2017 17:06:50 +0000 Subject: [PATCH 36/46] compat macro for early glibs --- libvips/include/vips/util.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libvips/include/vips/util.h b/libvips/include/vips/util.h index fb8fb2a6..027acb11 100644 --- a/libvips/include/vips/util.h +++ b/libvips/include/vips/util.h @@ -135,6 +135,14 @@ G_STMT_START { \ } \ } G_STMT_END +/* The g_info() macro was added in 2.40. + */ +#ifndef g_info +/* Hopefully we have varargs macros. Maybe revisit this. + */ +#define g_info(...) \ + g_log( G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__ ) +#endif /* Various integer range clips. Record over/under flows. */ From e7a6ea3d0c25f0899388a167578ba6c5d49ca2f2 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 3 Jan 2017 17:12:42 +0000 Subject: [PATCH 37/46] oops! revert previous --- libvips/include/vips/util.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/libvips/include/vips/util.h b/libvips/include/vips/util.h index 027acb11..97fa0f3a 100644 --- a/libvips/include/vips/util.h +++ b/libvips/include/vips/util.h @@ -135,15 +135,6 @@ G_STMT_START { \ } \ } G_STMT_END -/* The g_info() macro was added in 2.40. - */ -#ifndef g_info -/* Hopefully we have varargs macros. Maybe revisit this. - */ -#define g_info(...) \ - g_log( G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__ ) -#endif - /* Various integer range clips. Record over/under flows. */ #define VIPS_CLIP_UCHAR( V, SEQ ) \ From 07e6ac19944e97a20d7cccf446ef1f188608b7ce Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Jan 2017 07:23:50 +0000 Subject: [PATCH 38/46] support --vips-info now turns on glib g_info() output --- TODO | 20 -------------------- libvips/deprecated/rename.c | 12 ++++++++++++ libvips/include/vips/util.h | 8 ++++++++ libvips/include/vips/vips.h | 4 ++++ libvips/iofuncs/error.c | 21 +++++++++++++++++++++ libvips/iofuncs/init.c | 24 ++++++++++++++++++------ 6 files changed, 63 insertions(+), 26 deletions(-) diff --git a/TODO b/TODO index 285d5ee1..715f4c8f 100644 --- a/TODO +++ b/TODO @@ -1,23 +1,3 @@ -- need some simple way to turn on info output from CLI or env var - -- switch to glib logging - - vips_error() stuff must stay - - vips_info() and vips_warn() can change - - - switch to g_warning() and g_info(), have some compat stuff - - just calls - - g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, format, args); - - also see - - vips_info_set(), --vips-info, VIPS_INFO - - - - not sure about utf8 error messages on win - strange: diff --git a/libvips/deprecated/rename.c b/libvips/deprecated/rename.c index 9244cc33..b489eefb 100644 --- a/libvips/deprecated/rename.c +++ b/libvips/deprecated/rename.c @@ -734,6 +734,18 @@ void vips_info_set( gboolean info ) { vips__info = info; + + if( info ) { + const char *old; + char *new; + + old = g_getenv( "G_MESSAGES_DEBUG" ); + if( !old ) + old = ""; + new = g_strdup_printf( "%s VIPS", old ); + g_setenv( "G_MESSAGES_DEBUG", new, TRUE ); + g_free( new ); + } } void diff --git a/libvips/include/vips/util.h b/libvips/include/vips/util.h index fb8fb2a6..027acb11 100644 --- a/libvips/include/vips/util.h +++ b/libvips/include/vips/util.h @@ -135,6 +135,14 @@ G_STMT_START { \ } \ } G_STMT_END +/* The g_info() macro was added in 2.40. + */ +#ifndef g_info +/* Hopefully we have varargs macros. Maybe revisit this. + */ +#define g_info(...) \ + g_log( G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__ ) +#endif /* Various integer range clips. Record over/under flows. */ diff --git a/libvips/include/vips/vips.h b/libvips/include/vips/vips.h index 30be4706..8bbbf61b 100644 --- a/libvips/include/vips/vips.h +++ b/libvips/include/vips/vips.h @@ -82,6 +82,10 @@ extern "C" { #endif /*__cplusplus*/ +/* Use glib structured logging, + */ +#define G_LOG_USE_STRUCTURED + #include #include #include diff --git a/libvips/iofuncs/error.c b/libvips/iofuncs/error.c index 546a4e2c..3ca63d29 100644 --- a/libvips/iofuncs/error.c +++ b/libvips/iofuncs/error.c @@ -103,6 +103,27 @@ * * The domain argument most of these functions take is not localised and is * supposed to indicate the component which failed. + * + * libvips uses g_warning() and g_info() to send warning and information + * messages to the user. You can use the usual glib mechanisms to display or + * divert these messages. For example, info messages are hidden by default, but + * you can see them with: + * + * |[ + * $ G_MESSAGES_DEBUG=VIPS vipsthumbnail k2.jpg + * VIPS-INFO: thumbnailing k2.jpg + * VIPS-INFO: selected loader is VipsForeignLoadJpegFile + * VIPS-INFO: input size is 1450 x 2048 + * VIPS-INFO: loading jpeg with factor 8 pre-shrink + * VIPS-INFO: converting to processing space srgb + * VIPS-INFO: residual reducev by 0.5 + * VIPS-INFO: 13 point mask + * VIPS-INFO: using vector path + * VIPS-INFO: residual reduceh by 0.5 + * VIPS-INFO: 13 point mask + * VIPS-INFO: thumbnailing k2.jpg as ./tn_k2.jpg + * ]| + * */ /* Make global array to keep the error message buffer. diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c index a851f354..07cd8b1f 100644 --- a/libvips/iofuncs/init.c +++ b/libvips/iofuncs/init.c @@ -343,6 +343,9 @@ vips_init( const char *argv0 ) /* Deprecated, this is just for compat. */ + + /* If set by --vips-info. + */ if( g_getenv( "VIPS_INFO" ) || g_getenv( "IM_INFO" ) ) vips_info_set( TRUE ); @@ -579,12 +582,12 @@ vips__ngettext( const char *msgid, const char *plural, unsigned long int n ) } static gboolean -vips_lib_version_cb( const gchar *option_name, const gchar *value, +vips_lib_info_cb( const gchar *option_name, const gchar *value, gpointer data, GError **error ) { - printf( "libvips %s\n", VIPS_VERSION_STRING ); - vips_shutdown(); - exit( 0 ); + vips_info_set( TRUE ); + + return( TRUE ); } static gboolean @@ -605,9 +608,18 @@ vips_set_fatal_cb( const gchar *option_name, const gchar *value, return( TRUE ); } +static gboolean +vips_lib_version_cb( const gchar *option_name, const gchar *value, + gpointer data, GError **error ) +{ + printf( "libvips %s\n", VIPS_VERSION_STRING ); + vips_shutdown(); + exit( 0 ); +} + static GOptionEntry option_entries[] = { - { "vips-info", 0, G_OPTION_FLAG_HIDDEN, - G_OPTION_ARG_NONE, &vips__info, + { "vips-info", 0, G_OPTION_FLAG_HIDDEN | G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, (gpointer) &vips_lib_info_cb, N_( "show informative messages" ), NULL }, { "vips-fatal", 0, G_OPTION_FLAG_HIDDEN | G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &vips_set_fatal_cb, From 3a48ff9cc1b5647632172386b00cd3e1d377c1bd Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Jan 2017 09:22:32 +0000 Subject: [PATCH 39/46] don't warn for missing exif res don't warn for missing exif res (even though it should be there) since we fall back to jfif cs3 seems to avoid the exif res fields and just uses jfif see https://github.com/lovell/sharp/issues/657 --- libvips/foreign/jpeg2vips.c | 11 +++++++---- libvips/foreign/vips2jpeg.c | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c index 676ccd7e..4b1f111e 100644 --- a/libvips/foreign/jpeg2vips.c +++ b/libvips/foreign/jpeg2vips.c @@ -79,6 +79,8 @@ * 07/09/16 * - Don't use the exif resolution if x_resolution / y_resolution / * resolution_unit is missing + * 4/1/17 + * - Don't warn for missing exif res, since we fall back to jfif now */ /* @@ -574,11 +576,12 @@ res_from_exif( VipsImage *im, ExifData *ed ) */ if( get_entry_double( ed, 0, EXIF_TAG_X_RESOLUTION, &xres ) || get_entry_double( ed, 0, EXIF_TAG_Y_RESOLUTION, &yres ) || - get_entry_int( ed, 0, EXIF_TAG_RESOLUTION_UNIT, &unit ) ) { - vips_warn( "VipsJpeg", - "%s", _( "error reading resolution" ) ); + get_entry_int( ed, 0, EXIF_TAG_RESOLUTION_UNIT, &unit ) ) + /* Don't warn. There should be resolution info in the exif + * (according to the spec), but many images skip this and rely + * on silent fallback to the jfif resolution. + */ return( -1 ); - } #ifdef DEBUG printf( "res_from_exif: seen exif tags " diff --git a/libvips/foreign/vips2jpeg.c b/libvips/foreign/vips2jpeg.c index 9a6834b3..2bd0278c 100644 --- a/libvips/foreign/vips2jpeg.c +++ b/libvips/foreign/vips2jpeg.c @@ -455,7 +455,7 @@ write_tag( ExifData *ed, int ifd, ExifTag tag, write_fn fn, void *data ) /* This is different, we set the xres/yres from the vips header rather than * from the exif tags on the image metadata. * - * This is also called from the jpg reader to fix up bad exif resoltion. + * This is also called from the jpg reader to fix up bad exif resolution. */ int vips__set_exif_resolution( ExifData *ed, VipsImage *im ) From 0c4a40e82fecb81d958381d0f53c093dbb4af865 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Jan 2017 10:18:54 +0000 Subject: [PATCH 40/46] oop missed a merge conflict --- libvips/include/vips/util.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/libvips/include/vips/util.h b/libvips/include/vips/util.h index b8653621..027acb11 100644 --- a/libvips/include/vips/util.h +++ b/libvips/include/vips/util.h @@ -135,7 +135,6 @@ G_STMT_START { \ } \ } G_STMT_END -<<<<<<< HEAD /* The g_info() macro was added in 2.40. */ #ifndef g_info @@ -145,8 +144,6 @@ G_STMT_START { \ g_log( G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__ ) #endif -======= ->>>>>>> master /* Various integer range clips. Record over/under flows. */ #define VIPS_CLIP_UCHAR( V, SEQ ) \ From a0b8bac8f2ce1d17142adfdc1468c3f51b516b0d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Jan 2017 12:27:23 +0000 Subject: [PATCH 41/46] load fits images to equiv. data type so we have better support for bzero / bscale --- libvips/foreign/fits.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/libvips/foreign/fits.c b/libvips/foreign/fits.c index 583c272a..7dd95dbd 100644 --- a/libvips/foreign/fits.c +++ b/libvips/foreign/fits.c @@ -23,6 +23,9 @@ * - redo as a set of fns ready for wrapping in a new-style class * 23/6/13 * - fix ushort save with values >32k, thanks weaverwb + * 4/1/17 + * - load to equivalent data type, not raw image data type ... improves + * support for BSCALE / BZERO settings */ /* @@ -119,7 +122,7 @@ typedef struct { VipsPel *buffer; } VipsFits; -const char *vips__fits_suffs[] = { ".fits", NULL }; +const char *vips__fits_suffs[] = { ".fits", ".fit", NULL }; static void vips_fits_error( int status ) @@ -219,10 +222,21 @@ vips_fits_get_header( VipsFits *fits, VipsImage *out ) return( -1 ); } + /* cfitsio does automatic conversion from the format stored in + * the file to the equivalent type after scale/offset. We need + * to allocate a vips image of the equivalent type, not the original + * type. + */ + if( fits_get_img_equivtype( fits->fptr, &bitpix, &status ) ) { + vips_fits_error( status ); + return( -1 ); + } + #ifdef VIPS_DEBUG VIPS_DEBUG_MSG( "naxis = %d\n", fits->naxis ); for( i = 0; i < fits->naxis; i++ ) VIPS_DEBUG_MSG( "%d) %lld\n", i, fits->naxes[i] ); + VIPS_DEBUG_MSG( "fits2vips: bitpix = %d\n", bitpix ); #endif /*VIPS_DEBUG*/ height = 1; @@ -266,8 +280,8 @@ vips_fits_get_header( VipsFits *fits, VipsImage *out ) if( fits->band_select != -1 ) bands = 1; - /* Get image format. We want the 'raw' format of the image, our caller - * can convert using the meta info if they want. + /* Get image format. This is the equivalent format, or the format + * stored in the file. */ for( i = 0; i < VIPS_NUMBER( fits2vips_formats ); i++ ) if( fits2vips_formats[i][0] == bitpix ) From c06dc8455b5f61b4a7434fec7d50e0c6021da141 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Jan 2017 12:40:58 +0000 Subject: [PATCH 42/46] add another FITS suffix --- libvips/foreign/fits.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvips/foreign/fits.c b/libvips/foreign/fits.c index 7dd95dbd..dac90adb 100644 --- a/libvips/foreign/fits.c +++ b/libvips/foreign/fits.c @@ -122,7 +122,7 @@ typedef struct { VipsPel *buffer; } VipsFits; -const char *vips__fits_suffs[] = { ".fits", ".fit", NULL }; +const char *vips__fits_suffs[] = { ".fits", ".fit", ".fts", NULL }; static void vips_fits_error( int status ) From b29201595d37ac8ee5e7bc896d1a0f5f5cf49889 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Jan 2017 12:56:04 +0000 Subject: [PATCH 43/46] note fits improvements in changelog --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index ab5930cb..bc900b2c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,6 +16,7 @@ - gifload supports n/page-height - added #defines for VIPS_SONAME, VIPS_LIBRARY_CURRENT, VIPS_LIBRARY_REVISION, VIPS_LIBRARY_AGE +- better support for bscale / bzero in fits images 8/12/16 started 8.4.5 - allow libgsf-1.14.26 to help centos, thanks tdiprima From 9581e14dee513d3c368caa8b52be449f9d9d4348 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Jan 2017 14:45:35 +0000 Subject: [PATCH 44/46] all done --- ChangeLog | 3 +-- doc/using-command-line.xml | 8 ++++++++ libvips/iofuncs/init.c | 3 --- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2cdfb374..de091699 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,8 +16,7 @@ - gifload supports n/page-height - added #defines for VIPS_SONAME, VIPS_LIBRARY_CURRENT, VIPS_LIBRARY_REVISION, VIPS_LIBRARY_AGE -- deprecate vips_warn(), use g_warning() instead -- deprecate vips_info(), use g_info() instead +- deprecate vips_warn() / vips_info(); use g_warning() / g_info() instead 8/12/16 started 8.4.5 - allow libgsf-1.14.26 to help centos, thanks tdiprima diff --git a/doc/using-command-line.xml b/doc/using-command-line.xml index 95f48ec5..828fc256 100644 --- a/doc/using-command-line.xml +++ b/doc/using-command-line.xml @@ -208,6 +208,14 @@ rm t1.v leak-test on exit, and also display an estimate of peak memory use. + + + + Set G_MESSAGES_DEBUG=VIPS and GLib will display + informational and debug messages from libvips. + + + diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c index 07cd8b1f..d8bfd35f 100644 --- a/libvips/iofuncs/init.c +++ b/libvips/iofuncs/init.c @@ -343,9 +343,6 @@ vips_init( const char *argv0 ) /* Deprecated, this is just for compat. */ - - /* If set by --vips-info. - */ if( g_getenv( "VIPS_INFO" ) || g_getenv( "IM_INFO" ) ) vips_info_set( TRUE ); From 784c7c0669cb2f1515b0fb96e52bc6f2fd99e41c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Jan 2017 14:53:45 +0000 Subject: [PATCH 45/46] don't enable structured logging by default --- libvips/include/vips/vips.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libvips/include/vips/vips.h b/libvips/include/vips/vips.h index 8bbbf61b..30be4706 100644 --- a/libvips/include/vips/vips.h +++ b/libvips/include/vips/vips.h @@ -82,10 +82,6 @@ extern "C" { #endif /*__cplusplus*/ -/* Use glib structured logging, - */ -#define G_LOG_USE_STRUCTURED - #include #include #include From 0dd51c43acd491f5fff8d7de0ec2019044c2d4f0 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Jan 2017 15:51:59 +0000 Subject: [PATCH 46/46] don't rebuild everything all the time oops, soname.h was being rewritten all the time, triggering a full rebuild every time --- libvips/Makefile.am | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libvips/Makefile.am b/libvips/Makefile.am index fa678ff6..d530f8ad 100644 --- a/libvips/Makefile.am +++ b/libvips/Makefile.am @@ -65,8 +65,12 @@ EXTRA_DIST = \ CLEANFILES = all-local: - echo '/* This file is autogenerated, do not edit. */' > include/vips/soname.h; \ - source libvips.la; echo "#define VIPS_SONAME \"$$dlname\"" >> include/vips/soname.h; + echo '/* This file is autogenerated, do not edit. */' > soname.h && \ + source libvips.la && \ + echo "#define VIPS_SONAME \"$$dlname\"" >> soname.h && \ + ( cmp -s soname.h include/vips/soname.h || \ + cp soname.h include/vips ) && \ + rm soname.h -include $(INTROSPECTION_MAKEFILE) INTROSPECTION_GIRS =