From e793d38aa78ad05f9d910eef596aab91cbfa4a39 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 25 Sep 2012 22:06:24 +0100 Subject: [PATCH] new icc transformer compiles --- ChangeLog | 3 +- TODO | 3 + libvips/colour/Lab2XYZ.c | 5 +- libvips/colour/Makefile.am | 2 +- libvips/colour/XYZ2Lab.c | 7 +- libvips/colour/colour.c | 78 +- libvips/colour/colour.h | 21 +- libvips/colour/icc_transform.c | 1014 ++++++++++++++++ libvips/colour/im_icc_transform.c | 1715 ---------------------------- libvips/deprecated/vips7compat.c | 81 ++ libvips/include/vips/Makefile.am | 1 + libvips/include/vips/colour.h | 51 +- libvips/include/vips/enumtypes.h | 3 + libvips/include/vips/vips7compat.h | 19 + libvips/iofuncs/Makefile.am | 1 + libvips/iofuncs/enumtypes.c | 20 + 16 files changed, 1261 insertions(+), 1763 deletions(-) create mode 100644 libvips/colour/icc_transform.c delete mode 100644 libvips/colour/im_icc_transform.c diff --git a/ChangeLog b/ChangeLog index 7d658d70..d191ae3e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,7 +2,8 @@ - redone im_Lab2XYZ(), im_XYZ2Lab(), im_Lab2LCh(), im_LCh2Lab(), im_UCS2LCh, im_LCh2UCS(), im_XYZ2Yxy(), im_Yxy2XYZ(), im_float2rad(), im_rad2float(), im_Lab2LabQ(), im_LabQ2Lab(), im_Lab2LabS(), im_LabS2Lab(), im_LabQ2LabS(), - im_LabS2LabQ(), im_LabQ2disp(), im_XYZ2disp(), im_disp2XYZ() as classes + im_LabS2LabQ(), im_LabQ2disp(), im_XYZ2disp(), im_disp2XYZ(), + im_icc_import*(), im_icc_export*(), im_icc_transform*() as classes 13/9/12 started 7.30.3 - linecache sized itself too large diff --git a/TODO b/TODO index bd8cd700..1d80d2cf 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,6 @@ +- im_icc_ac2rc needs doing once we have a general 'convert' operator + + - boolean.c has vips_check_noncomplex( "VipsBoolean", binary->left ) ) diff --git a/libvips/colour/Lab2XYZ.c b/libvips/colour/Lab2XYZ.c index 6dd67a80..5d2c25da 100644 --- a/libvips/colour/Lab2XYZ.c +++ b/libvips/colour/Lab2XYZ.c @@ -195,9 +195,8 @@ vips_Lab2XYZ_init( VipsLab2XYZ *Lab2XYZ ) * * @temp: colour temperature * - * Turn Lab to XYZ. - * - * The colour temperature defaults to D65, but can be specified with @temp. + * Turn Lab to XYZ. The colour temperature defaults to D65, but can be + * specified with @temp. * * Returns: 0 on success, -1 on error */ diff --git a/libvips/colour/Makefile.am b/libvips/colour/Makefile.am index 54a26eb2..4438387b 100644 --- a/libvips/colour/Makefile.am +++ b/libvips/colour/Makefile.am @@ -24,7 +24,7 @@ libcolour_la_SOURCES = \ LabQ2sRGB.c \ XYZ2sRGB.c \ sRGB2XYZ.c \ - im_icc_transform.c \ + icc_transform.c \ im_lab_morph.c \ im_dE00_fromLab.c \ im_dECMC_fromLab.c \ diff --git a/libvips/colour/XYZ2Lab.c b/libvips/colour/XYZ2Lab.c index 1b39d674..99392e2d 100644 --- a/libvips/colour/XYZ2Lab.c +++ b/libvips/colour/XYZ2Lab.c @@ -249,7 +249,12 @@ vips_XYZ2Lab_init( VipsXYZ2Lab *XYZ2Lab ) * @in: input image * @out: output image * - * Turn XYZ to D65 Lab. + * optional arguments: + * + * @temp: colour temperature + * + * Turn XYZ to Lab, optionally specifying the colour temperature. @temp + * defaults to D65. * * Returns: 0 on success, -1 on error. */ diff --git a/libvips/colour/colour.c b/libvips/colour/colour.c index 0e7435a6..637021e8 100644 --- a/libvips/colour/colour.c +++ b/libvips/colour/colour.c @@ -83,11 +83,26 @@ vips_colour_gen( VipsRegion *or, return( 0 ); } +static int +vips_colour_attach_profile( VipsImage *im, const char *filename ) +{ + char *data; + unsigned int data_length; + + if( !(data = vips__file_read_name( filename, VIPS_ICC_DIR, + &data_length )) ) + return( -1 ); + vips_image_set_blob( im, VIPS_META_ICC_NAME, + (VipsCallbackFn) g_free, data, data_length ); + + return( 0 ); +} + static int vips_colour_build( VipsObject *object ) { + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsColour *colour = VIPS_COLOUR( object ); - VipsColourClass *class = VIPS_COLOUR_GET_CLASS( colour ); int i; @@ -104,7 +119,7 @@ vips_colour_build( VipsObject *object ) g_object_set( colour, "out", vips_image_new(), NULL ); if( colour->n > MAX_INPUT_IMAGES ) { - vips_error( "VipsColour", + vips_error( class->nickname, "%s", _( "too many input images" ) ); return( -1 ); } @@ -116,10 +131,15 @@ vips_colour_build( VipsObject *object ) return( -1 ); vips_demand_hint_array( colour->out, VIPS_DEMAND_STYLE_THINSTRIP, colour->in ); - colour->out->Coding = class->coding; - colour->out->Type = class->interpretation; - colour->out->BandFmt = class->format; - colour->out->Bands = class->bands; + colour->out->Coding = colour->coding; + colour->out->Type = colour->interpretation; + colour->out->BandFmt = colour->format; + colour->out->Bands = colour->bands; + + if( colour->profile_filename ) + if( vips_colour_attach_profile( colour->out, + colour->profile_filename ) ) + return( -1 ); if( vips_image_generate( colour->out, vips_start_many, vips_colour_gen, vips_stop_many, @@ -160,6 +180,12 @@ vips_colour_class_init( VipsColourClass *class ) static void vips_colour_init( VipsColour *colour ) { + VipsColourClass *class = VIPS_COLOUR_GET_CLASS( colour ); + + colour->coding = class->coding; + colour->interpretation = class->interpretation; + colour->format = class->format; + colour->bands = class->bands; } G_DEFINE_ABSTRACT_TYPE( VipsColourSpace, vips_colour_space, VIPS_TYPE_COLOUR ); @@ -266,7 +292,6 @@ vips_colour_code_build( VipsObject *object ) VipsColour *colour = VIPS_COLOUR( object ); VipsColourCode *code = VIPS_COLOUR_CODE( object ); VipsColourCodeClass *class = VIPS_COLOUR_CODE_GET_CLASS( object ); - VipsColourClass *colour_class = VIPS_COLOUR_CLASS( class ); VipsImage **t; VipsImage *in; @@ -279,22 +304,22 @@ vips_colour_code_build( VipsObject *object ) if( in && vips_check_coding( VIPS_OBJECT_CLASS( class )->nickname, - in, class->input_coding ) ) + in, code->input_coding ) ) return( -1 ); /* Extra band processing. don't do automatic detach/reattach if either * input or output will be coded. */ if( in && - class->input_coding == VIPS_CODING_NONE && - colour_class->coding == VIPS_CODING_NONE && - class->input_bands > 0 ) { - if( in->Bands > class->input_bands ) { + code->input_coding == VIPS_CODING_NONE && + colour->coding == VIPS_CODING_NONE && + code->input_bands > 0 ) { + if( in->Bands > code->input_bands ) { if( vips_extract_band( in, &t[1], 0, - "n", class->input_bands, NULL ) ) + "n", code->input_bands, NULL ) ) return( -1 ); - if( vips_extract_band( in, &t[2], class->input_bands, - "n", in->Bands - class->input_bands, + if( vips_extract_band( in, &t[2], code->input_bands, + "n", in->Bands - code->input_bands, NULL ) ) return( -1 ); in = t[1]; @@ -302,14 +327,14 @@ vips_colour_code_build( VipsObject *object ) } else if( vips_check_bands_atleast( VIPS_OBJECT_CLASS( class )->nickname, - in, class->input_bands ) ) + in, code->input_bands ) ) return( -1 ); } if( in && - class->input_coding == VIPS_CODING_NONE && - class->input_format != VIPS_FORMAT_NOTSET ) { - if( vips_cast( in, &t[3], class->input_format, NULL ) ) + code->input_coding == VIPS_CODING_NONE && + code->input_format != VIPS_FORMAT_NOTSET ) { + if( vips_cast( in, &t[3], code->input_format, NULL ) ) return( -1 ); in = t[3]; } @@ -365,6 +390,11 @@ vips_colour_code_class_init( VipsColourCodeClass *class ) static void vips_colour_code_init( VipsColourCode *code ) { + VipsColourCodeClass *class = VIPS_COLOUR_CODE_GET_CLASS( code ); + + code->input_coding = class->input_coding; + code->input_format = class->input_format; + code->input_bands = class->input_bands; } /* Called from iofuncs to init all operations in this dir. Use a plugin system @@ -392,6 +422,11 @@ vips_colour_operation_init( void ) extern GType vips_LabQ2sRGB_get_type( void ); extern GType vips_XYZ2sRGB_get_type( void ); extern GType vips_sRGB2XYZ_get_type( void ); +#if defined(HAVE_LCMS) || defined(HAVE_LCMS2) + extern GType vips_icc_import_get_type( void ); + extern GType vips_icc_export_get_type( void ); + extern GType vips_icc_transform_get_type( void ); +#endif vips_Lab2XYZ_get_type(); vips_XYZ2Lab_get_type(); @@ -412,4 +447,9 @@ vips_colour_operation_init( void ) vips_LabQ2sRGB_get_type(); vips_XYZ2sRGB_get_type(); vips_sRGB2XYZ_get_type(); +#if defined(HAVE_LCMS) || defined(HAVE_LCMS2) + vips_icc_import_get_type(); + vips_icc_export_get_type(); + vips_icc_transform_get_type(); +#endif } diff --git a/libvips/colour/colour.h b/libvips/colour/colour.h index 6922a019..992941bd 100644 --- a/libvips/colour/colour.h +++ b/libvips/colour/colour.h @@ -69,6 +69,17 @@ typedef struct _VipsColour { int n; VipsImage *out; + + /* Set fields on ->out from these. + */ + VipsCoding coding; + VipsInterpretation interpretation; + VipsBandFormat format; + int bands; + + /* Attach this profile, if set. + */ + char *profile_filename; } VipsColour; typedef struct _VipsColourClass { @@ -78,12 +89,13 @@ typedef struct _VipsColourClass { */ VipsColourProcessFn process_line; - /* Set fields on ->out from these. + /* Init fields on instance from these. */ VipsCoding coding; VipsInterpretation interpretation; VipsBandFormat format; int bands; + } VipsColourClass; GType vips_colour_get_type( void ); @@ -143,6 +155,12 @@ typedef struct _VipsColourCode { VipsImage *in; + /* Test in against these, init them from class. + */ + VipsCoding input_coding; + VipsBandFormat input_format; + int input_bands; + } VipsColourCode; typedef struct _VipsColourCodeClass { @@ -171,4 +189,3 @@ GType vips_colour_code_get_type( void ); #endif /*VIPS__COLOUR_H*/ - diff --git a/libvips/colour/icc_transform.c b/libvips/colour/icc_transform.c new file mode 100644 index 00000000..b188a5d7 --- /dev/null +++ b/libvips/colour/icc_transform.c @@ -0,0 +1,1014 @@ +/* Transform images with little cms + * + * 26/4/02 JC + * 26/8/05 + * - attach profiles and intents to output images + * - added im_icc_import_embedded() to import with an embedded profile + * 12/5/06 + * - lock around cmsDoTransform + * 23/1/07 + * - set RGB16 on 16-bit RGB export + * 6/4/09 + * - catch lcms error messages + * 2/11/09 + * - gtkdoc + * - small cleanups + * - call attach_profile() before im_wrapone() so the profile will get + * written if we are wrinting to a file + * 2/8/10 + * - add lcms2 + * 12/7/11 + * - import and export cast @in to an appropriate format for you + * 25/9/12 + * - redo as a class + */ + +/* + + 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 + +#if defined( HAVE_LCMS ) || defined( HAVE_LCMS2 ) + +#include +#include +#include + +/* Has to be before VIPS to avoid nameclashes. + */ +#ifdef HAVE_LCMS2 +#include +#else /*HAVE_LCMS*/ +#include +#endif + +#include + +#include + +#include "colour.h" + +/* Call lcms with up to this many pixels at once. + */ +#define PIXEL_BUFFER_SIZE (10000) + +/* LCMS1 was missing some stuff. + */ +#ifdef HAVE_LCMS +typedef DWORD cmsUInt32Number; + +#define cmsSigRgbData icSigRgbData: +#define cmsSigCmykData icSigCmykData: +#endif + +/** + * vips_icc_present: + * + * VIPS can optionally be built without the ICC library. Use this function to + * test for its availability. + * + * Returns: non-zero if the ICC library is present. + */ +int +vips_icc_present( void ) +{ + return( 1 ); +} + +#define VIPS_TYPE_ICC (vips_icc_get_type()) +#define VIPS_ICC( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_ICC, VipsIcc )) +#define VIPS_ICC_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_ICC, VipsIccClass)) +#define VIPS_IS_ICC( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_ICC )) +#define VIPS_IS_ICC_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_ICC )) +#define VIPS_ICC_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_ICC, VipsIccClass )) + +typedef struct _VipsIcc { + VipsColourCode parent_instance; + + VipsIntent intent; + int depth; + + cmsHPROFILE in_profile; + cmsHPROFILE out_profile; + cmsUInt32Number in_icc_format; + cmsUInt32Number out_icc_format; + cmsHTRANSFORM trans; + + /* We need to single-thread calls to LCMS 1. + */ + GMutex *lock; + +} VipsIcc; + +typedef VipsColourCodeClass VipsIccClass; + +G_DEFINE_ABSTRACT_TYPE( VipsIcc, vips_icc, VIPS_TYPE_COLOUR_CODE ); + +/* Error from lcms. + */ + +#ifdef HAVE_LCMS2 +static void +icc_error( cmsContext context, cmsUInt32Number code, const char *text ) +{ + vips_error( "VipsIcc", "%s", text ); +} +#else +static int +icc_error( int code, const char *text ) +{ + if( code == LCMS_ERRC_WARNING ) + vips_warn( "VipsIcc", "%s", text ); + else + vips_error( "VipsIcc", "%s", text ); + + return( 0 ); +} +#endif + +static void +vips_icc_dispose( GObject *gobject ) +{ + VipsIcc *icc = (VipsIcc *) gobject; + + VIPS_FREEF( cmsDeleteTransform, icc->trans ); + VIPS_FREEF( cmsCloseProfile, icc->in_profile ); + VIPS_FREEF( cmsCloseProfile, icc->out_profile ); + VIPS_FREEF( g_mutex_free, icc->lock ); + + G_OBJECT_CLASS( vips_icc_parent_class )->dispose( gobject ); +} + +static int +vips_icc_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsColour *colour = (VipsColour *) object; + VipsColourCode *code = (VipsColourCode *) object; + VipsIcc *icc = (VipsIcc *) object; + + if( icc->depth != 8 && + icc->depth != 16 ) { + vips_error( class->nickname, + "%s", _( "depth must be 8 or 16" ) ); + return( -1 ); + } + + if( icc->in_profile && + !cmsIsIntentSupported( icc->in_profile, + icc->intent, LCMS_USED_AS_INPUT ) ) + vips_warn( class->nickname, + _( "intent %d (%s) not supported by " + "input profile; falling back to default intent " + "(usually PERCEPTUAL)" ), + icc->intent, + vips_enum_nick( VIPS_TYPE_INTENT, icc->intent ) ); + + if( icc->out_profile && + !cmsIsIntentSupported( icc->out_profile, + icc->intent, LCMS_USED_AS_OUTPUT ) ) + vips_warn( class->nickname, + _( "intent %d (%s) not supported by " + "profile; falling back to default intent " + "(usually PERCEPTUAL)" ), + icc->intent, + vips_enum_nick( VIPS_TYPE_INTENT, icc->intent ) ); + + if( icc->in_profile && + code->in ) { + switch( cmsGetColorSpace( icc->in_profile ) ) { + case cmsSigRgbData: + code->input_bands = 3; + code->input_format = + code->in->BandFmt == VIPS_FORMAT_USHORT ? + VIPS_FORMAT_USHORT : VIPS_FORMAT_UCHAR; + icc->in_icc_format = + code->in->BandFmt == VIPS_FORMAT_USHORT ? + TYPE_RGB_16 : TYPE_RGB_8; + break; + + case cmsSigCmykData: + code->input_bands = 4; + code->input_format = + code->in->BandFmt == VIPS_FORMAT_USHORT ? + VIPS_FORMAT_USHORT : VIPS_FORMAT_UCHAR; + icc->in_icc_format = + code->in->BandFmt == VIPS_FORMAT_USHORT ? + TYPE_CMYK_16 : TYPE_CMYK_8; + break; + + case cmsSigLabData: + code->input_bands = 3; + code->input_format = VIPS_FORMAT_FLOAT; + icc->in_icc_format = TYPE_Lab_16; + break; + + default: + vips_error( class->nickname, + _( "unimplemented input color space 0x%x" ), + cmsGetColorSpace( icc->in_profile ) ); + return( -1 ); + } + } + + if( icc->out_profile ) + switch( cmsGetColorSpace( icc->out_profile ) ) { + case icSigRgbData: + colour->interpretation = VIPS_INTERPRETATION_RGB; + colour->format = VIPS_FORMAT_UCHAR; + colour->bands = 3; + icc->out_icc_format = + icc->depth == 16 ? + TYPE_RGB_16 : TYPE_RGB_8; + break; + + case icSigCmykData: + colour->interpretation = VIPS_INTERPRETATION_CMYK; + colour->format = + icc->depth == 8 ? + VIPS_FORMAT_UCHAR : VIPS_FORMAT_USHORT; + colour->bands = 4; + icc->out_icc_format = + icc->depth == 16 ? + TYPE_CMYK_16 : TYPE_CMYK_8; + break; + + case icSigLabData: + colour->interpretation = VIPS_INTERPRETATION_LAB; + colour->format = VIPS_FORMAT_FLOAT; + colour->bands = 3; + icc->out_icc_format = TYPE_Lab_16; + break; + + default: + vips_error( class->nickname, + _( "unimplemented output color space 0x%x" ), + cmsGetColorSpace( icc->out_profile ) ); + return( -1 ); + } + + /* At least one must be a device profile, see the buffer processor. + */ + if( icc->in_profile && + icc->out_profile && + cmsGetColorSpace( icc->in_profile ) == cmsSigLabData && + cmsGetColorSpace( icc->out_profile ) == cmsSigLabData ) { + vips_error( class->nickname, + "%s", _( "no device profiles" ) ); + return( -1 ); + } + + /* Use cmsFLAGS_NOCACHE to disable the 1-pixel cache and make + * calling cmsDoTransform() from multiple threads safe. + */ + if( !(icc->trans = cmsCreateTransform( + icc->in_profile, icc->in_icc_format, + icc->out_profile, icc->out_icc_format, + icc->intent, cmsFLAGS_NOCACHE )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_icc_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_icc_class_init( VipsIccClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->dispose = vips_icc_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "icc"; + object_class->description = _( "transform using ICC profiles" ); + object_class->build = vips_icc_build; + + VIPS_ARG_ENUM( class, "intent", 6, + _( "Intent" ), + _( "Rendering intent" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsIcc, intent ), + VIPS_TYPE_INTENT, VIPS_INTENT_RELATIVE ); + +#ifdef HAVE_LCMS2 + cmsSetLogErrorHandler( icc_error ); +#else + /* Ask lcms not to abort on error. + */ + cmsErrorAction( LCMS_ERROR_IGNORE ); + cmsSetErrorHandler( icc_error ); +#endif + +} + +static void +vips_icc_init( VipsIcc *icc ) +{ + icc->lock = g_mutex_new(); + icc->intent = VIPS_INTENT_RELATIVE; + icc->depth = 8; +} + +typedef struct _VipsIccImport { + VipsIcc parent_instance; + + gboolean embedded; + char *input_profile_filename; + +} VipsIccImport; + +typedef VipsIccClass VipsIccImportClass; + +G_DEFINE_TYPE( VipsIccImport, vips_icc_import, VIPS_TYPE_ICC ); + +static int +vips_icc_import_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsColourCode *code = (VipsColourCode *) object; + VipsIcc *icc = (VipsIcc *) object; + VipsIccImport *import = (VipsIccImport *) object; + + if( code->in && + import->embedded && + vips_image_get_typeof( code->in, VIPS_META_ICC_NAME ) ) { + void *data; + size_t data_length; + + if( vips_image_get_blob( code->in, VIPS_META_ICC_NAME, + &data, &data_length ) || + !(icc->in_profile = cmsOpenProfileFromMem( + data, data_length )) ) { + vips_error( class->nickname, + "%s", _( "unable to load embedded profile" ) ); + return( -1 ); + } + } + else if( import->input_profile_filename ) { + if( !(icc->in_profile = cmsOpenProfileFromFile( + import->input_profile_filename, "r" )) ) { + vips_error( class->nickname, + _( "unable to open profile \"%s\"" ), + import->input_profile_filename ); + return( -1 ); + } + } + else { + vips_error( class->nickname, "%s", _( "no input profile" ) ); + return( -1 ); + } + +#ifdef HAVE_LCMS2 +{ + cmsCIExyY white; + cmsWhitePointFromTemp( &white, 6500 ); + + icc->out_profile = cmsCreateLab4Profile( &white ); +} +#else + icc->out_profile = cmsCreateLabProfile( NULL ); +#endif + + if( VIPS_OBJECT_CLASS( vips_icc_import_parent_class )->build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +decode_lab( float *lab, guint16 *fixed, int n ) +{ + int i; + + for( i = 0; i < n; i++ ) { + lab[0] = (double) fixed[0] / 652.800; + lab[1] = ((double) fixed[1] / 256.0) - 128.0; + lab[2] = ((double) fixed[2] / 256.0) - 128.0; + + lab += 3; + fixed += 3; + } +} + +/* Process a buffer of data. + */ +static void +vips_icc_import_line( VipsColour *colour, + VipsPel *out, VipsPel **in, int width ) +{ + VipsIcc *icc = (VipsIcc *) colour; + + VipsPel *p = (VipsPel *) in[0]; + float *q = (float *) out; + + /* Buffer of encoded 16-bit pixels we transform. + */ + guint16 encoded[3 * PIXEL_BUFFER_SIZE]; + + while( width > 0 ) { + const int chunk = VIPS_MIN( width, PIXEL_BUFFER_SIZE ); + +#ifdef HAVE_LCMS2 + cmsDoTransform( icc->trans, encoded, p, chunk ); +#else + g_mutex_lock( icc->lock ); + cmsDoTransform( icc->trans, encoded, p, chunk ); + g_mutex_unlock( icc->lock ); +#endif + + decode_lab( q, encoded, chunk ); + + p += chunk * VIPS_IMAGE_SIZEOF_PEL( colour->out ); + q += chunk * 3; + width -= chunk; + } +} + +static void +vips_icc_import_class_init( VipsIccImportClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsColourClass *colour_class = VIPS_COLOUR_CLASS( class ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "icc_import"; + object_class->description = _( "import from device with ICC profile" ); + object_class->build = vips_icc_import_build; + + colour_class->process_line = vips_icc_import_line; + + VIPS_ARG_BOOL( class, "embedded", 110, + _( "Embedded" ), + _( "Use embedded input profile, if available" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsIccImport, embedded ), + FALSE ); + + VIPS_ARG_STRING( class, "input_profile", 120, + _( "Input profile" ), + _( "Filename to load input profile from" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsIccImport, input_profile_filename ), + NULL ); +} + +static void +vips_icc_import_init( VipsIccImport *import ) +{ +} + +/** + * vips_icc_import: + * @in: input image + * @out: output image + * + * Optional arguments: + * + * @input_profile: get the input profile from here + * @intent: transform with this intent + * @embedded: use profile embedded in input image + * + * Import an image from device space to D65 LAB with an ICC profile. + * + * If @embedded is set, the input profile is taken from the input image + * metadata. If there is no embedded profile, + * @input_profile_filename is used as a fall-back. + * + * If @embedded is not set, the input profile is taken from + * @input_profile. If @input_profile is not supplied, the + * metadata profile, if any, is used as a fall-back. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_icc_import( VipsImage *in, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "icc_import", ap, in, out ); + va_end( ap ); + + return( result ); +} + +typedef struct _VipsIccExport { + VipsIcc parent_instance; + + char *output_profile_filename; + +} VipsIccExport; + +typedef VipsIccClass VipsIccExportClass; + +G_DEFINE_TYPE( VipsIccExport, vips_icc_export, VIPS_TYPE_ICC ); + +static int +vips_icc_export_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsColour *colour = (VipsColour *) object; + VipsIcc *icc = (VipsIcc *) object; + VipsIccExport *export = (VipsIccExport *) object; + +#ifdef HAVE_LCMS2 +{ + cmsCIExyY white; + cmsWhitePointFromTemp( &white, 6500 ); + + icc->in_profile = cmsCreateLab4Profile( &white ); +} +#else + icc->in_profile = cmsCreateLabProfile( NULL ); +#endif + + if( export->output_profile_filename ) { + if( !(icc->out_profile = cmsOpenProfileFromFile( + export->output_profile_filename, "r" )) ) { + vips_error( class->nickname, + _( "unable to open profile \"%s\"" ), + export->output_profile_filename ); + return( -1 ); + } + + colour->profile_filename = export->output_profile_filename; + } + + if( VIPS_OBJECT_CLASS( vips_icc_export_parent_class )->build( object ) ) + return( -1 ); + + return( 0 ); +} + +/* Pack a buffer of floats into lcms's fixed-point formats. Cut from + * lcms-1.0.8. + */ +static void +encode_lab( guint16 *fixed, float *lab, int n ) +{ + int i; + + for( i = 0; i < n; i++ ) { + float L = lab[0]; + float a = lab[1]; + float b = lab[2]; + + if( L < 0 ) + L = 0; + if( L > 100. ) + L = 100.; + + if( a < -128. ) + a = -128; + if( a > 127.9961 ) + a = 127.9961; + if( b < -128. ) + b = -128; + if( b > 127.9961 ) + b = 127.9961; + + fixed[0] = L * 652.800 + 0.5; + fixed[1] = (a + 128.0) * 256.0 + 0.5; + fixed[2] = (b + 128.0) * 256.0 + 0.5; + + lab += 3; + fixed += 3; + } +} + +/* Process a buffer of data. + */ +static void +vips_icc_export_line( VipsColour *colour, + VipsPel *out, VipsPel **in, int width ) +{ + VipsIcc *icc = (VipsIcc *) colour; + + float *p = (float *) in[0]; + VipsPel *q = (VipsPel *) out; + + /* Buffer of encoded 16-bit pixels we transform. + */ + guint16 encoded[3 * PIXEL_BUFFER_SIZE]; + + while( width > 0 ) { + const int chunk = VIPS_MIN( width, PIXEL_BUFFER_SIZE ); + + encode_lab( encoded, p, chunk ); + +#ifdef HAVE_LCMS2 + cmsDoTransform( icc->trans, q, encoded, chunk ); +#else + g_mutex_lock( icc->lock ); + cmsDoTransform( icc->trans, q, encoded, chunk ); + g_mutex_unlock( icc->lock ); +#endif + + p += chunk * 3; + q += chunk * VIPS_IMAGE_SIZEOF_PEL( colour->out ); + width -= chunk; + } +} + +static void +vips_icc_export_class_init( VipsIccExportClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsColourClass *colour_class = VIPS_COLOUR_CLASS( class ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "icc_export"; + object_class->description = _( "output to device with ICC profile" ); + object_class->build = vips_icc_export_build; + + colour_class->process_line = vips_icc_export_line; + + VIPS_ARG_STRING( class, "output_profile", 110, + _( "Output profile" ), + _( "Filename to load output profile from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsIccExport, output_profile_filename ), + NULL ); + + VIPS_ARG_INT( class, "depth", 130, + _( "Depth" ), + _( "Output device space depth in bits" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsIcc, depth ), + 8, 16, 8 ); +} + +static void +vips_icc_export_init( VipsIccExport *export ) +{ +} + +/** + * vips_icc_export: + * @in: input image + * @out: output image + * @output_profile: get the output profile from here + * + * Optional arguments: + * + * @intent: transform with this intent + * @depth: depth of output image in bits + * + * Export an image from D65 LAB to device space with an ICC profile. + * @output_profile is attached to the output image. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_icc_export( VipsImage *in, VipsImage **out, + const char *output_profile, ... ) +{ + va_list ap; + int result; + + va_start( ap, output_profile ); + result = vips_call_split( "icc_export", ap, in, out, output_profile ); + va_end( ap ); + + return( result ); +} + +typedef struct _VipsIccTransform { + VipsIcc parent_instance; + + gboolean embedded; + char *input_profile_filename; + char *output_profile_filename; + +} VipsIccTransform; + +typedef VipsIccClass VipsIccTransformClass; + +G_DEFINE_TYPE( VipsIccTransform, vips_icc_transform, VIPS_TYPE_ICC ); + +static int +vips_icc_transform_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsColour *colour = (VipsColour *) object; + VipsColourCode *code = (VipsColourCode *) object; + VipsIcc *icc = (VipsIcc *) object; + VipsIccTransform *transform = (VipsIccTransform *) object; + + if( code->in && + transform->embedded && + vips_image_get_typeof( code->in, VIPS_META_ICC_NAME ) ) { + void *data; + size_t data_length; + + if( vips_image_get_blob( code->in, VIPS_META_ICC_NAME, + &data, &data_length ) || + !(icc->in_profile = cmsOpenProfileFromMem( + data, data_length )) ) { + vips_error( class->nickname, + "%s", _( "unable to load embedded profile" ) ); + return( -1 ); + } + } + else if( transform->input_profile_filename ) { + if( !(icc->in_profile = cmsOpenProfileFromFile( + transform->input_profile_filename, "r" )) ) { + vips_error( class->nickname, + _( "unable to open profile \"%s\"" ), + transform->input_profile_filename ); + return( -1 ); + } + } + else { + vips_error( class->nickname, "%s", _( "no input profile" ) ); + return( -1 ); + } + + if( transform->output_profile_filename ) { + if( !(icc->out_profile = cmsOpenProfileFromFile( + transform->output_profile_filename, "r" )) ) { + vips_error( class->nickname, + _( "unable to open profile \"%s\"" ), + transform->output_profile_filename ); + return( -1 ); + } + + colour->profile_filename = transform->output_profile_filename; + } + + if( VIPS_OBJECT_CLASS( vips_icc_transform_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +/* Process a buffer of data. + */ +static void +vips_icc_transform_line( VipsColour *colour, + VipsPel *out, VipsPel **in, int width ) +{ + VipsIcc *icc = (VipsIcc *) colour; + +#ifdef HAVE_LCMS2 + cmsDoTransform( icc->trans, out, in[0], width ); +#else + g_mutex_lock( icc->lock ); + cmsDoTransform( icc->trans, out, in[0], width ); + g_mutex_unlock( icc->lock ); +#endif +} + +static void +vips_icc_transform_class_init( VipsIccImportClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsColourClass *colour_class = VIPS_COLOUR_CLASS( class ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "icc_transform"; + object_class->description = + _( "transform between devices with ICC profiles" ); + object_class->build = vips_icc_transform_build; + + colour_class->process_line = vips_icc_transform_line; + + VIPS_ARG_STRING( class, "output_profile", 110, + _( "Output profile" ), + _( "Filename to load output profile from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsIccTransform, output_profile_filename ), + NULL ); + + VIPS_ARG_BOOL( class, "embedded", 120, + _( "Embedded" ), + _( "Use embedded input profile, if available" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsIccTransform, embedded ), + FALSE ); + + VIPS_ARG_STRING( class, "input_profile", 130, + _( "Input profile" ), + _( "Filename to load input profile from" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsIccTransform, input_profile_filename ), + NULL ); + + VIPS_ARG_INT( class, "depth", 140, + _( "Depth" ), + _( "Output device space depth in bits" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsIcc, depth ), + 8, 16, 8 ); + +} + +static void +vips_icc_transform_init( VipsIccTransform *transform ) +{ +} + +/** + * vips_icc_transform: + * @in: input image + * @out: output image + * @output_profile: get the output profile from here + * + * Optional arguments: + * + * @input_profile: get the input profile from here + * @intent: transform with this intent + * @depth: depth of output image in bits + * @embedded: use profile embedded in input image + * + * Transform an image with a pair of ICC profiles. The input image is moved to + * profile-connection space with the input profile and then to the output + * space with the output profile. + * + * If @embedded is set, the input profile is taken from the input image + * metadata, if present. If there is no embedded profile, + * @input_profile_filename is used as a fall-back. + * + * If @embedded is not set, the input profile is taken from + * @input_profile_filename. If @input_profile_filename is not supplied, the + * metadata profile, if any, is used as a fall-back. + * + * Use vips_icc_import() and vips_icc_export() to do either the first or + * second half of this operation in isolation. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_icc_transform( VipsImage *in, VipsImage **out, + const char *output_profile, ... ) +{ + va_list ap; + int result; + + va_start( ap, output_profile ); + result = vips_call_split( "icc_transform", ap, + in, out, output_profile ); + va_end( ap ); + + return( result ); +} + +/** + * vips_icc_ac2rc: + * @in: input image + * @out: output image + * @profile_filename: use this profile + * + * Transform an image from absolute to relative colorimetry using the + * MediaWhitePoint stored in the ICC profile. + * + * See also: im_icc_transform(), im_icc_import(). + * + * Returns: 0 on success, -1 on error. + */ +int +im_icc_ac2rc( VipsImage *in, VipsImage *out, const char *profile_filename ) +{ + cmsHPROFILE profile; + double X, Y, Z; + + double add[3]; + double mul[3]; + + IMAGE *t[2]; + + if( !(profile = cmsOpenProfileFromFile( profile_filename, "r" )) ) + return( -1 ); + +#ifdef HAVE_LCMS2 +{ + cmsCIEXYZ *media; + + if( !(media = cmsReadTag( profile, cmsSigMediaWhitePointTag )) ) { + im_error( "im_icc_ac2rc", "%s", _( "unable to get media " + "white point" ) ); + return( -1 ); + } + + X = media->X; + Y = media->Y; + Z = media->Z; +} +#else /*HAVE_LCMS*/ +{ + cmsCIEXYZ media; + + if( !cmsTakeMediaWhitePoint( &media, profile ) ) { + im_error( "im_icc_ac2rc", "%s", _( "unable to get media " + "white point" ) ); + return( -1 ); + } + + X = media.X; + Y = media.Y; + Z = media.Z; +} +#endif + + cmsCloseProfile( profile ); + + add[0] = 0.0; + add[1] = 0.0; + add[2] = 0.0; + + mul[0] = VIPS_D50_X0 / (X * 100.0); + mul[1] = VIPS_D50_Y0 / (Y * 100.0); + mul[2] = VIPS_D50_Z0 / (Z * 100.0); + + /* Do IM_CODING_LABQ too. + */ + if( in->Coding == IM_CODING_LABQ ) { + IMAGE *t1 = im_open_local( out, "im_icc_ac2rc-1", "p" ); + + if( !t1 || im_LabQ2Lab( in, t1 ) ) + return( -1 ); + + in = t1; + } + + /* Do IM_CODING_RAD. + */ + if( in->Coding == IM_CODING_RAD ) { + IMAGE *t1 = im_open_local( out, "im_icc_export:1", "p" ); + + if( !t1 || im_rad2float( in, t1 ) ) + return( -1 ); + + in = t1; + } + + if( im_open_local_array( out, t, 2, "im_icc_ac2rc-2", "p" ) || + im_Lab2XYZ_temp( in, t[0], IM_D50_X0, IM_D50_Y0, IM_D50_Z0 ) || + im_lintra_vec( 3, mul, t[0], add, t[1] ) || + im_XYZ2Lab_temp( t[1], out, IM_D50_X0, IM_D50_Y0, IM_D50_Z0 ) ) + return( -1 ); + + return( 0 ); +} + +#else /*!HAVE_LCMS*/ + +#include + +int +vips_icc_present( void ) +{ + return( 0 ); +} + +#endif /*HAVE_LCMS*/ diff --git a/libvips/colour/im_icc_transform.c b/libvips/colour/im_icc_transform.c deleted file mode 100644 index 04f765d7..00000000 --- a/libvips/colour/im_icc_transform.c +++ /dev/null @@ -1,1715 +0,0 @@ -/* Transform images with little cms - * - * 26/4/02 JC - * 26/8/05 - * - attach profiles and intents to output images - * - added im_icc_import_embedded() to import with an embedded profile - * 12/5/06 - * - lock around cmsDoTransform - * 23/1/07 - * - set RGB16 on 16-bit RGB export - * 6/4/09 - * - catch lcms error messages - * 2/11/09 - * - gtkdoc - * - small cleanups - * - call attach_profile() before im_wrapone() so the profile will get - * written if we are wrinting to a file - * 2/8/10 - * - add lcms2 - * 12/7/11 - * - import and export cast @in to an appropriate format for you - */ - -/* - - 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 - -#if defined( HAVE_LCMS ) - -#include -#include -#include - -/* Has to be before VIPS to avoid nameclashes. - */ -#include -#include - -#include -#include -#include -#include - -/** - * VipsIntent: - * @IM_INTENT_PERCEPTUAL: - * @IM_INTENT_RELATIVE_COLORIMETRIC: - * @IM_INTENT_SATURATION: - * @IM_INTENT_ABSOLUTE_COLORIMETRIC: - * - * The rendering intent. #IM_INTENT_ABSOLUTE_COLORIMETRIC is best for - * scientific work, #IM_INTENT_RELATIVE_COLORIMETRIC is usually best for - * accurate communication with other imaging libraries. - */ - -/* Call lcms with up to this many pixels at once. - */ -#define PIXEL_BUFFER_SIZE (10000) - -static const char * -decode_intent( VipsIntent intent ) -{ - switch( intent ) { - case IM_INTENT_PERCEPTUAL: return( "PERCEPTUAL" ); - case IM_INTENT_RELATIVE_COLORIMETRIC: return( "RELATIVE" ); - case IM_INTENT_SATURATION: return( "SATURATION" ); - case IM_INTENT_ABSOLUTE_COLORIMETRIC: return( "ABSOLUTE" ); - default: return( "" ); - } -} - -/** - * im_icc_present: - * - * VIPS can optionally be built without the ICC library. Use this function to - * test for its availability. - * - * Returns: non-zero if the ICC library is present. - */ -int -im_icc_present( void ) -{ - return( 1 ); -} - -/* Global state for a transform. - */ -typedef struct { - IMAGE *in; - IMAGE *out; - const char *input_profile_filename; - const char *output_profile_filename; - VipsIntent intent; - - cmsHPROFILE in_profile; - cmsHPROFILE out_profile; - cmsHTRANSFORM trans; - - /* We need to single-thread calls to LCMS. - */ - GMutex *lock; -} Icc; - -/* Error from lcms. - */ -static int -icc_error( int code, const char *text ) -{ - if( code == LCMS_ERRC_WARNING ) - im_warn( "im_icc", "%s", text ); - else - im_error( "im_icc", "%s", text ); - - return( 0 ); -} - -static int -icc_destroy( Icc *icc ) -{ - IM_FREEF( cmsDeleteTransform, icc->trans ); - IM_FREEF( cmsCloseProfile, icc->in_profile ); - IM_FREEF( cmsCloseProfile, icc->out_profile ); - IM_FREEF( g_mutex_free, icc->lock ); - - return( 0 ); -} - -static Icc * -icc_new( IMAGE *in, IMAGE *out, VipsIntent intent ) -{ - Icc *icc; - - /* Ask lcms not to abort on error. - */ - cmsErrorAction( LCMS_ERROR_IGNORE ); - cmsSetErrorHandler( icc_error ); - - if( !(icc = IM_NEW( out, Icc )) ) - return( NULL ); - - icc->in = in; - icc->out = out; - icc->input_profile_filename = NULL; - icc->output_profile_filename = NULL; - icc->intent = intent; - icc->in_profile = 0; - icc->out_profile = 0; - icc->trans = 0; - icc->lock = g_mutex_new(); - - if( im_add_close_callback( out, - (im_callback_fn) icc_destroy, icc, NULL ) ) - return( NULL ); - - return( icc ); -} - -static Icc * -icc_new_file( IMAGE *in, IMAGE *out, - const char *input_profile_filename, - const char *output_profile_filename, - VipsIntent intent ) -{ - Icc *icc; - - if( !(icc = icc_new( in, out, intent )) ) - return( NULL ); - - if( input_profile_filename ) { - icc->input_profile_filename = - im_strdup( out, input_profile_filename ); - if( !(icc->in_profile = cmsOpenProfileFromFile( - input_profile_filename, "r" )) ) - im_error( "im_icc_transform", - _( "unable to open profile \"%s\"" ), - input_profile_filename ); - } - - if( output_profile_filename ) { - icc->output_profile_filename = - im_strdup( out, output_profile_filename ); - if( !(icc->out_profile = cmsOpenProfileFromFile( - output_profile_filename, "r" )) ) - im_error( "im_icc_transform", - _( "unable to open profile \"%s\"" ), - output_profile_filename ); - } - - if( !output_profile_filename ) - icc->out_profile = cmsCreateLabProfile( NULL ); - if( !input_profile_filename ) - icc->in_profile = cmsCreateLabProfile( NULL ); - - if( !icc->in_profile || !icc->out_profile ) { - im_error( "im_icc_transform", - "%s", _( "unable to create profiles" ) ); - return( NULL ); - } - - return( icc ); -} - -static Icc * -icc_new_mem( IMAGE *in, IMAGE *out, - void *data, int data_length, - VipsIntent intent ) -{ - Icc *icc; - - if( !(icc = icc_new( in, out, intent )) ) - return( NULL ); - - if( !(icc->in_profile = cmsOpenProfileFromMem( data, data_length )) ) { - im_error( "im_icc_transform", - "%s", _( "unable to read profile" ) ); - return( NULL ); - } - icc->out_profile = cmsCreateLabProfile( NULL ); - - return( icc ); -} - -/* Pack a buffer of floats into lcms's fixed-point formats. Cut from - * lcms-1.0.8. - */ -static void -encode_lab( float *lab, WORD *fixed, int n ) -{ - int i; - - for( i = 0; i < n; i++ ) { - float L = lab[0]; - float a = lab[1]; - float b = lab[2]; - - if( L < 0 ) - L = 0; - if( L > 100. ) - L = 100.; - - if( a < -128. ) - a = -128; - if( a > 127.9961 ) - a = 127.9961; - if( b < -128. ) - b = -128; - if( b > 127.9961 ) - b = 127.9961; - - fixed[0] = L * 652.800 + 0.5; - fixed[1] = (a + 128.0) * 256.0 + 0.5; - fixed[2] = (b + 128.0) * 256.0 + 0.5; - - lab += 3; - fixed += 3; - } -} - -static void -decode_lab( WORD *fixed, float *lab, int n ) -{ - int i; - - for( i = 0; i < n; i++ ) { - lab[0] = (double) fixed[0] / 652.800; - lab[1] = ((double) fixed[1] / 256.0) - 128.0; - lab[2] = ((double) fixed[2] / 256.0) - 128.0; - - lab += 3; - fixed += 3; - } -} - -static void -transform_buf( VipsPel *in, VipsPel *out, int n, Icc *icc ) -{ - g_mutex_lock( icc->lock ); - cmsDoTransform( icc->trans, in, out, n ); - g_mutex_unlock( icc->lock ); -} - -static int -attach_profile( IMAGE *im, const char *filename ) -{ - char *data; - unsigned int data_length; - - if( !(data = im__file_read_name( filename, VIPS_ICC_DIR, - &data_length )) ) - return( -1 ); - im_meta_set_blob( im, IM_META_ICC_NAME, - (im_callback_fn) im_free, data, data_length ); - - return( 0 ); -} - -/** - * im_icc_transform: - * @in: input image - * @out: output image - * @input_profile_filename: get the input profile from here - * @output_profile_filename: get the output profile from here - * @intent: transform with this intent - * - * Transform an image with the ICC library. The input image is moved to - * profile-connection space with the input profile and then to the output - * space with the output profile. - * - * Use im_icc_import() and im_icc_export_depth() to do either the first or - * second half of this operation in isolation. - * - * See also: im_icc_import(), im_icc_export_depth(). - * - * Returns: 0 on success, -1 on error. - */ -int -im_icc_transform( IMAGE *in, IMAGE *out, - const char *input_profile_filename, - const char *output_profile_filename, - VipsIntent intent ) -{ - Icc *icc; - DWORD in_icc_format; - DWORD out_icc_format; - - if( im_check_uncoded( "im_icc_transform", in ) ) - return( -1 ); - - if( !(icc = icc_new_file( in, out, - input_profile_filename, output_profile_filename, intent )) ) - return( -1 ); - - if( !cmsIsIntentSupported( icc->in_profile, - intent, LCMS_USED_AS_INPUT ) ) - im_warn( "im_icc_transform", - _( "intent %d (%s) not supported by " - "profile \"%s\"; falling back to default intent " - "(usually PERCEPTUAL)" ), - intent, decode_intent( intent ), - input_profile_filename ); - - if( !cmsIsIntentSupported( icc->out_profile, - intent, LCMS_USED_AS_OUTPUT ) ) - im_warn( "im_icc_transform", - _( "intent %d (%s) not supported by " - "profile \"%s\"; falling back to default intent " - "(usually PERCEPTUAL)" ), - intent, decode_intent( intent ), - output_profile_filename ); - - switch( cmsGetColorSpace( icc->in_profile ) ) { - case icSigCmykData: - if( in->Bands != 4 ) { - im_error( "im_icc_transform", - "%s", _( "CMYK input profile " - "needs a 4 band input image" ) ); - return( -1 ); - } - in_icc_format = COLORSPACE_SH( PT_CMYK ) | CHANNELS_SH( 4 ); - break; - - case icSigRgbData: - if( in->Bands != 3 ) { - im_error( "im_icc_transform", - "%s", _( "RGB input profile " - "needs a 3 band input image" ) ); - return( -1 ); - } - in_icc_format = COLORSPACE_SH( PT_RGB ) | CHANNELS_SH( 3 ); - break; - - default: - im_error( "im_icc_transform", - _( "unimplemented input color space 0x%x" ), - cmsGetColorSpace( icc->in_profile ) ); - return( -1 ); - } - - /* Prepare the output image. - */ - if( im_cp_desc( out, in ) ) - return( -1 ); - - switch( cmsGetColorSpace( icc->out_profile ) ) { - case icSigCmykData: - out->Type = IM_TYPE_CMYK; - out->BandFmt = IM_BANDFMT_UCHAR; - out->Bands = 4; - out_icc_format = TYPE_CMYK_8; - break; - - case icSigRgbData: - out->Type = IM_TYPE_RGB; - out->BandFmt = IM_BANDFMT_UCHAR; - out->Bands = 3; - out_icc_format = TYPE_RGB_8; - break; - - default: - im_error( "im_icc_transform", - _( "unimplemented output color space 0x%x" ), - cmsGetColorSpace( icc->out_profile ) ); - return( -1 ); - } - - switch( in->BandFmt ) { - case IM_BANDFMT_UCHAR: - in_icc_format |= BYTES_SH( 1 ); - break; - - case IM_BANDFMT_USHORT: - in_icc_format |= BYTES_SH( 2 ); - break; - - default: - im_error( "im_icc_transform", - "%s", _( "uchar or ushort input only" ) ); - return( -1 ); - } - - if( !(icc->trans = cmsCreateTransform( icc->in_profile, in_icc_format, - icc->out_profile, out_icc_format, intent, 0 )) ) - return( -1 ); - - if( attach_profile( out, output_profile_filename ) ) - return( -1 ); - - /* Process! - */ - if( im_wrapone( in, out, - (im_wrapone_fn) transform_buf, icc, NULL ) ) - return( -1 ); - - return( 0 ); -} - -static void -import_buf( VipsPel *in, float *out, int n, Icc *icc ) -{ - /* Buffer of encoded 16-bit pixels we write to. - */ - WORD encoded[3 * PIXEL_BUFFER_SIZE]; - - while( n > 0 ) { - const int chunk = IM_MIN( n, PIXEL_BUFFER_SIZE ); - - g_mutex_lock( icc->lock ); - cmsDoTransform( icc->trans, in, encoded, chunk ); - g_mutex_unlock( icc->lock ); - decode_lab( encoded, out, chunk ); - - in += chunk * IM_IMAGE_SIZEOF_PEL( icc->in ); - out += chunk * 3; - n -= chunk; - } -} - -/* Save a bit of typing. - */ -#define UC IM_BANDFMT_UCHAR -#define US IM_BANDFMT_USHORT - -/* Type mapping: go to uchar or ushort. - */ -static int bandfmt_icc_import[10] = { -/* UC C US S UI I F X D DX */ - UC, UC, US, US, US, US, US, US, US, US -}; - -static int -icc_import( IMAGE *in, IMAGE *out, Icc *icc ) -{ - IMAGE *t; - DWORD icc_format; - - if( im_check_uncoded( "im_icc_import", in ) ) - return( -1 ); - - /* Cast in to u8/u16. - */ - if( !(t = im_open_local( out, "im_maplut", "p" )) || - im_clip2fmt( in, t, bandfmt_icc_import[in->BandFmt] ) ) - return( -1 ); - in = t; - - if( !cmsIsIntentSupported( icc->in_profile, - icc->intent, LCMS_USED_AS_INPUT ) ) - im_warn( "im_icc_import", - _( "intent %d (%s) not supported by " - "profile; falling back to default intent " - "(usually PERCEPTUAL)" ), - icc->intent, decode_intent( icc->intent ) ); - - /* Prepare the output image. - */ - if( im_cp_desc( out, in ) ) - return( -1 ); - out->Type = IM_TYPE_LAB; - out->BandFmt = IM_BANDFMT_FLOAT; - out->Bands = 3; - - switch( cmsGetColorSpace( icc->in_profile ) ) { - case icSigCmykData: - if( in->Bands != 4 ) { - im_error( "im_icc_import", - "%s", _( "CMYK profile needs a " - "4 band input image" ) ); - return( -1 ); - } - icc_format = COLORSPACE_SH( PT_CMYK ) | CHANNELS_SH( 4 ); - break; - - case icSigRgbData: - if( in->Bands != 3 ) { - im_error( "im_icc_import", - "%s", _( "RGB profile needs a " - "3 band input image" ) ); - return( -1 ); - } - icc_format = COLORSPACE_SH( PT_RGB ) | CHANNELS_SH( 3 ); - break; - - default: - im_error( "im_icc_import", _( "unimplemented input color " - "space 0x%x" ), cmsGetColorSpace( icc->in_profile ) ); - return( -1 ); - } - - switch( in->BandFmt ) { - case IM_BANDFMT_UCHAR: - icc_format |= BYTES_SH( 1 ); - break; - - case IM_BANDFMT_USHORT: - icc_format |= BYTES_SH( 2 ); - break; - - default: - im_error( "im_icc_transform", - "%s", _( "uchar or ushort input only" ) ); - return( -1 ); - } - - if( !(icc->trans = cmsCreateTransform( icc->in_profile, icc_format, - icc->out_profile, TYPE_Lab_16, icc->intent, 0 )) ) - return( -1 ); - - /* Process! - */ - if( im_wrapone( in, out, (im_wrapone_fn) import_buf, icc, NULL ) ) - return( -1 ); - - return( 0 ); -} - -/** - * im_icc_import: - * @in: input image - * @out: output image - * @input_profile_filename: get the input profile from here - * @intent: transform with this intent - * - * Import an image with the ICC library. The input image in device space - * is moved to D65 LAB with the input profile. - * - * See also: im_icc_transform(), im_icc_import_embedded(). - * - * Returns: 0 on success, -1 on error. - */ -int -im_icc_import( IMAGE *in, IMAGE *out, - const char *input_profile_filename, VipsIntent intent ) -{ - Icc *icc; - - if( !(icc = icc_new_file( in, out, - input_profile_filename, NULL, intent )) || - icc_import( in, out, icc ) ) - return( -1 ); - - return( 0 ); -} - -/** - * im_icc_import_embedded: - * @in: input image - * @out: output image - * @intent: transform with this intent - * - * Import an image with the ICC library. The input image in device space - * is moved to D65 LAB with the input profile attached to the image under the - * name #IM_META_ICC_NAME. - * - * See also: im_icc_transform(), im_icc_import(). - * - * Returns: 0 on success, -1 on error. - */ -int -im_icc_import_embedded( IMAGE *in, IMAGE *out, VipsIntent intent ) -{ - Icc *icc; - void *data; - size_t data_length; - - if( im_header_get_typeof( in, IM_META_ICC_NAME ) == 0 ) { - im_error( "im_icc_import_embedded", - "%s", _( "no embedded profile" ) ); - return( -1 ); - } - - if( im_meta_get_blob( in, IM_META_ICC_NAME, &data, &data_length ) || - !(icc = icc_new_mem( in, out, data, data_length, intent )) || - icc_import( in, out, icc ) ) - return( -1 ); - - return( 0 ); -} - -static void -export_buf( float *in, VipsPel *out, int n, Icc *icc ) -{ - /* Buffer of encoded 16-bit pixels we transform. - */ - WORD encoded[3 * PIXEL_BUFFER_SIZE]; - - while( n > 0 ) { - const int chunk = IM_MIN( n, PIXEL_BUFFER_SIZE ); - - encode_lab( in, encoded, chunk ); - g_mutex_lock( icc->lock ); - cmsDoTransform( icc->trans, encoded, out, chunk ); - g_mutex_unlock( icc->lock ); - - in += chunk * 3; - out += chunk * IM_IMAGE_SIZEOF_PEL( icc->out ); - n -= chunk; - } -} - -/** - * im_icc_export_depth: - * @in: input image - * @out: output image - * @depth: depth to export at - * @output_profile_filename: use this profile - * @intent: transform with this intent - * - * Export an image with the ICC library. The input image in - * D65 LAB is transformed to device space using the supplied profile. - * @depth can be 8 or 16, for 8 or 16-bit image export. - * - * See also: im_icc_transform(), im_icc_import(). - * - * Returns: 0 on success, -1 on error. - */ -int -im_icc_export_depth( IMAGE *in, IMAGE *out, int depth, - const char *output_profile_filename, VipsIntent intent ) -{ - IMAGE *t; - Icc *icc; - DWORD icc_format; - - /* Do IM_CODING_LABQ too. - */ - if( in->Coding == IM_CODING_LABQ ) { - IMAGE *t1 = im_open_local( out, "im_icc_export:1", "p" ); - - if( !t1 || im_LabQ2Lab( in, t1 ) ) - return( -1 ); - - in = t1; - } - - /* Do IM_CODING_RAD. - */ - if( in->Coding == IM_CODING_RAD ) { - IMAGE *t1 = im_open_local( out, "im_icc_export:1", "p" ); - - if( !t1 || im_rad2float( in, t1 ) ) - return( -1 ); - - in = t1; - } - - /* Force to float anyway. - */ - if( !(t = im_open_local( out, "im_icc_export", "p" )) || - im_clip2fmt( in, t, IM_BANDFMT_FLOAT ) ) - return( -1 ); - - /* Check input image. - */ - if( im_check_uncoded( "im_icc_export", in ) || - im_check_bands( "im_icc_export", in, 3 ) ) - return( -1 ); - - if( depth != 8 && depth != 16 ) { - im_error( "im_icc_export", "%s", _( "unsupported bit depth" ) ); - return( -1 ); - } - - if( !(icc = icc_new_file( in, out, - NULL, output_profile_filename, intent )) ) - return( -1 ); - - if( !cmsIsIntentSupported( icc->out_profile, - intent, LCMS_USED_AS_OUTPUT ) ) - im_warn( "im_icc_export", - _( "intent %d (%s) not supported by " - "profile \"%s\"; falling back to default intent " - "(usually PERCEPTUAL)" ), - intent, decode_intent( intent ), - output_profile_filename ); - - /* Prepare the output image. - */ - if( im_cp_desc( out, in ) ) - return( -1 ); - - switch( cmsGetColorSpace( icc->out_profile ) ) { - case icSigCmykData: - out->Type = IM_TYPE_CMYK; - out->BandFmt = depth == 8 ? - IM_BANDFMT_UCHAR : IM_BANDFMT_USHORT; - out->Bands = 4; - icc_format = depth == 8 ? TYPE_CMYK_8 : TYPE_CMYK_16; - break; - - case icSigRgbData: - out->Type = depth == 8 ? - IM_TYPE_RGB : IM_TYPE_RGB16; - out->BandFmt = depth == 8 ? - IM_BANDFMT_UCHAR : IM_BANDFMT_USHORT; - out->Bands = 3; - icc_format = depth == 8 ? TYPE_RGB_8 : TYPE_RGB_16; - break; - - default: - im_error( "im_icc_export", _( "unimplemented output color " - "space 0x%x" ), cmsGetColorSpace( icc->out_profile ) ); - return( -1 ); - } - - if( !(icc->trans = cmsCreateTransform( icc->in_profile, TYPE_Lab_16, - icc->out_profile, icc_format, intent, 0 )) ) - return( -1 ); - - if( attach_profile( out, output_profile_filename ) ) - return( -1 ); - - /* Process! - */ - if( im_wrapone( in, out, (im_wrapone_fn) export_buf, icc, NULL ) ) - return( -1 ); - - return( 0 ); -} - -/** - * im_icc_ac2rc: - * @in: input image - * @out: output image - * @profile_filename: use this profile - * - * Transform an image from absolute to relative colorimetry using the - * MediaWhitePoint stored in the ICC profile. - * - * See also: im_icc_transform(), im_icc_import(). - * - * Returns: 0 on success, -1 on error. - */ -int -im_icc_ac2rc( IMAGE *in, IMAGE *out, const char *profile_filename ) -{ - cmsHPROFILE profile; - cmsCIEXYZ media; - - double add[3]; - double mul[3]; - - IMAGE *t[2]; - - if( !(profile = cmsOpenProfileFromFile( profile_filename, "r" )) ) - return( -1 ); - - if( !cmsTakeMediaWhitePoint( &media, profile ) ) { - im_error( "im_icc_ac2rc", "%s", _( "unable to get media " - "white point" ) ); - return( -1 ); - } - - cmsCloseProfile( profile ); - - add[0] = 0.0; - add[1] = 0.0; - add[2] = 0.0; - - mul[0] = IM_D50_X0 / (media.X * 100.0); - mul[1] = IM_D50_Y0 / (media.Y * 100.0); - mul[2] = IM_D50_Z0 / (media.Z * 100.0); - - /* Do IM_CODING_LABQ too. - */ - if( in->Coding == IM_CODING_LABQ ) { - IMAGE *t1 = im_open_local( out, "im_icc_ac2rc-1", "p" ); - - if( !t1 || im_LabQ2Lab( in, t1 ) ) - return( -1 ); - - in = t1; - } - - /* Do IM_CODING_RAD. - */ - if( in->Coding == IM_CODING_RAD ) { - IMAGE *t1 = im_open_local( out, "im_icc_export:1", "p" ); - - if( !t1 || im_rad2float( in, t1 ) ) - return( -1 ); - - in = t1; - } - - if( im_open_local_array( out, t, 2, "im_icc_ac2rc-2", "p" ) || - im_Lab2XYZ_temp( in, t[0], IM_D50_X0, IM_D50_Y0, IM_D50_Z0 ) || - im_lintra_vec( 3, mul, t[0], add, t[1] ) || - im_XYZ2Lab_temp( t[1], out, IM_D50_X0, IM_D50_Y0, IM_D50_Z0 ) ) - return( -1 ); - - return( 0 ); -} - -#elif defined( HAVE_LCMS2 ) - -#include -#include -#include - -#include - -#include -#include -#include -#include - -/** - * VipsIntent: - * @IM_INTENT_PERCEPTUAL: perceptual rendering intent - * @IM_INTENT_RELATIVE_COLORIMETRIC: relative colorimetric rendering intent - * @IM_INTENT_SATURATION: saturation rendering intent - * @IM_INTENT_ABSOLUTE_COLORIMETRIC: absolute colorimetric rendering intent - * - * The rendering intent. #IM_INTENT_ABSOLUTE_COLORIMETRIC is best for - * scientific work, #IM_INTENT_RELATIVE_COLORIMETRIC is usually best for - * accurate communication with other imaging libraries. - */ - -/* Call lcms with up to this many pixels at once. - */ -#define PIXEL_BUFFER_SIZE (10000) - -static const char * -decode_intent( VipsIntent intent ) -{ - switch( intent ) { - case IM_INTENT_PERCEPTUAL: return( "PERCEPTUAL" ); - case IM_INTENT_RELATIVE_COLORIMETRIC: return( "RELATIVE" ); - case IM_INTENT_SATURATION: return( "SATURATION" ); - case IM_INTENT_ABSOLUTE_COLORIMETRIC: return( "ABSOLUTE" ); - default: return( "" ); - } -} - -/** - * im_icc_present: - * - * VIPS can optionally be built without the ICC library. Use this function to - * test for its availability. - * - * Returns: non-zero if the ICC library is present. - */ -int -im_icc_present( void ) -{ - return( 1 ); -} - -/* Global state for a transform. - */ -typedef struct { - IMAGE *in; - IMAGE *out; - const char *input_profile_filename; - const char *output_profile_filename; - VipsIntent intent; - - cmsHPROFILE in_profile; - cmsHPROFILE out_profile; - cmsHTRANSFORM trans; - - /* We need to single-thread calls to LCMS. - */ - GMutex *lock; -} Icc; - -/* Error from lcms. - */ -static void -icc_error( cmsContext context, cmsUInt32Number code, const char *text ) -{ - im_error( "im_icc", "%s", text ); -} - -static int -icc_destroy( Icc *icc ) -{ - IM_FREEF( cmsDeleteTransform, icc->trans ); - IM_FREEF( cmsCloseProfile, icc->in_profile ); - IM_FREEF( cmsCloseProfile, icc->out_profile ); - IM_FREEF( g_mutex_free, icc->lock ); - - return( 0 ); -} - -static Icc * -icc_new( IMAGE *in, IMAGE *out, VipsIntent intent ) -{ - Icc *icc; - - /* Ask lcms not to abort on error. - */ - cmsSetLogErrorHandler( icc_error ); - - if( !(icc = IM_NEW( out, Icc )) ) - return( NULL ); - - icc->in = in; - icc->out = out; - icc->input_profile_filename = NULL; - icc->output_profile_filename = NULL; - icc->intent = intent; - icc->in_profile = 0; - icc->out_profile = 0; - icc->trans = 0; - icc->lock = g_mutex_new(); - - if( im_add_close_callback( out, - (im_callback_fn) icc_destroy, icc, NULL ) ) - return( NULL ); - - return( icc ); -} - -static Icc * -icc_new_file( IMAGE *in, IMAGE *out, - const char *input_profile_filename, - const char *output_profile_filename, - VipsIntent intent ) -{ - Icc *icc; - cmsCIExyY white; - - if( !(icc = icc_new( in, out, intent )) ) - return( NULL ); - - if( input_profile_filename ) { - icc->input_profile_filename = - im_strdup( out, input_profile_filename ); - if( !(icc->in_profile = cmsOpenProfileFromFile( - input_profile_filename, "r" )) ) - im_error( "im_icc_transform", - _( "unable to open profile \"%s\"" ), - input_profile_filename ); - } - - if( output_profile_filename ) { - icc->output_profile_filename = - im_strdup( out, output_profile_filename ); - if( !(icc->out_profile = cmsOpenProfileFromFile( - output_profile_filename, "r" )) ) - im_error( "im_icc_transform", - _( "unable to open profile \"%s\"" ), - output_profile_filename ); - } - - cmsWhitePointFromTemp( &white, 6500 ); - if( !output_profile_filename ) - icc->out_profile = cmsCreateLab4Profile( &white ); - if( !input_profile_filename ) - icc->in_profile = cmsCreateLab4Profile( &white ); - - if( !icc->in_profile || !icc->out_profile ) { - im_error( "im_icc_transform", - "%s", _( "unable to create profiles" ) ); - return( NULL ); - } - - return( icc ); -} - -static Icc * -icc_new_mem( IMAGE *in, IMAGE *out, - void *data, int data_length, - VipsIntent intent ) -{ - Icc *icc; - cmsCIExyY white; - - if( !(icc = icc_new( in, out, intent )) ) - return( NULL ); - - if( !(icc->in_profile = cmsOpenProfileFromMem( data, data_length )) ) { - im_error( "im_icc_transform", - "%s", _( "unable to read profile" ) ); - return( NULL ); - } - - /* Use D65. - */ - cmsWhitePointFromTemp( &white, 6500 ); - icc->out_profile = cmsCreateLab4Profile( &white ); - - return( icc ); -} - -/* Pack a buffer of floats into lcms's fixed-point formats. Cut from - * lcms-1.0.8. - */ -static void -encode_lab( float *lab, guint16 *fixed, int n ) -{ - int i; - - for( i = 0; i < n; i++ ) { - float L = lab[0]; - float a = lab[1]; - float b = lab[2]; - - if( L < 0 ) - L = 0; - if( L > 100. ) - L = 100.; - - if( a < -128. ) - a = -128; - if( a > 127.9961 ) - a = 127.9961; - if( b < -128. ) - b = -128; - if( b > 127.9961 ) - b = 127.9961; - - fixed[0] = L * 652.800 + 0.5; - fixed[1] = (a + 128.0) * 256.0 + 0.5; - fixed[2] = (b + 128.0) * 256.0 + 0.5; - - lab += 3; - fixed += 3; - } -} - -static void -decode_lab( guint16 *fixed, float *lab, int n ) -{ - int i; - - for( i = 0; i < n; i++ ) { - lab[0] = (double) fixed[0] / 652.800; - lab[1] = ((double) fixed[1] / 256.0) - 128.0; - lab[2] = ((double) fixed[2] / 256.0) - 128.0; - - lab += 3; - fixed += 3; - } -} - -static void -transform_buf( VipsPel *in, VipsPel *out, int n, Icc *icc ) -{ - g_mutex_lock( icc->lock ); - cmsDoTransform( icc->trans, in, out, n ); - g_mutex_unlock( icc->lock ); -} - -static int -attach_profile( IMAGE *im, const char *filename ) -{ - char *data; - unsigned int data_length; - - if( !(data = im__file_read_name( filename, VIPS_ICC_DIR, - &data_length )) ) - return( -1 ); - if( im_meta_set_blob( im, IM_META_ICC_NAME, - (im_callback_fn) im_free, data, data_length ) ) { - im_free( data ); - return( -1 ); - } - - return( 0 ); -} - -/** - * im_icc_transform: - * @in: input image - * @out: output image - * @input_profile_filename: get the input profile from here - * @output_profile_filename: get the output profile from here - * @intent: transform with this intent - * - * Transform an image with the ICC library. The input image is moved to - * profile-connection space with the input profile and then to the output - * space with the output profile. - * - * Use im_icc_import() and im_icc_export_depth() to do either the first or - * second half of this operation in isolation. - * - * See also: im_icc_import(), im_icc_export_depth(). - * - * Returns: 0 on success, -1 on error. - */ -int -im_icc_transform( IMAGE *in, IMAGE *out, - const char *input_profile_filename, - const char *output_profile_filename, - VipsIntent intent ) -{ - Icc *icc; - cmsUInt32Number in_icc_format; - cmsUInt32Number out_icc_format; - - if( im_check_uncoded( "im_icc_transform", in ) ) - return( -1 ); - - if( !(icc = icc_new_file( in, out, - input_profile_filename, output_profile_filename, intent )) ) - return( -1 ); - - if( !cmsIsIntentSupported( icc->in_profile, - intent, LCMS_USED_AS_INPUT ) ) - im_warn( "im_icc_transform", - _( "intent %d (%s) not supported by " - "profile \"%s\"; falling back to default intent " - "(usually PERCEPTUAL)" ), - intent, decode_intent( intent ), - input_profile_filename ); - - if( !cmsIsIntentSupported( icc->out_profile, - intent, LCMS_USED_AS_OUTPUT ) ) - im_warn( "im_icc_transform", - _( "intent %d (%s) not supported by " - "profile \"%s\"; falling back to default intent " - "(usually PERCEPTUAL)" ), - intent, decode_intent( intent ), - output_profile_filename ); - - switch( cmsGetColorSpace( icc->in_profile ) ) { - case cmsSigCmykData: - if( in->Bands != 4 ) { - im_error( "im_icc_transform", - "%s", _( "CMYK input profile " - "needs a 4 band input image" ) ); - return( -1 ); - } - in_icc_format = COLORSPACE_SH( PT_CMYK ) | CHANNELS_SH( 4 ); - break; - - case cmsSigRgbData: - if( in->Bands != 3 ) { - im_error( "im_icc_transform", - "%s", _( "RGB input profile " - "needs a 3 band input image" ) ); - return( -1 ); - } - in_icc_format = COLORSPACE_SH( PT_RGB ) | CHANNELS_SH( 3 ); - break; - - default: - im_error( "im_icc_transform", - _( "unimplemented input color space 0x%x" ), - cmsGetColorSpace( icc->in_profile ) ); - return( -1 ); - } - - /* Prepare the output image. - */ - if( im_cp_desc( out, in ) ) - return( -1 ); - - switch( cmsGetColorSpace( icc->out_profile ) ) { - case cmsSigCmykData: - out->Type = IM_TYPE_CMYK; - out->BandFmt = IM_BANDFMT_UCHAR; - out->Bands = 4; - out_icc_format = TYPE_CMYK_8; - break; - - case cmsSigRgbData: - out->Type = IM_TYPE_RGB; - out->BandFmt = IM_BANDFMT_UCHAR; - out->Bands = 3; - out_icc_format = TYPE_RGB_8; - break; - - default: - im_error( "im_icc_transform", - _( "unimplemented output color space 0x%x" ), - cmsGetColorSpace( icc->out_profile ) ); - return( -1 ); - } - - switch( in->BandFmt ) { - case IM_BANDFMT_UCHAR: - in_icc_format |= BYTES_SH( 1 ); - break; - - case IM_BANDFMT_USHORT: - in_icc_format |= BYTES_SH( 2 ); - break; - - default: - im_error( "im_icc_transform", - "%s", _( "uchar or ushort input only" ) ); - return( -1 ); - } - - /* Use cmsFLAGS_NOCACHE to disable the 1-pixel cache and make - * calling cmsDoTrsnaform() from multiple threads safe. - */ - if( !(icc->trans = cmsCreateTransform( icc->in_profile, in_icc_format, - icc->out_profile, out_icc_format, intent, - cmsFLAGS_NOCACHE )) ) - return( -1 ); - - if( attach_profile( out, output_profile_filename ) ) - return( -1 ); - - /* Process! - */ - if( im_wrapone( in, out, - (im_wrapone_fn) transform_buf, icc, NULL ) ) - return( -1 ); - - return( 0 ); -} - -static void -import_buf( VipsPel *in, float *out, int n, Icc *icc ) -{ - /* Buffer of encoded 16-bit pixels we write to. - */ - guint16 encoded[3 * PIXEL_BUFFER_SIZE]; - - while( n > 0 ) { - const int chunk = IM_MIN( n, PIXEL_BUFFER_SIZE ); - - cmsDoTransform( icc->trans, in, encoded, chunk ); - decode_lab( encoded, out, chunk ); - - in += chunk * IM_IMAGE_SIZEOF_PEL( icc->in ); - out += chunk * 3; - n -= chunk; - } -} - -static int -icc_import( IMAGE *in, IMAGE *out, Icc *icc ) -{ - cmsUInt32Number icc_format; - - if( im_check_uncoded( "im_icc_import", in ) ) - return( -1 ); - - if( !cmsIsIntentSupported( icc->in_profile, - icc->intent, LCMS_USED_AS_INPUT ) ) - im_warn( "im_icc_import", - _( "intent %d (%s) not supported by " - "profile; falling back to default intent " - "(usually PERCEPTUAL)" ), - icc->intent, decode_intent( icc->intent ) ); - - /* Prepare the output image. - */ - if( im_cp_desc( out, in ) ) - return( -1 ); - out->Type = IM_TYPE_LAB; - out->BandFmt = IM_BANDFMT_FLOAT; - out->Bands = 3; - - switch( cmsGetColorSpace( icc->in_profile ) ) { - case cmsSigCmykData: - if( in->Bands != 4 ) { - im_error( "im_icc_import", - "%s", _( "CMYK profile needs a " - "4 band input image" ) ); - return( -1 ); - } - icc_format = COLORSPACE_SH( PT_CMYK ) | CHANNELS_SH( 4 ); - break; - - case cmsSigRgbData: - if( in->Bands != 3 ) { - im_error( "im_icc_import", - "%s", _( "RGB profile needs a " - "3 band input image" ) ); - return( -1 ); - } - icc_format = COLORSPACE_SH( PT_RGB ) | CHANNELS_SH( 3 ); - break; - - default: - im_error( "im_icc_import", _( "unimplemented input color " - "space 0x%x" ), cmsGetColorSpace( icc->in_profile ) ); - return( -1 ); - } - - switch( in->BandFmt ) { - case IM_BANDFMT_UCHAR: - icc_format |= BYTES_SH( 1 ); - break; - - case IM_BANDFMT_USHORT: - icc_format |= BYTES_SH( 2 ); - break; - - default: - im_error( "im_icc_transform", - "%s", _( "uchar or ushort input only" ) ); - return( -1 ); - } - - /* Use cmsFLAGS_NOCACHE to disable the 1-pixel cache and make - * calling cmsDoTrsnaform() from multiple threads safe. - */ - if( !(icc->trans = cmsCreateTransform( icc->in_profile, icc_format, - icc->out_profile, TYPE_Lab_16, icc->intent, - cmsFLAGS_NOCACHE )) ) - return( -1 ); - - /* Process! - */ - if( im_wrapone( in, out, (im_wrapone_fn) import_buf, icc, NULL ) ) - return( -1 ); - - return( 0 ); -} - -/** - * im_icc_import: - * @in: input image - * @out: output image - * @input_profile_filename: get the input profile from here - * @intent: transform with this intent - * - * Import an image with the ICC library. The input image in device space - * is moved to D65 LAB with the input profile. - * - * See also: im_icc_transform(), im_icc_import_embedded(). - * - * Returns: 0 on success, -1 on error. - */ -int -im_icc_import( IMAGE *in, IMAGE *out, - const char *input_profile_filename, VipsIntent intent ) -{ - Icc *icc; - - if( !(icc = icc_new_file( in, out, - input_profile_filename, NULL, intent )) || - icc_import( in, out, icc ) ) - return( -1 ); - - return( 0 ); -} - -/** - * im_icc_import_embedded: - * @in: input image - * @out: output image - * @intent: transform with this intent - * - * Import an image with the ICC library. The input image in device space - * is moved to D65 LAB with the input profile attached to the image under the - * name #IM_META_ICC_NAME. - * - * See also: im_icc_transform(), im_icc_import(). - * - * Returns: 0 on success, -1 on error. - */ -int -im_icc_import_embedded( IMAGE *in, IMAGE *out, VipsIntent intent ) -{ - Icc *icc; - void *data; - size_t data_length; - - if( im_header_get_typeof( in, IM_META_ICC_NAME ) == 0 ) { - im_error( "im_icc_import_embedded", - "%s", _( "no embedded profile" ) ); - return( -1 ); - } - - if( im_meta_get_blob( in, IM_META_ICC_NAME, &data, &data_length ) || - !(icc = icc_new_mem( in, out, data, data_length, intent )) || - icc_import( in, out, icc ) ) - return( -1 ); - - return( 0 ); -} - -static void -export_buf( float *in, VipsPel *out, int n, Icc *icc ) -{ - /* Buffer of encoded 16-bit pixels we transform. - */ - guint16 encoded[3 * PIXEL_BUFFER_SIZE]; - - while( n > 0 ) { - const int chunk = IM_MIN( n, PIXEL_BUFFER_SIZE ); - - encode_lab( in, encoded, chunk ); - cmsDoTransform( icc->trans, encoded, out, chunk ); - - in += chunk * 3; - out += chunk * IM_IMAGE_SIZEOF_PEL( icc->out ); - n -= chunk; - } -} - -/** - * im_icc_export_depth: - * @in: input image - * @out: output image - * @depth: depth to export at - * @output_profile_filename: use this profile - * @intent: transform with this intent - * - * Export an image with the ICC library. The input image in - * D65 LAB is transformed to device space using the supplied profile. - * @depth can be 8 or 16, for 8 or 16-bit image export. - * - * See also: im_icc_transform(), im_icc_import(). - * - * Returns: 0 on success, -1 on error. - */ -int -im_icc_export_depth( IMAGE *in, IMAGE *out, int depth, - const char *output_profile_filename, VipsIntent intent ) -{ - Icc *icc; - cmsUInt32Number icc_format; - - /* Do IM_CODING_LABQ too. - */ - if( in->Coding == IM_CODING_LABQ ) { - IMAGE *t1 = im_open_local( out, "im_icc_export:1", "p" ); - - if( !t1 || im_LabQ2Lab( in, t1 ) ) - return( -1 ); - - in = t1; - } - - /* Do IM_CODING_RAD. - */ - if( in->Coding == IM_CODING_RAD ) { - IMAGE *t1 = im_open_local( out, "im_icc_export:1", "p" ); - - if( !t1 || im_rad2float( in, t1 ) ) - return( -1 ); - - in = t1; - } - - /* Check input image. - */ - if( im_check_uncoded( "im_icc_export", in ) || - im_check_bands( "im_icc_export", in, 3 ) || - im_check_format( "im_icc_export", in, IM_BANDFMT_FLOAT ) ) - return( -1 ); - - if( depth != 8 && depth != 16 ) { - im_error( "im_icc_export", "%s", _( "unsupported bit depth" ) ); - return( -1 ); - } - - if( !(icc = icc_new_file( in, out, - NULL, output_profile_filename, intent )) ) - return( -1 ); - - if( !cmsIsIntentSupported( icc->out_profile, - intent, LCMS_USED_AS_OUTPUT ) ) - im_warn( "im_icc_export", - _( "intent %d (%s) not supported by " - "profile \"%s\"; falling back to default intent " - "(usually PERCEPTUAL)" ), - intent, decode_intent( intent ), - output_profile_filename ); - - /* Prepare the output image. - */ - if( im_cp_desc( out, in ) ) - return( -1 ); - - switch( cmsGetColorSpace( icc->out_profile ) ) { - case cmsSigCmykData: - out->Type = IM_TYPE_CMYK; - out->BandFmt = depth == 8 ? - IM_BANDFMT_UCHAR : IM_BANDFMT_USHORT; - out->Bands = 4; - icc_format = depth == 8 ? TYPE_CMYK_8 : TYPE_CMYK_16; - break; - - case cmsSigRgbData: - out->Type = depth == 8 ? - IM_TYPE_RGB : IM_TYPE_RGB16; - out->BandFmt = depth == 8 ? - IM_BANDFMT_UCHAR : IM_BANDFMT_USHORT; - out->Bands = 3; - icc_format = depth == 8 ? TYPE_RGB_8 : TYPE_RGB_16; - break; - - default: - im_error( "im_icc_export", _( "unimplemented output color " - "space 0x%x" ), cmsGetColorSpace( icc->out_profile ) ); - return( -1 ); - } - - /* Use cmsFLAGS_NOCACHE to disable the 1-pixel cache and make - * calling cmsDoTrsnaform() from multiple threads safe. - */ - if( !(icc->trans = cmsCreateTransform( icc->in_profile, TYPE_Lab_16, - icc->out_profile, icc_format, intent, - cmsFLAGS_NOCACHE )) ) - return( -1 ); - - if( attach_profile( out, output_profile_filename ) ) - return( -1 ); - - /* Process! - */ - if( im_wrapone( in, out, (im_wrapone_fn) export_buf, icc, NULL ) ) - return( -1 ); - - return( 0 ); -} - -/** - * im_icc_ac2rc: - * @in: input image - * @out: output image - * @profile_filename: use this profile - * - * Transform an image from absolute to relative colorimetry using the - * MediaWhitePoint stored in the ICC profile. - * - * See also: im_icc_transform(), im_icc_import(). - * - * Returns: 0 on success, -1 on error. - */ -int -im_icc_ac2rc( IMAGE *in, IMAGE *out, const char *profile_filename ) -{ - cmsHPROFILE profile; - cmsCIEXYZ *media; - double X, Y, Z; - - double add[3]; - double mul[3]; - - IMAGE *t[2]; - - if( !(profile = cmsOpenProfileFromFile( profile_filename, "r" )) ) - return( -1 ); - - if( !(media = cmsReadTag( profile, cmsSigMediaWhitePointTag )) ) { - im_error( "im_icc_ac2rc", "%s", _( "unable to get media " - "white point" ) ); - return( -1 ); - } - - X = media->X; - Y = media->Y; - Z = media->Z; - - cmsCloseProfile( profile ); - - add[0] = 0.0; - add[1] = 0.0; - add[2] = 0.0; - - mul[0] = IM_D50_X0 / (X * 100.0); - mul[1] = IM_D50_Y0 / (Y * 100.0); - mul[2] = IM_D50_Z0 / (Z * 100.0); - - /* Do IM_CODING_LABQ too. - */ - if( in->Coding == IM_CODING_LABQ ) { - IMAGE *t1 = im_open_local( out, "im_icc_ac2rc-1", "p" ); - - if( !t1 || im_LabQ2Lab( in, t1 ) ) - return( -1 ); - - in = t1; - } - - /* Do IM_CODING_RAD. - */ - if( in->Coding == IM_CODING_RAD ) { - IMAGE *t1 = im_open_local( out, "im_icc_export:1", "p" ); - - if( !t1 || im_rad2float( in, t1 ) ) - return( -1 ); - - in = t1; - } - - if( im_open_local_array( out, t, 2, "im_icc_ac2rc-2", "p" ) || - im_Lab2XYZ_temp( in, t[0], IM_D50_X0, IM_D50_Y0, IM_D50_Z0 ) || - im_lintra_vec( 3, mul, t[0], add, t[1] ) || - im_XYZ2Lab_temp( t[1], out, IM_D50_X0, IM_D50_Y0, IM_D50_Z0 ) ) - return( -1 ); - - return( 0 ); -} - -#else /*no lcms*/ - -#include - -int -im_icc_present( void ) -{ - return( 0 ); -} - -int -im_icc_transform( IMAGE *in, IMAGE *out, - const char *input_profile_filename, - const char *output_profile_filename, - VipsIntent intent ) -{ - im_error( "im_icc_transform", "%s", - _( "lcms library not linked to this VIPS" ) ); - - return( -1 ); -} - -int -im_icc_import( IMAGE *in, IMAGE *out, - const char *input_profile_filename, VipsIntent intent ) -{ - im_error( "im_icc_import", "%s", - _( "lmcs library not linked to this VIPS" ) ); - - return( -1 ); -} - -int -im_icc_import_embedded( IMAGE *in, IMAGE *out, VipsIntent intent ) -{ - im_error( "im_icc_import", "%s", - _( "lmcs library not linked to this VIPS" ) ); - - return( -1 ); -} - -int -im_icc_export_depth( IMAGE *in, IMAGE *out, int depth, - const char *output_profile_filename, VipsIntent intent ) -{ - im_error( "im_icc_export_depth", "%s", - _( "lmcs library not linked to this VIPS" ) ); - - return( -1 ); -} - -int -im_icc_ac2rc( IMAGE *in, IMAGE *out, const char *profile_filename ) -{ - im_error( "im_icc_ac2rc", "%s", - _( "lmcs library not linked to this VIPS" ) ); - - return( -1 ); -} - -#endif /*HAVE_LCMS*/ diff --git a/libvips/deprecated/vips7compat.c b/libvips/deprecated/vips7compat.c index e2f4d8ff..1a9c64af 100644 --- a/libvips/deprecated/vips7compat.c +++ b/libvips/deprecated/vips7compat.c @@ -2556,3 +2556,84 @@ im_LabQ2sRGB( IMAGE *in, IMAGE *out ) return( 0 ); } + +int +im_icc_transform( VipsImage *in, VipsImage *out, + const char *input_profile_filename, + const char *output_profile_filename, + VipsIntent intent ) +{ + VipsImage *x; + + if( vips_icc_transform( in, &x, output_profile_filename, + "input_profile", input_profile_filename, + "intent", intent, + NULL ) ) + return( -1 ); + if( im_copy( x, out ) ) { + g_object_unref( x ); + return( -1 ); + } + g_object_unref( x ); + + return( 0 ); +} + +int +im_icc_import( VipsImage *in, VipsImage *out, + const char *input_profile_filename, VipsIntent intent ) +{ + VipsImage *x; + + if( vips_icc_import( in, &x, + "input_profile", input_profile_filename, + "intent", intent, + NULL ) ) + return( -1 ); + if( im_copy( x, out ) ) { + g_object_unref( x ); + return( -1 ); + } + g_object_unref( x ); + + return( 0 ); +} + +int +im_icc_import_embedded( VipsImage *in, VipsImage *out, VipsIntent intent ) +{ + VipsImage *x; + + if( vips_icc_import( in, &x, + "embedded", TRUE, + "intent", intent, + NULL ) ) + return( -1 ); + if( im_copy( x, out ) ) { + g_object_unref( x ); + return( -1 ); + } + g_object_unref( x ); + + return( 0 ); +} + +int +im_icc_export_depth( VipsImage *in, VipsImage *out, int depth, + const char *output_profile_filename, VipsIntent intent ) +{ + VipsImage *x; + + if( vips_icc_export( in, &x, output_profile_filename, + "depth", depth, + "intent", intent, + NULL ) ) + return( -1 ); + if( im_copy( x, out ) ) { + g_object_unref( x ); + return( -1 ); + } + g_object_unref( x ); + + return( 0 ); +} diff --git a/libvips/include/vips/Makefile.am b/libvips/include/vips/Makefile.am index 40658b28..8d318fee 100644 --- a/libvips/include/vips/Makefile.am +++ b/libvips/include/vips/Makefile.am @@ -62,6 +62,7 @@ vips_scan_headers = \ ${top_srcdir}/libvips/include/vips/conversion.h \ ${top_srcdir}/libvips/include/vips/util.h \ ${top_srcdir}/libvips/include/vips/image.h \ + ${top_srcdir}/libvips/include/vips/colour.h \ ${top_srcdir}/libvips/include/vips/operation.h \ ${top_srcdir}/libvips/include/vips/object.h diff --git a/libvips/include/vips/colour.h b/libvips/include/vips/colour.h index 84282c2c..440bab8d 100644 --- a/libvips/include/vips/colour.h +++ b/libvips/include/vips/colour.h @@ -92,6 +92,24 @@ extern "C" { #define VIPS_D3250_Y0 (100.0) #define VIPS_D3250_Z0 (45.8501) +/** + * VipsIntent: + * @VIPS_INTENT_PERCEPTUAL: perceptual rendering intent + * @VIPS_INTENT_RELATIVE: relative colorimetric rendering intent + * @VIPS_INTENT_SATURATION: saturation rendering intent + * @VIPS_INTENT_ABSOLUTE: absolute colorimetric rendering intent + * + * The rendering intent. #VIPS_INTENT_ABSOLUTE is best for + * scientific work, #VIPS_INTENT_RELATIVE is usually best for + * accurate communication with other imaging libraries. + */ +typedef enum { + VIPS_INTENT_PERCEPTUAL = 0, + VIPS_INTENT_RELATIVE, + VIPS_INTENT_SATURATION, + VIPS_INTENT_ABSOLUTE +} VipsIntent; + int vips_LabQ2sRGB( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); int vips_rad2float( VipsImage *in, VipsImage **out, ... ) @@ -135,6 +153,18 @@ int vips_LabS2Lab( VipsImage *in, VipsImage **out, ... ) int vips_Lab2LabS( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_icc_present( void ); +int vips_icc_transform( VipsImage *in, VipsImage **out, + const char *output_profile_filename, ... ) + __attribute__((sentinel)); +int vips_icc_import( VipsImage *in, VipsImage **out, ... ) + __attribute__((sentinel)); +int vips_icc_export( VipsImage *in, VipsImage **out, + const char *output_profile, ... ) + __attribute__((sentinel)); +int vips_icc_ac2rc( VipsImage *in, VipsImage *out, + const char *profile_filename ); + void vips_col_Lab2XYZ( float L, float a, float b, float *X, float *Y, float *Z ); void vips_col_XYZ2Lab( float X, float Y, float Z, @@ -187,27 +217,6 @@ int im_lab_morph( VipsImage *in, VipsImage *out, double L_offset, double L_scale, double a_scale, double b_scale ); -/* Render intents for icc wrappers. - */ -typedef enum { - IM_INTENT_PERCEPTUAL = 0, - IM_INTENT_RELATIVE_COLORIMETRIC, - IM_INTENT_SATURATION, - IM_INTENT_ABSOLUTE_COLORIMETRIC -} VipsIntent; - -int im_icc_present( void ); -int im_icc_transform( VipsImage *in, VipsImage *out, - const char *input_profile_filename, - const char *output_profile_filename, - VipsIntent intent ); -int im_icc_import( VipsImage *in, VipsImage *out, - const char *input_profile_filename, VipsIntent intent ); -int im_icc_import_embedded( VipsImage *in, VipsImage *out, VipsIntent intent ); -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 ); - #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/include/vips/enumtypes.h b/libvips/include/vips/enumtypes.h index 44bc558d..1d2f9160 100644 --- a/libvips/include/vips/enumtypes.h +++ b/libvips/include/vips/enumtypes.h @@ -57,6 +57,9 @@ GType vips_band_format_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_BAND_FORMAT (vips_band_format_get_type()) GType vips_coding_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_CODING (vips_coding_get_type()) +/* enumerations from "../../../libvips/include/vips/colour.h" */ +GType vips_intent_get_type (void) G_GNUC_CONST; +#define VIPS_TYPE_INTENT (vips_intent_get_type()) /* enumerations from "../../../libvips/include/vips/operation.h" */ GType vips_operation_flags_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_OPERATION_FLAGS (vips_operation_flags_get_type()) diff --git a/libvips/include/vips/vips7compat.h b/libvips/include/vips/vips7compat.h index 4e7c396d..04f5e4bf 100644 --- a/libvips/include/vips/vips7compat.h +++ b/libvips/include/vips/vips7compat.h @@ -141,6 +141,11 @@ extern "C" { #define IMAGE VipsImage #define REGION VipsRegion +#define IM_INTENT_PERCEPTUAL VIPS_INTENT_PERCEPTUAL +#define IM_INTENT_RELATIVE_COLORIMETRIC VIPS_INTENT_RELATIVE_COLORIMETRIC +#define IM_INTENT_SATURATION VIPS_INTENT_SATURATION +#define IM_INTENT_ABSOLUTE_COLORIMETRIC VIPS_INTENT_ABSOLUTE_COLORIMETRIC + /* Renamed macros. */ @@ -750,6 +755,20 @@ int im_dECMC_fromdisp( IMAGE *, IMAGE *, IMAGE *, struct im_col_display * ); #define im_XYZ2disp(A, B, C) (im_XYZ2sRGB(A, B)) #define im_LabQ2disp(A, B, C) (im_LabQ2sRGB(A, B)) +int im_icc_transform( VipsImage *in, VipsImage *out, + const char *input_profile_filename, + const char *output_profile_filename, + VipsIntent intent ); + +#define im_icc_present vips_icc_present + +int im_icc_import( VipsImage *in, VipsImage *out, + const char *input_profile_filename, VipsIntent intent ); +int im_icc_import_embedded( VipsImage *in, VipsImage *out, VipsIntent intent ); +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 ); + /* ruby-vips uses this */ #define vips_class_map_concrete_all vips_class_map_all diff --git a/libvips/iofuncs/Makefile.am b/libvips/iofuncs/Makefile.am index 3c9cca3e..10e80cfd 100644 --- a/libvips/iofuncs/Makefile.am +++ b/libvips/iofuncs/Makefile.am @@ -47,6 +47,7 @@ vips_scan_headers = \ ${top_srcdir}/libvips/include/vips/util.h \ ${top_srcdir}/libvips/include/vips/image.h \ ${top_srcdir}/libvips/include/vips/operation.h \ + ${top_srcdir}/libvips/include/vips/colour.h \ ${top_srcdir}/libvips/include/vips/object.h enumtypes.c: $(vips_scan_headers) Makefile diff --git a/libvips/iofuncs/enumtypes.c b/libvips/iofuncs/enumtypes.c index f4856ec4..c7a9bda2 100644 --- a/libvips/iofuncs/enumtypes.c +++ b/libvips/iofuncs/enumtypes.c @@ -511,6 +511,26 @@ vips_operation_flags_get_type( void ) return( etype ); } +/* enumerations from "../../libvips/include/vips/colour.h" */ +GType +vips_intent_get_type( void ) +{ + static GType etype = 0; + + if( etype == 0 ) { + static const GEnumValue values[] = { + {VIPS_INTENT_PERCEPTUAL, "VIPS_INTENT_PERCEPTUAL", "perceptual"}, + {VIPS_INTENT_RELATIVE, "VIPS_INTENT_RELATIVE", "relative"}, + {VIPS_INTENT_SATURATION, "VIPS_INTENT_SATURATION", "saturation"}, + {VIPS_INTENT_ABSOLUTE, "VIPS_INTENT_ABSOLUTE", "absolute"}, + {0, NULL, NULL} + }; + + etype = g_enum_register_static( "VipsIntent", values ); + } + + return( etype ); +} /* enumerations from "../../libvips/include/vips/object.h" */ GType vips_argument_flags_get_type( void )