"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "libvips/colour/icc_transform.c" between
vips-8.10.6.tar.gz and vips-8.11.0.tar.gz

About: VIPS is a free image processing system (see also the GUI nip2).

icc_transform.c  (vips-8.10.6):icc_transform.c  (vips-8.11.0)
skipping to change at line 46 skipping to change at line 46
* - remove lcms1 support, it was untested * - remove lcms1 support, it was untested
* 10/10/17 * 10/10/17
* - more input profile sanity tests * - more input profile sanity tests
* 8/3/18 * 8/3/18
* - attach fallback profile on import if we used it * - attach fallback profile on import if we used it
* 28/12/18 * 28/12/18
* - remove warning messages from vips_icc_is_compatible_profile() since * - remove warning messages from vips_icc_is_compatible_profile() since
* they can be triggered under normal circumstances * they can be triggered under normal circumstances
* 17/4/19 kleisauke * 17/4/19 kleisauke
* - better rejection of broken embedded profiles * - better rejection of broken embedded profiles
* 29/3/21 [hanssonrickard]
* - add black_point_compensation
*/ */
/* /*
This file is part of VIPS. This file is part of VIPS.
VIPS is free software; you can redistribute it and/or modify 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 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 the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. (at your option) any later version.
skipping to change at line 75 skipping to change at line 77
02110-1301 USA 02110-1301 USA
*/ */
/* /*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/ */
/*
#define DEBUG
*/
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
#include <config.h> #include <config.h>
#endif /*HAVE_CONFIG_H*/ #endif /*HAVE_CONFIG_H*/
#include <vips/intl.h> #include <vips/intl.h>
#ifdef HAVE_LCMS2 #ifdef HAVE_LCMS2
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
#include <assert.h>
/* Has to be before VIPS to avoid nameclashes. /* Has to be before VIPS to avoid nameclashes.
*/ */
#include <lcms2.h> #include <lcms2.h>
#include <vips/vips.h> #include <vips/vips.h>
#include "pcolour.h" #include "pcolour.h"
/* Call lcms with up to this many pixels at once. /* Call lcms with up to this many pixels at once.
skipping to change at line 155 skipping to change at line 160
#define VIPS_ICC_GET_CLASS( obj ) \ #define VIPS_ICC_GET_CLASS( obj ) \
(G_TYPE_INSTANCE_GET_CLASS( (obj), \ (G_TYPE_INSTANCE_GET_CLASS( (obj), \
VIPS_TYPE_ICC, VipsIccClass )) VIPS_TYPE_ICC, VipsIccClass ))
typedef struct _VipsIcc { typedef struct _VipsIcc {
VipsColourCode parent_instance; VipsColourCode parent_instance;
VipsIntent intent; VipsIntent intent;
VipsPCS pcs; VipsPCS pcs;
int depth; int depth;
gboolean black_point_compensation;
VipsBlob *in_blob; VipsBlob *in_blob;
cmsHPROFILE in_profile; cmsHPROFILE in_profile;
VipsBlob *out_blob; VipsBlob *out_blob;
cmsHPROFILE out_profile; cmsHPROFILE out_profile;
cmsUInt32Number in_icc_format; cmsUInt32Number in_icc_format;
cmsUInt32Number out_icc_format; cmsUInt32Number out_icc_format;
cmsHTRANSFORM trans; cmsHTRANSFORM trans;
gboolean non_standard_input_profile;
} VipsIcc; } VipsIcc;
typedef VipsColourCodeClass VipsIccClass; typedef VipsColourCodeClass VipsIccClass;
G_DEFINE_ABSTRACT_TYPE( VipsIcc, vips_icc, VIPS_TYPE_COLOUR_CODE ); G_DEFINE_ABSTRACT_TYPE( VipsIcc, vips_icc, VIPS_TYPE_COLOUR_CODE );
/* Error from lcms. /* Error from lcms.
*/ */
static void static void
skipping to change at line 218 skipping to change at line 224
} }
static int static int
vips_icc_build( VipsObject *object ) vips_icc_build( VipsObject *object )
{ {
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
VipsColour *colour = (VipsColour *) object; VipsColour *colour = (VipsColour *) object;
VipsColourCode *code = (VipsColourCode *) object; VipsColourCode *code = (VipsColourCode *) object;
VipsIcc *icc = (VipsIcc *) object; VipsIcc *icc = (VipsIcc *) object;
cmsUInt32Number flags;
if( icc->depth != 8 && if( icc->depth != 8 &&
icc->depth != 16 ) { icc->depth != 16 ) {
vips_error( class->nickname, vips_error( class->nickname,
"%s", _( "depth must be 8 or 16" ) ); "%s", _( "depth must be 8 or 16" ) );
return( -1 ); return( -1 );
} }
if( icc->in_profile && if( icc->in_profile &&
code->in ) { code->in ) {
switch( cmsGetColorSpace( icc->in_profile ) ) { switch( cmsGetColorSpace( icc->in_profile ) ) {
skipping to change at line 356 skipping to change at line 364
is_pcs( icc->in_profile ) && is_pcs( icc->in_profile ) &&
is_pcs( icc->out_profile ) ) { is_pcs( icc->out_profile ) ) {
vips_error( class->nickname, vips_error( class->nickname,
"%s", _( "no device profile" ) ); "%s", _( "no device profile" ) );
return( -1 ); return( -1 );
} }
/* Use cmsFLAGS_NOCACHE to disable the 1-pixel cache and make /* Use cmsFLAGS_NOCACHE to disable the 1-pixel cache and make
* calling cmsDoTransform() from multiple threads safe. * calling cmsDoTransform() from multiple threads safe.
*/ */
flags = cmsFLAGS_NOCACHE;
if( icc->black_point_compensation )
flags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
if( !(icc->trans = cmsCreateTransform( if( !(icc->trans = cmsCreateTransform(
icc->in_profile, icc->in_icc_format, icc->in_profile, icc->in_icc_format,
icc->out_profile, icc->out_icc_format, icc->out_profile, icc->out_icc_format,
icc->intent, cmsFLAGS_NOCACHE )) ) icc->intent, flags )) )
return( -1 ); return( -1 );
if( VIPS_OBJECT_CLASS( vips_icc_parent_class )-> if( VIPS_OBJECT_CLASS( vips_icc_parent_class )->
build( object ) ) build( object ) )
return( -1 ); return( -1 );
return( 0 ); return( 0 );
} }
static void /* Get from an image.
vips_icc_class_init( VipsIccClass *class ) */
static VipsBlob *
vips_icc_get_profile_image( VipsImage *image )
{ {
GObjectClass *gobject_class = G_OBJECT_CLASS( class ); const void *data;
VipsObjectClass *object_class = (VipsObjectClass *) class; 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;
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 ); 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 );
static void return( vips_blob_new( NULL, data, size ) );
vips_icc_init( VipsIcc *icc )
{
icc->intent = VIPS_INTENT_RELATIVE;
icc->pcs = VIPS_PCS_LAB;
icc->depth = 8;
} }
typedef struct _VipsIccImport { #ifdef DEBUG
VipsIcc parent_instance;
gboolean embedded;
char *input_profile_filename;
} VipsIccImport;
typedef VipsIccClass VipsIccImportClass;
G_DEFINE_TYPE( VipsIccImport, vips_icc_import, VIPS_TYPE_ICC );
static void static void
vips_check_intent( const char *domain, vips_icc_print_profile( const char *name, cmsHPROFILE profile )
cmsHPROFILE profile, VipsIntent intent, int direction )
{ {
if( profile && static const cmsInfoType info_types[] = {
!cmsIsIntentSupported( profile, intent, direction ) ) cmsInfoDescription,
g_warning( _( "%s: intent %d (%s) not supported by " cmsInfoManufacturer,
"%s profile; falling back to default intent" ), cmsInfoModel,
domain, cmsInfoCopyright
intent, vips_enum_nick( VIPS_TYPE_INTENT, intent ), };
direction == LCMS_USED_AS_INPUT ? static const char *info_names[] = {
_( "input" ) : _( "output" ) ); "description",
} "manufacturer",
"model",
static int "copyright"
vips_icc_profile_needs_bands( cmsHPROFILE profile ) };
{
int needs_bands;
switch( cmsGetColorSpace( profile ) ) { int i;
case cmsSigGrayData: cmsUInt32Number n_bytes;
needs_bands = 1; cmsUInt32Number n_intents;
break; 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 );
}
}
case cmsSigRgbData: printf( "profile class: %#x\n", cmsGetDeviceClass( profile ) );
case cmsSigLabData: printf( "PCS: %#x\n", cmsGetPCS( profile ) );
case cmsSigXYZData:
needs_bands = 3;
break;
case cmsSigCmykData: printf( "matrix shaper: %d\n", cmsIsMatrixShaper( profile ) );
needs_bands = 4; printf( "version: %g\n", cmsGetProfileVersion( profile ) );
break;
default: n_intents = cmsGetSupportedIntents( 0, NULL, NULL );
needs_bands = -1; printf( "n_intents = %u\n", n_intents );
break; 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 );
return( needs_bands ); g_free( intent_descriptions );
} }
#endif /*DEBUG*/
/* How many bands we expect to see from an image after preprocessing by our /* How many bands we expect to see from an image after preprocessing by our
* parent classes. This is a bit fragile :-( * parent classes. This is a bit fragile :-(
* *
* FIXME ... split the _build() for colour into separate preprocess / process * FIXME ... split the _build() for colour into separate preprocess / process
* / postprocess phases so we can load profiles after preprocess but before * / postprocess phases so we can load profiles after preprocess but before
* actual processing takes place. * actual processing takes place.
*/ */
static int static int
vips_image_expected_bands( VipsImage *image ) vips_image_expected_bands( VipsImage *image )
skipping to change at line 513 skipping to change at line 513
default: default:
expected_bands = image->Bands; expected_bands = image->Bands;
break; break;
} }
expected_bands = VIPS_MIN( expected_bands, image->Bands ); expected_bands = VIPS_MIN( expected_bands, image->Bands );
return( expected_bands ); 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 /* What cmsColorSpaceSignature do we expect this image to be (roughly) after
* preprocessing. Again, fragile :( see the FIXME above. * preprocessing. Again, fragile :( see the FIXME above.
*/ */
static cmsColorSpaceSignature static cmsColorSpaceSignature
vips_image_expected_sig( VipsImage *image ) vips_image_expected_sig( VipsImage *image )
{ {
cmsColorSpaceSignature expected_sig; cmsColorSpaceSignature expected_sig;
switch( image->Type ) { switch( image->Type ) {
case VIPS_INTERPRETATION_B_W: case VIPS_INTERPRETATION_B_W:
skipping to change at line 594 skipping to change at line 622
case VIPS_INTERPRETATION_MATRIX: case VIPS_INTERPRETATION_MATRIX:
case VIPS_INTERPRETATION_FOURIER: case VIPS_INTERPRETATION_FOURIER:
default: default:
expected_sig = -1; expected_sig = -1;
break; break;
} }
return( expected_sig ); return( expected_sig );
} }
/* Get from an image. /* Load a profile from a blob and check compatibility with image, intent and
*/ * direction.
static VipsBlob * *
vips_icc_get_profile_image( VipsImage *image ) * Don't set any errors since this is used to test compatibility.
{
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 ) );
}
/* Load a profile from a blob and check compatibility.
*/ */
static cmsHPROFILE static cmsHPROFILE
vips_icc_load_profile_blob( VipsBlob *blob, VipsImage *image ) vips_icc_load_profile_blob( VipsBlob *blob,
VipsImage *image, VipsIntent intent, int direction )
{ {
const void *data; const void *data;
size_t size; size_t size;
cmsHPROFILE profile; cmsHPROFILE profile;
data = vips_blob_get( blob, &size ); data = vips_blob_get( blob, &size );
if( !(profile = cmsOpenProfileFromMem( data, size )) ) { if( !(profile = cmsOpenProfileFromMem( data, size )) ) {
g_warning( "%s", _( "corrupt profile" ) ); g_warning( "%s", _( "corrupt profile" ) );
return( NULL ); return( NULL );
} }
#ifdef DEBUG
vips_icc_print_profile( "from blob", profile );
#endif /*DEBUG*/
if( image && if( image &&
vips_image_expected_bands( image ) != vips_image_expected_bands( image ) !=
vips_icc_profile_needs_bands( profile ) ) { vips_icc_profile_needs_bands( profile ) ) {
VIPS_FREEF( cmsCloseProfile, profile ); VIPS_FREEF( cmsCloseProfile, profile );
g_warning( "%s", _( "profile incompatible with image" ) ); g_warning( "%s", _( "profile incompatible with image" ) );
return( NULL ); return( NULL );
} }
if( image && if( image &&
vips_image_expected_sig( image ) != vips_image_expected_sig( image ) !=
cmsGetColorSpace( profile ) ) { cmsGetColorSpace( profile ) ) {
VIPS_FREEF( cmsCloseProfile, profile ); VIPS_FREEF( cmsCloseProfile, profile );
g_warning( "%s", g_warning( "%s",
_( "profile colourspace differs from image" ) ); _( "profile colourspace differs from image" ) );
return( NULL ); return( NULL );
} }
if( !cmsIsIntentSupported( profile, intent, direction ) ) {
VIPS_FREEF( cmsCloseProfile, profile );
g_warning( _( "%s profile does not support %s intent" ),
direction == LCMS_USED_AS_INPUT ?
_( "input" ) : _( "output" ),
vips_enum_nick( VIPS_TYPE_INTENT, intent ) );
return( NULL );
}
return( profile ); return( profile );
} }
/* Verify that a blob is not corrupt and is compatible with this image. /* Verify that a blob is not corrupt and is compatible with this image.
* *
* unref the blob if it's useless. * unref the blob if it's useless.
*/ */
static cmsHPROFILE static cmsHPROFILE
vips_icc_verify_blob( VipsBlob **blob, VipsImage *image ) vips_icc_verify_blob( VipsBlob **blob,
VipsImage *image, VipsIntent intent, int direction )
{ {
if( *blob ) { if( *blob ) {
cmsHPROFILE profile; cmsHPROFILE profile;
if( !(profile = vips_icc_load_profile_blob( *blob, image )) ) { if( !(profile = vips_icc_load_profile_blob( *blob,
image, intent, direction )) ) {
vips_area_unref( (VipsArea *) *blob ); vips_area_unref( (VipsArea *) *blob );
*blob = NULL; *blob = NULL;
} }
return( profile ); return( profile );
} }
return( NULL ); 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 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 ); VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( icc );
VipsColour *colour = (VipsColour *) object; VipsColourCode *code = (VipsColourCode *) icc;
VipsColourCode *code = (VipsColourCode *) object;
VipsIcc *icc = (VipsIcc *) object;
VipsIccImport *import = (VipsIccImport *) object;
gboolean used_fallback; icc->non_standard_input_profile = FALSE;
/* We read the input profile like this: /* Try embedded profile.
*
* embedded filename action
* 0 0 image
* 1 0 image
* 0 1 file
* 1 1 image, then fall back to file
*/ */
used_fallback = FALSE;
if( code->in && if( code->in &&
(import->embedded || (embedded || !input_profile_filename) ) {
!import->input_profile_filename) ) {
icc->in_blob = vips_icc_get_profile_image( code->in ); icc->in_blob = vips_icc_get_profile_image( code->in );
icc->in_profile = icc->in_profile = vips_icc_verify_blob( &icc->in_blob,
vips_icc_verify_blob( &icc->in_blob, code->in ); code->in, icc->intent, LCMS_USED_AS_INPUT );
} }
/* Try profile from filename.
*/
if( code->in && if( code->in &&
!icc->in_blob && !icc->in_blob &&
import->input_profile_filename ) { input_profile_filename ) {
if( vips_profile_load( import->input_profile_filename, if(
&icc->in_blob, NULL ) ) !vips_profile_load( input_profile_filename,
return( -1 ); &icc->in_blob, NULL ) &&
icc->in_profile = (icc->in_profile = vips_icc_verify_blob( &icc->in_blob,
vips_icc_verify_blob( &icc->in_blob, code->in ); code->in, icc->intent, LCMS_USED_AS_INPUT )) )
used_fallback = TRUE; 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 ) { if( !icc->in_profile ) {
vips_error( class->nickname, "%s", _( "no input profile" ) ); vips_error( class->nickname, "%s", _( "unable to load or "
"find any compatible input profile" ) );
return( -1 ); return( -1 );
} }
vips_check_intent( class->nickname, return( 0 );
icc->in_profile, icc->intent, LCMS_USED_AS_INPUT ); }
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 );
VIPS_ARG_BOOL( class, "black_point_compensation", 7,
_( "Black point compensation" ),
_( "Enable black point compensation" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsIcc, black_point_compensation ),
FALSE );
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 ) { if( icc->pcs == VIPS_PCS_LAB ) {
cmsCIExyY white; cmsCIExyY white;
cmsWhitePointFromTemp( &white, 6500 ); cmsWhitePointFromTemp( &white, 6500 );
icc->out_profile = cmsCreateLab4Profile( &white ); icc->out_profile = cmsCreateLab4Profile( &white );
} }
else else
icc->out_profile = cmsCreateXYZProfile(); icc->out_profile = cmsCreateXYZProfile();
if( VIPS_OBJECT_CLASS( vips_icc_import_parent_class )->build( object ) ) if( VIPS_OBJECT_CLASS( vips_icc_import_parent_class )->build( object ) )
return( -1 ); return( -1 );
/* If we used the fallback profile, we need to attach it to the PCS /* If we used the fallback profile, we need to attach it to the PCS
* image, since the PCS image needs a route back to device space. * image, since the PCS image needs a route back to device space.
* *
* In the same way, we don't remove the embedded input profile on * In the same way, we don't remove the embedded input profile on
* import. * import.
*/ */
if( used_fallback && if( icc->non_standard_input_profile &&
icc->in_blob ) { icc->in_blob ) {
const void *data; const void *data;
size_t size; size_t size;
data = vips_blob_get( icc->in_blob, &size ); data = vips_blob_get( icc->in_blob, &size );
vips_image_set_blob( colour->out, VIPS_META_ICC_NAME, vips_image_set_blob( colour->out, VIPS_META_ICC_NAME,
NULL, data, size ); NULL, data, size );
} }
return( 0 ); return( 0 );
skipping to change at line 899 skipping to change at line 1019
if( !icc->out_blob && if( !icc->out_blob &&
export->output_profile_filename ) { export->output_profile_filename ) {
if( vips_profile_load( export->output_profile_filename, if( vips_profile_load( export->output_profile_filename,
&icc->out_blob, NULL ) ) &icc->out_blob, NULL ) )
return( -1 ); return( -1 );
colour->profile_filename = export->output_profile_filename; colour->profile_filename = export->output_profile_filename;
} }
if( icc->out_blob && if( icc->out_blob &&
!(icc->out_profile = !(icc->out_profile = vips_icc_load_profile_blob( icc->out_blob,
vips_icc_load_profile_blob( icc->out_blob, NULL )) ) { NULL, icc->intent, LCMS_USED_AS_OUTPUT )) ) {
vips_error( class->nickname, "%s", _( "no output profile" ) ); vips_error( class->nickname, "%s", _( "no output profile" ) );
return( -1 ); return( -1 );
} }
if( icc->out_profile )
vips_check_intent( class->nickname,
icc->out_profile, icc->intent, LCMS_USED_AS_OUTPUT );
if( VIPS_OBJECT_CLASS( vips_icc_export_parent_class )->build( object ) ) if( VIPS_OBJECT_CLASS( vips_icc_export_parent_class )->build( object ) )
return( -1 ); return( -1 );
return( 0 ); return( 0 );
} }
/* Pack a buffer of floats into lcms's fixed-point formats. Cut from /* Pack a buffer of floats into lcms's fixed-point formats. Cut from
* lcms-1.0.8. * lcms-1.0.8.
*/ */
static void static void
skipping to change at line 1076 skipping to change at line 1192
typedef VipsIccClass VipsIccTransformClass; typedef VipsIccClass VipsIccTransformClass;
G_DEFINE_TYPE( VipsIccTransform, vips_icc_transform, VIPS_TYPE_ICC ); G_DEFINE_TYPE( VipsIccTransform, vips_icc_transform, VIPS_TYPE_ICC );
static int static int
vips_icc_transform_build( VipsObject *object ) vips_icc_transform_build( VipsObject *object )
{ {
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
VipsColour *colour = (VipsColour *) object; VipsColour *colour = (VipsColour *) object;
VipsColourCode *code = (VipsColourCode *) object;
VipsIcc *icc = (VipsIcc *) object; VipsIcc *icc = (VipsIcc *) object;
VipsIccTransform *transform = (VipsIccTransform *) object; VipsIccTransform *transform = (VipsIccTransform *) object;
/* We read the input profile like this: if( vips_icc_set_import( icc,
* transform->embedded, transform->input_profile_filename ) )
* 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 );
}
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 );
}
if( !icc->in_profile ) {
vips_error( class->nickname, "%s", _( "no input profile" ) );
return( -1 ); return( -1 );
}
if( !icc->in_profile ) {
vips_error( class->nickname, "%s", _( "no input profile" ) );
return( -1 );
}
if( transform->output_profile_filename ) { if( transform->output_profile_filename ) {
if( vips_profile_load( transform->output_profile_filename, if( vips_profile_load( transform->output_profile_filename,
&icc->out_blob, NULL ) ) &icc->out_blob, NULL ) )
return( -1 ); return( -1 );
colour->profile_filename = transform->output_profile_filename; colour->profile_filename = transform->output_profile_filename;
} }
if( icc->out_blob ) if( icc->out_blob )
icc->out_profile = icc->out_profile = vips_icc_load_profile_blob( icc->out_blob,
vips_icc_load_profile_blob( icc->out_blob, NULL ); NULL, icc->intent, LCMS_USED_AS_OUTPUT );
if( !icc->out_profile ) { if( !icc->out_profile ) {
vips_error( class->nickname, "%s", _( "no output profile" ) ); vips_error( class->nickname, "%s", _( "no output profile" ) );
return( -1 ); return( -1 );
} }
vips_check_intent( class->nickname,
icc->in_profile, icc->intent, LCMS_USED_AS_INPUT );
vips_check_intent( class->nickname,
icc->out_profile, icc->intent, LCMS_USED_AS_OUTPUT );
if( VIPS_OBJECT_CLASS( vips_icc_transform_parent_class )-> if( VIPS_OBJECT_CLASS( vips_icc_transform_parent_class )->
build( object ) ) build( object ) )
return( -1 ); return( -1 );
return( 0 ); return( 0 );
} }
/* Process a buffer of data. /* Process a buffer of data.
*/ */
static void static void
skipping to change at line 1237 skipping to change at line 1312
cmsHPROFILE profile; cmsHPROFILE profile;
cmsCIEXYZ *media; cmsCIEXYZ *media;
double X, Y, Z; double X, Y, Z;
double *add; double *add;
double *mul; double *mul;
int i; int i;
if( !(profile = cmsOpenProfileFromFile( profile_filename, "r" )) ) if( !(profile = cmsOpenProfileFromFile( profile_filename, "r" )) )
return( -1 ); return( -1 );
#ifdef DEBUG
vips_icc_print_profile( profile_filename, profile );
#endif /*DEBUG*/
if( !(media = cmsReadTag( profile, cmsSigMediaWhitePointTag )) ) { if( !(media = cmsReadTag( profile, cmsSigMediaWhitePointTag )) ) {
vips_error( "vips_icc_ac2rc", vips_error( "vips_icc_ac2rc",
"%s", _( "unable to get media white point" ) ); "%s", _( "unable to get media white point" ) );
return( -1 ); return( -1 );
} }
X = media->X; X = media->X;
Y = media->Y; Y = media->Y;
Z = media->Z; Z = media->Z;
skipping to change at line 1296 skipping to change at line 1375
vips_icc_is_compatible_profile( VipsImage *image, vips_icc_is_compatible_profile( VipsImage *image,
const void *data, size_t data_length ) const void *data, size_t data_length )
{ {
cmsHPROFILE profile; cmsHPROFILE profile;
if( !(profile = cmsOpenProfileFromMem( data, data_length )) ) if( !(profile = cmsOpenProfileFromMem( data, data_length )) )
/* Corrupt profile. /* Corrupt profile.
*/ */
return( FALSE ); return( FALSE );
#ifdef DEBUG
vips_icc_print_profile( "from memory", profile );
#endif /*DEBUG*/
if( vips_image_expected_bands( image ) != if( vips_image_expected_bands( image ) !=
vips_icc_profile_needs_bands( profile ) ) { vips_icc_profile_needs_bands( profile ) ) {
VIPS_FREEF( cmsCloseProfile, profile ); VIPS_FREEF( cmsCloseProfile, profile );
return( FALSE ); return( FALSE );
} }
if( vips_image_expected_sig( image ) != cmsGetColorSpace( profile ) ) { if( vips_image_expected_sig( image ) != cmsGetColorSpace( profile ) ) {
VIPS_FREEF( cmsCloseProfile, profile ); VIPS_FREEF( cmsCloseProfile, profile );
return( FALSE ); return( FALSE );
} }
skipping to change at line 1348 skipping to change at line 1431
#endif /*HAVE_LCMS*/ #endif /*HAVE_LCMS*/
/** /**
* vips_icc_import: (method) * vips_icc_import: (method)
* @in: input image * @in: input image
* @out: (out): output image * @out: (out): output image
* @...: %NULL-terminated list of optional named arguments * @...: %NULL-terminated list of optional named arguments
* *
* Optional arguments: * Optional arguments:
* *
* * @input_profile: get the input profile from here * * @pcs: #VipsPCS, use XYZ or LAB PCS
* * @intent: transform with this intent * * @intent: #VipsIntent, transform with this intent
* * @embedded: use profile embedded in input image * * @black_point_compensation: %gboolean, enable black point compensation
* * @pcs: use XYZ or LAB PCS * * @embedded: %gboolean, use profile embedded in input image
* * @input_profile: %gchararray, get the input profile from here
* *
* Import an image from device space to D65 LAB with an ICC profile. If @pcs is * 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. * set to #VIPS_PCS_XYZ, use CIE XYZ PCS instead.
* *
* If @embedded is set, the input profile is taken from the input image * If @embedded is set, the input profile is taken from the input image
* metadata. If there is no embedded profile, * metadata. If there is no embedded profile,
* @input_profile_filename is used as a fall-back. * @input_profile_filename is used as a fall-back.
* You can test for the * You can test for the
* presence of an embedded profile with * presence of an embedded profile with
* vips_image_get_typeof() with #VIPS_META_ICC_NAME as an argument. This will * vips_image_get_typeof() with #VIPS_META_ICC_NAME as an argument. This will
* return %GType 0 if there is no profile. * return %GType 0 if there is no profile.
* *
* If @embedded is not set, the input profile is taken from * If @embedded is not set, the input profile is taken from
* @input_profile. If @input_profile is not supplied, the * @input_profile. If @input_profile is not supplied, the
* metadata profile, if any, is used as a fall-back. * metadata profile, if any, is used as a fall-back.
* *
* If @black_point_compensation is set, LCMS black point compensation is
* enabled.
*
* Returns: 0 on success, -1 on error. * Returns: 0 on success, -1 on error.
*/ */
int int
vips_icc_import( VipsImage *in, VipsImage **out, ... ) vips_icc_import( VipsImage *in, VipsImage **out, ... )
{ {
va_list ap; va_list ap;
int result; int result;
va_start( ap, out ); va_start( ap, out );
result = vips_call_split( "icc_import", ap, in, out ); result = vips_call_split( "icc_import", ap, in, out );
skipping to change at line 1391 skipping to change at line 1478
} }
/** /**
* vips_icc_export: (method) * vips_icc_export: (method)
* @in: input image * @in: input image
* @out: (out): output image * @out: (out): output image
* @...: %NULL-terminated list of optional named arguments * @...: %NULL-terminated list of optional named arguments
* *
* Optional arguments: * Optional arguments:
* *
* * @intent: transform with this intent * * @pcs: #VipsPCS, use XYZ or LAB PCS
* * @depth: depth of output image in bits * * @intent: #VipsIntent, transform with this intent
* * @output_profile: get the output profile from here * * @black_point_compensation: %gboolean, enable black point compensation
* * @pcs: use XYZ or LAB PCS * * @output_profile: %gchararray, get the output profile from here
* * @depth: %gint, depth of output image in bits
* *
* Export an image from D65 LAB to device space with an ICC profile. * Export an image from D65 LAB to device space with an ICC profile.
* If @pcs is * If @pcs is
* set to #VIPS_PCS_XYZ, use CIE XYZ PCS instead. * 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 not set, use the embedded profile, if any.
* If @output_profile is set, export with that and attach it to the output * If @output_profile is set, export with that and attach it to the output
* image. * image.
* *
* If @black_point_compensation is set, LCMS black point compensation is
* enabled.
*
* Returns: 0 on success, -1 on error. * Returns: 0 on success, -1 on error.
*/ */
int int
vips_icc_export( VipsImage *in, VipsImage **out, ... ) vips_icc_export( VipsImage *in, VipsImage **out, ... )
{ {
va_list ap; va_list ap;
int result; int result;
va_start( ap, out ); va_start( ap, out );
result = vips_call_split( "icc_export", ap, in, out ); result = vips_call_split( "icc_export", ap, in, out );
skipping to change at line 1427 skipping to change at line 1518
/** /**
* vips_icc_transform: (method) * vips_icc_transform: (method)
* @in: input image * @in: input image
* @out: (out): output image * @out: (out): output image
* @output_profile: get the output profile from here * @output_profile: get the output profile from here
* @...: %NULL-terminated list of optional named arguments * @...: %NULL-terminated list of optional named arguments
* *
* Optional arguments: * Optional arguments:
* *
* * @input_profile: get the input profile from here * * @pcs: #VipsPCS, use XYZ or LAB PCS
* * @intent: transform with this intent * * @intent: #VipsIntent, transform with this intent
* * @depth: depth of output image in bits * * @black_point_compensation: %gboolean, enable black point compensation
* * @embedded: use profile embedded in input image * * @embedded: %gboolean, use profile embedded in input image
* * @input_profile: %gchararray, get the input profile from here
* * @depth: %gint, depth of output image in bits
* *
* Transform an image with a pair of ICC profiles. The input image is moved to * 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 * profile-connection space with the input profile and then to the output
* space with the output profile. * space with the output profile.
* *
* If @embedded is set, the input profile is taken from the input image * If @embedded is set, the input profile is taken from the input image
* metadata, if present. If there is no embedded profile, * metadata, if present. If there is no embedded profile,
* @input_profile is used as a fall-back. * @input_profile is used as a fall-back.
* You can test for the * You can test for the
* presence of an embedded profile with * presence of an embedded profile with
* vips_image_get_typeof() with #VIPS_META_ICC_NAME as an argument. This will * vips_image_get_typeof() with #VIPS_META_ICC_NAME as an argument. This will
* return %GType 0 if there is no profile. * return %GType 0 if there is no profile.
* *
* If @embedded is not set, the input profile is taken from * If @embedded is not set, the input profile is taken from
* @input_profile. If @input_profile is not supplied, the * @input_profile. If @input_profile is not supplied, the
* metadata profile, if any, is used as a fall-back. * metadata profile, if any, is used as a fall-back.
* *
* If @black_point_compensation is set, LCMS black point compensation is
* enabled.
*
* The output image has the output profile attached to the #VIPS_META_ICC_NAME * The output image has the output profile attached to the #VIPS_META_ICC_NAME
* field. * field.
* *
* Use vips_icc_import() and vips_icc_export() to do either the first or * Use vips_icc_import() and vips_icc_export() to do either the first or
* second half of this operation in isolation. * second half of this operation in isolation.
* *
* Returns: 0 on success, -1 on error. * Returns: 0 on success, -1 on error.
*/ */
int int
vips_icc_transform( VipsImage *in, VipsImage **out, vips_icc_transform( VipsImage *in, VipsImage **out,
 End of changes. 57 change blocks. 
197 lines changed or deleted 293 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)