From 7c7c30a6010f072453e64312274e9a3c25e51a04 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 27 Sep 2017 08:58:36 +0100 Subject: [PATCH] works! but only OVER mode at the moment, and it's not especially quick --- libvips/conversion/Makefile.am | 1 + libvips/conversion/composite.c | 237 ++++++++++++++++++++++++------ libvips/conversion/conversion.c | 2 + libvips/include/vips/conversion.h | 5 + libvips/include/vips/enumtypes.h | 2 + libvips/iofuncs/enumtypes.c | 17 +++ 6 files changed, 222 insertions(+), 42 deletions(-) diff --git a/libvips/conversion/Makefile.am b/libvips/conversion/Makefile.am index 54e42b11..20ceb3cb 100644 --- a/libvips/conversion/Makefile.am +++ b/libvips/conversion/Makefile.am @@ -1,6 +1,7 @@ noinst_LTLIBRARIES = libconversion.la libconversion_la_SOURCES = \ + composite.c \ smartcrop.c \ conversion.c \ pconversion.h \ diff --git a/libvips/conversion/composite.c b/libvips/conversion/composite.c index 0eb381f9..10d37929 100644 --- a/libvips/conversion/composite.c +++ b/libvips/conversion/composite.c @@ -49,6 +49,16 @@ #include #include +#include "pconversion.h" + +/* Maximum number of input images -- why not? + */ +#define MAX_INPUT_IMAGES (64) + +/* Maximum number of image bands. + */ +#define MAX_BANDS (64) + /** * VipsBlendMode: * VIPS_BLEND_MODE_OVER: @@ -61,6 +71,13 @@ * @gasi's composite example https://gist.github.com/jcupitt/abacc012e2991f332e8b * * https://en.wikipedia.org/wiki/Alpha_compositing + * + * composite -compose over PNG_transparency_demonstration_1.png \ + * Opera-icon-high-res.png x.png + * + * vips composite "PNG_transparency_demonstration_1.png \ + * Opera-icon-high-res.png" x.png 0 + * */ typedef struct _VipsComposite { @@ -79,44 +96,112 @@ typedef struct _VipsComposite { */ VipsInterpretation compositing_space; + /* Set if the input images have already been premultiplied. + */ + gboolean premultiplied; + /* The number of inputs. This can be less than the number of images in * @in. */ int n; + + /* The number of bands we are blending. + */ + int bands; + } VipsComposite; typedef VipsConversionClass VipsCompositeClass; G_DEFINE_TYPE( VipsComposite, vips_composite, VIPS_TYPE_CONVERSION ); -#define COMPOSE_INT( TYPE ) { \ - TYPE +#define BLEND( OUT, AOUT, SRC1, A1, SRC2, A2 ) { \ + if( AOUT == 0 ) \ + OUT = 0; \ + else \ + switch( mode ) { \ + case VIPS_BLEND_MODE_OVER: \ + OUT = (SRC1 + SRC2 * (1 - A1)) / AOUT; \ + break; \ + \ + default: \ + g_assert_not_reached(); \ + } \ } +#define COMBINE( TYPE ) { \ + TYPE **tp = (TYPE **) p; \ + TYPE *tq = (TYPE *) q; \ + \ + for( x = 0; x < width; x++ ) { \ + int o = x * (bands + 1); \ + \ + for( b = 0; b < bands; b++ ) \ + pixel[b] = tp[n - 1][o + b]; \ + alpha = tp[n - 1][o + bands] / 255.0; \ + \ + for( i = n - 2; i >= 0; i-- ) { \ + TYPE *src1 = tp[i] + o; \ + double a1 = src1[bands] / 255.0; \ + double aout = a1 + alpha * (1 - a1); \ + VipsBlendMode mode = ((VipsBlendMode *) \ + composite->mode->area.data)[i]; \ + \ + for( b = 0; b < bands; b++ ) \ + BLEND( pixel[b], aout, \ + src1[b], a1, pixel[b], alpha ); \ + \ + alpha = aout; \ + } \ + \ + for( b = 0; b < bands; b++ ) \ + tq[o + b] = pixel[b]; \ + tq[o + bands] = alpha * 255; \ + } \ +} static void vips_composite_process_line( VipsComposite *composite, VipsBandFormat format, VipsPel *q, VipsPel **p, int width ) { int n = composite->n; - int i; + int bands = composite->bands; + + double pixel[MAX_BANDS]; + double alpha; + int x, i, b; switch( format ) { case VIPS_FORMAT_UCHAR: - COMPOSE_INT( unsigned char ); + COMBINE( unsigned char ); break; case VIPS_FORMAT_CHAR: - COMPOSE_INT( unsigned char ); + COMBINE( signed char ); break; case VIPS_FORMAT_USHORT: + COMBINE( unsigned short ); + break; + case VIPS_FORMAT_SHORT: + COMBINE( signed short ); + break; + case VIPS_FORMAT_UINT: + COMBINE( unsigned int ); + break; + case VIPS_FORMAT_INT: + COMBINE( signed int ); + break; + case VIPS_FORMAT_FLOAT: + COMBINE( float ); + break; + case VIPS_FORMAT_DOUBLE: - g_assert_not_reached(); + COMBINE( double ); break; case VIPS_FORMAT_COMPLEX: @@ -138,7 +223,7 @@ vips_composite_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop if( vips_reorder_prepare_many( or->im, ir, r ) ) return( -1 ); - for( i = 0; i < bandary->n; i++ ) + for( i = 0; i < composite->n; i++ ) p[i] = VIPS_REGION_ADDR( ir[i], r->left, r->top ); p[i] = NULL; q = VIPS_REGION_ADDR( or, r->left, r->top ); @@ -149,7 +234,7 @@ vips_composite_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop vips_composite_process_line( composite, ir[0]->im->BandFmt, q, p, r->width ); - for( i = 0; i < bandary->n; i++ ) + for( i = 0; i < composite->n; i++ ) p[i] += VIPS_REGION_LSKIP( ir[i] ); q += VIPS_REGION_LSKIP( or ); } @@ -164,8 +249,8 @@ vips_composite_build( VipsObject *object ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsConversion *conversion = VIPS_CONVERSION( object ); - VipsBandary *bandary = (VipsBandary *) object; VipsComposite *composite = (VipsComposite *) object; + VipsImage **t = (VipsImage **) vips_object_local_array( object, 5 ); int i; VipsImage **in; @@ -177,13 +262,49 @@ vips_composite_build( VipsObject *object ) VipsInterpretation compositing_space; int max_bands; VipsInterpretation max_interpretation; + VipsBlendMode *mode; + VipsImage *out; if( VIPS_OBJECT_CLASS( vips_composite_parent_class )->build( object ) ) return( -1 ); - composite->n = composite->in->area->n; + composite->n = composite->in->area.n; + + if( composite->n <= 0 ) { + vips_error( class->nickname, "%s", _( "no input images" ) ); + return( -1 ); + } + if( composite->mode->area.n != composite->n - 1 ) { + vips_error( class->nickname, + _( "for %d input images there must be %d blend modes" ), + composite->n, composite->n - 1 ); + return( -1 ); + } + mode = (VipsBlendMode *) composite->mode->area.data; + for( i = 0; i < composite->n - 1; i++ ) { + if( mode[i] < 0 || + mode[i] >= VIPS_BLEND_MODE_LAST ) { + vips_error( class->nickname, + _( "blend mode index %d (%d) invalid" ), + i, mode[i] ); + return( -1 ); + } + } + + in = (VipsImage **) composite->in->area.data; + + /* Premultiply alpha, if it hasn't been. + */ + if( !composite->premultiplied ) { + VipsImage **premultiply = (VipsImage **) + vips_object_local_array( object, composite->n ); + + for( i = 0; i < composite->n; i++ ) + if( vips_premultiply( in[i], &premultiply[i], NULL ) ) + return( -1 ); + in = premultiply; + } - in = (VipsImage **) composite->in->data; decode = (VipsImage **) vips_object_local_array( object, composite->n ); for( i = 0; i < composite->n; i++ ) if( vips_image_decode( in[i], &decode[i] ) ) @@ -197,7 +318,7 @@ vips_composite_build( VipsObject *object ) if( !vips_image_hasalpha( in[i] ) ) { VipsImage *x; - if( vips_bandjoin( in[i], &x, 255 ) ) + if( vips_bandjoin_const1( in[i], &x, 255, NULL ) ) return( -1 ); g_object_unref( in[i] ); in[i] = x; @@ -205,6 +326,12 @@ vips_composite_build( VipsObject *object ) break; } + if( composite->n > MAX_INPUT_IMAGES ) { + vips_error( class->nickname, + "%s", _( "too many input images" ) ); + return( -1 ); + } + /* Transform to compositing space. It defaults to sRGB or B_W. */ if( !vips_object_argument_isset( object, "compositing_space" ) ) { @@ -249,15 +376,37 @@ vips_composite_build( VipsObject *object ) return( -1 ); } - if( vips_image_pipeline_array( conversion->out, + if( in[0]->Bands > MAX_BANDS ) { + vips_error( class->nickname, + "%s", _( "too many input bands" ) ); + return( -1 ); + } + + composite->bands = in[0]->Bands - 1; + + t[0] = vips_image_new(); + out = t[0]; + + if( vips_image_pipeline_array( out, VIPS_DEMAND_STYLE_THINSTRIP, in ) ) return( -1 ); - if( vips_image_generate( conversion->out, + if( vips_image_generate( out, vips_start_many, vips_composite_gen, vips_stop_many, in, composite ) ) return( -1 ); + /* And unpremultiply alpha, if we need to. + */ + if( !composite->premultiplied ) { + if( vips_unpremultiply( out, &t[1], NULL ) ) + return( -1 ); + out = t[1]; + } + + if( vips_image_write( out, conversion->out ) ) + return( -1 ); + return( 0 ); } @@ -286,7 +435,7 @@ vips_composite_class_init( VipsCompositeClass *class ) G_STRUCT_OFFSET( VipsComposite, in ), VIPS_TYPE_ARRAY_IMAGE ); - VIPS_ARG_BOXED( class, "mode", 1, + VIPS_ARG_BOXED( class, "mode", 3, _( "Blend modes" ), _( "Array of VipsBlendMode to join with" ), VIPS_ARGUMENT_REQUIRED_INPUT, @@ -294,12 +443,19 @@ vips_composite_class_init( VipsCompositeClass *class ) VIPS_TYPE_ARRAY_INT ); VIPS_ARG_ENUM( class, "compositing_space", 10, - _( "Interpretation" ), - _( "Pixel interpretation" ), + _( "Compositing space" ), + _( "Composite images in this colour space" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsComposite, compositing_space ), VIPS_TYPE_INTERPRETATION, VIPS_INTERPRETATION_sRGB ); + VIPS_ARG_BOOL( class, "premultiplied", 11, + _( "Premultiplied" ), + _( "Images have premultiplied alpha" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsComposite, premultiplied ), + FALSE ); + } static void @@ -309,14 +465,18 @@ vips_composite_init( VipsComposite *composite ) } static int -vips_compositev( VipsImage **in, VipsImage **out, int n, va_list ap ) +vips_compositev( VipsImage **in, VipsImage **out, int n, int *mode, va_list ap ) { - VipsArrayImage *array; + VipsArrayImage *image_array; + VipsArrayInt *mode_array; int result; - array = vips_array_image_new( in, n ); - result = vips_call_split( "composite", ap, array, out ); - vips_area_unref( VIPS_AREA( array ) ); + image_array = vips_array_image_new( in, n ); + mode_array = vips_array_int_new( mode, n - 1 ); + result = vips_call_split( "composite", ap, + image_array, out, mode_array ); + vips_area_unref( VIPS_AREA( image_array ) ); + vips_area_unref( VIPS_AREA( mode_array ) ); return( result ); } @@ -326,34 +486,23 @@ vips_compositev( VipsImage **in, VipsImage **out, int n, va_list ap ) * @in: (array length=n) (transfer none): array of input images * @out: output image * @n: number of input images + * @mode: array of (@n - 1) #VipsBlendMode * @...: %NULL-terminated list of optional named arguments * - * Join a set of images together, bandwise. - * - * If the images - * have n and m bands, then the output image will have n + m - * bands, with the first n coming from the first image and the last m - * from the second. - * - * If the images differ in size, the smaller images are enlarged to match the - * larger by adding zero pixels along the bottom and right. - * - * The input images are cast up to the smallest common type (see table - * Smallest common format in - * arithmetic). + * Composite an array of images together. * * See also: vips_insert(). * * Returns: 0 on success, -1 on error */ int -vips_composite( VipsImage **in, VipsImage **out, int n, ... ) +vips_composite( VipsImage **in, VipsImage **out, int n, int *mode, ... ) { va_list ap; int result; - va_start( ap, n ); - result = vips_compositev( in, out, n, ap ); + va_start( ap, mode ); + result = vips_compositev( in, out, n, mode, ap ); va_end( ap ); return( result ); @@ -364,24 +513,28 @@ vips_composite( VipsImage **in, VipsImage **out, int n, ... ) * @in1: first input image * @in2: second input image * @out: output image + * @mode: composite with this blend mode * @...: %NULL-terminated list of optional named arguments * - * Join a pair of images together, bandwise. See vips_composite(). + * Composite a pair of images together. See vips_composite(). * * Returns: 0 on success, -1 on error */ int -vips_composite2( VipsImage *in1, VipsImage *in2, VipsImage **out, ... ) +vips_composite2( VipsImage *in1, VipsImage *in2, VipsImage **out, + VipsBlendMode mode1, ... ) { va_list ap; int result; VipsImage *in[2]; + int mode[1]; in[0] = in1; in[1] = in2; + mode[0] = mode1; - va_start( ap, out ); - result = vips_compositev( in, out, 2, ap ); + va_start( ap, mode1 ); + result = vips_compositev( in, out, 2, mode, ap ); va_end( ap ); return( result ); diff --git a/libvips/conversion/conversion.c b/libvips/conversion/conversion.c index 7ec4a04b..06bfa1fc 100644 --- a/libvips/conversion/conversion.c +++ b/libvips/conversion/conversion.c @@ -274,6 +274,7 @@ vips_conversion_operation_init( void ) extern GType vips_xyz_get_type( void ); extern GType vips_falsecolour_get_type( void ); extern GType vips_gamma_get_type( void ); + extern GType vips_composite_get_type( void ); vips_copy_get_type(); vips_tile_cache_get_type(); @@ -321,4 +322,5 @@ vips_conversion_operation_init( void ) vips_xyz_get_type(); vips_falsecolour_get_type(); vips_gamma_get_type(); + vips_composite_get_type(); } diff --git a/libvips/include/vips/conversion.h b/libvips/include/vips/conversion.h index f2099f59..fd22ba0b 100644 --- a/libvips/include/vips/conversion.h +++ b/libvips/include/vips/conversion.h @@ -89,6 +89,11 @@ typedef enum { VIPS_INTERESTING_LAST } VipsInteresting; +typedef enum { + VIPS_BLEND_MODE_OVER, + VIPS_BLEND_MODE_LAST +} VipsBlendMode; + int vips_copy( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); int vips_tilecache( VipsImage *in, VipsImage **out, ... ) diff --git a/libvips/include/vips/enumtypes.h b/libvips/include/vips/enumtypes.h index 545d8f65..06c2e085 100644 --- a/libvips/include/vips/enumtypes.h +++ b/libvips/include/vips/enumtypes.h @@ -44,6 +44,8 @@ GType vips_angle45_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_ANGLE45 (vips_angle45_get_type()) GType vips_interesting_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_INTERESTING (vips_interesting_get_type()) +GType vips_blend_mode_get_type (void) G_GNUC_CONST; +#define VIPS_TYPE_BLEND_MODE (vips_blend_mode_get_type()) /* enumerations from "../../../libvips/include/vips/convolution.h" */ GType vips_combine_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_COMBINE (vips_combine_get_type()) diff --git a/libvips/iofuncs/enumtypes.c b/libvips/iofuncs/enumtypes.c index d0b3f445..a1081dd1 100644 --- a/libvips/iofuncs/enumtypes.c +++ b/libvips/iofuncs/enumtypes.c @@ -348,6 +348,23 @@ vips_interesting_get_type( void ) return( etype ); } +GType +vips_blend_mode_get_type( void ) +{ + static GType etype = 0; + + if( etype == 0 ) { + static const GEnumValue values[] = { + {VIPS_BLEND_MODE_OVER, "VIPS_BLEND_MODE_OVER", "over"}, + {VIPS_BLEND_MODE_LAST, "VIPS_BLEND_MODE_LAST", "last"}, + {0, NULL, NULL} + }; + + etype = g_enum_register_static( "VipsBlendMode", values ); + } + + return( etype ); +} /* enumerations from "../../libvips/include/vips/convolution.h" */ GType vips_combine_get_type( void )