Merge branch 'master' of github.com:jcupitt/libvips

This commit is contained in:
John Cupitt 2014-06-13 11:44:27 +01:00
commit f5200dd4a0
9 changed files with 298 additions and 225 deletions

View File

@ -37,6 +37,8 @@
im_*mosaic1(), im_*merge1() redone as classes im_*mosaic1(), im_*merge1() redone as classes
- better filename tracking for globalbalance - better filename tracking for globalbalance
- revised vips8 image load/save API, now simpler and more logical - revised vips8 image load/save API, now simpler and more logical
- operations emit "invalidate" if any of their input images invalidate
- operation cache drops ops on invalidate
6/3/14 started 7.38.6 6/3/14 started 7.38.6
- grey ramp minimum was wrong - grey ramp minimum was wrong

57
TODO
View File

@ -8,6 +8,7 @@
- can we use postbuild elsewhere? look at use of "preclose" / "written", etc. - can we use postbuild elsewhere? look at use of "preclose" / "written", etc.
@ -32,42 +33,6 @@
- think of a better way to support skipahead
extract needs to hint to it's image sources what line it will start reading
at
in vips_extract_build(), call vips_image_set_start(), somehow sends a signal
the wrong way down the pipe to the vips_sequential() (if there is one)
telling it not to stall the first request if it's for that line
test --crop in vipsthumbnail on portrait images cropped to landscape
alternative: if extract sees a seq image, it deliberately reads and discards
the N scanlines
could be very slow :-(
the first vips_extract_area_gen() needs to somehow signal in
vips_region_prepare() that this is the first request
how do we pass the signal down? not clear
run the first tile single-threaded
so sinkdisc runs the first request, waits for it to come back, then
starts all the workers going
this is quite a good idea! we'd not slow down much, and there's a huge
amount of locking on the first tile anyway
now seq can support skipahead again
- now vips_linear() has uchar output, can we do something with orc? - now vips_linear() has uchar output, can we do something with orc?
- do restrict on some more packages, we've just done arithmetic so far - do restrict on some more packages, we've just done arithmetic so far
@ -107,6 +72,7 @@
- use g_log() instead of vips_info()? - use g_log() instead of vips_info()?
- quadratic doesn't work for order 3 - quadratic doesn't work for order 3
start to get jaggies on lines --- the 3rd differential isn't being start to get jaggies on lines --- the 3rd differential isn't being
@ -117,18 +83,6 @@
because we step across tiles left to right: y doesn't change, only x does because we step across tiles left to right: y doesn't change, only x does
- the operation cache needs to detect invalidate
tricky!
perhaps have operations always watching all of their inputs and resignalling
"invalidate" themselves
cache then just needs to watch for "invalidate" on operations it tracks
need to add an "invalidate" signal to operation
mosaic mosaic
====== ======
@ -194,9 +148,6 @@ packaging
- test _O_TEMPORARY thing on Windows - test _O_TEMPORARY thing on Windows
- do we bundle "convert" in the OS X / win32 builds? if we don't we
should
convolution convolution
=========== ===========
@ -233,10 +184,6 @@ convolution
arithmetic arithmetic
========== ==========
- avg/dev etc. should uncode images? eg. labq2lab etc.
how about ifthenelse?
- HAVE_HYPOT could define a hypot() macro? - HAVE_HYPOT could define a hypot() macro?
- fix a better NaN policy - fix a better NaN policy

View File

@ -283,6 +283,11 @@ typedef struct _VipsArgumentInstance {
* here. * here.
*/ */
gulong close_id; gulong close_id;
/* We need to listen for "invalidate" on input images and send our own
* "invalidate" out. If we go, we need to disconnect.
*/
gulong invalidate_id;
} VipsArgumentInstance; } VipsArgumentInstance;
/* Need to look up our VipsArgument structs from a pspec. Just hash the /* Need to look up our VipsArgument structs from a pspec. Just hash the

View File

@ -65,11 +65,6 @@ typedef gboolean (*VipsOperationBuildFn)( VipsObject * );
typedef struct _VipsOperation { typedef struct _VipsOperation {
VipsObject parent_instance; VipsObject parent_instance;
/* When we added this operation to cache .. used to find LRU for
* flush.
*/
int time;
/* Keep the hash here. /* Keep the hash here.
*/ */
guint hash; guint hash;
@ -92,12 +87,18 @@ typedef struct _VipsOperationClass {
*/ */
VipsOperationFlags (*get_flags)( VipsOperation * ); VipsOperationFlags (*get_flags)( VipsOperation * );
VipsOperationFlags flags; VipsOperationFlags flags;
/* One of our input images has signalled "invalidate". The cache uses
* VipsOperation::invalidate to drop dirty ops.
*/
void (*invalidate)( VipsOperation * );
} VipsOperationClass; } VipsOperationClass;
GType vips_operation_get_type( void ); GType vips_operation_get_type( void );
VipsOperationFlags vips_operation_get_flags( VipsOperation *operation ); VipsOperationFlags vips_operation_get_flags( VipsOperation *operation );
void vips_operation_class_print_usage( VipsOperationClass *operation_class ); void vips_operation_class_print_usage( VipsOperationClass *operation_class );
void vips_operation_invalidate( VipsOperation *operation );
int vips_operation_call_valist( VipsOperation *operation, va_list ap ); int vips_operation_call_valist( VipsOperation *operation, va_list ap );
VipsOperation *vips_operation_new( const char *name ); VipsOperation *vips_operation_new( const char *name );

View File

@ -37,8 +37,6 @@
TODO TODO
listen for invalidate
will we need to drop all on exit? unclear will we need to drop all on exit? unclear
what about delayed writes ... do we ever write in close? we shouldn't, what about delayed writes ... do we ever write in close? we shouldn't,
@ -123,6 +121,22 @@ static GMutex *vips_cache_lock = NULL;
#define VIPS_VALUE_GET_CHAR g_value_get_char #define VIPS_VALUE_GET_CHAR g_value_get_char
#endif #endif
/* A cache entry.
*/
typedef struct _VipsOperationCacheEntry {
VipsOperation *operation;
/* When we added this operation to cache .. used to find LRU for
* flush.
*/
int time;
/* We listen for "invalidate" from the operation. Track the id here so
* we can disconnect when we drop an operation.
*/
gulong invalidate_id;
} VipsOperationCacheEntry;
/* Pass in the pspec so we can get the generic type. For example, a /* Pass in the pspec so we can get the generic type. For example, a
* held in a GParamSpec allowing OBJECT, but the value could be of type * held in a GParamSpec allowing OBJECT, but the value could be of type
* VipsImage. generics are much faster to compare. * VipsImage. generics are much faster to compare.
@ -450,10 +464,12 @@ vips__cache_init( void )
static void * static void *
vips_cache_print_fn( void *value, void *a, void *b ) vips_cache_print_fn( void *value, void *a, void *b )
{ {
VipsOperationCacheEntry *entry = value;
char str[32768]; char str[32768];
VipsBuf buf = VIPS_BUF_STATIC( str ); VipsBuf buf = VIPS_BUF_STATIC( str );
vips_object_to_string( VIPS_OBJECT( value ), &buf ); vips_object_to_string( VIPS_OBJECT( entry->operation ), &buf );
printf( "%p - %s\n", value, vips_buf_all( &buf ) ); printf( "%p - %s\n", value, vips_buf_all( &buf ) );
@ -515,113 +531,25 @@ vips_cache_unref( VipsOperation *operation )
g_object_unref( operation ); g_object_unref( operation );
} }
/* Drop an operation from the cache. /* Remove an operation from the cache.
*/ */
static void static void
vips_cache_drop( VipsOperation *operation ) vips_cache_remove( VipsOperation *operation )
{ {
/* It must be in cache. VipsOperationCacheEntry *entry = (VipsOperationCacheEntry *)
*/ g_hash_table_lookup( vips_cache_table, operation );
g_assert( g_hash_table_lookup( vips_cache_table, operation ) );
g_assert( entry );
if( entry->invalidate_id ) {
g_signal_handler_disconnect( operation, entry->invalidate_id );
entry->invalidate_id = 0;
}
g_hash_table_remove( vips_cache_table, operation ); g_hash_table_remove( vips_cache_table, operation );
vips_cache_unref( operation ); vips_cache_unref( operation );
}
static void * g_free( entry );
vips_cache_get_first_fn( void *value, void *a, void *b )
{
return( value );
}
/* Return the first item.
*/
static VipsOperation *
vips_cache_get_first( void )
{
if( vips_cache_table )
return( VIPS_OPERATION( vips_hash_table_map( vips_cache_table,
vips_cache_get_first_fn, NULL, NULL ) ) );
else
return( NULL );
}
/**
* vips_cache_drop_all:
*
* Drop the whole operation cache, handy for leak tracking.
*/
void
vips_cache_drop_all( void )
{
g_mutex_lock( vips_cache_lock );
if( vips_cache_table ) {
VipsOperation *operation;
if( vips__cache_dump )
vips_cache_print();
/* We can't modify the hash in the callback from
* g_hash_table_foreach() and friends. Repeatedly drop the
* first item instead.
*/
while( (operation = vips_cache_get_first()) )
vips_cache_drop( operation );
VIPS_FREEF( g_hash_table_unref, vips_cache_table );
}
g_mutex_unlock( vips_cache_lock );
}
static void
vips_cache_get_lru_cb( VipsOperation *key, VipsOperation *value,
VipsOperation **best )
{
if( !*best ||
(*best)->time > value->time )
*best = value;
}
/* Get the least-recently-used cache item.
*
* TODO ... will this be too expensive? probably not
*/
static VipsOperation *
vips_cache_get_lru( void )
{
VipsOperation *operation;
operation = NULL;
g_hash_table_foreach( vips_cache_table,
(GHFunc) vips_cache_get_lru_cb, &operation );
return( operation );
}
/* Is the cache full? Drop until it's not.
*/
static void
vips_cache_trim( void )
{
VipsOperation *operation;
g_mutex_lock( vips_cache_lock );
while( vips_cache_table &&
(g_hash_table_size( vips_cache_table ) > vips_cache_max ||
vips_tracked_get_files() > vips_cache_max_files ||
vips_tracked_get_mem() > vips_cache_max_mem) &&
(operation = vips_cache_get_lru()) ) {
#ifdef DEBUG
printf( "vips_cache_trim: trimming %p\n", operation );
#endif /*DEBUG*/
vips_cache_drop( operation );
}
g_mutex_unlock( vips_cache_lock );
} }
static void * static void *
@ -649,8 +577,11 @@ vips_object_ref_arg( VipsObject *object,
static void static void
vips_operation_touch( VipsOperation *operation ) vips_operation_touch( VipsOperation *operation )
{ {
VipsOperationCacheEntry *entry = (VipsOperationCacheEntry *)
g_hash_table_lookup( vips_cache_table, operation );
vips_cache_time += 1; vips_cache_time += 1;
operation->time = vips_cache_time; entry->time = vips_cache_time;
} }
/* Ref an operation for the cache. The operation itself, plus all the output /* Ref an operation for the cache. The operation itself, plus all the output
@ -665,6 +596,130 @@ vips_cache_ref( VipsOperation *operation )
vips_operation_touch( operation ); vips_operation_touch( operation );
} }
static void
vips_cache_insert( VipsOperation *operation )
{
VipsOperationCacheEntry *entry = g_new( VipsOperationCacheEntry, 1 );
/* It must not be in cache.
*/
g_assert( !g_hash_table_lookup( vips_cache_table, operation ) );
entry->operation = operation;
entry->time = 0;
entry->invalidate_id = 0;
g_hash_table_insert( vips_cache_table, operation, entry );
vips_cache_ref( operation );
/* If the operation signals "invalidate", we must drop it.
*/
entry->invalidate_id = g_signal_connect( operation, "invalidate",
G_CALLBACK( vips_cache_remove ), NULL );
}
static void *
vips_cache_get_first_fn( void *value, void *a, void *b )
{
return( value );
}
/* Return the first item.
*/
static VipsOperation *
vips_cache_get_first( void )
{
VipsOperationCacheEntry *entry;
if( vips_cache_table &&
(entry = vips_hash_table_map( vips_cache_table,
vips_cache_get_first_fn, NULL, NULL )) )
return( VIPS_OPERATION( entry->operation ) );
return( NULL );
}
/**
* vips_cache_drop_all:
*
* Drop the whole operation cache, handy for leak tracking.
*/
void
vips_cache_drop_all( void )
{
g_mutex_lock( vips_cache_lock );
if( vips_cache_table ) {
VipsOperation *operation;
if( vips__cache_dump )
vips_cache_print();
/* We can't modify the hash in the callback from
* g_hash_table_foreach() and friends. Repeatedly drop the
* first item instead.
*/
while( (operation = vips_cache_get_first()) )
vips_cache_remove( operation );
VIPS_FREEF( g_hash_table_unref, vips_cache_table );
}
g_mutex_unlock( vips_cache_lock );
}
static void
vips_cache_get_lru_cb( VipsOperation *key, VipsOperationCacheEntry *value,
VipsOperationCacheEntry **best )
{
if( !*best ||
(*best)->time > value->time )
*best = value;
}
/* Get the least-recently-used cache item.
*
* TODO ... will this be too expensive? probably not
*/
static VipsOperation *
vips_cache_get_lru( void )
{
VipsOperationCacheEntry *entry;
entry = NULL;
g_hash_table_foreach( vips_cache_table,
(GHFunc) vips_cache_get_lru_cb, &entry );
if( entry )
return( entry->operation );
return( NULL );
}
/* Is the cache full? Drop until it's not.
*/
static void
vips_cache_trim( void )
{
VipsOperation *operation;
g_mutex_lock( vips_cache_lock );
while( vips_cache_table &&
(g_hash_table_size( vips_cache_table ) > vips_cache_max ||
vips_tracked_get_files() > vips_cache_max_files ||
vips_tracked_get_mem() > vips_cache_max_mem) &&
(operation = vips_cache_get_lru()) ) {
#ifdef DEBUG
printf( "vips_cache_trim: trimming %p\n", operation );
#endif /*DEBUG*/
vips_cache_remove( operation );
}
g_mutex_unlock( vips_cache_lock );
}
/** /**
* vips_cache_operation_buildp: (skip) * vips_cache_operation_buildp: (skip)
* @operation: pointer to operation to lookup * @operation: pointer to operation to lookup
@ -679,7 +734,7 @@ vips_cache_ref( VipsOperation *operation )
int int
vips_cache_operation_buildp( VipsOperation **operation ) vips_cache_operation_buildp( VipsOperation **operation )
{ {
VipsOperation *hit; VipsOperationCacheEntry *hit;
g_assert( VIPS_IS_OPERATION( *operation ) ); g_assert( VIPS_IS_OPERATION( *operation ) );
@ -693,15 +748,15 @@ vips_cache_operation_buildp( VipsOperation **operation )
if( (hit = g_hash_table_lookup( vips_cache_table, *operation )) ) { if( (hit = g_hash_table_lookup( vips_cache_table, *operation )) ) {
if( vips__cache_trace ) { if( vips__cache_trace ) {
printf( "vips cache-: " ); printf( "vips cache-: " );
vips_object_print_summary( VIPS_OBJECT( hit ) ); vips_object_print_summary( VIPS_OBJECT( *operation ) );
} }
/* Ref before unref in case *operation == hit. /* Ref before unref in case *operation == hit.
*/ */
vips_cache_ref( hit ); vips_cache_ref( hit->operation );
g_object_unref( *operation ); g_object_unref( *operation );
*operation = hit; *operation = hit->operation;
} }
g_mutex_unlock( vips_cache_lock ); g_mutex_unlock( vips_cache_lock );
@ -724,11 +779,8 @@ vips_cache_operation_buildp( VipsOperation **operation )
g_mutex_lock( vips_cache_lock ); g_mutex_lock( vips_cache_lock );
if( !(vips_operation_get_flags( *operation ) & if( !(vips_operation_get_flags( *operation ) &
VIPS_OPERATION_NOCACHE) ) { VIPS_OPERATION_NOCACHE) )
vips_cache_ref( *operation ); vips_cache_insert( *operation );
g_hash_table_insert( vips_cache_table,
*operation, *operation );
}
g_mutex_unlock( vips_cache_lock ); g_mutex_unlock( vips_cache_lock );
} }

View File

@ -2747,6 +2747,11 @@ vips_image_inplace( VipsImage *image )
return( -1 ); return( -1 );
} }
/* This image is about to be changed (probably). Make sure it's not
* in cache.
*/
vips_image_invalidate_all( image );
return( 0 ); return( 0 );
} }

View File

@ -392,16 +392,37 @@ vips_object_rewind( VipsObject *object )
/* Extra stuff we track for properties to do our argument handling. /* Extra stuff we track for properties to do our argument handling.
*/ */
static void
vips_argument_instance_detach( VipsArgumentInstance *argument_instance )
{
VipsObject *object = argument_instance->object;
VipsArgumentClass *argument_class = argument_instance->argument_class;
GObject *member = G_STRUCT_MEMBER( GObject *, object,
argument_class->offset );
if( argument_instance->close_id ) {
if( g_signal_handler_is_connected( member,
argument_instance->close_id ) )
g_signal_handler_disconnect( member,
argument_instance->close_id );
argument_instance->close_id = 0;
}
if( argument_instance->invalidate_id ) {
if( g_signal_handler_is_connected( member,
argument_instance->invalidate_id ) )
g_signal_handler_disconnect( member,
argument_instance->invalidate_id );
argument_instance->invalidate_id = 0;
}
}
/* Free a VipsArgumentInstance ... VipsArgumentClass can just be g_free()d. /* Free a VipsArgumentInstance ... VipsArgumentClass can just be g_free()d.
*/ */
static void static void
vips_argument_instance_free( VipsArgumentInstance *argument_instance ) vips_argument_instance_free( VipsArgumentInstance *argument_instance )
{ {
if( argument_instance->close_id ) { vips_argument_instance_detach( argument_instance );
g_signal_handler_disconnect( argument_instance->object,
argument_instance->close_id );
argument_instance->close_id = 0;
}
g_free( argument_instance ); g_free( argument_instance );
} }
@ -580,6 +601,7 @@ vips_argument_init( VipsObject *object )
argument_class->flags & argument_class->flags &
VIPS_ARGUMENT_SET_ALWAYS; VIPS_ARGUMENT_SET_ALWAYS;
argument_instance->close_id = 0; argument_instance->close_id = 0;
argument_instance->invalidate_id = 0;
vips_argument_table_replace( object->argument_table, vips_argument_table_replace( object->argument_table,
(VipsArgument *) argument_instance ); (VipsArgument *) argument_instance );
@ -720,14 +742,14 @@ vips_object_get_argument_priority( VipsObject *object, const char *name )
} }
static void static void
vips_object_clear_member( VipsObject *object, GParamSpec *pspec, vips_object_clear_member( VipsArgumentInstance *argument_instance )
GObject **member )
{ {
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsObject *object = argument_instance->object;
VipsArgumentClass *argument_class = (VipsArgumentClass *) VipsArgumentClass *argument_class = argument_instance->argument_class;
vips__argument_table_lookup( class->argument_table, pspec ); GObject **member = &G_STRUCT_MEMBER( GObject *, object,
VipsArgumentInstance *argument_instance = argument_class->offset );
vips__argument_get_instance( argument_class, object );
vips_argument_instance_detach( argument_instance );
if( *member ) { if( *member ) {
if( argument_class->flags & VIPS_ARGUMENT_INPUT ) { if( argument_class->flags & VIPS_ARGUMENT_INPUT ) {
@ -754,17 +776,6 @@ vips_object_clear_member( VipsObject *object, GParamSpec *pspec,
G_OBJECT( object )->ref_count - 1 ); G_OBJECT( object )->ref_count - 1 );
#endif /*DEBUG_REF*/ #endif /*DEBUG_REF*/
/* The object reffed us. Stop listening link to the
* object's "close" signal. We can come here from
* object being closed, in which case the handler
* will already have been disconnected for us.
*/
if( g_signal_handler_is_connected( object,
argument_instance->close_id ) )
g_signal_handler_disconnect( object,
argument_instance->close_id );
argument_instance->close_id = 0;
g_object_unref( object ); g_object_unref( object );
} }
@ -919,6 +930,18 @@ vips_object_finalize( GObject *gobject )
G_OBJECT_CLASS( vips_object_parent_class )->finalize( gobject ); G_OBJECT_CLASS( vips_object_parent_class )->finalize( gobject );
} }
static void
vips_object_arg_invalidate( GObject *argument,
VipsArgumentInstance *argument_instance )
{
/* Image @argument has signalled "invalidate" ... resignal on our
* operation.
*/
if( VIPS_IS_OPERATION( argument_instance->object ) )
vips_operation_invalidate(
VIPS_OPERATION( argument_instance->object ) );
}
static void static void
vips_object_arg_close( GObject *argument, vips_object_arg_close( GObject *argument,
VipsArgumentInstance *argument_instance ) VipsArgumentInstance *argument_instance )
@ -946,10 +969,11 @@ vips__object_set_member( VipsObject *object, GParamSpec *pspec,
vips__argument_table_lookup( class->argument_table, pspec ); vips__argument_table_lookup( class->argument_table, pspec );
VipsArgumentInstance *argument_instance = VipsArgumentInstance *argument_instance =
vips__argument_get_instance( argument_class, object ); vips__argument_get_instance( argument_class, object );
GType otype = G_PARAM_SPEC_VALUE_TYPE( pspec );
g_assert( argument_instance ); g_assert( argument_instance );
vips_object_clear_member( object, pspec, member ); vips_object_clear_member( argument_instance );
g_assert( !*member ); g_assert( !*member );
*member = argument; *member = argument;
@ -957,7 +981,7 @@ vips__object_set_member( VipsObject *object, GParamSpec *pspec,
if( *member ) { if( *member ) {
if( argument_class->flags & VIPS_ARGUMENT_INPUT ) { if( argument_class->flags & VIPS_ARGUMENT_INPUT ) {
#ifdef DEBUG_REF #ifdef DEBUG_REF
printf( "vips_object_set_member: vips object: " ); printf( "vips__object_set_member: vips object: " );
vips_object_print_name( object ); vips_object_print_name( object );
printf( " refers to gobject %s (%p)\n", printf( " refers to gobject %s (%p)\n",
G_OBJECT_TYPE_NAME( *member ), *member ); G_OBJECT_TYPE_NAME( *member ), *member );
@ -971,7 +995,7 @@ vips__object_set_member( VipsObject *object, GParamSpec *pspec,
} }
else if( argument_class->flags & VIPS_ARGUMENT_OUTPUT ) { else if( argument_class->flags & VIPS_ARGUMENT_OUTPUT ) {
#ifdef DEBUG_REF #ifdef DEBUG_REF
printf( "vips_object_set_member: gobject %s (%p)\n", printf( "vips__object_set_member: gobject %s (%p)\n",
G_OBJECT_TYPE_NAME( *member ), *member ); G_OBJECT_TYPE_NAME( *member ), *member );
printf( " refers to vips object: " ); printf( " refers to vips object: " );
vips_object_print_name( object ); vips_object_print_name( object );
@ -982,10 +1006,23 @@ vips__object_set_member( VipsObject *object, GParamSpec *pspec,
/* The argument reffs us. /* The argument reffs us.
*/ */
g_object_ref( object ); g_object_ref( object );
}
}
/* FIXME ... could use a NULLing weakref if( *member &&
*/ g_type_is_a( otype, VIPS_TYPE_IMAGE ) ) {
if( argument_class->flags & VIPS_ARGUMENT_INPUT ) {
g_assert( !argument_instance->invalidate_id );
argument_instance->invalidate_id =
g_signal_connect( *member, "invalidate",
G_CALLBACK(
vips_object_arg_invalidate ),
argument_instance );
}
else if( argument_class->flags & VIPS_ARGUMENT_OUTPUT ) {
g_assert( !argument_instance->close_id ); g_assert( !argument_instance->close_id );
argument_instance->close_id = argument_instance->close_id =
g_signal_connect( *member, "close", g_signal_connect( *member, "close",
G_CALLBACK( vips_object_arg_close ), G_CALLBACK( vips_object_arg_close ),
@ -1098,7 +1135,7 @@ vips_object_set_property( GObject *gobject,
GObject **member = &G_STRUCT_MEMBER( GObject *, object, GObject **member = &G_STRUCT_MEMBER( GObject *, object,
argument_class->offset ); argument_class->offset );
vips__object_set_member( object, pspec, member, vips__object_set_member( object, pspec, member,
g_value_get_object( value ) ); g_value_get_object( value ) );
} }
else if( G_IS_PARAM_SPEC_INT( pspec ) ) { else if( G_IS_PARAM_SPEC_INT( pspec ) ) {

View File

@ -92,6 +92,15 @@
/* Abstract base class for operations. /* Abstract base class for operations.
*/ */
/* Our signals.
*/
enum {
SIG_INVALIDATE,
SIG_LAST
};
static guint vips_operation_signals[SIG_LAST] = { 0 };
G_DEFINE_ABSTRACT_TYPE( VipsOperation, vips_operation, VIPS_TYPE_OBJECT ); G_DEFINE_ABSTRACT_TYPE( VipsOperation, vips_operation, VIPS_TYPE_OBJECT );
static void static void
@ -380,6 +389,14 @@ vips_operation_class_init( VipsOperationClass *class )
class->usage = vips_operation_usage; class->usage = vips_operation_usage;
class->get_flags = vips_operation_real_get_flags; class->get_flags = vips_operation_real_get_flags;
vips_operation_signals[SIG_INVALIDATE] = g_signal_new( "invalidate",
G_TYPE_FROM_CLASS( class ),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET( VipsOperationClass, invalidate ),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0 );
} }
static void static void
@ -419,6 +436,17 @@ vips_operation_class_print_usage( VipsOperationClass *operation_class )
printf( "%s", vips_buf_all( &buf ) ); printf( "%s", vips_buf_all( &buf ) );
} }
void
vips_operation_invalidate( VipsOperation *operation )
{
/*
printf( "vips_operation_invalidate: %p\n", operation );
vips_object_print_summary( VIPS_OBJECT( operation ) );
*/
g_signal_emit( operation, vips_operation_signals[SIG_INVALIDATE], 0 );
}
VipsOperation * VipsOperation *
vips_operation_new( const char *name ) vips_operation_new( const char *name )
{ {

View File

@ -60,11 +60,11 @@ static int
vips_labelregions_build( VipsObject *object ) vips_labelregions_build( VipsObject *object )
{ {
VipsMorphology *morphology = VIPS_MORPHOLOGY( object ); VipsMorphology *morphology = VIPS_MORPHOLOGY( object );
VipsLabelregions *labelregions = (VipsLabelregions *) object;
VipsImage *in = morphology->in; VipsImage *in = morphology->in;
VipsImage **t = (VipsImage **) vips_object_local_array( object, 7 ); VipsImage **t = (VipsImage **) vips_object_local_array( object, 2 );
VipsImage *mask;
int serial; int segments;
int *m; int *m;
int x, y; int x, y;
@ -72,43 +72,39 @@ vips_labelregions_build( VipsObject *object )
build( object ) ) build( object ) )
return( -1 ); return( -1 );
/* Create the zero mask image. /* Create the zero mask image in memory.
*/ */
mask = vips_image_new_memory();
g_object_set( object,
"mask", mask,
NULL );
if( vips_black( &t[0], in->Xsize, in->Ysize, NULL ) || if( vips_black( &t[0], in->Xsize, in->Ysize, NULL ) ||
vips_cast( t[0], &t[1], VIPS_FORMAT_INT, NULL ) ) vips_cast( t[0], &t[1], VIPS_FORMAT_INT, NULL ) ||
vips_image_write( t[1], mask ) )
return( -1 ); return( -1 );
/* Search the mask image, flooding as we find zero pixels. segments = 1;
*/ m = (int *) mask->data;
if( vips_image_inplace( t[1] ) ) for( y = 0; y < mask->Ysize; y++ ) {
return( -1 ); for( x = 0; x < mask->Xsize; x++ ) {
serial = 1;
m = (int *) t[1]->data;
for( y = 0; y < t[1]->Ysize; y++ ) {
for( x = 0; x < t[1]->Xsize; x++ ) {
if( !m[x] ) { if( !m[x] ) {
/* Use a direct path for speed. /* Use a direct path for speed.
*/ */
if( vips__draw_flood_direct( t[1], in, if( vips__draw_flood_direct( mask, in,
serial, x, y ) ) segments, x, y ) )
return( -1 ); return( -1 );
serial += 1; segments += 1;
} }
} }
m += t[1]->Xsize; m += mask->Xsize;
} }
g_object_set( object, g_object_set( object,
"mask", vips_image_new(), "segments", segments,
"segments", serial,
NULL ); NULL );
if( vips_image_write( t[1], labelregions->mask ) )
return( -1 );
return( 0 ); return( 0 );
} }