add suport for N colour ICC profiles (#3046)

* seems to export to cmyk+2 correctly

We'll need something fancier for import.

* more hacking

* Revert "more hacking"

This reverts commit fcdad5b491fc4d5a1750388947e42d1e98b1d9d7.

* generalise to many colour export

Should work up to 12 colour profiles (all that lcms supports).

* support import with N-colour profiles
This commit is contained in:
John Cupitt 2022-10-19 09:19:18 +01:00 committed by GitHub
parent c8a88c618b
commit 1297f2c6d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 145 additions and 225 deletions

View File

@ -13,6 +13,7 @@ master
- add ".pnm" save [ewelot]
- threaded tiff jp2k and jpeg decompress
- improve speed and efficiency of animated WebP write [dloebl]
- support for N-colour ICC profiles
11/10/22 started 8.13.3
- improve rules for 16-bit heifsave [johntrunc]

View File

@ -213,6 +213,46 @@ is_pcs( cmsHPROFILE profile )
cmsGetColorSpace( profile ) == cmsSigXYZData );
}
/* Info for all supported lcms profile signatures.
*/
typedef struct _VipsIccInfo {
int signature;
int bands;
guint lcms_type8;
guint lcms_type16;
} VipsIccInfo;
static VipsIccInfo vips_icc_info_table[] = {
{ cmsSigGrayData, 1, TYPE_GRAY_8, TYPE_GRAY_16 },
{ cmsSigRgbData, 3, TYPE_RGB_8, TYPE_RGB_16 },
{ cmsSigLabData, 3, TYPE_Lab_8, TYPE_Lab_16 },
{ cmsSigXYZData, 3, -1, TYPE_XYZ_16 },
{ cmsSigCmykData, 4, TYPE_CMYK_8, TYPE_CMYK_16 },
{ cmsSig4colorData, 4, TYPE_CMYK_8, TYPE_CMYK_16 },
{ cmsSig5colorData, 5, TYPE_CMYK5_8, TYPE_CMYK5_16 },
{ cmsSig6colorData, 6, TYPE_CMYK6_8, TYPE_CMYK6_16 },
{ cmsSig7colorData, 7, TYPE_CMYK7_8, TYPE_CMYK7_16 },
{ cmsSig8colorData, 8, TYPE_CMYK8_8, TYPE_CMYK8_16 },
{ cmsSig9colorData, 9, TYPE_CMYK9_8, TYPE_CMYK9_16 },
{ cmsSig10colorData, 10, TYPE_CMYK10_8, TYPE_CMYK10_16 },
{ cmsSig11colorData, 11, TYPE_CMYK11_8, TYPE_CMYK11_16 },
{ cmsSig12colorData, 12, TYPE_CMYK12_8, TYPE_CMYK12_16 },
};
static VipsIccInfo *
vips_icc_info( int signature )
{
int i;
for( i = 0; i < VIPS_NUMBER( vips_icc_info_table ); i++ )
if( vips_icc_info_table[i].signature == signature )
return( &vips_icc_info_table[i] );
return( NULL );
}
static int
vips_icc_build( VipsObject *object )
{
@ -232,75 +272,95 @@ vips_icc_build( VipsObject *object )
if( icc->in_profile &&
code->in ) {
switch( cmsGetColorSpace( icc->in_profile ) ) {
case cmsSigRgbData:
colour->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;
int signature;
VipsIccInfo *info;
signature = cmsGetColorSpace( icc->in_profile );
if( !(info = vips_icc_info( signature )) ) {
vips_error( class->nickname,
_( "unimplemented input color space 0x%x" ),
signature );
return( -1 );
}
colour->input_bands = info->bands;
switch( signature ) {
case cmsSigGrayData:
colour->input_bands = 1;
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_GRAY_16 : TYPE_GRAY_8;
info->lcms_type16 : info->lcms_type8;
break;
case cmsSigCmykData:
colour->input_bands = 4;
case cmsSigRgbData:
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;
info->lcms_type16 : info->lcms_type8;
break;
case cmsSigLabData:
colour->input_bands = 3;
code->input_format = VIPS_FORMAT_FLOAT;
code->input_interpretation =
VIPS_INTERPRETATION_LAB;
icc->in_icc_format = TYPE_Lab_16;
icc->in_icc_format = info->lcms_type16;
break;
case cmsSigXYZData:
colour->input_bands = 3;
code->input_format = VIPS_FORMAT_FLOAT;
icc->in_icc_format = TYPE_XYZ_16;
code->input_interpretation =
VIPS_INTERPRETATION_XYZ;
icc->in_icc_format = info->lcms_type16;
break;
case cmsSigCmykData:
case cmsSig5colorData:
case cmsSig6colorData:
case cmsSig7colorData:
case cmsSig8colorData:
case cmsSig9colorData:
case cmsSig10colorData:
case cmsSig11colorData:
case cmsSig12colorData:
/* Treat as forms of CMYK.
*/
info = vips_icc_info(
cmsGetColorSpace( icc->in_profile ) );
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 ?
info->lcms_type16 : info->lcms_type8;
break;
default:
vips_error( class->nickname,
_( "unimplemented input color space 0x%x" ),
cmsGetColorSpace( icc->in_profile ) );
g_assert_not_reached();
return( -1 );
}
}
if( icc->out_profile )
switch( cmsGetColorSpace( icc->out_profile ) ) {
case cmsSigRgbData:
colour->interpretation =
icc->depth == 8 ?
VIPS_INTERPRETATION_sRGB :
VIPS_INTERPRETATION_RGB16;
colour->format =
icc->depth == 8 ?
VIPS_FORMAT_UCHAR : VIPS_FORMAT_USHORT;
colour->bands = 3;
icc->out_icc_format =
icc->depth == 16 ?
TYPE_RGB_16 : TYPE_RGB_8;
break;
if( icc->out_profile ) {
int signature;
VipsIccInfo *info;
signature = cmsGetColorSpace( icc->out_profile );
if( !(info = vips_icc_info( signature )) ) {
vips_error( class->nickname,
_( "unimplemented output color space 0x%x" ),
signature );
return( -1 );
}
colour->bands = info->bands;
switch( signature ) {
case cmsSigGrayData:
colour->interpretation =
icc->depth == 8 ?
@ -309,43 +369,61 @@ vips_icc_build( VipsObject *object )
colour->format =
icc->depth == 8 ?
VIPS_FORMAT_UCHAR : VIPS_FORMAT_USHORT;
colour->bands = 1;
icc->out_icc_format =
icc->depth == 16 ?
TYPE_GRAY_16 : TYPE_GRAY_8;
info->lcms_type16 : info->lcms_type8;
break;
case cmsSigCmykData:
colour->interpretation = VIPS_INTERPRETATION_CMYK;
case cmsSigRgbData:
colour->interpretation =
icc->depth == 8 ?
VIPS_INTERPRETATION_sRGB :
VIPS_INTERPRETATION_RGB16;
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;
info->lcms_type16 : info->lcms_type8;
break;
case cmsSigLabData:
colour->interpretation = VIPS_INTERPRETATION_LAB;
colour->format = VIPS_FORMAT_FLOAT;
colour->bands = 3;
icc->out_icc_format = TYPE_Lab_16;
icc->out_icc_format = info->lcms_type16;
break;
case cmsSigXYZData:
colour->interpretation = VIPS_INTERPRETATION_XYZ;
colour->format = VIPS_FORMAT_FLOAT;
colour->bands = 3;
icc->out_icc_format = TYPE_XYZ_16;
icc->out_icc_format = info->lcms_type16;
break;
case cmsSigCmykData:
case cmsSig5colorData:
case cmsSig6colorData:
case cmsSig7colorData:
case cmsSig8colorData:
case cmsSig9colorData:
case cmsSig10colorData:
case cmsSig11colorData:
case cmsSig12colorData:
/* Treat as forms of CMYK.
*/
colour->interpretation = VIPS_INTERPRETATION_CMYK;
colour->format =
icc->depth == 8 ?
VIPS_FORMAT_UCHAR : VIPS_FORMAT_USHORT;
icc->out_icc_format =
icc->depth == 16 ?
info->lcms_type16 : info->lcms_type8;
break;
default:
vips_error( class->nickname,
_( "unimplemented output color space 0x%x" ),
cmsGetColorSpace( icc->out_profile ) );
g_assert_not_reached();
return( -1 );
}
}
/* At least one must be a device profile.
*/
@ -467,166 +545,6 @@ vips_icc_print_profile( const char *name, cmsHPROFILE profile )
}
#endif /*DEBUG*/
/* How many bands we expect to see from an image after preprocessing by our
* parent classes. This is a bit fragile :-(
*
* FIXME ... split the _build() for colour into separate preprocess / process
* / postprocess phases so we can load profiles after preprocess but before
* actual processing takes place.
*/
static int
vips_image_expected_bands( VipsImage *image )
{
int expected_bands;
switch( image->Type ) {
case VIPS_INTERPRETATION_B_W:
case VIPS_INTERPRETATION_GREY16:
expected_bands = 1;
break;
case VIPS_INTERPRETATION_XYZ:
case VIPS_INTERPRETATION_LAB:
case VIPS_INTERPRETATION_LABQ:
case VIPS_INTERPRETATION_RGB:
case VIPS_INTERPRETATION_CMC:
case VIPS_INTERPRETATION_LCH:
case VIPS_INTERPRETATION_LABS:
case VIPS_INTERPRETATION_sRGB:
case VIPS_INTERPRETATION_YXY:
case VIPS_INTERPRETATION_RGB16:
case VIPS_INTERPRETATION_scRGB:
case VIPS_INTERPRETATION_HSV:
expected_bands = 3;
break;
case VIPS_INTERPRETATION_CMYK:
expected_bands = 4;
break;
case VIPS_INTERPRETATION_MULTIBAND:
case VIPS_INTERPRETATION_HISTOGRAM:
case VIPS_INTERPRETATION_MATRIX:
case VIPS_INTERPRETATION_FOURIER:
default:
expected_bands = image->Bands;
break;
}
expected_bands = VIPS_MIN( expected_bands, image->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
* preprocessing. Again, fragile :( see the FIXME above.
*/
static cmsColorSpaceSignature
vips_image_expected_sig( VipsImage *image )
{
cmsColorSpaceSignature expected_sig;
switch( image->Type ) {
case VIPS_INTERPRETATION_B_W:
case VIPS_INTERPRETATION_GREY16:
expected_sig = cmsSigGrayData;
break;
case VIPS_INTERPRETATION_LAB:
case VIPS_INTERPRETATION_LABQ:
case VIPS_INTERPRETATION_LABS:
expected_sig = cmsSigLabData;
break;
case VIPS_INTERPRETATION_sRGB:
case VIPS_INTERPRETATION_RGB:
case VIPS_INTERPRETATION_RGB16:
case VIPS_INTERPRETATION_scRGB:
expected_sig = cmsSigRgbData;
break;
case VIPS_INTERPRETATION_XYZ:
expected_sig = cmsSigXYZData;
break;
case VIPS_INTERPRETATION_CMYK:
expected_sig = cmsSigCmykData;
break;
case VIPS_INTERPRETATION_HSV:
expected_sig = cmsSigHsvData;
break;
case VIPS_INTERPRETATION_YXY:
expected_sig = cmsSigYxyData;
break;
case VIPS_INTERPRETATION_MULTIBAND:
/* A generic many-band image. Try to guess from the number of
* image bands instead.
*/
switch( image->Bands ) {
case 1:
case 2:
expected_sig = cmsSigGrayData;
break;
case 3:
expected_sig = cmsSigRgbData;
break;
case 4:
case 5:
expected_sig = cmsSigCmykData;
break;
default:
expected_sig = -1;
break;
}
break;
case VIPS_INTERPRETATION_LCH:
case VIPS_INTERPRETATION_CMC:
case VIPS_INTERPRETATION_HISTOGRAM:
case VIPS_INTERPRETATION_MATRIX:
case VIPS_INTERPRETATION_FOURIER:
default:
expected_sig = -1;
break;
}
return( expected_sig );
}
/* Load a profile from a blob and check compatibility with image, intent and
* direction.
*
@ -639,6 +557,7 @@ vips_icc_load_profile_blob( VipsBlob *blob,
const void *data;
size_t size;
cmsHPROFILE profile;
VipsIccInfo *info;
#ifdef DEBUG
printf( "loading %s profile, intent %s, from blob %p\n",
@ -657,20 +576,16 @@ vips_icc_load_profile_blob( VipsBlob *blob,
vips_icc_print_profile( "loaded from blob to make", profile );
#endif /*DEBUG*/
if( image &&
vips_image_expected_bands( image ) !=
vips_icc_profile_needs_bands( profile ) ) {
if( !(info = vips_icc_info( cmsGetColorSpace( profile ) )) ) {
VIPS_FREEF( cmsCloseProfile, profile );
g_warning( "%s", _( "profile incompatible with image" ) );
return( NULL );
}
g_warning( "%s", _( "unsupported profile" ) );
return( NULL );
}
if( image &&
vips_image_expected_sig( image ) !=
cmsGetColorSpace( profile ) ) {
image->Bands < info->bands ) {
VIPS_FREEF( cmsCloseProfile, profile );
g_warning( "%s",
_( "profile colourspace differs from image" ) );
g_warning( "%s", _( "profile incompatible with image" ) );
return( NULL );
}
@ -1385,6 +1300,7 @@ vips_icc_is_compatible_profile( VipsImage *image,
const void *data, size_t data_length )
{
cmsHPROFILE profile;
VipsIccInfo *info;
if( !(profile = cmsOpenProfileFromMem( data, data_length )) )
/* Corrupt profile.
@ -1395,13 +1311,16 @@ vips_icc_is_compatible_profile( VipsImage *image,
vips_icc_print_profile( "from memory", profile );
#endif /*DEBUG*/
if( vips_image_expected_bands( image ) !=
vips_icc_profile_needs_bands( profile ) ) {
if( !(info = vips_icc_info( cmsGetColorSpace( profile ) )) ) {
/* Unsupported profile.
*/
VIPS_FREEF( cmsCloseProfile, profile );
return( FALSE );
}
return( FALSE );
}
if( vips_image_expected_sig( image ) != cmsGetColorSpace( profile ) ) {
if( image->Bands < info->bands ) {
/* Too few bands,
*/
VIPS_FREEF( cmsCloseProfile, profile );
return( FALSE );
}