/* 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 #endif /*HAVE_CONFIG_H*/ #include #include #include #include #include #include #include #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 ); }