diff --git a/ChangeLog b/ChangeLog index 357f5cc0..d1905dbb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -20,11 +20,13 @@ - interpolators use type introspection - added vips --list classes, does formats too - include sys/param.h to get PATH_MAX in more places -- VIPS_INTERPOLATE_SHIFT bumped to 12, we need the extra precision for u16 gel - data - added vips_format_get_flags() - oop, forgot to check for cancel during tiled tiff write - don't use mmap for tiff read: no performance advantage, chews up VM +- VIPS_INTERPOLATE_SHIFT bumped to 12, we need the extra precision for u16 gel + data +- added string->double transform for cmdline args +- merged class-params branch back into trunk 11/9/08 started 7.16.3 - oop typo in manpage for im_project() diff --git a/TODO b/TODO index edec7a98..af7d412f 100644 --- a/TODO +++ b/TODO @@ -1,62 +1,15 @@ +- try making vips_add(), an operator as a class +- nickname, description etc need to be properties so nip2 can read them + arg, they are class properties, argh, can we support these? -- add params - - use property system - - use construtors, see docs for g_object_new(), rather than vips8 build funcs - - nickname, description etc need to be properties so nip2 can read them - - add a g_get_children( "classname" ) -> ["child1-name", ..] to nip2, see old - vips8.c cooe? - - - -- started adding - -#define IM_TYPE_GOBJECT "gobject" /* A GObject of a specified class */ +- make a new package for "resample"? im_shrink & friends could go in there too - make a "deprecated" package too - im_affinei and im_affinei_all need docs -- all the interpolators - - want bicubic args to be - - IM_GOBJECT( "VipsInterpolate", "interpolate" ); - - ie. pass a gobject which is a sub-type of VipsInterpolate in an arg called - "interpolate" - - then we need to create an object of that class, get the param list, set from - any args, call object_init, and pass - - on return, unref the object - - so - - im_affinei_all in.v out.v "yafrsmooth(2.4)" 0.9 0 0 0.9 0 0 - - creates a yafrsmooth, sets the param, - - - in nip2, - - inter = g_object_new "VipsInterpolateBicubic" [$sharpness => 2.4]; - - also need something to return a list of GTypes below a name etc., find class - description:w - - - need k - - - -- make a new package for "resample"? im_shrink & friends could go in there too - - how to expose things like yafrsmooth's "sharpening" parameter to nip2/C++/Python? can we write a find-by-nickname function? eg. @@ -69,6 +22,18 @@ would get us the GType for VipsInterpolatorBicubic + add a g_get_children( "classname" ) -> ["child1-name", ..] to nip2, see old + vips8.c code? + + in nip2, + + inter = g_object_new "yafrsmooth" [$sharpness => 2.4]; + + also need something to return a list of GTypes below a name etc., find class + description:w + + need k + - redo the format system in this way too? need a way to init a package: the init would register all the types that @@ -87,13 +52,19 @@ now always included in vips.h -- build nip2 - - check mosaic1, global_balance, similarity etc. use of im__affine how can we move them to im_affinei ? -- rename Transformation -> Transform + + + + + + + + + - update the Portfiles on the mac and on the website from the macports ones diff --git a/include/vips/dispatch.h b/include/vips/dispatch.h index edddc9af..70468e4b 100644 --- a/include/vips/dispatch.h +++ b/include/vips/dispatch.h @@ -55,7 +55,7 @@ extern "C" { #define IM_TYPE_IMAGE "image" /* IMAGE descriptor */ #define IM_TYPE_DISPLAY "display" /* Display descriptor */ #define IM_TYPE_GVALUE "gvalue" /* GValue wrapper */ -#define IM_TYPE_GOBJECT "gobject" /* A GObject of a specified class */ +#define IM_TYPE_INTERPOLATE "interpolate"/* A subclass of VipsInterpolate */ typedef char *im_arg_type; /* Type of argument id */ /* Internal representation of an argument to an image processing function. @@ -210,6 +210,8 @@ extern im_type_desc im__output_display; extern im_type_desc im__input_gvalue; extern im_type_desc im__output_gvalue; +extern im_type_desc im__input_interpolate; + /* VIPS print functions. */ int im__iprint( im_object obj ); /* int */ @@ -255,6 +257,8 @@ int im__gprint( im_object obj ); /* GValue */ #define IM_INPUT_GVALUE( S ) { S, &im__input_gvalue, NULL } #define IM_OUTPUT_GVALUE( S ) { S, &im__output_gvalue, im__gprint } +#define IM_INPUT_INTERPOLATE( S ) { S, &im__input_interpolate, NULL } + /* Add a plug-in package. */ im_package *im_load_plugin( const char *name ); diff --git a/include/vips/format.h b/include/vips/format.h index 4d72038e..0d1b375f 100644 --- a/include/vips/format.h +++ b/include/vips/format.h @@ -1,4 +1,4 @@ -/* Abstract base type for supported image formats. Subclass this to add a new +/* Base type for supported image formats. Subclass this to add a new * format. */ @@ -50,15 +50,14 @@ extern "C" { (G_TYPE_INSTANCE_GET_CLASS( (obj), \ VIPS_TYPE_FORMAT, VipsFormatClass )) -/* Image file properties. OR these together to get the result of - * im_format_flags_fn(). 0 is default. +/* Image file properties. */ typedef enum { VIPS_FORMAT_NONE = 0, /* No flags set */ VIPS_FORMAT_PARTIAL = 1 /* Lazy read OK (eg. tiled tiff) */ } VipsFormatFlags; -/* Function protos for formats. +/* Don't instantiate these things, just use the class stuff. */ typedef struct _VipsFormat { diff --git a/include/vips/object.h b/include/vips/object.h index 097c7317..3190d6cd 100644 --- a/include/vips/object.h +++ b/include/vips/object.h @@ -34,6 +34,130 @@ extern "C" { #endif /*__cplusplus*/ +typedef struct _VipsObject VipsObject; +typedef struct _VipsObjectClass VipsObjectClass; + +/* Track extra stuff for arguments to objects + */ + +/* Flags we associate with each argument. + */ +typedef enum _VipsArgumentFlags { + VIPS_ARGUMENT_NONE = 0, + + /* Must be set in the constructor. + */ + VIPS_ARGUMENT_REQUIRED = 1, + + /* Can only be set in the constructor. + */ + VIPS_ARGUMENT_CONSTRUCT = 2, + + /* Can only be set once. + */ + VIPS_ARGUMENT_SET_ONCE = 4, + + /* Have input & output flags. Both set is an error; neither set is OK. + */ + + /* Is an input argument (one we depend on) ... if it's a gobject, we + * should ref it. In our _dispose(), we should unref it. + */ + VIPS_ARGUMENT_INPUT = 8, + + /* Is an output argument (one that depends on us) ... if it's a + * gobject, we should ref ourselves. We watch "destroy" on the + * argument: if it goes, we unref ourselves. If we dispose, we + * disconnect the signal. + */ + VIPS_ARGUMENT_OUTPUT = 16 +} VipsArgumentFlags; + +/* Useful flag combinations. User-visible ones are: + +VIPS_ARGUMENT_REQURED_INPUT Eg. the "left" argument for an add operation + +VIPS_ARGUMENT_OPTIONAL_INPUT Eg. the "caption" for an object + +VIPS_ARGUMENT_REQURED_OUTPUT Eg. the "result" of an add operation + +VIPS_ARGUMENT_OPTIONAL_OUTPUT Eg. the "width" of an image + + Other combinations are used internally, eg. supplying the cast-table for an + arithmetic operation + + */ + +#define VIPS_ARGUMENT_REQUIRED_INPUT \ + (VIPS_ARGUMENT_INPUT | VIPS_ARGUMENT_REQUIRED | \ + VIPS_ARGUMENT_CONSTRUCT | VIPS_ARGUMENT_SET_ONCE) + +#define VIPS_ARGUMENT_OPTIONAL_INPUT \ + (VIPS_ARGUMENT_INPUT | \ + VIPS_ARGUMENT_CONSTRUCT | VIPS_ARGUMENT_SET_ONCE) + +#define VIPS_ARGUMENT_REQUIRED_OUTPUT \ + (VIPS_ARGUMENT_OUTPUT | VIPS_ARGUMENT_REQUIRED | \ + VIPS_ARGUMENT_SET_ONCE) + +#define VIPS_ARGUMENT_OPTIONAL_OUTPUT \ + (VIPS_ARGUMENT_OUTPUT | \ + VIPS_ARGUMENT_SET_ONCE) + +/* Keep one of these for every argument. + */ +typedef struct _VipsArgument { + GParamSpec *pspec; /* pspec for this argument */ + + /* More stuff, see below */ +} VipsArgument; + +/* Keep one of these in the class struct for every argument. + */ +typedef struct _VipsArgumentClass { + VipsArgument parent; + + /* The class of the object we are an arg for. + */ + VipsObjectClass *object_class; + + VipsArgumentFlags flags; + guint offset; /* G_STRUCT_OFFSET of member in object */ +} VipsArgumentClass; + +/* Keep one of these in the object struct for every argument instance. + */ +typedef struct _VipsArgumentInstance { + VipsArgument parent; + + /* The object we are attached to. + */ + VipsObject *object; + + /* Has been set. + */ + gboolean assigned; + + /* If this is an output argument, keep the id of our "destroy" handler + * here. + */ + gulong destroy_id; +} VipsArgumentInstance; + +/* Need to look up our VipsArgument structs from a pspec. Just hash the + * pointer (ie. we assume pspecs are never shared, is this correct?) + */ +typedef GHashTable VipsArgumentTable; + +VipsArgumentInstance *vips__argument_get_instance( VipsArgumentClass *, + VipsObject *); +VipsArgument *vips__argument_table_lookup( VipsArgumentTable *, + GParamSpec *); +typedef void *(*VipsArgumentMapFn)( VipsObject *, GParamSpec *, + VipsArgumentClass *, VipsArgumentInstance *, void *a, void *b ); +void *vips_argument_map( VipsObject *object, + VipsArgumentMapFn fn, void *a, void *b ); + #define VIPS_TYPE_OBJECT (vips_object_get_type()) #define VIPS_OBJECT( obj ) \ (G_TYPE_CHECK_INSTANCE_CAST( (obj), VIPS_TYPE_OBJECT, VipsObject )) @@ -46,17 +170,25 @@ extern "C" { #define VIPS_OBJECT_GET_CLASS( obj ) \ (G_TYPE_INSTANCE_GET_CLASS( (obj), VIPS_TYPE_OBJECT, VipsObjectClass )) -typedef struct _VipsObject { +struct _VipsObject { GObject parent_object; - /* Optional instance name. - */ - char *name; -} VipsObject; + char *name; /* Optional instance name */ + gboolean constructed; /* Construct done and checked */ -typedef struct _VipsObjectClass { + /* Table of argument instances for this class and any derived classes. + */ + VipsArgumentTable *argument_table; +}; + +struct _VipsObjectClass { GObjectClass parent_class; + /* Build the object ... all argument properties have been set, + * now build the thing. + */ + int (*build)( VipsObject *object ); + /* Something about the object has changed. Should use glib's properties * but fix this later. */ @@ -78,14 +210,33 @@ typedef struct _VipsObjectClass { /* Class description. Used for help messages, so internationalised. */ const char *description; -} VipsObjectClass; + /* Table of arguments for this class and any derived classes. Order + * is important, so keep a traverse list too. We can't rely on the + * ordering given by g_object_class_list_properties() since it comes + * from a hash :-( + */ + VipsArgumentTable *argument_table; + GSList *argument_table_traverse; +}; + +void vips_object_set_property( GObject *gobject, + guint property_id, const GValue *value, GParamSpec *pspec ); +void vips_object_get_property( GObject *gobject, + guint property_id, GValue *value, GParamSpec *pspec ); + +int vips_object_build( VipsObject *object ); void *vips_object_changed( VipsObject *object ); void vips_object_print_class( VipsObjectClass *klass ); void vips_object_print( VipsObject *object ); GType vips_object_get_type( void ); +void vips_object_class_install_argument( VipsObjectClass *, + GParamSpec *pspec, VipsArgumentFlags flags, guint offset ); + +VipsObject *vips_object_new_from_string( const char *base, const char *str ); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/include/vips/util.h b/include/vips/util.h index 66df0db6..7c474aab 100644 --- a/include/vips/util.h +++ b/include/vips/util.h @@ -338,6 +338,21 @@ char *im__file_read( FILE *fp, const char *name, unsigned int *length_out ); char *im__file_read_name( const char *name, unsigned int *length_out ); int im__file_write( void *data, size_t size, size_t nmemb, FILE *stream ); +typedef enum { + VIPS_TOKEN_LEFT = 1, /* ({[ */ + VIPS_TOKEN_RIGHT, /* ]}) */ + VIPS_TOKEN_STRING, /* string or "str\"ing" */ + VIPS_TOKEN_EQUALS, /* = */ + VIPS_TOKEN_COMMA /* , */ +} VipsToken; + +const char *vips__token_get( const char *buffer, + VipsToken *token, char *string, int size ); +const char *vips__token_must( const char *buffer, VipsToken *token, + char *string, int size ); +const char *vips__token_need( const char *buffer, VipsToken need_token, + char *string, int size ); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libsrc/iofuncs/dispatch_types.c b/libsrc/iofuncs/dispatch_types.c index edf7c65b..75c02bdf 100644 --- a/libsrc/iofuncs/dispatch_types.c +++ b/libsrc/iofuncs/dispatch_types.c @@ -874,9 +874,31 @@ output_gvalue_init( im_object *obj ) im_type_desc im__output_gvalue = { IM_TYPE_GVALUE, - sizeof( GValue ), /* Need some storage */ - IM_TYPE_OUTPUT, /* No arg needed (just print) */ + sizeof( GValue ), /* Need some storage */ + IM_TYPE_OUTPUT, /* No arg needed (just print) */ (im_init_obj_fn) output_gvalue_init, /* Init function */ (im_dest_obj_fn) gvalue_free /* Destroy function */ }; +/* Init function for input interpolate. + */ +static int +input_interpolate_init( im_object *obj, char *str ) +{ + VipsObject *object; + + if( !(object = vips_object_new_from_string( "VipsInterpolate", str )) ) + return( -1 ); + *obj = object; + + return( 0 ); +} + +im_type_desc im__input_interpolate = { + IM_TYPE_INTERPOLATE, + 0, /* No storage required */ + IM_TYPE_ARG, /* It requires a command-line arg */ + (im_init_obj_fn) input_interpolate_init,/* Init function */ + (im_dest_obj_fn) g_object_unref /* Destroy function */ +}; + diff --git a/libsrc/iofuncs/object.c b/libsrc/iofuncs/object.c index 590e13a0..14e491f2 100644 --- a/libsrc/iofuncs/object.c +++ b/libsrc/iofuncs/object.c @@ -76,6 +76,19 @@ vips_object_changed( VipsObject *object ) return( NULL ); } +int +vips_object_build( VipsObject *object ) +{ + VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS( object ); + +#ifdef DEBUG + printf( "vips_object_build: " ); + vips_object_print( object ); +#endif /*DEBUG*/ + + return( object_class->build( object ) ); +} + void vips_object_print_class( VipsObjectClass *class ) { @@ -100,16 +113,244 @@ vips_object_print( VipsObject *object ) printf( "\n%s (%p)\n", im_buf_all( &buf ), object ); } +/* Extra stuff we track for properties to do our argument handling. + */ + +/* Free a VipsArgumentInstance ... VipsArgumentClass can just be g_free()d. + */ +static void +vips_argument_instance_free( VipsArgumentInstance *argument_instance ) +{ + if( argument_instance->destroy_id ) { + g_signal_handler_disconnect( argument_instance->object, + argument_instance->destroy_id ); + argument_instance->destroy_id = 0; + } + g_free( argument_instance ); +} + +VipsArgument * +vips__argument_table_lookup( VipsArgumentTable *table, GParamSpec *pspec ) +{ + return( g_hash_table_lookup( table, pspec ) ); +} + +static void +vips_argument_table_replace( VipsArgumentTable *table, VipsArgument *argument ) +{ + g_hash_table_replace( table, argument->pspec, argument ); +} + +static void +vips_argument_table_destroy( VipsArgumentTable *table ) +{ + g_hash_table_destroy( table ); +} + +/* Loop over the vips_arguments to an object. + */ +void * +vips_argument_map( VipsObject *object, + VipsArgumentMapFn fn, void *a, void *b ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + GSList *p; + + for( p = class->argument_table_traverse; p; p = p->next ) { + VipsArgumentClass *argument_class = + (VipsArgumentClass *) p->data; + VipsArgument *argument = (VipsArgument *) argument_class; + GParamSpec *pspec = argument->pspec; + VipsArgumentInstance *argument_instance = + vips__argument_get_instance( argument_class, object ); + + /* We have many props on the arg table ... filter out the ones + * for this class. + */ + if( g_object_class_find_property( G_OBJECT_CLASS( class ), + pspec->name ) == pspec ) { + void *result; + + if( (result = fn( object, pspec, + argument_class, argument_instance, a, b )) ) + return( result ); + } + } + + return( NULL ); +} + +static void * +vips_argument_init2( VipsObject *object, GParamSpec *pspec, + VipsArgumentClass *argument_class, + VipsArgumentInstance *argument_instance, + void *a, void *b ) +{ + VipsArgument *argument; + +#ifdef DEBUG + printf( "vips_argument_init_sub: adding instance argument for %s\n", + pspec->name ); +#endif /*DEBUG*/ + + /* argument_instance should be NULL since we've not set it yet. + */ + g_assert( argument_instance == NULL ); + + argument_instance = g_new( VipsArgumentInstance, 1 ); + argument = (VipsArgument *) argument_instance; + + argument->pspec = ((VipsArgument *) argument_class)->pspec; + argument_instance->object = object; + argument_instance->assigned = FALSE; + argument_instance->destroy_id = 0; + + vips_argument_table_replace( object->argument_table, argument ); + + return( NULL ); +} + +/* Create a VipsArgumentInstance for each installed argument property. Ideally + * we'd do this during _init() but g_object_class_find_property() does not seem + * to work then :-( so we have to delay it until first access. + */ +static void +vips_argument_init( VipsObject *object ) +{ + if( !object->argument_table ) { + object->argument_table = g_hash_table_new_full( g_direct_hash, + g_direct_equal, NULL, + (GDestroyNotify) vips_argument_instance_free ); + + /* Make a VipsArgumentInstance for each installed argument + * property. + */ + vips_argument_map( object, vips_argument_init2, NULL, NULL ); + } +} + +/* Convenience ... given the VipsArgumentClass, get the VipsArgumentInstance. + */ +VipsArgumentInstance * +vips__argument_get_instance( VipsArgumentClass *argument_class, + VipsObject *object ) +{ + /* Make sure the instance args are built. + */ + vips_argument_init( object ); + + return( (VipsArgumentInstance *) + vips__argument_table_lookup( object->argument_table, + ((VipsArgument *) argument_class)->pspec ) ); +} + +static void +vips_object_clear_object( VipsObject *object, GParamSpec *pspec ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsArgumentClass *argument_class = (VipsArgumentClass *) + vips__argument_table_lookup( class->argument_table, pspec ); + VipsArgumentInstance *argument_instance = + vips__argument_get_instance( argument_class, object ); + GObject **member = &G_STRUCT_MEMBER( GObject *, object, + argument_class->offset ); + + if( *member ) { + if( argument_class->flags & VIPS_ARGUMENT_INPUT ) { +#ifdef DEBUG_REF + printf( "vips_object_clear_object: vips object: " ); + vips_object_print( object ); + printf( " no longer refers to gobject %s (%p)\n", + G_OBJECT_TYPE_NAME( *member ), *member ); + printf( " count down to %d\n", + G_OBJECT( *member )->ref_count - 1 ); +#endif /*DEBUG_REF*/ + + /* We reffed the object. + */ + g_object_unref( *member ); + } + else if( argument_class->flags & VIPS_ARGUMENT_OUTPUT ) { +#ifdef DEBUG_REF + printf( "vips_object_clear_object: gobject %s (%p)\n", + G_OBJECT_TYPE_NAME( *member ), *member ); + printf( " no longer refers to vips object: " ); + vips_object_print( object ); + printf( " count down to %d\n", + G_OBJECT( object )->ref_count - 1 ); +#endif /*DEBUG_REF*/ + + /* The object reffed us. Stop listening link to the + * object's "destroy" signal. We can come here from + * object being destroyed, in which case the handler + * will already have been disconnected for us. + */ + if( g_signal_handler_is_connected( object, + argument_instance->destroy_id ) ) + g_signal_handler_disconnect( object, + argument_instance->destroy_id ); + argument_instance->destroy_id = 0; + *member = NULL; + + g_object_unref( object ); + } + + *member = NULL; + } +} + +/* Free any args which are holding resources. + */ +static void * +vips_object_dispose_argument( VipsObject *object, GParamSpec *pspec, + VipsArgumentClass *argument_class, + VipsArgumentInstance *argument_instance, + void *a, void *b ) +{ +#ifdef DEBUG + printf( "vips_object_dispose_argument: %s.%s\n", + object->name, pspec->name ); +#endif /*DEBUG*/ + + g_assert( ((VipsArgument *) argument_class)->pspec == pspec ); + g_assert( ((VipsArgument *) argument_instance)->pspec == pspec ); + + if( G_IS_PARAM_SPEC_STRING( pspec ) ) { + char **member = &G_STRUCT_MEMBER( char *, object, + argument_class->offset ); + + IM_FREE( *member ); + } + else if( G_IS_PARAM_SPEC_OBJECT( pspec ) ) + vips_object_clear_object( object, pspec ); + else if( G_IS_PARAM_SPEC_BOXED( pspec ) ) { + gpointer *member = &G_STRUCT_MEMBER( gpointer, object, + argument_class->offset ); + + if( *member ) { + g_boxed_free( G_PARAM_SPEC_VALUE_TYPE( pspec ), + *member ); + *member = NULL; + } + } + + return( NULL ); +} + static void vips_object_dispose( GObject *gobject ) { -#ifdef DEBUG VipsObject *object = VIPS_OBJECT( gobject ); +#ifdef DEBUG printf( "vips_object_dispose: " ); vips_object_print( object ); #endif /*DEBUG*/ + /* Clear all our arguments: they may be holding refs we should drop. + */ + vips_argument_map( object, vips_object_dispose_argument, NULL, NULL ); + G_OBJECT_CLASS( vips_object_parent_class )->dispose( gobject ); } @@ -123,17 +364,315 @@ vips_object_finalize( GObject *gobject ) vips_object_print( object ); #endif /*DEBUG*/ + IM_FREEF( vips_argument_table_destroy, object->argument_table ); IM_FREE( object->name ); G_OBJECT_CLASS( vips_object_parent_class )->finalize( gobject ); } +static void +vips_object_arg_destroy( GObject *argument, + VipsArgumentInstance *argument_instance ) +{ + VipsObject *object = argument_instance->object; + GParamSpec *pspec = ((VipsArgument *) argument_instance)->pspec; + + /* Argument had reffed us ... now it's being destroyed, so we unref. + */ + vips_object_clear_object( object, pspec ); +} + +static void +vips_object_set_object( VipsObject *object, GParamSpec *pspec, + GObject *argument ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsArgumentClass *argument_class = (VipsArgumentClass *) + vips__argument_table_lookup( class->argument_table, pspec ); + VipsArgumentInstance *argument_instance = + vips__argument_get_instance( argument_class, object ); + GObject **member = &G_STRUCT_MEMBER( GObject *, object, + argument_class->offset ); + + g_assert( !*member ); + + *member = argument; + + if( *member ) { + if( argument_class->flags & VIPS_ARGUMENT_INPUT ) { +#ifdef DEBUG_REF + printf( "vips_object_set_object: vips object: " ); + vips_object_print( object ); + printf( " refers to gobject %s (%p)\n", + G_OBJECT_TYPE_NAME( *member ), *member ); + printf( " count up to %d\n", + G_OBJECT( *member )->ref_count ); +#endif /*DEBUG_REF*/ + + /* Ref the argument. + */ + g_object_ref( *member ); + } + else if( argument_class->flags & VIPS_ARGUMENT_OUTPUT ) { +#ifdef DEBUG_REF + printf( "vips_object_set_object: gobject %s (%p)\n", + G_OBJECT_TYPE_NAME( *member ), *member ); + printf( " refers to vips object: " ); + vips_object_print( object ); + printf( " count up to %d\n", + G_OBJECT (object)->ref_count ); +#endif /*DEBUG_REF*/ + + /* The argument reffs us. + */ + g_object_ref( object ); + argument_instance->destroy_id = + g_signal_connect( *member, "destroy", + G_CALLBACK( vips_object_arg_destroy ), + argument_instance ); + } + } +} + +/* Also used by subclasses, so not static. + */ +void +vips_object_set_property( GObject *gobject, + guint property_id, const GValue *value, GParamSpec *pspec ) +{ + VipsObject *object = VIPS_OBJECT( gobject ); + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gobject ); + VipsArgumentClass *argument_class = (VipsArgumentClass *) + vips__argument_table_lookup( class->argument_table, pspec ); + VipsArgumentInstance *argument_instance = + vips__argument_get_instance( argument_class, object ); + + if( !argument_class ) { + G_OBJECT_WARN_INVALID_PROPERTY_ID( gobject, + property_id, pspec ); + return; + } + +#ifdef DEBUG +{ + char *str_value; + + str_value = g_strdup_value_contents( value ); + printf( "vips_object_set_property: %s.%s = %s\n", + object->name, pspec->name, str_value ); + g_free( str_value ); +} +#endif /*DEBUG*/ + + g_assert( ((VipsArgument *) argument_class)->pspec == pspec ); + g_assert( ((VipsArgument *) argument_instance)->pspec == pspec ); + + /* If this is a construct-only argument, we can only set before we've + * built. + */ + if( argument_class->flags & VIPS_ARGUMENT_CONSTRUCT && + object->constructed ) { + g_warning( "%s: can't assign '%s' after construct", G_STRLOC, + ((VipsArgument *) argument_class)->pspec->name); + return; + } + + /* If this is a set-once argument, check we've not set it before. + */ + if( argument_class->flags & VIPS_ARGUMENT_SET_ONCE && + argument_instance->assigned ) { + g_warning( "%s: can only assign '%s' once", G_STRLOC, + ((VipsArgument *)argument_class)->pspec->name ); + return; + } + + if( G_IS_PARAM_SPEC_STRING( pspec ) ) { + char **member = &G_STRUCT_MEMBER( char *, object, + argument_class->offset ); + + IM_SETSTR( *member, g_value_get_string( value ) ); + } + else if( G_IS_PARAM_SPEC_OBJECT( pspec ) ) { + /* Remove any old object. + */ + vips_object_clear_object( object, pspec ); + + /* Install the new object. + */ + vips_object_set_object( object, pspec, + g_value_get_object( value ) ); + } + else if( G_IS_PARAM_SPEC_INT( pspec ) ) { + int *member = &G_STRUCT_MEMBER( int, object, + argument_class->offset ); + + *member = g_value_get_int( value ); + } + else if( G_IS_PARAM_SPEC_BOOLEAN( pspec ) ) { + gboolean *member = &G_STRUCT_MEMBER( gboolean, object, + argument_class->offset ); + + *member = g_value_get_boolean( value ); + } + else if( G_IS_PARAM_SPEC_ENUM( pspec ) ) { + int *member = &G_STRUCT_MEMBER( int, object, + argument_class->offset ); + + *member = g_value_get_enum( value ); + } + else if( G_IS_PARAM_SPEC_POINTER( pspec ) ) { + gpointer *member = &G_STRUCT_MEMBER( gpointer, object, + argument_class->offset ); + + *member = g_value_get_pointer( value ); + } + else if( G_IS_PARAM_SPEC_DOUBLE( pspec ) ) { + double *member = &G_STRUCT_MEMBER( double, object, + argument_class->offset ); + + *member = g_value_get_double( value ); + } + else if( G_IS_PARAM_SPEC_BOXED( pspec ) ) { + gpointer *member = &G_STRUCT_MEMBER( gpointer, object, + argument_class->offset ); + + if( *member ) { + g_boxed_free( G_PARAM_SPEC_VALUE_TYPE( pspec ), + *member ); + *member = NULL; + } + + /* Copy the boxed into our pointer (will use eg. + * vips__object_vector_dup ()). + */ + *member = g_value_dup_boxed( value ); + } + else { + g_warning( "%s: '%s' has unimplemented type", G_STRLOC, + ((VipsArgument *) argument_class)->pspec->name ); + } + + /* Note that it's now been set. + */ + argument_instance->assigned = TRUE; +} + +/* Also used by subclasses, so not static. + */ +void +vips_object_get_property( GObject *gobject, + guint property_id, GValue *value, GParamSpec *pspec ) +{ + VipsObject *object = VIPS_OBJECT( gobject ); + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gobject ); + VipsArgumentClass *argument_class = (VipsArgumentClass *) + vips__argument_table_lookup( class->argument_table, pspec ); + + if( !argument_class ) { + G_OBJECT_WARN_INVALID_PROPERTY_ID( gobject, + property_id, pspec ); + return; + } + + g_assert( ((VipsArgument *) argument_class)->pspec == pspec ); + + if( G_IS_PARAM_SPEC_STRING( pspec ) ) { + char *member = G_STRUCT_MEMBER( char *, object, + argument_class->offset ); + + g_value_set_string( value, member ); + } + else if( G_IS_PARAM_SPEC_OBJECT( pspec ) ) { + GObject **member = &G_STRUCT_MEMBER( GObject *, object, + argument_class->offset ); + + g_value_set_object( value, *member ); + } + else if( G_IS_PARAM_SPEC_INT( pspec ) ) { + int *member = &G_STRUCT_MEMBER( int, object, + argument_class->offset ); + + g_value_set_int( value, *member ); + } + else if( G_IS_PARAM_SPEC_BOOLEAN( pspec ) ) { + gboolean *member = &G_STRUCT_MEMBER( gboolean, object, + argument_class->offset ); + + g_value_set_boolean( value, *member ); + } + else if( G_IS_PARAM_SPEC_ENUM( pspec ) ) { + int *member = &G_STRUCT_MEMBER( int, object, + argument_class->offset ); + + g_value_set_enum( value, *member ); + } + else if( G_IS_PARAM_SPEC_POINTER( pspec ) ) { + gpointer *member = &G_STRUCT_MEMBER( gpointer, object, + argument_class->offset ); + + g_value_set_pointer( value, *member ); + } + else if( G_IS_PARAM_SPEC_BOXED( pspec ) ) { + gpointer *member = &G_STRUCT_MEMBER( gpointer, object, + argument_class->offset ); + + /* Copy the boxed into our pointer (will use eg. + * vips__object_vector_dup ()). + */ + g_value_set_boxed( value, *member ); + } + else { + g_warning( "%s: unimplemented property type", G_STRLOC ); + } +} + +static void * +vips_object_check_required( VipsObject *object, GParamSpec *pspec, + VipsArgumentClass *argument_class, + VipsArgumentInstance *argument_instance, + void *a, void *b ) +{ + int *result = (int *) a; + + if( (argument_class->flags & VIPS_ARGUMENT_REQUIRED) && + (argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) && + !argument_instance->assigned ) { + im_error( "check_required", + _( "required construct param %s to %s %s not set" ), + pspec->name, + G_OBJECT_TYPE_NAME( object ), + object->name ); + *result = -1; + } + + return( NULL ); +} + +static int +vips_object_real_build( VipsObject *object ) +{ + int result; + + g_assert( !object->constructed ); + + /* Check all required arguments have been supplied, don't stop on 1st + * error. + */ + result = 0; + (void) vips_argument_map( object, + vips_object_check_required, &result, NULL ); + + /* ... more checks go here. + */ + object->constructed = TRUE; + + return( result ); +} + static void vips_object_real_changed( VipsObject *object ) { #ifdef DEBUG - VipsObject *object = VIPS_OBJECT( gobject ); - printf( "vips_object_real_changed: " ); vips_object_print( object ); #endif /*DEBUG*/ @@ -156,6 +695,13 @@ vips_object_real_print( VipsObject *object, im_buf_t *buf ) im_buf_appendf( buf, "\"%s\"", object->name ); } +static void +transform_string_double( const GValue *src_value, GValue *dest_value ) +{ + g_value_set_double( dest_value, + g_ascii_strtod( g_value_get_string( src_value ), NULL ) ); +} + static void vips_object_class_init( VipsObjectClass *class ) { @@ -163,13 +709,22 @@ vips_object_class_init( VipsObjectClass *class ) gobject_class->dispose = vips_object_dispose; gobject_class->finalize = vips_object_finalize; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + class->build = vips_object_real_build; class->changed = vips_object_real_changed; class->print_class = vips_object_real_print_class; class->print = vips_object_real_print; class->nickname = "object"; class->description = _( "VIPS base class" ); + /* Table of VipsArgumentClass ... we can just g_free() them. + */ + class->argument_table = g_hash_table_new_full( + g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_free ); + class->argument_table_traverse = NULL; + vips_object_signals[SIG_CHANGED] = g_signal_new( "changed", G_OBJECT_CLASS_TYPE( gobject_class ), G_SIGNAL_RUN_FIRST, @@ -177,6 +732,11 @@ vips_object_class_init( VipsObjectClass *class ) NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0 ); + + /* For setting double arguments from the command-line. + */ + g_value_register_transform_func( G_TYPE_STRING, G_TYPE_DOUBLE, + transform_string_double ); } static void @@ -195,3 +755,172 @@ vips_object_set_name( VipsObject *object, const char *name ) vips_object_changed( object ); } +/* Add a vipsargument ... automate some stuff with this. + */ +void +vips_object_class_install_argument( VipsObjectClass *object_class, + GParamSpec *pspec, VipsArgumentFlags flags, guint offset ) +{ + VipsArgumentClass *argument_class = g_new( VipsArgumentClass, 1 ); + +#ifdef DEBUG + printf( "vips_object_class_install_argument: %s\n", pspec->name ); +#endif /*DEBUG*/ + + /* Must be a new one. + */ + g_assert( !vips__argument_table_lookup( object_class->argument_table, + pspec ) ); + + /* Mustn't have INPUT and OUTPUT both set. + */ + g_assert( !( + (flags & VIPS_ARGUMENT_INPUT) && + (flags & VIPS_ARGUMENT_OUTPUT)) ); + + ((VipsArgument *) argument_class)->pspec = pspec; + argument_class->object_class = object_class; + argument_class->flags = flags; + argument_class->offset = offset; + + vips_argument_table_replace( object_class->argument_table, + (VipsArgument *) argument_class ); + object_class->argument_table_traverse = g_slist_append( + object_class->argument_table_traverse, argument_class ); +} + +/* Set a named arg from a string. + */ +static int +vips_object_set_arg( VipsObject *object, const char *name, const char *value ) +{ + GValue gvalue = { 0, }; + + g_value_init( &gvalue, G_TYPE_STRING ); + g_value_set_string( &gvalue, value ); + g_object_set_property( G_OBJECT( object ), name, &gvalue ); + g_value_unset( &gvalue ); + + return( 0 ); +} + +static void * +vips_object_set_required_test( VipsObject *object, + GParamSpec *pspec, + VipsArgumentClass *argument_class, + VipsArgumentInstance *argument_instance, + void *a, void *b ) +{ + if( (argument_class->flags & VIPS_ARGUMENT_REQUIRED) && + (argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) && + !argument_instance->assigned ) + return( pspec ); + + return( NULL ); +} + +/* Set the first unassigned required arg to the string. + */ +static int +vips_object_set_required( VipsObject *object, const char *value ) +{ + GParamSpec *pspec; + + if( !(pspec = vips_argument_map( object, + vips_object_set_required_test, NULL, NULL )) ) { + im_error( "vips_object_set_required", + _( "no unset required arguments for %s" ), value ); + return( -1 ); + } + + if( vips_object_set_arg( object, pspec->name, value ) ) + return( -1 ); + + return( 0 ); +} + +/* Set object args from a string. We've seen the '(', we need to check for the + * closing ')' and make sure there's no extra stuff. + */ +static int +vips_object_set_args( VipsObject *object, const char *p ) +{ + VipsToken token; + char string[PATH_MAX]; + char string2[PATH_MAX]; + + do { + if( !(p = vips__token_need( p, VIPS_TOKEN_STRING, + string, PATH_MAX )) ) + return( -1 ); + + /* We have to look for a '=', ')' or a ',' to see if string is + * a param name or a value. + */ + if( !(p = vips__token_must( p, &token, string2, PATH_MAX )) ) + return( -1 ); + if( token == VIPS_TOKEN_EQUALS ) { + if( !(p = vips__token_need( p, VIPS_TOKEN_STRING, + string2, PATH_MAX )) ) + return( -1 ); + if( vips_object_set_arg( object, string, string2 ) ) + return( -1 ); + if( !(p = vips__token_must( p, &token, + string2, PATH_MAX )) ) + return( -1 ); + } + else { + if( vips_object_set_required( object, string ) ) + return( -1 ); + } + + /* Now must be a , or a ). + */ + if( token != VIPS_TOKEN_RIGHT && token != VIPS_TOKEN_COMMA ) { + im_error( "set_args", "%s", + _( "not , or ) after parameter" ) ); + return( -1 ); + } + } while( token != VIPS_TOKEN_RIGHT ); + + if( (p = vips__token_get( p, &token, string, PATH_MAX )) ) { + im_error( "set_args", "%s", + _( "extra tokens after ')'" ) ); + return( -1 ); + } + + return( 0 ); +} + +VipsObject * +vips_object_new_from_string( const char *basename, const char *p ) +{ + VipsToken token; + char string[PATH_MAX]; + GType type; + VipsObject *object; + + if( !(p = vips__token_need( p, VIPS_TOKEN_STRING, string, PATH_MAX )) || + !(type = vips_type_find( basename, string )) ) + return( NULL ); + + object = VIPS_OBJECT( g_object_new( type, NULL ) ); + + if( (p = vips__token_get( p, &token, string, PATH_MAX )) ) { + if( token == VIPS_TOKEN_LEFT && + vips_object_set_args( object, p ) ) { + im_error( "object_new", "%s", + _( "bad object arguents" ) ); + g_object_unref( object ); + return( NULL ); + } + } + + if( vips_object_build( object ) ) { + g_object_unref( object ); + return( NULL ); + } + + return( object ); +} + diff --git a/libsrc/iofuncs/util.c b/libsrc/iofuncs/util.c index f432f5f6..b62f8d12 100644 --- a/libsrc/iofuncs/util.c +++ b/libsrc/iofuncs/util.c @@ -362,7 +362,7 @@ vips_type_find( const char *basename, const char *nickname ) /* FIXME ... we've not supposed to use G_TYPE_FROM_CLASS(), I think. * I'm not * sure what the alternative is. */ - return( G_TYPE_FROM_CLASS( class ) ); + return( G_TYPE_FROM_CLASS( class ) ); } /* Like strncpy(), but always NULL-terminate, and don't pad with NULLs. @@ -1159,13 +1159,19 @@ im__file_write( void *data, size_t size, size_t nmemb, FILE *stream ) gboolean im_isnative( im_arch_type arch ) { - switch ( arch ) { - case IM_ARCH_NATIVE: return( TRUE ); - case IM_ARCH_BYTE_SWAPPED: return( FALSE ); - case IM_ARCH_LSB_FIRST: return( !im_amiMSBfirst() ); - case IM_ARCH_MSB_FIRST: return( im_amiMSBfirst() ); - } - abort(); + switch( arch ) { + case IM_ARCH_NATIVE: + return( TRUE ); + case IM_ARCH_BYTE_SWAPPED: + return( FALSE ); + case IM_ARCH_LSB_FIRST: + return( !im_amiMSBfirst() ); + case IM_ARCH_MSB_FIRST: + return( im_amiMSBfirst() ); + + default: + g_assert( 0 ); + } } /* Read a few bytes from the start of a file. For sniffing file types. @@ -1193,3 +1199,164 @@ im__get_bytes( const char *filename, unsigned char buf[], int len ) return( 1 ); } + +/* Break a command-line argument into tokens separated by whitespace. Strings + * can't be adjacent, so "hello world" (without quotes) is a single string. + * Strings are written (with \" escaped) into string, which must be size + * characters large. + */ +const char * +vips__token_get( const char *p, VipsToken *token, char *string, int size ) +{ + const char *q; + int ch; + int n; + + /* Parse this token with p. + */ + if( !p ) + return( NULL ); + + /* Skip initial whitespace. + */ + p += strspn( p, " \t\n\r" ); + if( !p[0] ) + return( NULL ); + + switch( (ch = p[0]) ) { + case '{': + case '[': + case '(': + *token = VIPS_TOKEN_LEFT; + p += 1; + break; + + case ')': + case ']': + case '}': + *token = VIPS_TOKEN_RIGHT; + p += 1; + break; + + case '=': + *token = VIPS_TOKEN_EQUALS; + p += 1; + break; + + case ',': + *token = VIPS_TOKEN_COMMA; + p += 1; + break; + + case '"': + case '\'': + /* Parse a quoted string. Copy up to ", interpret any \", + * error if no closing ". + */ + *token = VIPS_TOKEN_STRING; + + do { + /* Number of characters until the next quote + * character or end of string. + */ + if( (q = strchr( p + 1, ch )) ) + n = q - p + 1; + else + n = strlen( p + 1 ); + + g_assert( size > n + 1 ); + memcpy( string, p + 1, n ); + string[n] = '\0'; + + /* p[n + 1] might not be " if there's no closing ". + */ + if( p[n + 1] == ch && p[n] == '\\' ) + /* An escaped ": overwrite the '\' with '"' + */ + string[n - 1] = ch; + + string += n; + size -= n; + p += n + 1; + } while( p[0] && p[-1] == '\\' ); + + p += 1; + + break; + + default: + /* It's an unquoted string: read up to the next non-string + * character. We don't allow two strings next to each other, + * so the next break must be bracket, equals, comma. + */ + *token = VIPS_TOKEN_STRING; + n = strcspn( p, "[{()}]=," ); + g_assert( size > n + 1 ); + memcpy( string, p, n ); + string[n] = '\0'; + p += n; + + /* We remove leading whitespace, so we trim trailing + * whitespace from unquoted strings too. + */ + while( isspace( string[n - 1] ) ) { + string[n - 1] = '\0'; + n -= 1; + } + + break; + } + + return( p ); +} + +/* We expect a token. + */ +const char * +vips__token_must( const char *p, VipsToken *token, + char *string, int size ) +{ + if( !(p = vips__token_get( p, token, string, size )) ) { + im_error( "get_token", "%s", _( "unexpected end of string" ) ); + return( NULL ); + } + + return( p ); +} + +/* Turn a VipsToken into a string. + */ +static const char * +vips__token_string( VipsToken token ) +{ + switch( token ) { + case VIPS_TOKEN_LEFT: return( _( "opening brace" ) ); + case VIPS_TOKEN_RIGHT: return( _( "closing brace" ) ); + case VIPS_TOKEN_STRING: return( _( "string" ) ); + case VIPS_TOKEN_EQUALS: return( "=" ); + case VIPS_TOKEN_COMMA: return( "," ); + + default: + g_assert( 0 ); + } +} + +/* We expect a certain token. + */ +const char * +vips__token_need( const char *p, VipsToken need_token, + char *string, int size ) +{ + VipsToken token; + + if( !(p = vips__token_must( p, &token, string, size )) ) + return( NULL ); + if( token != need_token ) { + im_error( "get_token", _( "expected %s, saw %s" ), + vips__token_string( need_token ), + vips__token_string( token ) ); + return( NULL ); + } + + return( p ); +} diff --git a/libsrc/mosaicing/interpolate.c b/libsrc/mosaicing/interpolate.c index 51f9178c..e154161e 100644 --- a/libsrc/mosaicing/interpolate.c +++ b/libsrc/mosaicing/interpolate.c @@ -465,5 +465,6 @@ vips_interpolate_new( const char *nickname ) if( !(type = vips_type_find( "VipsInterpolate", nickname )) ) return( NULL ); - return( VIPS_INTERPOLATE( g_object_new( type, NULL ) ) ); + return( VIPS_INTERPOLATE( g_object_new( type, + "sharpening", 2.0, NULL ) ) ); } diff --git a/libsrc/mosaicing/mosaicing_dispatch.c b/libsrc/mosaicing/mosaicing_dispatch.c index d0153d82..55cea88a 100644 --- a/libsrc/mosaicing/mosaicing_dispatch.c +++ b/libsrc/mosaicing/mosaicing_dispatch.c @@ -579,7 +579,7 @@ static im_function affinei_desc = { static im_arg_desc affinei_all_args[] = { IM_INPUT_IMAGE( "in" ), IM_OUTPUT_IMAGE( "out" ), - IM_INPUT_STRING( "interpolate" ), + IM_INPUT_INTERPOLATE( "interpolate" ), IM_INPUT_DOUBLE( "a" ), IM_INPUT_DOUBLE( "b" ), IM_INPUT_DOUBLE( "c" ), @@ -593,23 +593,16 @@ static im_arg_desc affinei_all_args[] = { static int affinei_all_vec( im_object *argv ) { - const char *interpol = argv[2]; + VipsInterpolate *interpolate = VIPS_INTERPOLATE( argv[2] ); double a = *((double *) argv[3]); double b = *((double *) argv[4]); double c = *((double *) argv[5]); double d = *((double *) argv[6]); double dx = *((double *) argv[7]); double dy = *((double *) argv[8]); - VipsInterpolate *interpolate; - int result; - if( !(interpolate = vips_interpolate_new( interpol )) ) - return( -1 ); - result = im_affinei_all( argv[0], argv[1], interpolate, - a, b, c, d, dx, dy ); - g_object_unref( interpolate ); - - return( result ); + return( im_affinei_all( argv[0], argv[1], interpolate, + a, b, c, d, dx, dy ) ); } /* Description of im_affinei. diff --git a/libsrc/mosaicing/yafrsmooth.cpp b/libsrc/mosaicing/yafrsmooth.cpp index 188c9f2b..5f3a8335 100644 --- a/libsrc/mosaicing/yafrsmooth.cpp +++ b/libsrc/mosaicing/yafrsmooth.cpp @@ -740,10 +740,14 @@ vips_interpolate_yafrsmooth_interpolate( VipsInterpolate *interpolate, static void vips_interpolate_yafrsmooth_class_init( VipsInterpolateYafrsmoothClass *iclass ) { + GObjectClass *gobject_class = G_OBJECT_CLASS( iclass ); VipsObjectClass *object_class = VIPS_OBJECT_CLASS( iclass ); VipsInterpolateClass *interpolate_class = VIPS_INTERPOLATE_CLASS( iclass ); + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + object_class->nickname = "yafrsmooth"; object_class->description = _( "YAFR smooth interpolation" ); diff --git a/src/iofuncs/vips.c b/src/iofuncs/vips.c index 27a4ebd4..41eb3fe1 100644 --- a/src/iofuncs/vips.c +++ b/src/iofuncs/vips.c @@ -56,6 +56,10 @@ */ +/* +#define DEBUG_FATAL + */ + #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ @@ -861,6 +865,18 @@ main( int argc, char **argv ) if( im_init_world( argv[0] ) ) error_exit( "unable to start VIPS" ); +#ifdef DEBUG_FATAL + /* Set masks for debugging ... stop on any problem. + */ + g_log_set_always_fatal( + G_LOG_FLAG_RECURSION | + G_LOG_FLAG_FATAL | + G_LOG_LEVEL_ERROR | + G_LOG_LEVEL_CRITICAL | + G_LOG_LEVEL_WARNING ); + + fprintf( stderr, "*** DEBUG_FATAL: will abort() on first warning\n" ); +#endif /*!DEBUG_FATAL*/ context = g_option_context_new( _( "- VIPS driver program" ) );