diff --git a/ChangeLog b/ChangeLog index 1e4a0583..d8b3982f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,7 +5,7 @@ - allow \ as an escape character in vips_break_token() [akemrir] - tiffsave has a "depth" param to set max pyr depth - libtiff LOGLUV images load and save as libvips XYZ -- add gifload_source, csvload_source, csvsave_target +- add gifload_source, csvload_source, csvsave_target, matrixload_source - revise vipsthumbnail flags - add VIPS_LEAK env var - add vips_pipe_read_limit_set(), --vips-pipe-read-limit, diff --git a/libvips/foreign/csvload.c b/libvips/foreign/csvload.c index 2505e449..df5d2665 100644 --- a/libvips/foreign/csvload.c +++ b/libvips/foreign/csvload.c @@ -89,7 +89,7 @@ typedef struct _VipsForeignLoadCsv { * a double. */ char item[MAX_ITEM_SIZE]; - + /* A line of pixels. */ double *linebuf; @@ -295,8 +295,8 @@ vips_foreign_load_csv_read_double( VipsForeignLoadCsv *csv, double *out ) item = vips_foreign_load_csv_fetch_item( csv ); if( !item ) return( EOF ); - *out = g_ascii_strtod( item, NULL ); - if( errno ) + + if( vips_strtod( item, out ) ) /* Only a warning, since (for example) exported * spreadsheets will often have text or date fields. */ diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 56826faf..47b22647 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2039,7 +2039,8 @@ vips_foreign_operation_init( void ) extern GType vips_foreign_save_csv_file_get_type( void ); extern GType vips_foreign_save_csv_target_get_type( void ); - extern GType vips_foreign_load_matrix_get_type( void ); + extern GType vips_foreign_load_matrix_file_get_type( void ); + extern GType vips_foreign_load_matrix_source_get_type( void ); extern GType vips_foreign_save_matrix_get_type( void ); extern GType vips_foreign_print_matrix_get_type( void ); @@ -2115,7 +2116,8 @@ vips_foreign_operation_init( void ) vips_foreign_load_csv_source_get_type(); vips_foreign_save_csv_file_get_type(); vips_foreign_save_csv_target_get_type(); - vips_foreign_load_matrix_get_type(); + vips_foreign_load_matrix_file_get_type(); + vips_foreign_load_matrix_source_get_type(); vips_foreign_save_matrix_get_type(); vips_foreign_print_matrix_get_type(); vips_foreign_load_raw_get_type(); diff --git a/libvips/foreign/matrixload.c b/libvips/foreign/matrixload.c index 4f7c0b4b..305c1776 100644 --- a/libvips/foreign/matrixload.c +++ b/libvips/foreign/matrixload.c @@ -2,6 +2,8 @@ * * 5/12/11 * - from csvload.c + * 22/2/20 + * - rewrite for source API */ /* @@ -53,9 +55,17 @@ typedef struct _VipsForeignLoadMatrix { VipsForeignLoad parent_object; - /* Filename for load. + /* Set by subclasses. */ - char *filename; + VipsSource *source; + + /* Buffered source. + */ + VipsSbuf *sbuf; + + /* A line of pixels. + */ + double *linebuf; } VipsForeignLoadMatrix; @@ -64,18 +74,99 @@ typedef VipsForeignLoadClass VipsForeignLoadMatrixClass; G_DEFINE_TYPE( VipsForeignLoadMatrix, vips_foreign_load_matrix, VIPS_TYPE_FOREIGN_LOAD ); -static VipsForeignFlags -vips_foreign_load_matrix_get_flags_filename( const char *filename ) +static void +vips_foreign_load_matrix_dispose( GObject *gobject ) { + VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) gobject; + + VIPS_UNREF( matrix->source ); + VIPS_UNREF( matrix->sbuf ); + VIPS_FREE( matrix->linebuf ); + + G_OBJECT_CLASS( vips_foreign_load_matrix_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_load_matrix_build( VipsObject *object ) +{ + VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) object; + + if( !(matrix->sbuf = vips_sbuf_new_from_source( matrix->source )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_matrix_parent_class )-> + build( object ) ) + return( -1 ); + return( 0 ); } static VipsForeignFlags vips_foreign_load_matrix_get_flags( VipsForeignLoad *load ) { - VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) load; + return( 0 ); +} - return( vips_foreign_load_matrix_get_flags_filename( matrix->filename ) ); +/* Parse a header line. Two numbers for width and height, and two optional + * numbers for scale and offset. + * + * We can have scale and no offset, in which case we assume offset = 0. + */ +static int +parse_matrix_header( char *line, + int *width, int *height, double *scale, double *offset ) +{ + double header[4]; + char *p, *q; + int i; + + for( i = 0, p = line; + (q = vips_break_token( p, " \t" )) && + i < 4; + i++, p = q ) + if( vips_strtod( p, &header[i] ) ) { + vips_error( "matload", + _( "bad number \"%s\"" ), p ); + return( -1 ); + } + + if( i < 4 ) + header[3] = 0.0; + if( i < 3 ) + header[2] = 1.0; + if( i < 2 ) { + vips_error( "matload", "%s", _( "no width / height" ) ); + return( -1 ); + } + + if( VIPS_FLOOR( header[0] ) != header[0] || + VIPS_FLOOR( header[1] ) != header[1] ) { + vips_error( "mask2vips", "%s", _( "width / height not int" ) ); + return( -1 ); + } + + /* Width / height can be 65536 for a 16-bit LUT, for example. + */ + *width = header[0]; + *height = header[1]; + if( *width <= 0 || + *width > 100000 || + *height <= 0 || + *height > 100000 ) { + vips_error( "mask2vips", + "%s", _( "width / height out of range" ) ); + return( -1 ); + } + if( header[2] == 0.0 ) { + vips_error( "mask2vips", "%s", _( "zero scale" ) ); + return( -1 ); + } + + *scale = header[2]; + *offset = header[3]; + + return( 0 ); } static int @@ -83,23 +174,39 @@ vips_foreign_load_matrix_header( VipsForeignLoad *load ) { VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) load; + char *line; int width; int height; double scale; double offset; + int result; - if( vips__matrix_read_header( matrix->filename, - &width, &height, &scale, &offset ) ) + /* Rewind. + */ + vips_sbuf_unbuffer( matrix->sbuf ); + if( vips_source_rewind( matrix->source ) ) return( -1 ); + line = vips_sbuf_get_line_copy( matrix->sbuf ); + result = parse_matrix_header( line, &width, &height, &scale, &offset ); + g_free( line ); + if( result ) + return( -1 ); + + vips_image_pipelinev( load->out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); vips_image_init_fields( load->out, width, height, 1, VIPS_FORMAT_DOUBLE, VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, 1.0, 1.0 ); + vips_image_set_double( load->out, "scale", scale ); vips_image_set_double( load->out, "offset", offset ); - VIPS_SETSTR( load->out->filename, matrix->filename ); + VIPS_SETSTR( load->out->filename, + vips_connection_filename( VIPS_CONNECTION( matrix->source ) ) ); + + if( !(matrix->linebuf = VIPS_ARRAY( NULL, width, double )) ) + return( -1 ); return( 0 ); } @@ -108,22 +215,142 @@ static int vips_foreign_load_matrix_load( VipsForeignLoad *load ) { VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) load; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); - VipsImage *out; + int x, y; - if( !(out = vips__matrix_read( matrix->filename )) ) - return( -1 ); - if( vips_image_write( out, load->real ) ) { - g_object_unref( out ); - return( -1 ); + vips_image_pipelinev( load->real, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); + vips_image_init_fields( load->real, + load->out->Xsize, load->out->Ysize, 1, + VIPS_FORMAT_DOUBLE, + VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, 1.0, 1.0 ); + + for( y = 0; y < load->real->Ysize; y++ ) { + char *line; + char *p, *q; + + line = vips_sbuf_get_line_copy( matrix->sbuf ); + + for( x = 0, p = line; + (q = vips_break_token( p, " \t" )) && + x < load->out->Xsize; + x++, p = q ) + if( vips_strtod( p, &matrix->linebuf[x] ) ) { + g_free( line ); + vips_error( class->nickname, + _( "bad number \"%s\"" ), p ); + return( -1 ); + } + + g_free( line ); + + if( x != load->out->Xsize ) { + vips_error( class->nickname, + _( "line %d too short" ), y ); + return( -1 ); + } + + if( vips_image_write_line( load->real, y, + (VipsPel *) matrix->linebuf ) ) + return( -1 ); } - g_object_unref( out ); return( 0 ); } static void vips_foreign_load_matrix_class_init( VipsForeignLoadMatrixClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->dispose = vips_foreign_load_matrix_dispose; + + object_class->nickname = "matrixload_base"; + object_class->description = _( "load matrix" ); + object_class->build = vips_foreign_load_matrix_build; + + load_class->get_flags = vips_foreign_load_matrix_get_flags; + load_class->header = vips_foreign_load_matrix_header; + load_class->load = vips_foreign_load_matrix_load; + +} + +static void +vips_foreign_load_matrix_init( VipsForeignLoadMatrix *matrix ) +{ +} + +typedef struct _VipsForeignLoadMatrixFile { + VipsForeignLoadMatrix parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadMatrixFile; + +typedef VipsForeignLoadMatrixClass VipsForeignLoadMatrixFileClass; + +G_DEFINE_TYPE( VipsForeignLoadMatrixFile, vips_foreign_load_matrix_file, + vips_foreign_load_matrix_get_type() ); + +static VipsForeignFlags +vips_foreign_load_matrix_file_get_flags_filename( const char *filename ) +{ + return( 0 ); +} + +static int +vips_foreign_load_matrix_file_build( VipsObject *object ) +{ + VipsForeignLoadMatrixFile *file = (VipsForeignLoadMatrixFile *) object; + VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) object; + + if( file->filename ) + if( !(matrix->source = + vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_matrix_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static const char *vips_foreign_load_matrix_suffs[] = { + ".mat", + NULL +}; + +static gboolean +vips_foreign_load_matrix_file_is_a( const char *filename ) +{ + unsigned char line[80]; + guint64 bytes; + int width; + int height; + double scale; + double offset; + int result; + + if( (bytes = vips__get_bytes( filename, line, 79 )) <= 0 ) + return( FALSE ); + line[bytes] = '\0'; + + vips_error_freeze(); + result = parse_matrix_header( (char *) line, + &width, &height, &scale, &offset ); + vips_error_thaw(); + + return( result == 0 ); +} + +static void +vips_foreign_load_matrix_file_class_init( + VipsForeignLoadMatrixFileClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; @@ -134,31 +361,111 @@ vips_foreign_load_matrix_class_init( VipsForeignLoadMatrixClass *class ) gobject_class->get_property = vips_object_get_property; object_class->nickname = "matrixload"; - object_class->description = _( "load matrix from file" ); + object_class->build = vips_foreign_load_matrix_file_build; - foreign_class->suffs = vips__foreign_matrix_suffs; + foreign_class->suffs = vips_foreign_load_matrix_suffs; - /* is_a() is not that quick ... lower the priority. - */ - foreign_class->priority = -50; - - load_class->is_a = vips__matrix_ismatrix; + load_class->is_a = vips_foreign_load_matrix_file_is_a; load_class->get_flags_filename = - vips_foreign_load_matrix_get_flags_filename; - load_class->get_flags = vips_foreign_load_matrix_get_flags; - load_class->header = vips_foreign_load_matrix_header; - load_class->load = vips_foreign_load_matrix_load; + vips_foreign_load_matrix_file_get_flags_filename; - VIPS_ARG_STRING( class, "filename", 1, + VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), _( "Filename to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadMatrix, filename ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadMatrixFile, filename ), NULL ); + } static void -vips_foreign_load_matrix_init( VipsForeignLoadMatrix *matrix ) +vips_foreign_load_matrix_file_init( VipsForeignLoadMatrixFile *file ) +{ +} + +typedef struct _VipsForeignLoadMatrixSource { + VipsForeignLoadMatrix parent_object; + + VipsSource *source; + +} VipsForeignLoadMatrixSource; + +typedef VipsForeignLoadMatrixClass VipsForeignLoadMatrixSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadMatrixSource, vips_foreign_load_matrix_source, + vips_foreign_load_matrix_get_type() ); + +static int +vips_foreign_load_matrix_source_build( VipsObject *object ) +{ + VipsForeignLoadMatrixSource *source = + (VipsForeignLoadMatrixSource *) object; + VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) object; + + if( matrix->source ) { + matrix->source = source->source; + g_object_ref( matrix->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_matrix_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_matrix_source_is_a_source( VipsSource *source ) +{ + unsigned char *data; + size_t bytes_read; + char line[80]; + int width; + int height; + double scale; + double offset; + int result; + + if( (bytes_read = vips_source_sniff_at_most( source, + &data, 79 )) <= 0 ) + return( FALSE ); + vips_strncpy( line, (const char *) data, 80 ); + + vips_error_freeze(); + result = parse_matrix_header( line, + &width, &height, &scale, &offset ); + vips_error_thaw(); + + return( result == 0 ); +} + +static void +vips_foreign_load_matrix_source_class_init( + VipsForeignLoadMatrixFileClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "matrixload_source"; + object_class->build = vips_foreign_load_matrix_source_build; + + load_class->is_a_source = vips_foreign_load_matrix_source_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadMatrixSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_matrix_source_init( VipsForeignLoadMatrixSource *source ) { } @@ -190,7 +497,7 @@ vips_foreign_load_matrix_init( VipsForeignLoadMatrix *matrix ) * Extra characters at the ends of lines or at the end of the file are * ignored. * - * See also: vips_csvload(). + * See also: vips_matrixload(). * * Returns: 0 on success, -1 on error. */ @@ -207,3 +514,28 @@ vips_matrixload( const char *filename, VipsImage **out, ... ) return( result ); } +/** + * vips_matrixload_source: + * @source: source to load + * @out: (out): output image + * @...: %NULL-terminated list of optional named arguments + * + * Exactly as vips_matrixload(), but read from a source. + * + * See also: vips_matrixload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_matrixload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "matrixload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} + diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 52f7a344..3a2f620a 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -533,6 +533,8 @@ int vips_csvsave_target( VipsImage *in, VipsTarget *target, ... ) int vips_matrixload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_matrixload_source( VipsSource *source, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_matrixsave( VipsImage *in, const char *filename, ... ) __attribute__((sentinel)); int vips_matrixprint( VipsImage *in, ... ) diff --git a/libvips/include/vips/util.h b/libvips/include/vips/util.h index 61e09837..fe0d4b7e 100644 --- a/libvips/include/vips/util.h +++ b/libvips/include/vips/util.h @@ -346,6 +346,8 @@ const char *vips__windows_prefix( void ); char *vips__get_iso8601( void ); +int vips_strtod( const char *str, double *out ); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/iofuncs/util.c b/libvips/iofuncs/util.c index b143ed89..2399485d 100644 --- a/libvips/iofuncs/util.c +++ b/libvips/iofuncs/util.c @@ -2066,3 +2066,33 @@ vips__get_iso8601( void ) return( date ); } + +/* Convert a string to a double in the ASCII locale (ie. decimal point is + * "."). + */ +int +vips_strtod( const char *str, double *out ) +{ + const char *p; + + *out = 0; + + /* The str we fetched must contain at least 1 digit. This + * helps stop us trying to convert "MATLAB" (for example) to + * a number and getting zero. + */ + for( p = str; *p; p++ ) + if( isdigit( *p ) ) + break; + if( !*p ) + return( -1 ); + + /* This will fail for out of range numbers, like 1e343434, but + * is quite happy with eg. "banana". + */ + *out = g_ascii_strtod( str, NULL ); + if( errno ) + return( -1 ); + + return( 0 ); +}