From 0257dda2703e0fdab2e3aa084231ec57ec05cc84 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 12 Nov 2013 14:58:59 +0000 Subject: [PATCH] first hack at supporting XYZ PCS still need to fix new encode / decode --- libvips/colour/colour.c | 12 ++- libvips/colour/icc_transform.c | 161 +++++++++++++++++++++++++++---- libvips/colour/pcolour.h | 1 + libvips/include/vips/colour.h | 17 ++-- libvips/include/vips/enumtypes.h | 2 + libvips/iofuncs/enumtypes.c | 18 ++++ 6 files changed, 182 insertions(+), 29 deletions(-) diff --git a/libvips/colour/colour.c b/libvips/colour/colour.c index eb70d8e1..2d024425 100644 --- a/libvips/colour/colour.c +++ b/libvips/colour/colour.c @@ -486,7 +486,7 @@ vips_colour_code_build( VipsObject *object ) VipsImage *in; VipsImage *extra; - t = (VipsImage **) vips_object_local_array( object, 4 ); + t = (VipsImage **) vips_object_local_array( object, 5 ); in = code->in; extra = NULL; @@ -538,6 +538,15 @@ vips_colour_code_build( VipsObject *object ) in = t[3]; } + if( in && + code->input_coding == VIPS_CODING_NONE && + code->input_interpretation != VIPS_INTERPRETATION_ERROR ) { + if( vips_colourspace( in, &t[4], + code->input_interpretation, NULL ) ) + return( -1 ); + in = t[4]; + } + colour->n = 1; colour->in = (VipsImage **) vips_object_local_array( object, 2 ); colour->in[0] = in; @@ -588,6 +597,7 @@ vips_colour_code_class_init( VipsColourCodeClass *class ) static void vips_colour_code_init( VipsColourCode *code ) { + code->input_interpretation = VIPS_INTERPRETATION_ERROR; } G_DEFINE_ABSTRACT_TYPE( VipsColourDifference, vips_colour_difference, diff --git a/libvips/colour/icc_transform.c b/libvips/colour/icc_transform.c index a6a360f2..7ef62b5e 100644 --- a/libvips/colour/icc_transform.c +++ b/libvips/colour/icc_transform.c @@ -76,6 +76,7 @@ #define cmsSigRgbData icSigRgbData #define cmsSigLabData icSigLabData #define cmsSigCmykData icSigCmykData +#define cmsSigXYZData icSigXYZData #endif #include @@ -96,6 +97,28 @@ typedef DWORD cmsUInt32Number; #define cmsFLAGS_NOCACHE (0) #endif +/** + * 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. + */ + +/** + * VipsPCS: + * @VIPS_PCS_LAB: use CIELAB D65 as the Profile Connection Space + * @VIPS_PCS_XYZ: use XYZ as the Profile Connection Space + * + * Pick a Profile Connection Space for vips_icc_import() and + * vips_icc_export(). LAB is usually best, XYZ can be more convenient in some + * cases. + */ + /** * vips_icc_present: * @@ -129,6 +152,7 @@ typedef struct _VipsIcc { VipsColourCode parent_instance; VipsIntent intent; + VipsPCS pcs; int depth; cmsHPROFILE in_profile; @@ -182,6 +206,15 @@ vips_icc_dispose( GObject *gobject ) G_OBJECT_CLASS( vips_icc_parent_class )->dispose( gobject ); } +/* Is a profile just a pcs stub. + */ +static gboolean +is_pcs( cmsHPROFILE profile ) +{ + return( cmsGetColorSpace( profile ) == cmsSigLabData || + cmsGetColorSpace( profile ) == cmsSigXYZData ); +} + static int vips_icc_build( VipsObject *object ) { @@ -223,9 +256,19 @@ vips_icc_build( VipsObject *object ) case cmsSigLabData: code->input_bands = 3; code->input_format = VIPS_FORMAT_FLOAT; + code->input_interpretation = + VIPS_INTERPRETATION_LAB; icc->in_icc_format = TYPE_Lab_16; break; + case cmsSigXYZData: + code->input_bands = 3; + code->input_format = VIPS_FORMAT_FLOAT; + code->input_interpretation = + VIPS_INTERPRETATION_XYZ; + icc->in_icc_format = TYPE_XYZ_16; + break; + default: vips_error( class->nickname, _( "unimplemented input color space 0x%x" ), @@ -268,6 +311,13 @@ vips_icc_build( VipsObject *object ) icc->out_icc_format = TYPE_Lab_16; break; + case cmsSigXYZData: + colour->interpretation = VIPS_INTERPRETATION_XYZ; + colour->format = VIPS_FORMAT_FLOAT; + colour->bands = 3; + icc->out_icc_format = TYPE_XYZ_16; + break; + default: vips_error( class->nickname, _( "unimplemented output color space 0x%x" ), @@ -279,8 +329,8 @@ vips_icc_build( VipsObject *object ) */ if( icc->in_profile && icc->out_profile && - cmsGetColorSpace( icc->in_profile ) == cmsSigLabData && - cmsGetColorSpace( icc->out_profile ) == cmsSigLabData ) { + is_pcs( icc->in_profile ) && + is_pcs( icc->out_profile ) ) { vips_error( class->nickname, "%s", _( "no device profile" ) ); return( -1 ); @@ -323,6 +373,13 @@ vips_icc_class_init( VipsIccClass *class ) G_STRUCT_OFFSET( VipsIcc, intent ), VIPS_TYPE_INTENT, VIPS_INTENT_RELATIVE ); + VIPS_ARG_ENUM( class, "pcs", 6, + _( "PCS" ), + _( "Set Profile Connection Space" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsIcc, pcs ), + VIPS_TYPE_PCS, VIPS_PCS_LAB ); + #ifdef HAVE_LCMS2 cmsSetLogErrorHandler( icc_error ); #else @@ -339,6 +396,7 @@ vips_icc_init( VipsIcc *icc ) { icc->lock = vips_g_mutex_new(); icc->intent = VIPS_INTENT_RELATIVE; + icc->pcs = VIPS_PCS_LAB; icc->depth = 8; } @@ -420,16 +478,18 @@ vips_icc_import_build( VipsObject *object ) vips_check_intent( class->nickname, icc->in_profile, icc->intent, LCMS_USED_AS_INPUT ); + if( icc->pcs == VIPS_PCS_LAB ) { #ifdef HAVE_LCMS2 -{ - cmsCIExyY white; - cmsWhitePointFromTemp( &white, 6500 ); + cmsCIExyY white; + cmsWhitePointFromTemp( &white, 6500 ); - icc->out_profile = cmsCreateLab4Profile( &white ); -} + icc->out_profile = cmsCreateLab4Profile( &white ); #else - icc->out_profile = cmsCreateLabProfile( NULL ); + icc->out_profile = cmsCreateLabProfile( NULL ); #endif + } + else + icc->out_profile = cmsCreateXYZProfile(); if( VIPS_OBJECT_CLASS( vips_icc_import_parent_class )->build( object ) ) return( -1 ); @@ -452,6 +512,21 @@ decode_lab( guint16 *fixed, float *lab, int n ) } } +static void +decode_xyz( guint16 *fixed, float *xyz, int n ) +{ + int i; + + for( i = 0; i < n; i++ ) { + xyz[0] = (double) fixed[0] / 652.800; + xyz[1] = (double) fixed[1] / 652.800; + xyz[2] = (double) fixed[2] / 652.800; + + xyz += 3; + fixed += 3; + } +} + /* Process a buffer of data. */ static void @@ -481,7 +556,10 @@ vips_icc_import_line( VipsColour *colour, g_mutex_unlock( icc->lock ); #endif - decode_lab( encoded, q, chunk ); + if( icc->pcs == VIPS_PCS_LAB ) + decode_lab( encoded, q, chunk ); + else + decode_xyz( encoded, q, chunk ); p += PIXEL_BUFFER_SIZE * VIPS_IMAGE_SIZEOF_PEL( colour->in[0] ); q += PIXEL_BUFFER_SIZE * 3; @@ -517,6 +595,7 @@ vips_icc_import_class_init( VipsIccImportClass *class ) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsIccImport, input_profile_filename ), NULL ); + } static void @@ -544,16 +623,18 @@ vips_icc_export_build( VipsObject *object ) VipsIcc *icc = (VipsIcc *) object; VipsIccExport *export = (VipsIccExport *) object; + if( icc->pcs == VIPS_PCS_LAB ) { #ifdef HAVE_LCMS2 -{ - cmsCIExyY white; - cmsWhitePointFromTemp( &white, 6500 ); + cmsCIExyY white; + cmsWhitePointFromTemp( &white, 6500 ); - icc->in_profile = cmsCreateLab4Profile( &white ); -} + icc->in_profile = cmsCreateLab4Profile( &white ); #else - icc->in_profile = cmsCreateLabProfile( NULL ); + icc->in_profile = cmsCreateLabProfile( NULL ); #endif + } + else + icc->in_profile = cmsCreateXYZProfile(); if( code->in && !export->output_profile_filename && @@ -631,6 +712,44 @@ encode_lab( float *lab, guint16 *fixed, int n ) } } +#define MAX_ENCODEABLE_XYZ (100 * (1.0 + 32767.0 / 32768.0)) + +// 1.15 fixed point for XYZ + +static void +encode_xyz( float *xyz, guint16 *fixed, int n ) +{ + int i; + + for( i = 0; i < n; i++ ) { + float X = xyz[0]; + float Y = xyz[1]; + float Z = xyz[2]; + + if( X < 0 ) + X = 0; + if( X > MAX_ENCODEABLE_XYZ ) + X = MAX_ENCODEABLE_XYZ; + + if( Y < 0 ) + Y = 0; + if( Y > MAX_ENCODEABLE_XYZ ) + Y = MAX_ENCODEABLE_XYZ; + + if( Z < 0 ) + Z = 0; + if( Z > MAX_ENCODEABLE_XYZ ) + Z = MAX_ENCODEABLE_XYZ; + + fixed[0] = X * 652.800 + 0.5; + fixed[1] = Y * 652.800 + 0.5; + fixed[2] = Z * 652.800 + 0.5; + + xyz += 3; + fixed += 3; + } +} + /* Process a buffer of data. */ static void @@ -652,7 +771,10 @@ vips_icc_export_line( VipsColour *colour, for( x = 0; x < width; x += PIXEL_BUFFER_SIZE ) { const int chunk = VIPS_MIN( width - x, PIXEL_BUFFER_SIZE ); - encode_lab( p, encoded, chunk ); + if( icc->pcs == VIPS_PCS_LAB ) + encode_lab( p, encoded, chunk ); + else + encode_xyz( p, encoded, chunk ); #ifdef HAVE_LCMS2 cmsDoTransform( icc->trans, encoded, q, chunk ); @@ -982,8 +1104,10 @@ vips_icc_ac2rc( VipsImage *in, VipsImage **out, const char *profile_filename ) * @input_profile: get the input profile from here * @intent: transform with this intent * @embedded: use profile embedded in input image + * @pcs: use XYZ or LAB PCS * - * Import an image from device space to D65 LAB with an ICC profile. + * Import an image from device space to D65 LAB with an ICC profile. If @pcs is + * set to #VIPS_PCS_XYZ, use CIE XYZ PCS instead. * * If @embedded is set, the input profile is taken from the input image * metadata. If there is no embedded profile, @@ -1018,8 +1142,11 @@ vips_icc_import( VipsImage *in, VipsImage **out, ... ) * @intent: transform with this intent * @depth: depth of output image in bits * @output_profile: get the output profile from here + * @pcs: use XYZ or LAB PCS * * Export an image from D65 LAB to device space with an ICC profile. + * If @pcs is + * set to #VIPS_PCS_XYZ, use CIE XYZ PCS instead. * If @output_profile is not set, use the embedded profile, if any. * If @output_profile is set, export with that and attach it to the output * image. diff --git a/libvips/colour/pcolour.h b/libvips/colour/pcolour.h index 71828744..7bfc2ad9 100644 --- a/libvips/colour/pcolour.h +++ b/libvips/colour/pcolour.h @@ -150,6 +150,7 @@ typedef struct _VipsColourCode { VipsCoding input_coding; VipsBandFormat input_format; int input_bands; + VipsInterpretation input_interpretation; } VipsColourCode; diff --git a/libvips/include/vips/colour.h b/libvips/include/vips/colour.h index 5dda55e7..47291f9e 100644 --- a/libvips/include/vips/colour.h +++ b/libvips/include/vips/colour.h @@ -93,17 +93,6 @@ 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, @@ -111,6 +100,12 @@ typedef enum { VIPS_INTENT_ABSOLUTE } VipsIntent; +typedef enum { + VIPS_PCS_LAB, + VIPS_PCS_XYZ, + VIPS_PCS_LAST +} VipsPCS; + gboolean vips_colourspace_issupported( const VipsImage *image ); int vips_colourspace( VipsImage *in, VipsImage **out, VipsInterpretation space, ... ) diff --git a/libvips/include/vips/enumtypes.h b/libvips/include/vips/enumtypes.h index 4c1fa2f0..311a452c 100644 --- a/libvips/include/vips/enumtypes.h +++ b/libvips/include/vips/enumtypes.h @@ -68,6 +68,8 @@ GType vips_access_get_type (void) G_GNUC_CONST; /* enumerations from "../../../libvips/include/vips/colour.h" */ GType vips_intent_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_INTENT (vips_intent_get_type()) +GType vips_pcs_get_type (void) G_GNUC_CONST; +#define VIPS_TYPE_PCS (vips_pcs_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/iofuncs/enumtypes.c b/libvips/iofuncs/enumtypes.c index 75f8bdc8..273a4f90 100644 --- a/libvips/iofuncs/enumtypes.c +++ b/libvips/iofuncs/enumtypes.c @@ -594,6 +594,24 @@ vips_intent_get_type( void ) return( etype ); } +GType +vips_pcs_get_type( void ) +{ + static GType etype = 0; + + if( etype == 0 ) { + static const GEnumValue values[] = { + {VIPS_PCS_LAB, "VIPS_PCS_LAB", "lab"}, + {VIPS_PCS_XYZ, "VIPS_PCS_XYZ", "xyz"}, + {VIPS_PCS_LAST, "VIPS_PCS_LAST", "last"}, + {0, NULL, NULL} + }; + + etype = g_enum_register_static( "VipsPCS", values ); + } + + return( etype ); +} /* enumerations from "../../libvips/include/vips/operation.h" */ GType vips_operation_flags_get_type( void )