libvips/libvips/conversion/ifthenelse.c

561 lines
14 KiB
C

/* ifthenelse.c --- use a condition image to join two images together
*
* Modified:
* 9/2/95 JC
* - partialed and ANSIfied
* 11/9/95 JC
* - return( 0 ) missing! oops
* 15/4/05
* - now just evals left/right if all zero/all one
* 7/10/06
* - set THINSTRIP
* 23/9/09
* - gtkdoc comment
* 23/9/09
* - use im_check*()
* - allow many-band conditional and single-band a/b
* - allow a/b to differ in format and bands
* 25/6/10
* - let the conditional image be any format by adding a (!=0) if
* necessary
* 17/5/11
* - added sizealike
* 14/11/11
* - redone as a class
* 19/4/12
* - fix blend
* - small blend speedup
*/
/*
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 <stdlib.h>
#include <math.h>
#include <vips/vips.h>
#include <vips/internal.h>
#include <vips/debug.h>
#include "pconversion.h"
typedef struct _VipsIfthenelse {
VipsConversion parent_instance;
/* Params.
*/
VipsImage *cond;
VipsImage *in1;
VipsImage *in2;
gboolean blend;
} VipsIfthenelse;
typedef VipsConversionClass VipsIfthenelseClass;
G_DEFINE_TYPE( VipsIfthenelse, vips_ifthenelse, VIPS_TYPE_CONVERSION );
#define IBLEND1( TYPE ) { \
TYPE *a = (TYPE *) ap; \
TYPE *b = (TYPE *) bp; \
TYPE *q = (TYPE *) qp; \
\
for( i = 0, x = 0; x < n; i++, x += bands ) { \
const int v = c[i]; \
\
for( z = x; z < x + bands; z++ ) \
q[z] = (v * a[z] + (255 - v) * b[z] + 128) / 255; \
} \
}
#define IBLENDN( TYPE ) { \
TYPE *a = (TYPE *) ap; \
TYPE *b = (TYPE *) bp; \
TYPE *q = (TYPE *) qp; \
\
for( x = 0; x < n; x += bands ) { \
for( z = x; z < x + bands; z++ ) { \
const int v = c[z]; \
\
q[z] = (v * a[z] + (255 - v) * b[z] + 128) / 255; \
} \
} \
}
#define FBLEND1( TYPE ) { \
TYPE *a = (TYPE *) ap; \
TYPE *b = (TYPE *) bp; \
TYPE *q = (TYPE *) qp; \
\
for( i = 0, x = 0; x < n; i++, x += bands ) { \
const double v = c[i] / 255.0; \
\
for( z = x; z < x + bands; z++ ) \
q[z] = v * a[z] + (1.0 - v) * b[z]; \
} \
}
#define FBLENDN( TYPE ) { \
TYPE *a = (TYPE *) ap; \
TYPE *b = (TYPE *) bp; \
TYPE *q = (TYPE *) qp; \
\
for( x = 0; x < n; x += bands ) { \
for( z = x; z < x + bands; z++ ) { \
const double v = c[z] / 255.0; \
\
q[z] = v * a[z] + (1.0 - v) * b[z]; \
} \
} \
}
#define CBLEND1( TYPE ) { \
TYPE *a = (TYPE *) ap; \
TYPE *b = (TYPE *) bp; \
TYPE *q = (TYPE *) qp; \
\
for( i = 0, x = 0; x < n; i++, x += bands ) { \
const double v = c[i] / 255.0; \
\
for( z = x; z < x + 2 * bands; z++ ) \
q[z] = v * a[z] + (1.0 - v) * b[z]; \
} \
}
#define CBLENDN( TYPE ) { \
TYPE *a = (TYPE *) ap; \
TYPE *b = (TYPE *) bp; \
TYPE *q = (TYPE *) qp; \
\
for( x = 0; x < n; x += bands ) { \
for( z = x; z < x + bands; z++ ) { \
const double v = c[z] / 255.0; \
\
q[2 * z] = v * a[2 * z] + (1.0 - v) * b[2 * z]; \
q[2 * z + 1] = v * a[2 * z + 1] + \
(1.0 - v) * b[2 * z + 1]; \
} \
} \
}
/* Blend with a 1-band conditional image.
*/
static void
vips_blend1_buffer( VipsPel *qp,
VipsPel *c, VipsPel *ap, VipsPel *bp, int width,
VipsImage *im )
{
int i, x, z;
const int bands = im->Bands;
const int n = width * bands;
switch( im->BandFmt ) {
case VIPS_FORMAT_UCHAR: IBLEND1( unsigned char ); break;
case VIPS_FORMAT_CHAR: IBLEND1( signed char ); break;
case VIPS_FORMAT_USHORT: IBLEND1( unsigned short ); break;
case VIPS_FORMAT_SHORT: IBLEND1( signed short ); break;
case VIPS_FORMAT_UINT: IBLEND1( unsigned int ); break;
case VIPS_FORMAT_INT: IBLEND1( signed int ); break;
case VIPS_FORMAT_FLOAT: FBLEND1( float ); break;
case VIPS_FORMAT_DOUBLE: FBLEND1( double ); break;
case VIPS_FORMAT_COMPLEX: CBLEND1( float ); break;
case VIPS_FORMAT_DPCOMPLEX: CBLEND1( double ); break;
default:
g_assert_not_reached();
}
}
/* Blend with a many band conditional image.
*/
static void
vips_blendn_buffer( VipsPel *qp,
VipsPel *c, VipsPel *ap, VipsPel *bp, int width,
VipsImage *im )
{
int x, z;
const int bands = im->Bands;
const int n = width * bands;
switch( im->BandFmt ) {
case VIPS_FORMAT_UCHAR: IBLENDN( unsigned char ); break;
case VIPS_FORMAT_CHAR: IBLENDN( signed char ); break;
case VIPS_FORMAT_USHORT: IBLENDN( unsigned short ); break;
case VIPS_FORMAT_SHORT: IBLENDN( signed short ); break;
case VIPS_FORMAT_UINT: IBLENDN( unsigned int ); break;
case VIPS_FORMAT_INT: IBLENDN( signed int ); break;
case VIPS_FORMAT_FLOAT: FBLENDN( float ); break;
case VIPS_FORMAT_DOUBLE: FBLENDN( double ); break;
case VIPS_FORMAT_COMPLEX: CBLENDN( float ); break;
case VIPS_FORMAT_DPCOMPLEX: CBLENDN( double ); break;
default:
g_assert_not_reached();
}
}
static int
vips_blend_gen( VipsRegion *or, void *seq, void *client1, void *client2,
gboolean *stop )
{
VipsRegion **ir = (VipsRegion **) seq;
VipsRect *r = &or->valid;
int le = r->left;
int to = r->top;
int bo = VIPS_RECT_BOTTOM( r );
VipsImage *c = ir[2]->im;
VipsImage *a = ir[0]->im;
int x, y;
int all0, all255;
if( vips_region_prepare( ir[2], r ) )
return( -1 );
/* Is the conditional all zero or all 255? We can avoid asking
* for one of the inputs to be calculated.
*/
all0 = *VIPS_REGION_ADDR( ir[2], le, to ) == 0;
all255 = *VIPS_REGION_ADDR( ir[2], le, to ) == 255;
for( y = to; y < bo; y++ ) {
VipsPel *p = VIPS_REGION_ADDR( ir[2], le, y );
int width = r->width * c->Bands;
for( x = 0; x < width; x++ ) {
all0 &= p[x] == 0;
all255 &= p[x] == 255;
}
if( !all0 && !all255 )
break;
}
if( all255 ) {
/* All 255. Point or at the then image.
*/
if( vips_region_prepare( ir[0], r ) ||
vips_region_region( or, ir[0], r, r->left, r->top ) )
return( -1 );
}
else if( all0 ) {
/* All zero. Point or at the else image.
*/
if( vips_region_prepare( ir[1], r ) ||
vips_region_region( or, ir[1], r, r->left, r->top ) )
return( -1 );
}
else {
/* Mix of set and clear ... ask for both then and else parts
* and interleave.
*
* We can't use vips_reorder_prepare_many() since we always
* want the c image first.
*/
if( vips_region_prepare( ir[0], r ) ||
vips_region_prepare( ir[1], r ) )
return( -1 );
for( y = to; y < bo; y++ ) {
VipsPel *ap = VIPS_REGION_ADDR( ir[0], le, y );
VipsPel *bp = VIPS_REGION_ADDR( ir[1], le, y );
VipsPel *cp = VIPS_REGION_ADDR( ir[2], le, y );
VipsPel *q = VIPS_REGION_ADDR( or, le, y );
if( c->Bands == 1 )
vips_blend1_buffer( q, cp, ap, bp,
r->width, a );
else
vips_blendn_buffer( q, cp, ap, bp,
r->width, a );
}
}
return( 0 );
}
static int
vips_ifthenelse_gen( VipsRegion *or, void *seq, void *client1, void *client2,
gboolean *stop )
{
VipsRegion **ir = (VipsRegion **) seq;
VipsRect *r = &or->valid;
int le = r->left;
int to = r->top;
int bo = VIPS_RECT_BOTTOM( r );
VipsImage *c = ir[2]->im;
VipsImage *a = ir[0]->im;
int size, width;
int i, x, y, z;
int all0, alln0;
if( c->Bands == 1 ) {
/* Copying PEL-sized units with a one-band conditional.
*/
size = VIPS_IMAGE_SIZEOF_PEL( a );
width = r->width;
}
else {
/* Copying ELEMENT sized-units with an n-band conditional.
*/
size = VIPS_IMAGE_SIZEOF_ELEMENT( a );
width = r->width * a->Bands;
}
if( vips_region_prepare( ir[2], r ) )
return( -1 );
/* Is the conditional all zero or all non-zero? We can avoid asking
* for one of the inputs to be calculated.
*/
all0 = *VIPS_REGION_ADDR( ir[2], le, to ) == 0;
alln0 = *VIPS_REGION_ADDR( ir[2], le, to ) != 0;
for( y = to; y < bo; y++ ) {
VipsPel *p = VIPS_REGION_ADDR( ir[2], le, y );
for( x = 0; x < width; x++ ) {
all0 &= p[x] == 0;
alln0 &= p[x] != 0;
}
if( !all0 && !alln0 )
break;
}
if( alln0 ) {
/* All non-zero. Point or at the then image.
*/
if( vips_region_prepare( ir[0], r ) ||
vips_region_region( or, ir[0], r, r->left, r->top ) )
return( -1 );
}
else if( all0 ) {
/* All zero. Point or at the else image.
*/
if( vips_region_prepare( ir[1], r ) ||
vips_region_region( or, ir[1], r, r->left, r->top ) )
return( -1 );
}
else {
/* Mix of set and clear ... ask for both then and else parts
* and interleave.
*/
if( vips_region_prepare( ir[0], r ) ||
vips_region_prepare( ir[1], r ) )
return( -1 );
for( y = to; y < bo; y++ ) {
VipsPel *ap = VIPS_REGION_ADDR( ir[0], le, y );
VipsPel *bp = VIPS_REGION_ADDR( ir[1], le, y );
VipsPel *cp = VIPS_REGION_ADDR( ir[2], le, y );
VipsPel *q = VIPS_REGION_ADDR( or, le, y );
for( x = 0, i = 0; i < width; i++, x += size )
if( cp[i] )
for( z = x; z < x + size; z++ )
q[z] = ap[z];
else
for( z = x; z < x + size; z++ )
q[z] = bp[z];
}
}
return( 0 );
}
static int
vips_ifthenelse_build( VipsObject *object )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
VipsConversion *conversion = VIPS_CONVERSION( object );
VipsIfthenelse *ifthenelse = (VipsIfthenelse *) object;
VipsGenerateFn generate_fn = ifthenelse->blend ?
vips_blend_gen : vips_ifthenelse_gen;
VipsImage **band = (VipsImage **) vips_object_local_array( object, 3 );
VipsImage **size = (VipsImage **) vips_object_local_array( object, 3 );
VipsImage **format =
(VipsImage **) vips_object_local_array( object, 3 );
VipsImage *all[3];
if( VIPS_OBJECT_CLASS( vips_ifthenelse_parent_class )->build( object ) )
return( -1 );
/* We have to have the condition image last since we want the output
* image to inherit its properties from the then/else parts.
*/
all[0] = ifthenelse->in1;
all[1] = ifthenelse->in2;
all[2] = ifthenelse->cond;
/* No need to check input images, sizealike and friends will do this
* for us.
*/
/* Cast our input images up to a common bands and size.
*/
if( vips__bandalike_vec( class->nickname, all, band, 3, 0 ) ||
vips__sizealike_vec( band, size, 3 ) )
return( -1 );
/* Condition is cast to uchar, then/else to a common type.
*/
if( size[2]->BandFmt != VIPS_FORMAT_UCHAR ) {
if( vips_cast( size[2], &format[2], VIPS_FORMAT_UCHAR, NULL ) )
return( -1 );
}
else {
format[2] = size[2];
g_object_ref( format[2] );
}
if( vips__formatalike_vec( size, format, 2 ) )
return( -1 );
if( vips_image_pipeline_array( conversion->out,
VIPS_DEMAND_STYLE_SMALLTILE, format ) )
return( -1 );
if( vips_image_generate( conversion->out,
vips_start_many, generate_fn, vips_stop_many,
format, ifthenelse ) )
return( -1 );
return( 0 );
}
static void
vips_ifthenelse_class_init( VipsIfthenelseClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );
VIPS_DEBUG_MSG( "vips_ifthenelse_class_init\n" );
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
vobject_class->nickname = "ifthenelse";
vobject_class->description = _( "ifthenelse an image" );
vobject_class->build = vips_ifthenelse_build;
VIPS_ARG_IMAGE( class, "cond", -2,
_( "Condition" ),
_( "Condition input image" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsIfthenelse, cond ) );
VIPS_ARG_IMAGE( class, "in1", -1,
_( "Then image" ),
_( "Source for TRUE pixels" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsIfthenelse, in1 ) );
VIPS_ARG_IMAGE( class, "in2", 0,
_( "Else image" ),
_( "Source for FALSE pixels" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsIfthenelse, in2 ) );
VIPS_ARG_BOOL( class, "blend", 4,
_( "Blend" ),
_( "Blend smoothly between then and else parts" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsIfthenelse, blend ),
FALSE );
}
static void
vips_ifthenelse_init( VipsIfthenelse *ifthenelse )
{
}
/**
* vips_ifthenelse:
* @cond: condition #VipsImage
* @in1: then #VipsImage
* @in2: else #VipsImage
* @out: (out): output #VipsImage
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* * @blend: blend smoothly between @in1 and @in2
*
* This operation scans the condition image @cond
* and uses it to select pixels from either the then image @in1 or the else
* image @in2. Non-zero means @in1, 0 means @in2.
*
* Any image can have either 1 band or n bands, where n is the same for all
* the non-1-band images. Single band images are then effectively copied to
* make n-band images.
*
* Images @in1 and @in2 are cast up to the smallest common format. @cond is
* cast to uchar.
*
* If the images differ in size, the smaller images are enlarged to match the
* largest by adding zero pixels along the bottom and right.
*
* If @blend is %TRUE, then values in @out are smoothly blended between @in1
* and @in2 using the formula:
*
* @out = (@cond / 255) * @in1 + (1 - @cond / 255) * @in2
*
* See also: vips_equal().
*
* Returns: 0 on success, -1 on error
*/
int
vips_ifthenelse( VipsImage *cond, VipsImage *in1, VipsImage *in2,
VipsImage **out, ... )
{
va_list ap;
int result;
va_start( ap, out );
result = vips_call_split( "ifthenelse", ap, cond, in1, in2, out );
va_end( ap );
return( result );
}