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
- better filename tracking for globalbalance
- 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
- 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.
@ -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?
- do restrict on some more packages, we've just done arithmetic so far
@ -107,6 +72,7 @@
- use g_log() instead of vips_info()?
- quadratic doesn't work for order 3
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
- 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
======
@ -194,9 +148,6 @@ packaging
- test _O_TEMPORARY thing on Windows
- do we bundle "convert" in the OS X / win32 builds? if we don't we
should
convolution
===========
@ -233,10 +184,6 @@ convolution
arithmetic
==========
- avg/dev etc. should uncode images? eg. labq2lab etc.
how about ifthenelse?
- HAVE_HYPOT could define a hypot() macro?
- fix a better NaN policy

View File

@ -283,6 +283,11 @@ typedef struct _VipsArgumentInstance {
* here.
*/
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;
/* 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 {
VipsObject parent_instance;
/* When we added this operation to cache .. used to find LRU for
* flush.
*/
int time;
/* Keep the hash here.
*/
guint hash;
@ -92,12 +87,18 @@ typedef struct _VipsOperationClass {
*/
VipsOperationFlags (*get_flags)( VipsOperation * );
VipsOperationFlags flags;
/* One of our input images has signalled "invalidate". The cache uses
* VipsOperation::invalidate to drop dirty ops.
*/
void (*invalidate)( VipsOperation * );
} VipsOperationClass;
GType vips_operation_get_type( void );
VipsOperationFlags vips_operation_get_flags( VipsOperation *operation );
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 );
VipsOperation *vips_operation_new( const char *name );

View File

@ -37,8 +37,6 @@
TODO
listen for invalidate
will we need to drop all on exit? unclear
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
#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
* held in a GParamSpec allowing OBJECT, but the value could be of type
* VipsImage. generics are much faster to compare.
@ -450,10 +464,12 @@ vips__cache_init( void )
static void *
vips_cache_print_fn( void *value, void *a, void *b )
{
VipsOperationCacheEntry *entry = value;
char str[32768];
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 ) );
@ -515,113 +531,25 @@ vips_cache_unref( VipsOperation *operation )
g_object_unref( operation );
}
/* Drop an operation from the cache.
/* Remove an operation from the cache.
*/
static void
vips_cache_drop( VipsOperation *operation )
vips_cache_remove( VipsOperation *operation )
{
/* It must be in cache.
*/
g_assert( g_hash_table_lookup( vips_cache_table, operation ) );
VipsOperationCacheEntry *entry = (VipsOperationCacheEntry *)
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 );
vips_cache_unref( operation );
}
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 )
{
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 );
g_free( entry );
}
static void *
@ -649,8 +577,11 @@ vips_object_ref_arg( VipsObject *object,
static void
vips_operation_touch( VipsOperation *operation )
{
VipsOperationCacheEntry *entry = (VipsOperationCacheEntry *)
g_hash_table_lookup( vips_cache_table, operation );
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
@ -665,6 +596,130 @@ vips_cache_ref( VipsOperation *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)
* @operation: pointer to operation to lookup
@ -679,7 +734,7 @@ vips_cache_ref( VipsOperation *operation )
int
vips_cache_operation_buildp( VipsOperation **operation )
{
VipsOperation *hit;
VipsOperationCacheEntry *hit;
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( vips__cache_trace ) {
printf( "vips cache-: " );
vips_object_print_summary( VIPS_OBJECT( hit ) );
vips_object_print_summary( VIPS_OBJECT( *operation ) );
}
/* Ref before unref in case *operation == hit.
*/
vips_cache_ref( hit );
vips_cache_ref( hit->operation );
g_object_unref( *operation );
*operation = hit;
*operation = hit->operation;
}
g_mutex_unlock( vips_cache_lock );
@ -724,11 +779,8 @@ vips_cache_operation_buildp( VipsOperation **operation )
g_mutex_lock( vips_cache_lock );
if( !(vips_operation_get_flags( *operation ) &
VIPS_OPERATION_NOCACHE) ) {
vips_cache_ref( *operation );
g_hash_table_insert( vips_cache_table,
*operation, *operation );
}
VIPS_OPERATION_NOCACHE) )
vips_cache_insert( *operation );
g_mutex_unlock( vips_cache_lock );
}

View File

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

View File

@ -392,16 +392,37 @@ vips_object_rewind( VipsObject *object )
/* 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.
*/
static void
vips_argument_instance_free( VipsArgumentInstance *argument_instance )
{
if( argument_instance->close_id ) {
g_signal_handler_disconnect( argument_instance->object,
argument_instance->close_id );
argument_instance->close_id = 0;
}
vips_argument_instance_detach( argument_instance );
g_free( argument_instance );
}
@ -580,6 +601,7 @@ vips_argument_init( VipsObject *object )
argument_class->flags &
VIPS_ARGUMENT_SET_ALWAYS;
argument_instance->close_id = 0;
argument_instance->invalidate_id = 0;
vips_argument_table_replace( object->argument_table,
(VipsArgument *) argument_instance );
@ -720,14 +742,14 @@ vips_object_get_argument_priority( VipsObject *object, const char *name )
}
static void
vips_object_clear_member( VipsObject *object, GParamSpec *pspec,
GObject **member )
vips_object_clear_member( VipsArgumentInstance *argument_instance )
{
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 );
VipsObject *object = argument_instance->object;
VipsArgumentClass *argument_class = argument_instance->argument_class;
GObject **member = &G_STRUCT_MEMBER( GObject *, object,
argument_class->offset );
vips_argument_instance_detach( argument_instance );
if( *member ) {
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 );
#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 );
}
@ -919,6 +930,18 @@ vips_object_finalize( GObject *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
vips_object_arg_close( GObject *argument,
VipsArgumentInstance *argument_instance )
@ -946,10 +969,11 @@ vips__object_set_member( VipsObject *object, GParamSpec *pspec,
vips__argument_table_lookup( class->argument_table, pspec );
VipsArgumentInstance *argument_instance =
vips__argument_get_instance( argument_class, object );
GType otype = G_PARAM_SPEC_VALUE_TYPE( pspec );
g_assert( argument_instance );
vips_object_clear_member( object, pspec, member );
vips_object_clear_member( argument_instance );
g_assert( !*member );
*member = argument;
@ -957,7 +981,7 @@ vips__object_set_member( VipsObject *object, GParamSpec *pspec,
if( *member ) {
if( argument_class->flags & VIPS_ARGUMENT_INPUT ) {
#ifdef DEBUG_REF
printf( "vips_object_set_member: vips object: " );
printf( "vips__object_set_member: vips object: " );
vips_object_print_name( object );
printf( " refers to gobject %s (%p)\n",
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 ) {
#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 );
printf( " refers to vips object: " );
vips_object_print_name( object );
@ -982,10 +1006,23 @@ vips__object_set_member( VipsObject *object, GParamSpec *pspec,
/* The argument reffs us.
*/
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 );
argument_instance->close_id =
g_signal_connect( *member, "close",
G_CALLBACK( vips_object_arg_close ),
@ -1098,7 +1135,7 @@ vips_object_set_property( GObject *gobject,
GObject **member = &G_STRUCT_MEMBER( GObject *, object,
argument_class->offset );
vips__object_set_member( object, pspec, member,
vips__object_set_member( object, pspec, member,
g_value_get_object( value ) );
}
else if( G_IS_PARAM_SPEC_INT( pspec ) ) {

View File

@ -92,6 +92,15 @@
/* 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 );
static void
@ -380,6 +389,14 @@ vips_operation_class_init( VipsOperationClass *class )
class->usage = vips_operation_usage;
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
@ -419,6 +436,17 @@ vips_operation_class_print_usage( VipsOperationClass *operation_class )
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 *
vips_operation_new( const char *name )
{

View File

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