From 6e9834e623748c0893ffaba59d7374dec5fff0b6 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 11 Dec 2015 15:14:08 +0000 Subject: [PATCH] add vips_arrayjoin() --- ChangeLog | 1 + libvips/conversion/Makefile.am | 1 + libvips/conversion/arrayjoin.c | 437 ++++++++++++++++++++++++++++++ libvips/conversion/conversion.c | 2 + libvips/conversion/insert.c | 20 +- libvips/conversion/join.c | 5 +- libvips/include/vips/conversion.h | 2 + libvips/include/vips/internal.h | 3 + python/Vips.py | 1 + 9 files changed, 463 insertions(+), 9 deletions(-) create mode 100644 libvips/conversion/arrayjoin.c diff --git a/ChangeLog b/ChangeLog index 75cf5fbe..52b82744 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,6 +16,7 @@ - vips_resize() can do non-square resizes - dzsave removes tile metadata by default, thanks Benjamin - added vips_image_new_from_memory_copy() +- added vips_arrayjoin() 7/5/15 started 8.1.1 - oop, vips-8.0 wrapper script should be vips-8.1, thanks Danilo diff --git a/libvips/conversion/Makefile.am b/libvips/conversion/Makefile.am index 7ed43355..776e2bf8 100644 --- a/libvips/conversion/Makefile.am +++ b/libvips/conversion/Makefile.am @@ -16,6 +16,7 @@ libconversion_la_SOURCES = \ flip.c \ insert.c \ join.c \ + arrayjoin.c \ extract.c \ replicate.c \ cast.c \ diff --git a/libvips/conversion/arrayjoin.c b/libvips/conversion/arrayjoin.c new file mode 100644 index 00000000..1e9c043d --- /dev/null +++ b/libvips/conversion/arrayjoin.c @@ -0,0 +1,437 @@ +/* join an array of images together + * + * 11/12/15 + * - from join.c + */ + +/* + + 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 VIPS_DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include +#include + +#include "pconversion.h" + +/* Round N down to P boundary. + */ +#define ROUND_DOWN( N, P ) ((N) - ((N) % P)) + +/* Round N up to P boundary. + */ +#define ROUND_UP( N, P ) (ROUND_DOWN( (N) + (P) - 1, (P) )) + +typedef struct _VipsArrayjoin { + VipsConversion parent_instance; + + /* Params. + */ + VipsArrayImage *in; + int across; + int shim; + VipsArea *background; + VipsAlign halign; + VipsAlign valign; + int hspacing; + int vspacing; + + int down; + VipsRect *rects; + +} VipsArrayjoin; + +typedef VipsConversionClass VipsArrayjoinClass; + +G_DEFINE_TYPE( VipsArrayjoin, vips_arrayjoin, VIPS_TYPE_CONVERSION ); + +static int +vips_arrayjoin_gen( VipsRegion *or, void *seq, + void *a, void *b, gboolean *stop ) +{ + VipsRegion **ir = (VipsRegion **) seq; + VipsArrayjoin *join = (VipsArrayjoin *) b; + VipsRect *r = &or->valid; + int n = ((VipsArea *) join->in)->n; + + int i; + + /* Does this rect fit within one of our inputs? If it does, we + * can pass just the request on. + */ + for( i = 0; i < n; i++ ) + if( vips_rect_includesrect( &join->rects[i], r ) ) + return( vips__insert_just_one( or, ir[i], + join->rects[i].left, join->rects[i].top ) ); + + /* Output requires more than one input. Paste all touching inputs into + * the output. + */ + for( i = 0; i < n; i++ ) + if( vips__insert_paste_region( or, ir[i], &join->rects[i] ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_arrayjoin_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsConversion *conversion = VIPS_CONVERSION( object ); + VipsArrayjoin *join = (VipsArrayjoin *) object; + + VipsImage **in; + int n; + + VipsImage **format; + VipsImage **band; + VipsImage **size; + + int hspacing; + int vspacing; + int output_width; + int output_height; + int i; + + if( VIPS_OBJECT_CLASS( vips_arrayjoin_parent_class )->build( object ) ) + return( -1 ); + + in = vips_array_image_get( join->in, &n ); + + /* Move all input images to a common format and number of bands. + */ + format = (VipsImage **) vips_object_local_array( object, n ); + if( vips__formatalike_vec( in, format, n ) ) + return( -1 ); + in = format; + + /* We have to include the number of bands in @background in our + * calculation. + */ + band = (VipsImage **) vips_object_local_array( object, n ); + if( vips__bandalike_vec( class->nickname, + in, band, n, join->background->n ) ) + return( -1 ); + in = band; + + /* Now sizealike: search for the largest image. + */ + hspacing = in[0]->Xsize; + vspacing = in[0]->Ysize; + for( i = 1; i < n; i++ ) { + if( in[i]->Xsize > hspacing ) + hspacing = in[i]->Xsize; + if( in[i]->Ysize > vspacing ) + vspacing = in[i]->Ysize; + } + + if( !vips_object_argument_isset( object, "hspacing" ) ) + g_object_set( object, "hspacing", hspacing, NULL ); + if( !vips_object_argument_isset( object, "vspacing" ) ) + g_object_set( object, "vspacing", vspacing, NULL ); + + hspacing = join->hspacing; + vspacing = join->vspacing; + + if( !vips_object_argument_isset( object, "across" ) ) + g_object_set( object, "across", n, NULL ); + + /* How many images down the grid? + */ + join->down = ROUND_UP( n, join->across ) / join->across; + + /* The output size. + */ + output_width = hspacing * join->across + + join->shim * (join->across - 1); + output_height = vspacing * join->down + + join->shim * (join->down - 1); + + /* Make a rect for the position of each input. + */ + join->rects = VIPS_ARRAY( join, n, VipsRect ); + for( i = 0; i < n; i++ ) { + int x = i % join->across; + int y = i / join->across; + + join->rects[i].left = x * (hspacing + join->shim); + join->rects[i].top = y * (vspacing + join->shim); + join->rects[i].width = hspacing; + join->rects[i].height = vspacing; + + /* In the centre of the array, we make width / height larger + * by shim. + */ + if( x != join->across - 1 ) + join->rects[i].width += join->shim; + if( y != join->down - 1 ) + join->rects[i].height += join->shim; + + /* The right edge of the final image is stretched to the right + * to fill the whole row. + */ + if( i == n - 1 ) + join->rects[i].width = + output_width - join->rects[i].left; + } + + /* Each image must be cropped and aligned within an @hspacing by + * @vspacing box. + */ + size = (VipsImage **) vips_object_local_array( object, n ); + for( i = 0; i < n; i++ ) { + int left, top; + int width, height; + + switch( join->halign ) { + case VIPS_ALIGN_LOW: + left = 0; + break; + + case VIPS_ALIGN_CENTRE: + left = (hspacing - in[i]->Xsize) / 2; + break; + + case VIPS_ALIGN_HIGH: + left = hspacing - in[i]->Xsize; + break; + + default: + g_assert( 0 ); + break; + } + + switch( join->valign ) { + case VIPS_ALIGN_LOW: + top = 0; + break; + + case VIPS_ALIGN_CENTRE: + top = (vspacing - in[i]->Ysize) / 2; + break; + + case VIPS_ALIGN_HIGH: + top = vspacing - in[i]->Ysize; + break; + + default: + g_assert( 0 ); + break; + } + + width = join->rects[i].width; + height = join->rects[i].height; + + if( vips_embed( in[i], &size[i], left, top, width, height, + "extend", VIPS_EXTEND_BACKGROUND, + "background", join->background, + NULL ) ) + return( -1 ); + } + + if( vips_image_pipeline_array( conversion->out, + VIPS_DEMAND_STYLE_SMALLTILE, size ) ) + return( -1 ); + + conversion->out->Xsize = output_width; + conversion->out->Ysize = output_height; + + if( vips_image_generate( conversion->out, + vips_start_many, vips_arrayjoin_gen, vips_stop_many, + size, join ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_arrayjoin_class_init( VipsArrayjoinClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + + VIPS_DEBUG_MSG( "vips_arrayjoin_class_init\n" ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "arrayjoin"; + vobject_class->description = _( "join an array of images" ); + vobject_class->build = vips_arrayjoin_build; + + VIPS_ARG_BOXED( class, "in", -1, + _( "Input" ), + _( "Array of input images" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsArrayjoin, in ), + VIPS_TYPE_ARRAY_IMAGE ); + + VIPS_ARG_INT( class, "across", 4, + _( "Across" ), + _( "Number of images across grid" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsArrayjoin, across ), + 1, 1000000, 1 ); + + VIPS_ARG_INT( class, "shim", 5, + _( "Shim" ), + _( "Pixels between images" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsArrayjoin, shim ), + 0, 1000000, 0 ); + + VIPS_ARG_BOXED( class, "background", 6, + _( "Background" ), + _( "Colour for new pixels" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsArrayjoin, background ), + VIPS_TYPE_ARRAY_DOUBLE ); + + VIPS_ARG_ENUM( class, "halign", 7, + _( "Horizontal align" ), + _( "Align on the left, centre or right" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsArrayjoin, halign ), + VIPS_TYPE_ALIGN, VIPS_ALIGN_LOW ); + + VIPS_ARG_ENUM( class, "valign", 8, + _( "Vertical align" ), + _( "Align on the top, centre or bottom" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsArrayjoin, valign ), + VIPS_TYPE_ALIGN, VIPS_ALIGN_LOW ); + + VIPS_ARG_INT( class, "hspacing", 9, + _( "Horizontal spacing" ), + _( "Horizontal spacing between images" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsArrayjoin, hspacing ), + 1, 1000000, 1 ); + + VIPS_ARG_INT( class, "vspacing", 10, + _( "Vertical spacing" ), + _( "Vertical spacing between images" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsArrayjoin, vspacing ), + 1, 1000000, 1 ); + +} + +static void +vips_arrayjoin_init( VipsArrayjoin *join ) +{ + /* Init our instance fields. + */ + join->background = + vips_area_new_array( G_TYPE_DOUBLE, sizeof( double ), 1 ); + ((double *) (join->background->data))[0] = 0.0; +} + +static int +vips_arrayjoinv( VipsImage **in, VipsImage **out, int n, va_list ap ) +{ + VipsArrayImage *array; + int result; + + array = vips_array_image_new( in, n ); + result = vips_call_split( "arrayjoin", ap, array, out ); + vips_area_unref( VIPS_AREA( array ) ); + + return( result ); +} + +/** + * vips_arrayjoin: + * @in: (array length=n) (transfer none): array of input images + * @out: output image + * @n: number of input images + * + * Optional arguments: + * + * @across: number of images per row + * @shim: space between images, in pixels + * @background: background ink colour + * @halign: low, centre or high alignment + * @valign: low, centre or high alignment + * @hspacing: horizontal distance between images + * @vspacing: vertical distance between images + * + * Lay out the images in @in in a grid. The grid is @across images across and + * however high is necessary to use up all of @in. Images are set down + * left-to-right and top-to-bottom. @across defaults to @n. + * + * Each input image is placed with a box of size @hspacing by @vspacing + * pixels and cropped. These default to the largest width and largest height + * of the input images. + * + * Space between images is filled with @background. This defaults to 0 + * (black). + * + * Images are positioned within their @hspacing by @vspacing box at low, + * centre or high coordinate values, controlled by @halign and @valign. These + * default to left-top. + * + * Boxes are joined and separated by @shim pixels. This defaults to 0. + * + * If the number of bands in the input images differs, all but one of the + * images must have one band. In this case, an n-band image is formed from the + * one-band image by joining n copies of the one-band image together, and then + * the n-band images are operated upon. + * + * The input images are cast up to the smallest common type (see table + * Smallest common format in + * arithmetic). + * + * See also: vips_join(), vips_insert(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_arrayjoin( VipsImage **in, VipsImage **out, int n, ... ) +{ + va_list ap; + int result; + + va_start( ap, n ); + result = vips_arrayjoinv( in, out, n, ap ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/conversion/conversion.c b/libvips/conversion/conversion.c index 9ec4f420..b0ed5e80 100644 --- a/libvips/conversion/conversion.c +++ b/libvips/conversion/conversion.c @@ -223,6 +223,7 @@ vips_conversion_operation_init( void ) extern GType vips_flip_get_type( void ); extern GType vips_insert_get_type( void ); extern GType vips_join_get_type( void ); + extern GType vips_arrayjoin_get_type( void ); extern GType vips_extract_area_get_type( void ); extern GType vips_crop_get_type( void ); extern GType vips_extract_band_get_type( void ); @@ -268,6 +269,7 @@ vips_conversion_operation_init( void ) vips_flip_get_type(); vips_insert_get_type(); vips_join_get_type(); + vips_arrayjoin_get_type(); vips_extract_area_get_type(); vips_crop_get_type(); vips_extract_band_get_type(); diff --git a/libvips/conversion/insert.c b/libvips/conversion/insert.c index 534df5c7..f37dbd68 100644 --- a/libvips/conversion/insert.c +++ b/libvips/conversion/insert.c @@ -110,9 +110,11 @@ typedef VipsConversionClass VipsInsertClass; G_DEFINE_TYPE( VipsInsert, vips_insert, VIPS_TYPE_CONVERSION ); /* Trivial case: we just need pels from one of the inputs. + * + * Also used by vips_arrayjoin. */ -static int -vips_insert_just_one( VipsRegion *or, VipsRegion *ir, int x, int y ) +int +vips__insert_just_one( VipsRegion *or, VipsRegion *ir, int x, int y ) { VipsRect need; @@ -134,9 +136,11 @@ vips_insert_just_one( VipsRegion *or, VipsRegion *ir, int x, int y ) /* Paste in parts of ir that fall within or --- ir is an input REGION for an * image positioned at pos within or. + * + * Also used by vips_arrayjoin. */ -static int -vips_insert_paste_region( VipsRegion *or, VipsRegion *ir, VipsRect *pos ) +int +vips__insert_paste_region( VipsRegion *or, VipsRegion *ir, VipsRect *pos ) { VipsRect ovl; @@ -175,7 +179,7 @@ vips_insert_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) * sub-image? */ if( vips_rect_includesrect( &insert->rsub, &or->valid ) ) - return( vips_insert_just_one( or, ir[1], + return( vips__insert_just_one( or, ir[1], insert->rsub.left, insert->rsub.top ) ); /* Does it fall entirely inside the main, and not at all inside the @@ -184,7 +188,7 @@ vips_insert_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) vips_rect_intersectrect( &or->valid, &insert->rsub, &ovl ); if( vips_rect_includesrect( &insert->rmain, &or->valid ) && vips_rect_isempty( &ovl ) ) - return( vips_insert_just_one( or, ir[0], + return( vips__insert_just_one( or, ir[0], insert->rmain.left, insert->rmain.top ) ); /* Output requires both (or neither) input. If it is not entirely @@ -197,12 +201,12 @@ vips_insert_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) /* Paste from main. */ - if( vips_insert_paste_region( or, ir[0], &insert->rmain ) ) + if( vips__insert_paste_region( or, ir[0], &insert->rmain ) ) return( -1 ); /* Paste from sub. */ - if( vips_insert_paste_region( or, ir[1], &insert->rsub ) ) + if( vips__insert_paste_region( or, ir[1], &insert->rsub ) ) return( -1 ); return( 0 ); diff --git a/libvips/conversion/join.c b/libvips/conversion/join.c index 4b8a03b6..c74f851c 100644 --- a/libvips/conversion/join.c +++ b/libvips/conversion/join.c @@ -329,7 +329,10 @@ vips_join_init( VipsJoin *join ) * Smallest common format in * arithmetic). * - * See also: vips_insert(). + * If you are going to be joining many thousands of images in a regular + * grid, vips_arrayjoin() is a better choice. + * + * See also: vips_arrayjoin(), vips_insert(). * * Returns: 0 on success, -1 on error */ diff --git a/libvips/include/vips/conversion.h b/libvips/include/vips/conversion.h index dd4fdd25..1a47fc16 100644 --- a/libvips/include/vips/conversion.h +++ b/libvips/include/vips/conversion.h @@ -105,6 +105,8 @@ int vips_insert( VipsImage *main, VipsImage *sub, VipsImage **out, int vips_join( VipsImage *in1, VipsImage *in2, VipsImage **out, VipsDirection direction, ... ) __attribute__((sentinel)); +int vips_arrayjoin( VipsImage **in, VipsImage **out, int n, ... ) + __attribute__((sentinel)); int vips_extract_area( VipsImage *in, VipsImage **out, int left, int top, int width, int height, ... ) __attribute__((sentinel)); diff --git a/libvips/include/vips/internal.h b/libvips/include/vips/internal.h index 979eed81..aaae7e2d 100644 --- a/libvips/include/vips/internal.h +++ b/libvips/include/vips/internal.h @@ -197,6 +197,9 @@ void vips__draw_line_direct( VipsImage *image, int x1, int y1, int x2, int y2, void vips__draw_circle_direct( VipsImage *image, int cx, int cy, int r, VipsDrawScanline draw_scanline, void *client ); +int vips__insert_just_one( VipsRegion *out, VipsRegion *in, int x, int y ); +int vips__insert_paste_region( VipsRegion *out, VipsRegion *in, VipsRect *pos ); + /* Register base vips interpolators, called during startup. */ void vips__interpolate_init( void ); diff --git a/python/Vips.py b/python/Vips.py index e4f59513..b78d2d70 100644 --- a/python/Vips.py +++ b/python/Vips.py @@ -1050,6 +1050,7 @@ class_methods = [ "system", "sum", "bandjoin", + "arrayjoin", "bandrank", "black", "gaussnoise",