libvips/libvips/iofuncs/reorder.c

389 lines
9.6 KiB
C

/* reorder.c ... manage reorder reordering
*
* 11/1/17
* - first version
*/
/*
This file is part of VIPS.
VIPS is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
/*
#define DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib/gi18n-lib.h>
#include <vips/vips.h>
#include <vips/internal.h>
#include <vips/debug.h>
/* Have one of these on every image, identified by a quark.
*/
typedef struct _VipsReorder {
/* The image we are attached to.
*/
VipsImage *image;
/* The direct inputs to this image, so a copy of the array that is
* passed to vips_image_pipeline_array(), and in the same order.
* NULL-terminated.
*
* Score is the priority we give to the inputs as we de-dupe the source
* arrays.
*
* The recomp order is the order we prepare regions in ... just make a
* range then sort by score.
*/
int n_inputs;
VipsImage **input;
int *score;
int *recomp_order;
/* Source images are images with no input images, so file load,
* vips_black(), etc. NULL-terminated array.
*
* The cumulative margin is the total margin that has been added to
* each source image up to this point in the pipeline.
*/
int n_sources;
VipsImage **source;
int *cumulative_margin;
} VipsReorder;
GQuark vips__image_reorder_quark = 0;
#ifdef DEBUG
static void
vips_reorder_print( VipsReorder *reorder )
{
int i;
printf( "vips_reorder_print: " );
vips_object_print_name( VIPS_OBJECT( reorder->image ) );
printf( "\n" );
printf( "n_inputs = %d\n", reorder->n_inputs );
printf( " n score order\n" );
for( i = 0; i < reorder->n_inputs; i++ ) {
printf( "%2d - %8d, %8d, ",
i, reorder->score[i], reorder->recomp_order[i] );
vips_object_print_name( VIPS_OBJECT( reorder->input[i] ) );
printf( "\n" );
}
printf( "n_sources = %d\n", reorder->n_sources );
printf( " n margin\n" );
for( i = 0; i < reorder->n_sources; i++ ) {
printf( "%2d - %8d, ",
i, reorder->cumulative_margin[i] );
vips_object_print_name( VIPS_OBJECT( reorder->source[i] ) );
printf( "\n" );
}
}
#endif /*DEBUG*/
static void
vips_reorder_free( VipsReorder *reorder )
{
/* We free explicitly, rather than using VIPS_ARRAY( image ... ), since
* we need to make sure these pointers are valid to this point in the
* close cycle.
*/
VIPS_FREE( reorder->input );
VIPS_FREE( reorder->score );
VIPS_FREE( reorder->recomp_order );
VIPS_FREE( reorder->source );
VIPS_FREE( reorder->cumulative_margin );
}
static void
vips_reorder_destroy( VipsReorder *reorder )
{
vips_reorder_free( reorder );
VIPS_FREE( reorder );
}
static VipsReorder *
vips_reorder_get( VipsImage *image )
{
VipsReorder *reorder;
if( (reorder = g_object_get_qdata( G_OBJECT( image ),
vips__image_reorder_quark )) )
return( reorder );
reorder = VIPS_NEW( NULL, VipsReorder );
reorder->image = image;
reorder->n_inputs = 0;
reorder->input = NULL;
reorder->score = NULL;
reorder->recomp_order = NULL;
reorder->n_sources = 0;
reorder->source = NULL;
reorder->cumulative_margin = NULL;
g_object_set_qdata_full( G_OBJECT( image ), vips__image_reorder_quark,
reorder, (GDestroyNotify) vips_reorder_destroy );
return( reorder );
}
static int
vips_reorder_compare_score( const void *a, const void *b, void *arg )
{
int i1 = *((int *) a);
int i2 = *((int *) b);
VipsReorder *reorder = (VipsReorder *) arg;
return( reorder->score[i2] - reorder->score[i1] );
}
int
vips__reorder_set_input( VipsImage *image, VipsImage **in )
{
VipsReorder *reorder = vips_reorder_get( image );
int i;
int total;
/* We have to support being called more than once on the same image.
* Two cases:
*
* 1. in the first call, no images were set ... we throw away
* everything from the first call and try again. foreign can do this.
*
* 2. warn if the args were different and do nothing.
*/
if( reorder->source ) {
if( reorder->n_inputs == 0 ) {
reorder->n_sources = 0;
vips_reorder_free( reorder );
}
else {
for( i = 0; in[i]; i++ )
if( i >= reorder->n_inputs ||
in[i] != reorder->input[i] ) {
/* Should never happen.
*/
g_warning( "vips__reorder_set_input: "
"args differ\n" );
break;
}
return( 0 );
}
}
/* Make a copy of the input array.
*/
for( i = 0; in[i]; i++ )
;
reorder->n_inputs = i;
reorder->input = VIPS_ARRAY( NULL, reorder->n_inputs + 1, VipsImage * );
reorder->score = VIPS_ARRAY( NULL, reorder->n_inputs, int );
reorder->recomp_order = VIPS_ARRAY( NULL, reorder->n_inputs, int );
if( !reorder->input )
return( -1 );
if( reorder->n_inputs &&
(!reorder->score ||
!reorder->recomp_order) )
return( -1 );
for( i = 0; i < reorder->n_inputs; i++ ) {
reorder->input[i] = in[i];
reorder->score[i] = 0;
reorder->recomp_order[i] = i;
}
reorder->input[i] = NULL;
/* Find the total number of source images -- this gives an upper bound
* to the size of the unique source image array we will need.
*/
total = 0;
for( i = 0; i < reorder->n_inputs; i++ )
total += vips_reorder_get( reorder->input[i] )->n_sources;
/* No source images means this must itself be a source image, so it has
* a source image of itself.
*/
total = VIPS_MAX( 1, total );
reorder->source = VIPS_ARRAY( NULL, total + 1, VipsImage * );
reorder->cumulative_margin = VIPS_ARRAY( NULL, total, int );
if( !reorder->source ||
!reorder->cumulative_margin )
return( -1 );
/* Copy source images over, removing duplicates. If we find a
* duplicate, we have a reordering opportunity, and we adjust the
* scores of the two images containing the dupe.
*/
for( i = 0; i < reorder->n_inputs; i++ ) {
VipsReorder *input = vips_reorder_get( reorder->input[i] );
int j;
for( j = 0; j < input->n_sources; j++ ) {
int k;
/* Search for dupe.
*/
for( k = 0; k < reorder->n_sources; k++ )
if( reorder->source[k] == input->source[j] )
break;
if( k < reorder->n_sources ) {
/* Found a dupe. Does this new use of
* input->source[j] have a larger or smaller
* margin? Adjust the score to reflect the
* change, note the new max.
*/
reorder->score[i] +=
input->cumulative_margin[j] -
reorder->cumulative_margin[k];
reorder->cumulative_margin[k] = VIPS_MAX(
reorder->cumulative_margin[k],
input->cumulative_margin[j] );
}
else {
/* No dupe, just add to the table.
*/
reorder->source[reorder->n_sources] =
input->source[j];
reorder->cumulative_margin[reorder->n_sources] =
input->cumulative_margin[j];
reorder->n_sources += 1;
}
}
}
/* Sort recomp_order by score. qsort_r() is a GNU libc thing, don't use
* it.
*/
if( reorder->n_inputs > 1 )
g_qsort_with_data( reorder->recomp_order,
reorder->n_inputs,
sizeof( int ),
vips_reorder_compare_score, reorder );
/* No sources ... make one, us!
*/
if( reorder->n_inputs == 0 ) {
reorder->source[0] = image;
reorder->cumulative_margin[0] = 0;
reorder->n_sources = 1;
}
#ifdef DEBUG
vips_reorder_print( reorder );
#endif /*DEBUG*/
return( 0 );
}
/**
* vips_reorder_prepare_many: (method)
* @image: the image that's being written
* @regions: (array): the set of regions to prepare
* @r: the #VipsRect to prepare on each region
*
* vips_reorder_prepare_many() runs vips_region_prepare() on each region in
* @regions, requesting the pixels in @r.
*
* It tries to request the regions in the order which will cause least
* recomputation. This can give a large speedup, in some cases.
*
* See also: vips_region_prepare(), vips_reorder_margin_hint().
*
* Returns: 0 on success, or -1 on error.
*/
int
vips_reorder_prepare_many( VipsImage *image, VipsRegion **regions, VipsRect *r )
{
VipsReorder *reorder = vips_reorder_get( image );
int i;
for( i = 0; i < reorder->n_inputs; i++ ) {
g_assert( regions[i] );
if( vips_region_prepare(
regions[reorder->recomp_order[i]], r ) )
return( -1 );
}
return( 0 );
}
/**
* vips_reorder_margin_hint: (method)
* @image: the image to hint on
* @margin: the size of the margin this operation has added
*
* vips_reorder_margin_hint() sets a hint that @image contains a margin, that
* is, that each vips_region_prepare() on @image will request a slightly larger
* region from it's inputs. A good value for @margin is (width * height) for
* the window the operation uses.
*
* This information is used by vips_image_prepare_many() to attempt to reorder
* computations to minimise recomputation.
*
* See also: vips_image_prepare_many().
*/
void
vips_reorder_margin_hint( VipsImage *image, int margin )
{
VipsReorder *reorder = vips_reorder_get( image );
int i;
for( i = 0; i < reorder->n_sources; i++ )
reorder->cumulative_margin[i] += margin;
}
void
vips__reorder_clear( VipsImage *image )
{
g_object_set_qdata( G_OBJECT( image ),
vips__image_reorder_quark, NULL );
}
void
vips__reorder_init( void )
{
if( !vips__image_reorder_quark )
vips__image_reorder_quark =
g_quark_from_static_string( "vips-image-reorder" );
}