diff --git a/libvips/colour/icc_transform.c b/libvips/colour/icc_transform.c index ecb1b43c..9d6e4cb8 100644 --- a/libvips/colour/icc_transform.c +++ b/libvips/colour/icc_transform.c @@ -163,7 +163,7 @@ typedef struct _VipsIcc { cmsUInt32Number in_icc_format; cmsUInt32Number out_icc_format; cmsHTRANSFORM trans; - + gboolean non_standard_input_profile; } VipsIcc; typedef VipsColourCodeClass VipsIccClass; @@ -369,84 +369,85 @@ vips_icc_build( VipsObject *object ) return( 0 ); } -static void -vips_icc_class_init( VipsIccClass *class ) +/* Get from an image. + */ +static VipsBlob * +vips_icc_get_profile_image( VipsImage *image ) { - GObjectClass *gobject_class = G_OBJECT_CLASS( class ); - VipsObjectClass *object_class = (VipsObjectClass *) class; + const void *data; + size_t size; - gobject_class->dispose = vips_icc_dispose; - gobject_class->set_property = vips_object_set_property; - gobject_class->get_property = vips_object_get_property; + if( !vips_image_get_typeof( image, VIPS_META_ICC_NAME ) ) + return( NULL ); + if( vips_image_get_blob( image, VIPS_META_ICC_NAME, &data, &size ) ) + return( NULL ); - 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 ); - - 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 ); - - cmsSetLogErrorHandler( icc_error ); + return( vips_blob_new( NULL, data, size ) ); } +#ifdef DEBUG static void -vips_icc_init( VipsIcc *icc ) +vips_icc_print_profile( const char *name, cmsHPROFILE profile ) { - icc->intent = VIPS_INTENT_RELATIVE; - icc->pcs = VIPS_PCS_LAB; - icc->depth = 8; -} + static const cmsInfoType info_types[] = { + cmsInfoDescription, + cmsInfoManufacturer, + cmsInfoModel, + cmsInfoCopyright + }; + static const char *info_names[] = { + "description", + "manufacturer", + "model", + "copyright" + }; -typedef struct _VipsIccImport { - VipsIcc parent_instance; + int i; + cmsUInt32Number n_bytes; + cmsUInt32Number n_intents; + cmsUInt32Number *intent_codes; + char **intent_descriptions; + + printf( "icc profile %s: %p\n", name, profile ); + for( i = 0; i < VIPS_NUMBER( info_types ); i++ ) { + if( (n_bytes = cmsGetProfileInfoASCII( profile, + info_types[i], "en", "US", + NULL, 0 )) ) { + char *buffer; - gboolean embedded; - char *input_profile_filename; - -} VipsIccImport; - -typedef VipsIccClass VipsIccImportClass; - -G_DEFINE_TYPE( VipsIccImport, vips_icc_import, VIPS_TYPE_ICC ); - -static int -vips_icc_profile_needs_bands( cmsHPROFILE profile ) -{ - int needs_bands; - - switch( cmsGetColorSpace( profile ) ) { - case cmsSigGrayData: - needs_bands = 1; - break; - - case cmsSigRgbData: - case cmsSigLabData: - case cmsSigXYZData: - needs_bands = 3; - break; - - case cmsSigCmykData: - needs_bands = 4; - break; - - default: - needs_bands = -1; - break; + buffer = VIPS_ARRAY( NULL, n_bytes, char ); + (void) cmsGetProfileInfoASCII( profile, + info_types[i], "en", "US", + buffer, n_bytes ); + printf( "%s: %s\n", info_names[i], buffer ); + g_free( buffer ); + } } - return( needs_bands ); + printf( "profile class: %#x\n", cmsGetDeviceClass( profile ) ); + printf( "PCS: %#x\n", cmsGetPCS( profile ) ); + + printf( "matrix shaper: %d\n", cmsIsMatrixShaper( profile ) ); + printf( "version: %g\n", cmsGetProfileVersion( profile ) ); + + n_intents = cmsGetSupportedIntents( 0, NULL, NULL ); + printf( "n_intents = %u\n", n_intents ); + intent_codes = VIPS_ARRAY( NULL, n_intents, cmsUInt32Number ); + intent_descriptions = VIPS_ARRAY( NULL, n_intents, char * ); + (void) cmsGetSupportedIntents( n_intents, + intent_codes, intent_descriptions ); + for( i = 0; i < n_intents; i++ ) { + printf( " %#x: %s, in CLUT = %d, out CLUT = %d\n", + intent_codes[i], intent_descriptions[i], + cmsIsCLUT( profile, + intent_codes[i], LCMS_USED_AS_INPUT ), + cmsIsCLUT( profile, + intent_codes[i], LCMS_USED_AS_OUTPUT ) ); + } + g_free( intent_codes ); + g_free( intent_descriptions ); } +#endif /*DEBUG*/ /* How many bands we expect to see from an image after preprocessing by our * parent classes. This is a bit fragile :-( @@ -499,6 +500,34 @@ vips_image_expected_bands( VipsImage *image ) return( expected_bands ); } +static int +vips_icc_profile_needs_bands( cmsHPROFILE profile ) +{ + int needs_bands; + + switch( cmsGetColorSpace( profile ) ) { + case cmsSigGrayData: + needs_bands = 1; + break; + + case cmsSigRgbData: + case cmsSigLabData: + case cmsSigXYZData: + needs_bands = 3; + break; + + case cmsSigCmykData: + needs_bands = 4; + break; + + default: + needs_bands = -1; + break; + } + + return( needs_bands ); +} + /* What cmsColorSpaceSignature do we expect this image to be (roughly) after * preprocessing. Again, fragile :( see the FIXME above. */ @@ -580,86 +609,6 @@ vips_image_expected_sig( VipsImage *image ) return( expected_sig ); } -/* Get from an image. - */ -static VipsBlob * -vips_icc_get_profile_image( VipsImage *image ) -{ - const void *data; - size_t size; - - if( !vips_image_get_typeof( image, VIPS_META_ICC_NAME ) ) - return( NULL ); - if( vips_image_get_blob( image, VIPS_META_ICC_NAME, &data, &size ) ) - return( NULL ); - - return( vips_blob_new( NULL, data, size ) ); -} - -#ifdef DEBUG -static void -vips_icc_print_profile( const char *name, cmsHPROFILE profile ) -{ - static const cmsInfoType info_types[] = { - cmsInfoDescription, - cmsInfoManufacturer, - cmsInfoModel, - cmsInfoCopyright - }; - static const char *info_names[] = { - "description", - "manufacturer", - "model", - "copyright" - }; - - int i; - cmsUInt32Number n_bytes; - cmsUInt32Number n_intents; - cmsUInt32Number *intent_codes; - char **intent_descriptions; - - printf( "icc profile %s: %p\n", name, profile ); - for( i = 0; i < VIPS_NUMBER( info_types ); i++ ) { - if( (n_bytes = cmsGetProfileInfoASCII( profile, - info_types[i], "en", "US", - NULL, 0 )) ) { - char *buffer; - - buffer = VIPS_ARRAY( NULL, n_bytes, char ); - (void) cmsGetProfileInfoASCII( profile, - info_types[i], "en", "US", - buffer, n_bytes ); - printf( "%s: %s\n", info_names[i], buffer ); - g_free( buffer ); - } - } - - printf( "profile class: %#x\n", cmsGetDeviceClass( profile ) ); - printf( "PCS: %#x\n", cmsGetPCS( profile ) ); - - printf( "matrix shaper: %d\n", cmsIsMatrixShaper( profile ) ); - printf( "version: %g\n", cmsGetProfileVersion( profile ) ); - - n_intents = cmsGetSupportedIntents( 0, NULL, NULL ); - printf( "n_intents = %u\n", n_intents ); - intent_codes = VIPS_ARRAY( NULL, n_intents, cmsUInt32Number ); - intent_descriptions = VIPS_ARRAY( NULL, n_intents, char * ); - (void) cmsGetSupportedIntents( n_intents, - intent_codes, intent_descriptions ); - for( i = 0; i < n_intents; i++ ) { - printf( " %#x: %s, in CLUT = %d, out CLUT = %d\n", - intent_codes[i], intent_descriptions[i], - cmsIsCLUT( profile, - intent_codes[i], LCMS_USED_AS_INPUT ), - cmsIsCLUT( profile, - intent_codes[i], LCMS_USED_AS_OUTPUT ) ); - } - g_free( intent_codes ); - g_free( intent_descriptions ); -} -#endif /*DEBUG*/ - /* Load a profile from a blob and check compatibility with image, intent and * direction. */ @@ -733,47 +682,63 @@ vips_icc_verify_blob( VipsBlob **blob, return( NULL ); } +/* Try to set the inport profile. We read the input profile like this: + * + * embedded filename action + * 0 0 image + * 1 0 image + * 0 1 file + * 1 1 image, then fall back to file + * + * If averything fails, we fall back to our built-in profiles, either + * srgb or cmyk, depending on the input image. + * + * We set attach_input_profile if we used a non-emdedded profile. The profile + * in in_blob will need to be attached to the output image in some way. + */ static int -vips_icc_import_build( VipsObject *object ) +vips_icc_set_import( VipsIcc *icc, + gboolean embedded, const char *input_profile_filename ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); - VipsColour *colour = (VipsColour *) object; - VipsColourCode *code = (VipsColourCode *) object; - VipsIcc *icc = (VipsIcc *) object; - VipsIccImport *import = (VipsIccImport *) object; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( icc ); + VipsColourCode *code = (VipsColourCode *) icc; - gboolean used_fallback; + icc->non_standard_input_profile = FALSE; - /* We read the input profile like this: - * - * embedded filename action - * 0 0 image - * 1 0 image - * 0 1 file - * 1 1 image, then fall back to file + /* Try embedded profile. */ - - used_fallback = FALSE; - if( code->in && - (import->embedded || - !import->input_profile_filename) ) { + (embedded || !input_profile_filename) ) { icc->in_blob = vips_icc_get_profile_image( code->in ); - icc->in_profile = - vips_icc_verify_blob( &icc->in_blob, - code->in, icc->intent, LCMS_USED_AS_INPUT ); + icc->in_profile = vips_icc_verify_blob( &icc->in_blob, + code->in, icc->intent, LCMS_USED_AS_INPUT ); } + /* Try profile from filename. + */ if( code->in && !icc->in_blob && - import->input_profile_filename ) { - if( vips_profile_load( import->input_profile_filename, - &icc->in_blob, NULL ) ) - return( -1 ); - icc->in_profile = - vips_icc_verify_blob( &icc->in_blob, - code->in, icc->intent, LCMS_USED_AS_INPUT ); - used_fallback = TRUE; + input_profile_filename ) { + if( + !vips_profile_load( input_profile_filename, + &icc->in_blob, NULL ) && + (icc->in_profile = vips_icc_verify_blob( &icc->in_blob, + code->in, icc->intent, LCMS_USED_AS_INPUT )) ) + icc->non_standard_input_profile = TRUE; + } + + /* Try built-in profile. + */ + if( code->in && + !icc->in_profile ) { + const char *name = code->in->Type == VIPS_INTERPRETATION_CMYK ? + "cmyk" : "srgb"; + + if( + !vips_profile_load( name, &icc->in_blob, NULL ) && + (icc->in_profile = vips_icc_verify_blob( &icc->in_blob, + code->in, icc->intent, LCMS_USED_AS_INPUT )) ) + icc->non_standard_input_profile = TRUE; } if( !icc->in_profile ) { @@ -781,6 +746,71 @@ vips_icc_import_build( VipsObject *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 ); + + 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 ); + + cmsSetLogErrorHandler( icc_error ); +} + +static void +vips_icc_init( VipsIcc *icc ) +{ + icc->intent = VIPS_INTENT_RELATIVE; + icc->pcs = VIPS_PCS_LAB; + 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 ) +{ + VipsColour *colour = (VipsColour *) object; + VipsIcc *icc = (VipsIcc *) object; + VipsIccImport *import = (VipsIccImport *) object; + + if( vips_icc_set_import( icc, + import->embedded, import->input_profile_filename ) ) + return( -1 ); + if( icc->pcs == VIPS_PCS_LAB ) { cmsCIExyY white; cmsWhitePointFromTemp( &white, 6500 ); @@ -799,7 +829,7 @@ vips_icc_import_build( VipsObject *object ) * In the same way, we don't remove the embedded input profile on * import. */ - if( used_fallback && + if( icc->non_standard_input_profile && icc->in_blob ) { const void *data; size_t size; @@ -1139,50 +1169,12 @@ 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; - /* We read the input profile like this: - * - * embedded filename action - * 0 0 image - * 1 0 image - * 0 1 file - * 1 1 image, then fall back to file - * - * see also import_build. - */ - - if( code->in && - (transform->embedded || - !transform->input_profile_filename) ) { - icc->in_blob = vips_icc_get_profile_image( code->in ); - icc->in_profile = - vips_icc_verify_blob( &icc->in_blob, - code->in, icc->intent, LCMS_USED_AS_INPUT ); - } - - if( code->in && - !icc->in_blob && - transform->input_profile_filename ) { - if( vips_profile_load( transform->input_profile_filename, - &icc->in_blob, NULL ) ) - return( -1 ); - icc->in_profile = - vips_icc_verify_blob( &icc->in_blob, - code->in, icc->intent, LCMS_USED_AS_INPUT ); - } - - if( !icc->in_profile ) { - vips_error( class->nickname, "%s", _( "no input profile" ) ); + if( vips_icc_set_import( icc, + transform->embedded, transform->input_profile_filename ) ) return( -1 ); - } - - if( !icc->in_profile ) { - vips_error( class->nickname, "%s", _( "no input profile" ) ); - return( -1 ); - } if( transform->output_profile_filename ) { if( vips_profile_load( transform->output_profile_filename, diff --git a/libvips/colour/profile_load.c b/libvips/colour/profile_load.c index 3105ef1c..9bce2a6c 100644 --- a/libvips/colour/profile_load.c +++ b/libvips/colour/profile_load.c @@ -88,7 +88,8 @@ vips_profile_fallback_get( const char *name, size_t *length ) return( data ); } else { g_free( data ); - g_warning( "fallback profile decompression failed" ); + g_warning( "fallback profile " + "decompression failed" ); } } @@ -182,7 +183,7 @@ vips_profile_load_init( VipsProfileLoad *load ) * case. * * - @name can be the name of one of the ICC profiles embedded in libvips. - * These names can be at least `"cmyk"` and `"srgb"`. + * These names can be at least `"cmyk"`, `"p3"` and `"srgb"`. * * - @name can be the full path to a file. *