diff --git a/ChangeLog b/ChangeLog index d191ae3e..c79c8c64 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,7 @@ im_Lab2LabQ(), im_LabQ2Lab(), im_Lab2LabS(), im_LabS2Lab(), im_LabQ2LabS(), im_LabS2LabQ(), im_LabQ2disp(), im_XYZ2disp(), im_disp2XYZ(), im_icc_import*(), im_icc_export*(), im_icc_transform*() as classes +- added vips_colour_convert(), replaces all derived conversions 13/9/12 started 7.30.3 - linecache sized itself too large diff --git a/TODO b/TODO index e22e1812..2c6155eb 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,6 @@ - im_icc_ac2rc needs doing once we have a general 'convert' operator -- auto cast to float in XYZ2sRGB seems broken? +- test new icc stuff with lcms1 - should we have a better thing to stop convert_saveable running for vips images? @@ -8,7 +8,8 @@ - convert_saveable should become a lot simpler with a general colourspace converter -- export should allow use of the embedded profile as an output? +- why do we need the extra vips_image_write() at the end of + vips_colour_convert_build()? why can't we just g_object_set() x? diff --git a/libvips/colour/Makefile.am b/libvips/colour/Makefile.am index 4438387b..bdeac870 100644 --- a/libvips/colour/Makefile.am +++ b/libvips/colour/Makefile.am @@ -4,7 +4,7 @@ libcolour_la_SOURCES = \ colour.c \ colour_funcs.c \ colour_dispatch.c \ - derived.c \ + icc_transform.c \ Lab2XYZ.c \ Lab2LCh.c \ LCh2Lab.c \ @@ -24,7 +24,6 @@ libcolour_la_SOURCES = \ LabQ2sRGB.c \ XYZ2sRGB.c \ sRGB2XYZ.c \ - icc_transform.c \ im_lab_morph.c \ im_dE00_fromLab.c \ im_dECMC_fromLab.c \ diff --git a/libvips/colour/colour.c b/libvips/colour/colour.c index 34191083..07527253 100644 --- a/libvips/colour/colour.c +++ b/libvips/colour/colour.c @@ -396,6 +396,234 @@ vips_colour_code_init( VipsColourCode *code ) { } +/* A colour-transforming function. + */ +typedef int (*VipsColourTransformFn)( VipsImage *in, VipsImage **out, ... ); + +/* Maximum number of steps we allow in a route. 10 steps should be enough + * for anyone. + */ +#define MAX_STEPS (10) + +/* A route between two colour spaces. + * + * 10 steps should be enough for anyone. + */ +typedef struct _VipsColourRoute { + VipsInterpretation from; + VipsInterpretation to; + VipsColourTransformFn route[MAX_STEPS + 1]; +} VipsColourRoute; + +/* Some defines to save typing. These are the colour spaces we support + * conversions between. + */ +#define XYZ VIPS_INTERPRETATION_XYZ +#define LAB VIPS_INTERPRETATION_LAB +#define LABQ VIPS_INTERPRETATION_LABQ +#define LCH VIPS_INTERPRETATION_LCH +#define UCS VIPS_INTERPRETATION_UCS +#define LABS VIPS_INTERPRETATION_LABS +#define sRGB VIPS_INTERPRETATION_sRGB +#define YXY VIPS_INTERPRETATION_YXY + +/* All the routes we know about. + */ +static VipsColourRoute vips_colour_routes[] = { + { XYZ, LAB, { vips_XYZ2Lab, NULL } }, + { XYZ, LABQ, { vips_XYZ2Lab, vips_Lab2LabQ, NULL } }, + { XYZ, LCH, { vips_XYZ2Lab, vips_Lab2LCh, NULL } }, + { XYZ, UCS, { vips_XYZ2Lab, vips_Lab2LCh, vips_LCh2UCS, NULL } }, + { XYZ, LABS, { vips_XYZ2Lab, vips_Lab2LabS, NULL } }, + { XYZ, sRGB, { vips_XYZ2sRGB, NULL } }, + { XYZ, YXY, { vips_XYZ2Yxy, NULL } }, + + { LAB, XYZ, { vips_Lab2XYZ, NULL } }, + { LAB, LABQ, { vips_Lab2LabQ, NULL } }, + { LAB, LCH, { vips_Lab2LCh, NULL } }, + { LAB, UCS, { vips_Lab2LCh, vips_LCh2UCS, NULL } }, + { LAB, LABS, { vips_Lab2LabS, NULL } }, + { LAB, sRGB, { vips_Lab2XYZ, vips_XYZ2sRGB, NULL } }, + { LAB, YXY, { vips_Lab2XYZ, vips_XYZ2Yxy, NULL } }, + + { LABQ, XYZ, { vips_LabQ2Lab, vips_Lab2XYZ, NULL } }, + { LABQ, LAB, { vips_LabQ2Lab, NULL } }, + { LABQ, LCH, { vips_LabQ2Lab, vips_Lab2LCh, NULL } }, + { LABQ, UCS, { vips_LabQ2Lab, vips_Lab2LCh, vips_LCh2UCS, NULL } }, + { LABQ, LABS, { vips_LabQ2LabS, NULL } }, + { LABQ, sRGB, { vips_LabQ2sRGB, NULL } }, + { LABQ, YXY, { vips_LabQ2Lab, vips_Lab2XYZ, vips_XYZ2Yxy, NULL } }, + + { LCH, XYZ, { vips_LCh2Lab, vips_Lab2XYZ, NULL } }, + { LCH, LAB, { vips_LCh2Lab, NULL } }, + { LCH, LABQ, { vips_LCh2Lab, vips_Lab2LabQ, NULL } }, + { LCH, UCS, { vips_LCh2UCS, NULL } }, + { LCH, LABS, { vips_LCh2Lab, vips_Lab2LabS, NULL } }, + { LCH, sRGB, { vips_LCh2Lab, vips_Lab2XYZ, vips_XYZ2sRGB, NULL } }, + { LCH, YXY, { vips_LCh2Lab, vips_Lab2XYZ, vips_XYZ2Yxy, NULL } }, + + { UCS, XYZ, { vips_UCS2LCh, vips_LCh2Lab, vips_Lab2XYZ, NULL } }, + { UCS, LAB, { vips_UCS2LCh, vips_LCh2Lab, NULL } }, + { UCS, LABQ, { vips_UCS2LCh, vips_LCh2Lab, vips_Lab2LabQ, NULL } }, + { UCS, LCH, { vips_UCS2LCh, NULL } }, + { UCS, LABS, { vips_UCS2LCh, vips_LCh2Lab, vips_Lab2LabS, NULL } }, + { UCS, sRGB, { vips_UCS2LCh, vips_LCh2Lab, vips_Lab2XYZ, + vips_XYZ2sRGB, NULL } }, + { UCS, YXY, { vips_UCS2LCh, vips_LCh2Lab, vips_Lab2XYZ, + vips_XYZ2Yxy, NULL } }, + + { LABS, XYZ, { vips_LabS2Lab, vips_Lab2XYZ, NULL } }, + { LABS, LAB, { vips_LabS2Lab, NULL } }, + { LABS, LABQ, { vips_LabS2LabQ, NULL } }, + { LABS, LCH, { vips_LabS2Lab, vips_Lab2LCh, NULL } }, + { LABS, UCS, { vips_LabS2Lab, vips_Lab2LCh, vips_LCh2UCS, NULL } }, + { LABS, sRGB, { vips_LabS2Lab, vips_Lab2XYZ, vips_XYZ2sRGB, NULL } }, + { LABS, YXY, { vips_LabS2Lab, vips_Lab2XYZ, vips_XYZ2Yxy, NULL } }, + + { sRGB, XYZ, { vips_sRGB2XYZ, NULL } }, + { sRGB, LAB, { vips_sRGB2XYZ, vips_XYZ2Lab, NULL } }, + { sRGB, LABQ, { vips_sRGB2XYZ, vips_XYZ2Lab, vips_Lab2LabQ, NULL } }, + { sRGB, LCH, { vips_sRGB2XYZ, vips_XYZ2Lab, vips_Lab2LCh, NULL } }, + { sRGB, UCS, { vips_sRGB2XYZ, vips_XYZ2Lab, vips_Lab2LCh, + vips_LCh2UCS, NULL } }, + { sRGB, LABS, { vips_sRGB2XYZ, vips_XYZ2Lab, vips_Lab2LabS, NULL } }, + { sRGB, YXY, { vips_sRGB2XYZ, vips_XYZ2Yxy, NULL } }, + + { YXY, XYZ, { vips_Yxy2XYZ, NULL } }, + { YXY, LAB, { vips_Yxy2XYZ, vips_XYZ2Lab, NULL } }, + { YXY, LABQ, { vips_Yxy2XYZ, vips_XYZ2Lab, vips_Lab2LabQ, NULL } }, + { YXY, LCH, { vips_Yxy2XYZ, vips_XYZ2Lab, vips_Lab2LCh, NULL } }, + { YXY, UCS, { vips_Yxy2XYZ, vips_XYZ2Lab, vips_Lab2LCh, + vips_LCh2UCS, NULL } }, + { YXY, LABS, { vips_Yxy2XYZ, vips_XYZ2Lab, vips_Lab2LabS, NULL } }, + { YXY, sRGB, { vips_Yxy2XYZ, vips_XYZ2sRGB, NULL } }, +}; + +typedef struct _VipsColourConvert { + VipsOperation parent_instance; + + VipsImage *in; + VipsImage *out; + VipsInterpretation space; +} VipsColourConvert; + +typedef VipsOperationClass VipsColourConvertClass; + +G_DEFINE_TYPE( VipsColourConvert, vips_colour_convert, VIPS_TYPE_OPERATION ); + +static int +vips_colour_convert_build( VipsObject *object ) +{ + VipsColourConvert *convert = (VipsColourConvert *) object; + + int i, j; + VipsImage *x; + VipsImage **t; + + t = (VipsImage **) vips_object_local_array( object, MAX_STEPS ); + + /* Verify that all input args have been set. + */ + if( VIPS_OBJECT_CLASS( vips_colour_convert_parent_class )-> + build( object ) ) + return( -1 ); + + x = convert->in; + + for( i = 0; i < VIPS_NUMBER( vips_colour_routes ); i++ ) + if( vips_colour_routes[i].from == x->Type && + vips_colour_routes[i].to == convert->space ) + break; + if( i == VIPS_NUMBER( vips_colour_routes ) ) { + vips_error( "vips_colour_convert", + _( "no known route between '%s' and '%s'" ), + vips_enum_nick( VIPS_TYPE_INTERPRETATION, x->Type ), + vips_enum_nick( VIPS_TYPE_INTERPRETATION, + convert->space ) ); + return( -1 ); + } + + for( j = 0; vips_colour_routes[i].route[j]; j++ ) { + if( vips_colour_routes[i].route[j]( x, &t[j], NULL ) ) + return( -1 ); + x = t[j]; + } + + g_object_set( convert, "out", vips_image_new(), NULL ); + if( vips_image_write( x, convert->out ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_colour_convert_class_init( VipsColourConvertClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "convert"; + vobject_class->description = _( "convert to a new colourspace" ); + vobject_class->build = vips_colour_convert_build; + + operation_class->flags = VIPS_OPERATION_SEQUENTIAL; + + VIPS_ARG_IMAGE( class, "in", 1, + _( "Output" ), + _( "Output image" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsColourConvert, in ) ); + + VIPS_ARG_IMAGE( class, "out", 2, + _( "Output" ), + _( "Output image" ), + VIPS_ARGUMENT_REQUIRED_OUTPUT, + G_STRUCT_OFFSET( VipsColourConvert, out ) ); + + VIPS_ARG_ENUM( class, "space", 6, + _( "Space" ), + _( "Destination colour space" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsColourConvert, space ), + VIPS_TYPE_INTERPRETATION, VIPS_INTERPRETATION_sRGB ); +} + +static void +vips_colour_convert_init( VipsColourConvert *convert ) +{ +} + +/** + * vips_colour_convert: + * @in: input image + * @out: output image + * @space: convert to this colour space + * + * This convenience function looks at the interpretation field of @in and runs + * a set of colourspace conversion functions to move it to @space. + * + * For example, given an image tagged as #VIPS_INTERPRETATION_YXY, running + * vips_colour_convert() with @space set to #VIPS_INTERPRETATION_LAB will + * convert with vips_Yxy2XYZ() and vips_XYZ2Lab(). + */ +int +vips_colour_convert( VipsImage *in, VipsImage **out, + VipsInterpretation space, ... ) +{ + va_list ap; + int result; + + va_start( ap, space ); + result = vips_call_split( "convert", ap, in, out, space ); + va_end( ap ); + + return( result ); +} + /* Called from iofuncs to init all operations in this dir. Use a plugin system * instead? */ @@ -427,6 +655,7 @@ vips_colour_operation_init( void ) extern GType vips_icc_transform_get_type( void ); #endif + vips_colour_convert_get_type(); vips_Lab2XYZ_get_type(); vips_XYZ2Lab_get_type(); vips_Lab2LCh_get_type(); diff --git a/libvips/colour/derived.c b/libvips/colour/derived.c deleted file mode 100644 index f3af723c..00000000 --- a/libvips/colour/derived.c +++ /dev/null @@ -1,183 +0,0 @@ -/* Derived colour space functions. - * - * 14/9/95 JC - * - horrible error killed im_dE_fromXYZ() and im_dE_fromdisp() - * 4/3/98 JC - * - sRGB added - * 17/6/99 JC - * - minor reformatting - * 30/10/09 - * - gtkdoc comments - * - minor cleanups - */ - -/* - - 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - */ - -/* - - These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk - - */ - -#ifdef HAVE_CONFIG_H -#include -#endif /*HAVE_CONFIG_H*/ -#include - -#include -#include - -#include - -/** - * im_LabQ2XYZ: - * @in: input image - * @out: output image - * - * Convert an image from LabQ (Coding == IM_CODING_LABQ) to XYZ. - * - * Returns: 0 on success, -1 on error. - */ -int -im_LabQ2XYZ( IMAGE *in, IMAGE *out ) -{ - IMAGE *t[1]; - - if( im_open_local_array( out, t, 1, "im_LabQ2XYZ:1", "p" ) || - im_LabQ2Lab( in, t[0] ) || - im_Lab2XYZ( t[0], out ) ) - return( -1 ); - - return( 0 ); -} - -/** - * im_Lab2UCS: - * @in: input image - * @out: output image - * - * Convert an image from Lab to UCS. - * - * Returns: 0 on success, -1 on error. - */ -int -im_Lab2UCS( IMAGE *in, IMAGE *out ) -{ - IMAGE *t[1]; - - if( im_open_local_array( out, t, 1, "im_Lab2UCS:1", "p" ) || - im_Lab2LCh( in, t[0] ) || - im_LCh2UCS( t[0], out ) ) - return( -1 ); - - return( 0 ); -} - -/** - * im_UCS2Lab: - * @in: input image - * @out: output image - * - * Convert an image from UCS to Lab. - * - * Returns: 0 on success, -1 on error. - */ -int -im_UCS2Lab( IMAGE *in, IMAGE *out ) -{ - IMAGE *t[1]; - - if( im_open_local_array( out, t, 1, "im_UCS2Lab:1", "p" ) || - im_UCS2LCh( in, t[0] ) || - im_LCh2Lab( t[0], out ) ) - return( -1 ); - - return( 0 ); -} - -/** - * im_UCS2XYZ: - * @in: input image - * @out: output image - * - * Convert an image from UCS to XYZ. - * - * Returns: 0 on success, -1 on error. - */ -int -im_UCS2XYZ( IMAGE *in, IMAGE *out ) -{ - IMAGE *t[1]; - - if( im_open_local_array( out, t, 1, "im_UCS2XYZ:1", "p" ) || - im_UCS2Lab( in, t[0] ) || - im_Lab2XYZ( t[0], out ) ) - return( -1 ); - - return( 0 ); -} - - -/** - * im_XY2UCS: - * @in: input image - * @out: output image - * - * Convert an image from XYZ to UCS. - * - * Returns: 0 on success, -1 on error. - */ -int -im_XYZ2UCS( IMAGE *in, IMAGE *out ) -{ - IMAGE *t[1]; - - if( im_open_local_array( out, t, 1, "im_XYZ2UCS:1", "p" ) || - im_XYZ2Lab( in, t[0] ) || - im_Lab2UCS( t[0], out ) ) - return( -1 ); - - return( 0 ); -} - -/** - * im_dE_fromXYZ: - * @in1: first input image - * @in2: second input image - * @out: output image - * - * Calculate CIELAB dE 1976 from a pair of XYZ images. - * - * Returns: 0 on success, -1 on error. - */ -int -im_dE_fromXYZ( IMAGE *in1, IMAGE *in2, IMAGE *out ) -{ - IMAGE *t[2]; - - if( im_open_local_array( out, t, 2, "im_dE_fromXYZ:1", "p" ) || - im_XYZ2Lab( in1, t[0] ) || - im_XYZ2Lab( in2, t[1] ) || - im_dE_fromLab( t[0], t[1], out ) ) - return( -1 ); - - return( 0 ); -} diff --git a/libvips/deprecated/vips7compat.c b/libvips/deprecated/vips7compat.c index bb204325..f843ff2f 100644 --- a/libvips/deprecated/vips7compat.c +++ b/libvips/deprecated/vips7compat.c @@ -2638,3 +2638,138 @@ im_icc_export_depth( VipsImage *in, VipsImage *out, int depth, return( 0 ); } + +/** + * im_LabQ2XYZ: + * @in: input image + * @out: output image + * + * Convert an image from LabQ (Coding == IM_CODING_LABQ) to XYZ. + * + * Returns: 0 on success, -1 on error. + */ +int +im_LabQ2XYZ( IMAGE *in, IMAGE *out ) +{ + IMAGE *t[1]; + + if( im_open_local_array( out, t, 1, "im_LabQ2XYZ:1", "p" ) || + im_LabQ2Lab( in, t[0] ) || + im_Lab2XYZ( t[0], out ) ) + return( -1 ); + + return( 0 ); +} + +/** + * im_Lab2UCS: + * @in: input image + * @out: output image + * + * Convert an image from Lab to UCS. + * + * Returns: 0 on success, -1 on error. + */ +int +im_Lab2UCS( IMAGE *in, IMAGE *out ) +{ + IMAGE *t[1]; + + if( im_open_local_array( out, t, 1, "im_Lab2UCS:1", "p" ) || + im_Lab2LCh( in, t[0] ) || + im_LCh2UCS( t[0], out ) ) + return( -1 ); + + return( 0 ); +} + +/** + * im_UCS2Lab: + * @in: input image + * @out: output image + * + * Convert an image from UCS to Lab. + * + * Returns: 0 on success, -1 on error. + */ +int +im_UCS2Lab( IMAGE *in, IMAGE *out ) +{ + IMAGE *t[1]; + + if( im_open_local_array( out, t, 1, "im_UCS2Lab:1", "p" ) || + im_UCS2LCh( in, t[0] ) || + im_LCh2Lab( t[0], out ) ) + return( -1 ); + + return( 0 ); +} + +/** + * im_UCS2XYZ: + * @in: input image + * @out: output image + * + * Convert an image from UCS to XYZ. + * + * Returns: 0 on success, -1 on error. + */ +int +im_UCS2XYZ( IMAGE *in, IMAGE *out ) +{ + IMAGE *t[1]; + + if( im_open_local_array( out, t, 1, "im_UCS2XYZ:1", "p" ) || + im_UCS2Lab( in, t[0] ) || + im_Lab2XYZ( t[0], out ) ) + return( -1 ); + + return( 0 ); +} + + +/** + * im_XY2UCS: + * @in: input image + * @out: output image + * + * Convert an image from XYZ to UCS. + * + * Returns: 0 on success, -1 on error. + */ +int +im_XYZ2UCS( IMAGE *in, IMAGE *out ) +{ + IMAGE *t[1]; + + if( im_open_local_array( out, t, 1, "im_XYZ2UCS:1", "p" ) || + im_XYZ2Lab( in, t[0] ) || + im_Lab2UCS( t[0], out ) ) + return( -1 ); + + return( 0 ); +} + +/** + * im_dE_fromXYZ: + * @in1: first input image + * @in2: second input image + * @out: output image + * + * Calculate CIELAB dE 1976 from a pair of XYZ images. + * + * Returns: 0 on success, -1 on error. + */ +int +im_dE_fromXYZ( IMAGE *in1, IMAGE *in2, IMAGE *out ) +{ + IMAGE *t[2]; + + if( im_open_local_array( out, t, 2, "im_dE_fromXYZ:1", "p" ) || + im_XYZ2Lab( in1, t[0] ) || + im_XYZ2Lab( in2, t[1] ) || + im_dE_fromLab( t[0], t[1], out ) ) + return( -1 ); + + return( 0 ); +} diff --git a/libvips/include/vips/colour.h b/libvips/include/vips/colour.h index 6e318554..a31e5fa5 100644 --- a/libvips/include/vips/colour.h +++ b/libvips/include/vips/colour.h @@ -110,6 +110,10 @@ typedef enum { VIPS_INTENT_ABSOLUTE } VipsIntent; +int vips_colour_convert( VipsImage *in, VipsImage **out, + VipsInterpretation space, ... ) + __attribute__((sentinel)); + int vips_LabQ2sRGB( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); int vips_rad2float( VipsImage *in, VipsImage **out, ... ) @@ -187,25 +191,16 @@ int vips_col_XYZ2sRGB( float X, float Y, float Z, int vips_col_sRGB2XYZ( int r, int g, int b, float *X, float *Y, float *Z ); - - /* Colour loading and conversion functions. */ float im_col_pythagoras( float L1, float a1, float b1, float L2, float a2, float b2 ); - float im_col_dECMC( float L1, float a1, float b1, float L2, float a2, float b2 ); float im_col_dE00( float L1, float a1, float b1, float L2, float a2, float b2 ); -int im_LabQ2XYZ( VipsImage *in, VipsImage *out ); -int im_UCS2XYZ( VipsImage *in, VipsImage *out ); -int im_UCS2Lab( VipsImage *in, VipsImage *out ); -int im_Lab2UCS( VipsImage *in, VipsImage *out ); -int im_XYZ2UCS( VipsImage *in, VipsImage *out ); - int im_dECMC_fromLab( VipsImage *in1, VipsImage *in2, VipsImage *out ); int im_dE00_fromLab( VipsImage *in1, VipsImage *in2, VipsImage *out ); int im_dE_fromXYZ( VipsImage *in1, VipsImage *in2, VipsImage *out ); diff --git a/libvips/include/vips/vips7compat.h b/libvips/include/vips/vips7compat.h index 8094cf55..49567e29 100644 --- a/libvips/include/vips/vips7compat.h +++ b/libvips/include/vips/vips7compat.h @@ -769,6 +769,12 @@ int im_icc_export_depth( VipsImage *in, VipsImage *out, int depth, const char *output_profile_filename, VipsIntent intent ); int im_icc_ac2rc( VipsImage *in, VipsImage *out, const char *profile_filename ); +int im_LabQ2XYZ( VipsImage *in, VipsImage *out ); +int im_UCS2XYZ( VipsImage *in, VipsImage *out ); +int im_UCS2Lab( VipsImage *in, VipsImage *out ); +int im_Lab2UCS( VipsImage *in, VipsImage *out ); +int im_XYZ2UCS( VipsImage *in, VipsImage *out ); + /* ruby-vips uses this */ #define vips_class_map_concrete_all vips_class_map_all