but only OVER mode at the moment, and it's not especially quick
This commit is contained in:
John Cupitt 2017-09-27 08:58:36 +01:00
parent e44921f481
commit 7c7c30a601
6 changed files with 222 additions and 42 deletions

View File

@ -1,6 +1,7 @@
noinst_LTLIBRARIES = libconversion.la
libconversion_la_SOURCES = \
composite.c \
smartcrop.c \
conversion.c \
pconversion.h \

View File

@ -49,6 +49,16 @@
#include <vips/internal.h>
#include <vips/debug.h>
#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
* <link linkend="libvips-arithmetic">arithmetic</link>).
* 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 );

View File

@ -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();
}

View File

@ -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, ... )

View File

@ -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())

View File

@ -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 )