Skip to content

Commit 2903876

Browse files
authored
colour: use floating-point APIs of lcms (#3373)
Avoids the need to pack the buffer of floats into lcms's fixed-point formats and ensures full precision internally in lcms. Resolves: #3150. Supersedes: #3368.
1 parent 28d4d72 commit 2903876

File tree

2 files changed

+60
-107
lines changed

2 files changed

+60
-107
lines changed

ChangeLog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ TBD 8.14.2
1111
- earlier abort of webpsave on kill [dloebl]
1212
- fix thumbnail of CMYK images with an embedded ICC profile [kleisauke]
1313
- fix ICC handling of RGB images with a monochrome profile [kleisauke]
14+
- ensure ICC transforms keep all precision [kleisauke]
1415

1516
9/1/23 8.14.1
1617

libvips/colour/icc_transform.c

Lines changed: 59 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,8 @@ static VipsIccInfo vips_icc_info_table[] = {
226226
{ cmsSigGrayData, 1, TYPE_GRAY_8, TYPE_GRAY_16 },
227227

228228
{ cmsSigRgbData, 3, TYPE_RGB_8, TYPE_RGB_16 },
229-
{ cmsSigLabData, 3, TYPE_Lab_8, TYPE_Lab_16 },
230-
{ cmsSigXYZData, 3, -1, TYPE_XYZ_16 },
229+
{ cmsSigLabData, 3, TYPE_Lab_FLT, TYPE_Lab_16 },
230+
{ cmsSigXYZData, 3, TYPE_XYZ_FLT, TYPE_XYZ_16 },
231231

232232
{ cmsSigCmykData, 4, TYPE_CMYK_8, TYPE_CMYK_16 },
233233
{ cmsSig4colorData, 4, TYPE_CMYK_8, TYPE_CMYK_16 },
@@ -308,14 +308,14 @@ vips_icc_build( VipsObject *object )
308308
code->input_format = VIPS_FORMAT_FLOAT;
309309
code->input_interpretation =
310310
VIPS_INTERPRETATION_LAB;
311-
icc->in_icc_format = info->lcms_type16;
311+
icc->in_icc_format = info->lcms_type8;
312312
break;
313313

314314
case cmsSigXYZData:
315315
code->input_format = VIPS_FORMAT_FLOAT;
316316
code->input_interpretation =
317317
VIPS_INTERPRETATION_XYZ;
318-
icc->in_icc_format = info->lcms_type16;
318+
icc->in_icc_format = info->lcms_type8;
319319
break;
320320

321321
case cmsSigCmykData:
@@ -654,14 +654,13 @@ vips_icc_load_profile_blob( VipsBlob *blob,
654654
* unref the blob if it's useless.
655655
*/
656656
static cmsHPROFILE
657-
vips_icc_verify_blob( VipsBlob **blob,
658-
VipsImage *image, VipsIntent intent, int direction )
657+
vips_icc_verify_blob( VipsBlob **blob, VipsImage *image, VipsIntent intent )
659658
{
660659
if( *blob ) {
661660
cmsHPROFILE profile;
662661

663662
if( !(profile = vips_icc_load_profile_blob( *blob,
664-
image, intent, direction )) ) {
663+
image, intent, LCMS_USED_AS_INPUT )) ) {
665664
vips_area_unref( (VipsArea *) *blob );
666665
*blob = NULL;
667666
}
@@ -701,7 +700,7 @@ vips_icc_set_import( VipsIcc *icc,
701700
(embedded || !input_profile_filename) ) {
702701
icc->in_blob = vips_icc_get_profile_image( code->in );
703702
icc->in_profile = vips_icc_verify_blob( &icc->in_blob,
704-
code->in, icc->intent, LCMS_USED_AS_INPUT );
703+
code->in, icc->intent );
705704
}
706705

707706
/* Try profile from filename.
@@ -713,7 +712,7 @@ vips_icc_set_import( VipsIcc *icc,
713712
!vips_profile_load( input_profile_filename,
714713
&icc->in_blob, NULL ) &&
715714
(icc->in_profile = vips_icc_verify_blob( &icc->in_blob,
716-
code->in, icc->intent, LCMS_USED_AS_INPUT )) )
715+
code->in, icc->intent )) )
717716
icc->non_standard_input_profile = TRUE;
718717
}
719718

@@ -727,7 +726,7 @@ vips_icc_set_import( VipsIcc *icc,
727726
if(
728727
!vips_profile_load( name, &icc->in_blob, NULL ) &&
729728
(icc->in_profile = vips_icc_verify_blob( &icc->in_blob,
730-
code->in, icc->intent, LCMS_USED_AS_INPUT )) )
729+
code->in, icc->intent )) )
731730
icc->non_standard_input_profile = TRUE;
732731
}
733732

@@ -840,44 +839,48 @@ vips_icc_import_build( VipsObject *object )
840839
return( 0 );
841840
}
842841

843-
static void
842+
static void
844843
decode_lab( guint16 *fixed, float *lab, int n )
845844
{
846845
int i;
847846

848-
for( i = 0; i < n; i++ ) {
849-
lab[0] = (double) fixed[0] / 652.800;
850-
lab[1] = ((double) fixed[1] / 256.0) - 128.0;
851-
lab[2] = ((double) fixed[2] / 256.0) - 128.0;
847+
for( i = 0; i < n; i++ ) {
848+
/* cmsLabEncoded2Float inlined.
849+
*/
850+
lab[0] = (double) fixed[0] / 655.35;
851+
lab[1] = ((double) fixed[1] / 257.0) - 128.0;
852+
lab[2] = ((double) fixed[2] / 257.0) - 128.0;
852853

853-
lab += 3;
854-
fixed += 3;
855-
}
854+
lab += 3;
855+
fixed += 3;
856+
}
856857
}
857858

858-
#define X_FAC (VIPS_D50_X0 * 32768 / (VIPS_D65_X0 * 100))
859-
#define Y_FAC (VIPS_D50_Y0 * 32768 / (VIPS_D65_Y0 * 100))
860-
#define Z_FAC (VIPS_D50_Z0 * 32768 / (VIPS_D65_Z0 * 100))
859+
#define X_FAC (cmsD50X / VIPS_D65_X0)
860+
#define Y_FAC (cmsD50Y / VIPS_D65_Y0)
861+
#define Z_FAC (cmsD50Z / VIPS_D65_Z0)
861862

862-
static void
863+
static void
863864
decode_xyz( guint16 *fixed, float *xyz, int n )
864865
{
865866
int i;
866867

867-
for( i = 0; i < n; i++ ) {
868-
xyz[0] = (double) fixed[0] / X_FAC;
869-
xyz[1] = (double) fixed[1] / Y_FAC;
870-
xyz[2] = (double) fixed[2] / Z_FAC;
868+
for( i = 0; i < n; i++ ) {
869+
/* cmsXYZEncoded2Float inlined.
870+
*/
871+
xyz[0] = (double) fixed[0] / (X_FAC * 32768.0);
872+
xyz[1] = (double) fixed[1] / (Y_FAC * 32768.0);
873+
xyz[2] = (double) fixed[2] / (Z_FAC * 32768.0);
871874

872-
xyz += 3;
873-
fixed += 3;
874-
}
875+
xyz += 3;
876+
fixed += 3;
877+
}
875878
}
876879

877880
/* Process a buffer of data.
878881
*/
879882
static void
880-
vips_icc_import_line( VipsColour *colour,
883+
vips_icc_import_line( VipsColour *colour,
881884
VipsPel *out, VipsPel **in, int width )
882885
{
883886
VipsIcc *icc = (VipsIcc *) colour;
@@ -897,7 +900,7 @@ vips_icc_import_line( VipsColour *colour,
897900

898901
cmsDoTransform( icc->trans, p, encoded, chunk );
899902

900-
if( icc->pcs == VIPS_PCS_LAB )
903+
if( icc->pcs == VIPS_PCS_LAB )
901904
decode_lab( encoded, q, chunk );
902905
else
903906
decode_xyz( encoded, q, chunk );
@@ -1006,84 +1009,23 @@ vips_icc_export_build( VipsObject *object )
10061009
return( 0 );
10071010
}
10081011

1009-
/* Pack a buffer of floats into lcms's fixed-point formats. Cut from
1010-
* lcms-1.0.8.
1011-
*/
1012-
static void
1013-
encode_lab( float *lab, guint16 *fixed, int n )
1014-
{
1015-
int i;
1016-
1017-
for( i = 0; i < n; i++ ) {
1018-
float L = lab[0];
1019-
float a = lab[1];
1020-
float b = lab[2];
1021-
1022-
if( L < 0 )
1023-
L = 0;
1024-
if( L > 100. )
1025-
L = 100.;
1026-
1027-
if( a < -128. )
1028-
a = -128;
1029-
if( a > 127.9961 )
1030-
a = 127.9961;
1031-
if( b < -128. )
1032-
b = -128;
1033-
if( b > 127.9961 )
1034-
b = 127.9961;
1035-
1036-
fixed[0] = L * 652.800 + 0.5;
1037-
fixed[1] = (a + 128.0) * 256.0 + 0.5;
1038-
fixed[2] = (b + 128.0) * 256.0 + 0.5;
1039-
1040-
lab += 3;
1041-
fixed += 3;
1042-
}
1043-
}
1044-
1045-
#define MAX_ENCODEABLE_XYZ (100 * (1.0 + 32767.0 / 32768.0))
1046-
1047-
// 1.15 fixed point for XYZ
1048-
1049-
static void
1050-
encode_xyz( float *xyz, guint16 *fixed, int n )
1012+
static void
1013+
encode_xyz( float *in, float *out, int n )
10511014
{
10521015
int i;
10531016

10541017
for( i = 0; i < n; i++ ) {
1055-
float X = xyz[0];
1056-
float Y = xyz[1];
1057-
float Z = xyz[2];
1058-
1059-
if( X < 0 )
1060-
X = 0;
1061-
if( X > MAX_ENCODEABLE_XYZ )
1062-
X = MAX_ENCODEABLE_XYZ;
1063-
1064-
if( Y < 0 )
1065-
Y = 0;
1066-
if( Y > MAX_ENCODEABLE_XYZ )
1067-
Y = MAX_ENCODEABLE_XYZ;
1018+
out[0] = in[0] * X_FAC;
1019+
out[1] = in[1] * Y_FAC;
1020+
out[2] = in[2] * Z_FAC;
10681021

1069-
if( Z < 0 )
1070-
Z = 0;
1071-
if( Z > MAX_ENCODEABLE_XYZ )
1072-
Z = MAX_ENCODEABLE_XYZ;
1073-
1074-
fixed[0] = X * X_FAC + 0.5;
1075-
fixed[1] = Y * Y_FAC + 0.5;
1076-
fixed[2] = Z * Z_FAC + 0.5;
1077-
1078-
xyz += 3;
1079-
fixed += 3;
1022+
in += 3;
1023+
out += 3;
10801024
}
10811025
}
10821026

1083-
/* Process a buffer of data.
1084-
*/
10851027
static void
1086-
vips_icc_export_line( VipsColour *colour,
1028+
vips_icc_export_line_xyz( VipsColour *colour,
10871029
VipsPel *out, VipsPel **in, int width )
10881030
{
10891031
VipsIcc *icc = (VipsIcc *) colour;
@@ -1092,27 +1034,37 @@ vips_icc_export_line( VipsColour *colour,
10921034
VipsPel *q;
10931035
int x;
10941036

1095-
/* Buffer of encoded 16-bit pixels we transform.
1037+
/* Buffer of encoded float pixels we transform.
10961038
*/
1097-
guint16 encoded[3 * PIXEL_BUFFER_SIZE];
1039+
float encoded[3 * PIXEL_BUFFER_SIZE];
10981040

10991041
p = (float *) in[0];
11001042
q = (VipsPel *) out;
11011043
for( x = 0; x < width; x += PIXEL_BUFFER_SIZE ) {
11021044
const int chunk = VIPS_MIN( width - x, PIXEL_BUFFER_SIZE );
11031045

1104-
if( icc->pcs == VIPS_PCS_LAB )
1105-
encode_lab( p, encoded, chunk );
1106-
else
1107-
encode_xyz( p, encoded, chunk );
1108-
1046+
encode_xyz( p, encoded, chunk );
11091047
cmsDoTransform( icc->trans, encoded, q, chunk );
11101048

11111049
p += PIXEL_BUFFER_SIZE * 3;
11121050
q += PIXEL_BUFFER_SIZE * VIPS_IMAGE_SIZEOF_PEL( colour->out );
11131051
}
11141052
}
11151053

1054+
/* Process a buffer of data.
1055+
*/
1056+
static void
1057+
vips_icc_export_line( VipsColour *colour,
1058+
VipsPel *out, VipsPel **in, int width )
1059+
{
1060+
VipsIcc *icc = (VipsIcc *) colour;
1061+
1062+
if( icc->pcs == VIPS_PCS_LAB )
1063+
cmsDoTransform( icc->trans, in[0], out, width );
1064+
else
1065+
vips_icc_export_line_xyz( colour, out, in, width );
1066+
}
1067+
11161068
static void
11171069
vips_icc_export_class_init( VipsIccExportClass *class )
11181070
{

0 commit comments

Comments
 (0)