some cleanups, a bit quicker
it now has separate premultiplied and not-premultiplied paths
This commit is contained in:
parent
8cd336033a
commit
4b24d82e8c
@ -21,6 +21,7 @@
|
|||||||
- vips7 and vips8 python bindings default to off ... use the new pyvips
|
- vips7 and vips8 python bindings default to off ... use the new pyvips
|
||||||
binding instead
|
binding instead
|
||||||
- better svgload: larger output, handle missing width/height, thanks lovell
|
- better svgload: larger output, handle missing width/height, thanks lovell
|
||||||
|
- add vips_composite(): merge an array of images with porter-duff
|
||||||
|
|
||||||
29/8/17 started 8.5.9
|
29/8/17 started 8.5.9
|
||||||
- make --fail stop jpeg read on any libjpeg warning, thanks @mceachen
|
- make --fail stop jpeg read on any libjpeg warning, thanks @mceachen
|
||||||
|
@ -72,11 +72,16 @@
|
|||||||
*
|
*
|
||||||
* https://en.wikipedia.org/wiki/Alpha_compositing
|
* https://en.wikipedia.org/wiki/Alpha_compositing
|
||||||
*
|
*
|
||||||
* composite -compose over PNG_transparency_demonstration_1.png \
|
* Benchmark:
|
||||||
* Opera-icon-high-res.png x.png
|
|
||||||
*
|
*
|
||||||
* vips composite "PNG_transparency_demonstration_1.png \
|
* vips replicate PNG_transparency_demonstration_1.png x.png 15 15
|
||||||
* Opera-icon-high-res.png" x.png 0
|
* vips crop x.png wtc_overlay.png 0 0 9372 9372
|
||||||
|
*
|
||||||
|
* composite -compose over wtc_overlay.png.png wtc.jpg x.jpg
|
||||||
|
*
|
||||||
|
* vips composite "wtc_overlay.png.png wtc.jpg" x.jpg 0
|
||||||
|
*
|
||||||
|
* convert -compose over -composite wtc.jpg wtc_overlay.png x.jpg
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -100,6 +105,10 @@ typedef struct _VipsComposite {
|
|||||||
*/
|
*/
|
||||||
gboolean premultiplied;
|
gboolean premultiplied;
|
||||||
|
|
||||||
|
/* The maximum value of the alpha channel. Defaults to 255 or 65535.
|
||||||
|
*/
|
||||||
|
double max_alpha;
|
||||||
|
|
||||||
/* The number of inputs. This can be less than the number of images in
|
/* The number of inputs. This can be less than the number of images in
|
||||||
* @in.
|
* @in.
|
||||||
*/
|
*/
|
||||||
@ -115,48 +124,115 @@ typedef VipsConversionClass VipsCompositeClass;
|
|||||||
|
|
||||||
G_DEFINE_TYPE( VipsComposite, vips_composite, VIPS_TYPE_CONVERSION );
|
G_DEFINE_TYPE( VipsComposite, vips_composite, VIPS_TYPE_CONVERSION );
|
||||||
|
|
||||||
#define BLEND( OUT, AOUT, SRC1, A1, SRC2, A2 ) { \
|
#define BLEND_PREMULTIPLIED( MODE, OUT, AOUT, SRC1, A1, SRC2, A2 ) { \
|
||||||
if( AOUT == 0 ) \
|
switch( MODE ) { \
|
||||||
OUT = 0; \
|
case VIPS_BLEND_MODE_OVER: \
|
||||||
else \
|
OUT = (SRC1 + SRC2 * (1 - A1)) / AOUT; \
|
||||||
switch( mode ) { \
|
break; \
|
||||||
case VIPS_BLEND_MODE_OVER: \
|
\
|
||||||
OUT = (SRC1 + SRC2 * (1 - A1)) / AOUT; \
|
default: \
|
||||||
break; \
|
g_assert_not_reached(); \
|
||||||
\
|
} \
|
||||||
default: \
|
|
||||||
g_assert_not_reached(); \
|
|
||||||
} \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define COMBINE( TYPE ) { \
|
#define COMBINE_PREMULTIPLIED( TYPE ) { \
|
||||||
TYPE **tp = (TYPE **) p; \
|
TYPE **tp = (TYPE **) p; \
|
||||||
TYPE *tq = (TYPE *) q; \
|
TYPE *tq = (TYPE *) q; \
|
||||||
\
|
\
|
||||||
for( x = 0; x < width; x++ ) { \
|
for( x = 0; x < width; x++ ) { \
|
||||||
int o = x * (bands + 1); \
|
|
||||||
\
|
|
||||||
for( b = 0; b < bands; b++ ) \
|
for( b = 0; b < bands; b++ ) \
|
||||||
pixel[b] = tp[n - 1][o + b]; \
|
pixel[b] = tp[0][b]; \
|
||||||
alpha = tp[n - 1][o + bands] / 255.0; \
|
alpha = tp[0][bands] / composite->max_alpha; \
|
||||||
|
tp[0] += bands + 1; \
|
||||||
\
|
\
|
||||||
for( i = n - 2; i >= 0; i-- ) { \
|
for( i = 1; i < n; i++ ) { \
|
||||||
TYPE *src1 = tp[i] + o; \
|
TYPE * restrict src1 = tp[i]; \
|
||||||
double a1 = src1[bands] / 255.0; \
|
double a1 = src1[bands] / composite->max_alpha; \
|
||||||
double aout = a1 + alpha * (1 - a1); \
|
double aout = a1 + alpha * (1 - a1); \
|
||||||
VipsBlendMode mode = ((VipsBlendMode *) \
|
VipsBlendMode modei = mode[(n - 1) - i]; \
|
||||||
composite->mode->area.data)[i]; \
|
|
||||||
\
|
|
||||||
for( b = 0; b < bands; b++ ) \
|
|
||||||
BLEND( pixel[b], aout, \
|
|
||||||
src1[b], a1, pixel[b], alpha ); \
|
|
||||||
\
|
\
|
||||||
|
if( aout == 0 ) \
|
||||||
|
for( b = 0; b < bands; b++ ) \
|
||||||
|
pixel[b] = 0; \
|
||||||
|
else { \
|
||||||
|
for( b = 0; b < bands; b++ ) \
|
||||||
|
BLEND_PREMULTIPLIED( modei, \
|
||||||
|
pixel[b], aout, \
|
||||||
|
src1[b], a1, \
|
||||||
|
pixel[b], alpha ); \
|
||||||
|
} \
|
||||||
alpha = aout; \
|
alpha = aout; \
|
||||||
|
\
|
||||||
|
tp[i] += bands + 1; \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
for( b = 0; b < bands; b++ ) \
|
for( b = 0; b < bands; b++ ) \
|
||||||
tq[o + b] = pixel[b]; \
|
tq[b] = pixel[b]; \
|
||||||
tq[o + bands] = alpha * 255; \
|
tq[bands] = alpha * composite->max_alpha; \
|
||||||
|
\
|
||||||
|
tq += bands + 1; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BLEND_MULTIPLY( MODE, OUT, AOUT, SRC1, A1, SRC2, A2 ) { \
|
||||||
|
switch( MODE ) { \
|
||||||
|
case VIPS_BLEND_MODE_OVER: \
|
||||||
|
OUT = (A1 * SRC1 + A2 * SRC2 * (1 - A1)) / AOUT; \
|
||||||
|
break; \
|
||||||
|
\
|
||||||
|
default: \
|
||||||
|
g_assert_not_reached(); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define COMBINE_MULTIPLY( TYPE ) { \
|
||||||
|
TYPE **tp = (TYPE **) p; \
|
||||||
|
TYPE *tq = (TYPE *) q; \
|
||||||
|
\
|
||||||
|
for( x = 0; x < width; x++ ) { \
|
||||||
|
for( b = 0; b < bands; b++ ) \
|
||||||
|
pixel[b] = tp[0][b]; \
|
||||||
|
alpha = tp[0][bands] / composite->max_alpha; \
|
||||||
|
tp[0] += bands + 1; \
|
||||||
|
\
|
||||||
|
for( i = 1; i < n; i++ ) { \
|
||||||
|
TYPE * restrict src1 = tp[i]; \
|
||||||
|
double a1 = src1[bands] / composite->max_alpha; \
|
||||||
|
double aout = a1 + alpha * (1 - a1); \
|
||||||
|
VipsBlendMode modei = mode[(n - 1) - i]; \
|
||||||
|
\
|
||||||
|
if( aout == 0 ) \
|
||||||
|
for( b = 0; b < bands; b++ ) \
|
||||||
|
pixel[b] = 0; \
|
||||||
|
else \
|
||||||
|
for( b = 0; b < bands; b++ ) \
|
||||||
|
BLEND_MULTIPLY( modei, \
|
||||||
|
pixel[b], aout, \
|
||||||
|
src1[b], a1, \
|
||||||
|
pixel[b], alpha ); \
|
||||||
|
alpha = aout; \
|
||||||
|
\
|
||||||
|
tp[i] += bands + 1; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
if( alpha == 0 ) \
|
||||||
|
for( b = 0; b < bands; b++ ) \
|
||||||
|
tq[b] = 0; \
|
||||||
|
else \
|
||||||
|
for( b = 0; b < bands; b++ ) \
|
||||||
|
tq[b] = pixel[b] * alpha; \
|
||||||
|
tq[bands] = alpha * composite->max_alpha; \
|
||||||
|
\
|
||||||
|
tq += bands + 1; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define COMBINE( TYPE ) { \
|
||||||
|
if( composite->premultiplied ) { \
|
||||||
|
COMBINE_PREMULTIPLIED( TYPE ); \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
COMBINE_MULTIPLY( TYPE ); \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +242,8 @@ vips_composite_process_line( VipsComposite *composite, VipsBandFormat format,
|
|||||||
{
|
{
|
||||||
int n = composite->n;
|
int n = composite->n;
|
||||||
int bands = composite->bands;
|
int bands = composite->bands;
|
||||||
|
VipsBlendMode * restrict mode =
|
||||||
|
(VipsBlendMode *) composite->mode->area.data;
|
||||||
|
|
||||||
double pixel[MAX_BANDS];
|
double pixel[MAX_BANDS];
|
||||||
double alpha;
|
double alpha;
|
||||||
@ -218,25 +296,26 @@ vips_composite_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop
|
|||||||
VipsComposite *composite = (VipsComposite *) b;
|
VipsComposite *composite = (VipsComposite *) b;
|
||||||
VipsRect *r = &or->valid;
|
VipsRect *r = &or->valid;
|
||||||
|
|
||||||
VipsPel *p[MAX_INPUT_IMAGES], *q;
|
int y;
|
||||||
int y, i;
|
|
||||||
|
|
||||||
if( vips_reorder_prepare_many( or->im, ir, r ) )
|
if( vips_reorder_prepare_many( or->im, ir, r ) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
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 );
|
|
||||||
|
|
||||||
VIPS_GATE_START( "vips_composite_gen: work" );
|
VIPS_GATE_START( "vips_composite_gen: work" );
|
||||||
|
|
||||||
for( y = 0; y < r->height; y++ ) {
|
for( y = 0; y < r->height; y++ ) {
|
||||||
|
VipsPel *p[MAX_INPUT_IMAGES];
|
||||||
|
VipsPel *q;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for( i = 0; i < composite->n; i++ )
|
||||||
|
p[(composite->n - 1) - i] =
|
||||||
|
VIPS_REGION_ADDR( ir[i], r->left, r->top + y );
|
||||||
|
p[i] = NULL;
|
||||||
|
q = VIPS_REGION_ADDR( or, r->left, r->top + y );
|
||||||
|
|
||||||
vips_composite_process_line( composite, ir[0]->im->BandFmt,
|
vips_composite_process_line( composite, ir[0]->im->BandFmt,
|
||||||
q, p, r->width );
|
q, p, r->width );
|
||||||
|
|
||||||
for( i = 0; i < composite->n; i++ )
|
|
||||||
p[i] += VIPS_REGION_LSKIP( ir[i] );
|
|
||||||
q += VIPS_REGION_LSKIP( or );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VIPS_GATE_STOP( "vips_composite_gen: work" );
|
VIPS_GATE_STOP( "vips_composite_gen: work" );
|
||||||
@ -258,10 +337,6 @@ vips_composite_build( VipsObject *object )
|
|||||||
VipsImage **compositing;
|
VipsImage **compositing;
|
||||||
VipsImage **format;
|
VipsImage **format;
|
||||||
VipsImage **size;
|
VipsImage **size;
|
||||||
VipsImage **ready;
|
|
||||||
VipsInterpretation compositing_space;
|
|
||||||
int max_bands;
|
|
||||||
VipsInterpretation max_interpretation;
|
|
||||||
VipsBlendMode *mode;
|
VipsBlendMode *mode;
|
||||||
VipsImage *out;
|
VipsImage *out;
|
||||||
|
|
||||||
@ -293,18 +368,6 @@ vips_composite_build( VipsObject *object )
|
|||||||
|
|
||||||
in = (VipsImage **) composite->in->area.data;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
decode = (VipsImage **) vips_object_local_array( object, composite->n );
|
decode = (VipsImage **) vips_object_local_array( object, composite->n );
|
||||||
for( i = 0; i < composite->n; i++ )
|
for( i = 0; i < composite->n; i++ )
|
||||||
if( vips_image_decode( in[i], &decode[i] ) )
|
if( vips_image_decode( in[i], &decode[i] ) )
|
||||||
@ -356,6 +419,15 @@ vips_composite_build( VipsObject *object )
|
|||||||
return( -1 );
|
return( -1 );
|
||||||
in = compositing;
|
in = compositing;
|
||||||
|
|
||||||
|
/* Is max-alpha unset? Default to the correct value for this
|
||||||
|
* interpretation.
|
||||||
|
*/
|
||||||
|
if( !vips_object_argument_isset( object, "max_alpha" ) )
|
||||||
|
if( composite->compositing_space == VIPS_INTERPRETATION_GREY16 ||
|
||||||
|
composite->compositing_space ==
|
||||||
|
VIPS_INTERPRETATION_RGB16 )
|
||||||
|
composite->max_alpha = 65535;
|
||||||
|
|
||||||
/* Transform the input images to match in size and format.
|
/* Transform the input images to match in size and format.
|
||||||
*/
|
*/
|
||||||
format = (VipsImage **) vips_object_local_array( object, composite->n );
|
format = (VipsImage **) vips_object_local_array( object, composite->n );
|
||||||
@ -396,14 +468,6 @@ vips_composite_build( VipsObject *object )
|
|||||||
in, composite ) )
|
in, composite ) )
|
||||||
return( -1 );
|
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 ) )
|
if( vips_image_write( out, conversion->out ) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
@ -423,7 +487,8 @@ vips_composite_class_init( VipsCompositeClass *class )
|
|||||||
gobject_class->get_property = vips_object_get_property;
|
gobject_class->get_property = vips_object_get_property;
|
||||||
|
|
||||||
vobject_class->nickname = "composite";
|
vobject_class->nickname = "composite";
|
||||||
vobject_class->description = _( "blend an array of images according to an array of blend modes" );
|
vobject_class->description =
|
||||||
|
_( "blend an array of images with an array of blend modes" );
|
||||||
vobject_class->build = vips_composite_build;
|
vobject_class->build = vips_composite_build;
|
||||||
|
|
||||||
operation_class->flags = VIPS_OPERATION_SEQUENTIAL;
|
operation_class->flags = VIPS_OPERATION_SEQUENTIAL;
|
||||||
@ -456,12 +521,20 @@ vips_composite_class_init( VipsCompositeClass *class )
|
|||||||
G_STRUCT_OFFSET( VipsComposite, premultiplied ),
|
G_STRUCT_OFFSET( VipsComposite, premultiplied ),
|
||||||
FALSE );
|
FALSE );
|
||||||
|
|
||||||
|
VIPS_ARG_DOUBLE( class, "max_alpha", 115,
|
||||||
|
_( "Maximum alpha" ),
|
||||||
|
_( "Maximum value of alpha channel" ),
|
||||||
|
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||||
|
G_STRUCT_OFFSET( VipsComposite, max_alpha ),
|
||||||
|
0, 100000000, 255 );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
vips_composite_init( VipsComposite *composite )
|
vips_composite_init( VipsComposite *composite )
|
||||||
{
|
{
|
||||||
composite->compositing_space = VIPS_INTERPRETATION_sRGB;
|
composite->compositing_space = VIPS_INTERPRETATION_sRGB;
|
||||||
|
composite->max_alpha = 255.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
Loading…
Reference in New Issue
Block a user