libvips/libvips/conversion/arrayjoin.c

521 lines
13 KiB
C

/* join an array of images together
*
* 11/12/15
* - from join.c
* 6/9/21
* - minmise inputs once we've used them
* 29/12/22
* - much faster with large arrays
*/
/*
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 <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib/gi18n-lib.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <vips/vips.h>
#include <vips/internal.h>
#include <vips/debug.h>
#include "pconversion.h"
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;
gboolean *minimised;
} 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 )
{
VipsImage **in = (VipsImage **) a;
VipsArrayjoin *join = (VipsArrayjoin *) b;
VipsConversion *conversion = VIPS_CONVERSION( join );
VipsRect *r = &or->valid;
int n;
/* Find the left/top/width/height of the cells this region touches.
*/
int cell_width = join->hspacing + join->shim;
int cell_height = join->vspacing + join->shim;
int left = r->left / cell_width;
int top = r->top / cell_height;
int width = (VIPS_ROUND_UP( VIPS_RECT_RIGHT( r ), cell_width ) -
VIPS_ROUND_DOWN( r->left, cell_width )) / cell_width;
int height = (VIPS_ROUND_UP( VIPS_RECT_BOTTOM( r ), cell_height ) -
VIPS_ROUND_DOWN( r->top, cell_height )) / cell_height;
int i;
VipsRegion *reg;
/* Size of image array.
*/
vips_array_image_get( join->in, &n );
/* Does this rect fit completely within one of our inputs? We can just
* forward the request.
*/
if( width == 1 && height == 1 ) {
i = VIPS_MIN( n - 1, left + top * join->across );
reg = vips_region_new( in[i] );
if( vips__insert_just_one( or, reg,
join->rects[i].left, join->rects[i].top ) ) {
g_object_unref( reg );
return( -1 );
}
g_object_unref( reg );
}
else {
/* Output requires more than one input. Paste all touching
* inputs into the output.
*/
int x, y;
for( y = 0; y < height; y++ )
for( x = 0; x < width; x++ ) {
i = VIPS_MIN( n - 1,
x + left + (y + top) * join->across );
reg = vips_region_new( in[i] );
if( vips__insert_paste_region( or, reg,
&join->rects[i] ) ) {
g_object_unref( reg );
return( -1 );
}
g_object_unref( reg );
}
}
if( vips_image_is_sequential( conversion->out ) )
/* In sequential mode, we can minimise an input once our
* generate point is well past the end of it. This can save a
* lot of memory and file descriptors on large image arrays.
*
* minimise_all is quite expensive, so only trigger once for
* each input.
*
* We don't lock for minimised[], but it's harmless.
*/
for( i = 0; i < n; i++ ) {
int bottom_edge = VIPS_RECT_BOTTOM( &join->rects[i] );
if( !join->minimised[i] &&
r->top > bottom_edge + 1024 ) {
join->minimised[i] = TRUE;
vips_image_minimise_all( in[i] );
}
}
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 );
/* Array length zero means error.
*/
if( n == 0 )
return( -1 );
/* 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" ) )
join->hspacing = hspacing;
if( !vips_object_argument_isset( object, "vspacing" ) )
join->vspacing = vspacing;
hspacing = join->hspacing;
vspacing = join->vspacing;
if( !vips_object_argument_isset( object, "across" ) )
join->across = n;
/* How many images down the grid?
*/
join->down = VIPS_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;
}
/* A thing to track which inputs we've signalled minimise on.
*/
join->minimised = VIPS_ARRAY( join, n, gboolean );
for( i = 0; i < n; i++ )
join->minimised[i] = FALSE;
/* 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;
/* Compiler warnings.
*/
left = 0;
top = 0;
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_not_reached();
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_not_reached();
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_THINSTRIP, size ) )
return( -1 );
conversion->out->Xsize = output_width;
conversion->out->Ysize = output_height;
/* DOn't use start_many -- the set of input images can be huge (many
* 10s of 1000s) and we don't want to have 20,000 regions active. It's
* much quicker to make them on demand.
*/
if( vips_image_generate( conversion->out,
NULL, vips_arrayjoin_gen, NULL, 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 );
VipsOperationClass *operation_class = VIPS_OPERATION_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;
operation_class->flags = VIPS_OPERATION_SEQUENTIAL;
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: (out): output image
* @n: number of input images
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* * @across: %gint, number of images per row
* * @shim: %gint, space between images, in pixels
* * @background: #VipsArrayDouble, background ink colour
* * @halign: #VipsAlign, low, centre or high alignment
* * @valign: #VipsAlign, low, centre or high alignment
* * @hspacing: %gint, horizontal distance between images
* * @vspacing: %gint, 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
* <link linkend="libvips-arithmetic">arithmetic</link>).
*
* vips_colourspace() can be useful for moving the images to a common
* colourspace for compositing.
*
* See also: vips_join(), vips_insert(), vips_colourspace().
*
* 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 );
}