3 // Copyright (C) 1998-2007 Marti Maria
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the "Software"),
7 // to deal in the Software without restriction, including without limitation
8 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 // and/or sell copies of the Software, and to permit persons to whom the Software
10 // is furnished to do so, subject to the following conditions:
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
17 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 Gamut check by default is a catching of 0xFFFF/0xFFFF/0xFFFF PCS values, used
28 internally by lcms to hold invalid values. Matrix LUT's, operates in a way that
29 unencodeable values are marked as this combination, if PCS is XYZ, this is a very
30 high value since encoding is a 1.15 fixed point, something like 1.9997, 1.9997, 1.9997
31 not a very common color after all. Lab PCS is not to be a problem, since L>100 are truely
32 undefined. There is a posibility than ICC comitee defines L>100 as a valid means
33 to use highlights, then it will be lost.
35 (1.10 - Actually ICC did it, so this should be checked for full ICC 4.0 support)
40 LCMSBOOL
_cmsEndPointsBySpace(icColorSpaceSignature Space
, WORD
**White
, WORD
**Black
,
43 // Only most common spaces
45 static WORD RGBblack
[4] = { 0, 0, 0 };
46 static WORD RGBwhite
[4] = { 0xffff, 0xffff, 0xffff };
47 static WORD CMYKblack
[4] = { 0xffff, 0xffff, 0xffff, 0xffff }; // 400% of ink
48 static WORD CMYKwhite
[4] = { 0, 0, 0, 0 };
49 static WORD LABblack
[4] = { 0, 0x8000, 0x8000 };
50 static WORD LABwhite
[4] = { 0xFF00, 0x8000, 0x8000 };
51 static WORD CMYblack
[4] = { 0xffff, 0xffff, 0xffff };
52 static WORD CMYwhite
[4] = { 0, 0, 0 };
53 static WORD Grayblack
[4] = { 0 };
54 static WORD GrayWhite
[4] = { 0xffff };
58 case icSigGrayData
: if (White
) *White
= GrayWhite
;
59 if (Black
) *Black
= Grayblack
;
60 if (nOutputs
) *nOutputs
= 1;
63 case icSigRgbData
: if (White
) *White
= RGBwhite
;
64 if (Black
) *Black
= RGBblack
;
65 if (nOutputs
) *nOutputs
= 3;
68 case icSigLabData
: if (White
) *White
= LABwhite
;
69 if (Black
) *Black
= LABblack
;
70 if (nOutputs
) *nOutputs
= 3;
73 case icSigCmykData
: if (White
) *White
= CMYKwhite
;
74 if (Black
) *Black
= CMYKblack
;
75 if (nOutputs
) *nOutputs
= 4;
78 case icSigCmyData
: if (White
) *White
= CMYwhite
;
79 if (Black
) *Black
= CMYblack
;
80 if (nOutputs
) *nOutputs
= 3;
90 WORD
*_cmsWhiteBySpace(icColorSpaceSignature Space
)
92 WORD
*White
= NULL
, *Black
= NULL
;
94 static WORD Default
[MAXCHANNELS
];
96 if (_cmsEndPointsBySpace(Space
, &White
, &Black
, &Dummy
))
106 WORD
Clamp_L(Fixed32 in
)
108 if (in
== 0xFFFF) return 0xFFFFU
; // Marker
110 if (in
> 0xFF00) return 0xFF00U
; // L* = 100.0
115 #define ENCODE_AB(x) (WORD) (((x) + 128.0) * 256.0 + 0.5)
117 WORD
Clamp_ab(Fixed32 in
)
119 if (in
== 0xFFFF) return 0xFFFFU
; // Marker
121 if (in
< 0) return ENCODE_AB(-128.0); // Max negative number
122 if (in
> 0xFFFF) return ENCODE_AB(+127.9961); // Max positive number
128 // Returns dE on two Lab values
130 double LCMSEXPORT
cmsDeltaE(LPcmsCIELab Lab1
, LPcmsCIELab Lab2
)
135 Lab2
-> L
< 0) return 65536.;
137 if (Lab1
-> a
< -200 || Lab1
-> a
> 200) return 65536.;
138 if (Lab1
-> b
< -200 || Lab1
-> b
> 200) return 65536.;
140 if (Lab2
-> a
< -200 || Lab2
-> a
> 200) return 65536.;
141 if (Lab2
-> b
< -200 || Lab2
-> b
> 200) return 65536.;
143 if (Lab1
->L
== 0 && Lab2
->L
== 0) return 0;
145 dL
= fabs(Lab1
-> L
- Lab2
-> L
);
146 da
= fabs(Lab1
-> a
- Lab2
-> a
);
147 db
= fabs(Lab1
-> b
- Lab2
-> b
);
149 return pow(dL
*dL
+ da
* da
+ db
* db
, 0.5);
161 // Return the CIE94 Delta E
162 double LCMSEXPORT
cmsCIE94DeltaE(LPcmsCIELab Lab1
, LPcmsCIELab Lab2
)
164 cmsCIELCh LCh1
, LCh2
;
165 double dE
, dL
, dC
, dh
, dhsq
;
168 if (Lab1
->L
== 0 && Lab2
->L
== 0) return 0;
170 dL
= fabs(Lab1
->L
- Lab2
->L
);
172 cmsLab2LCh(&LCh1
, Lab1
);
173 cmsLab2LCh(&LCh2
, Lab2
);
175 dC
= fabs(LCh1
.C
- LCh2
.C
);
176 dE
= cmsDeltaE(Lab1
, Lab2
);
178 dhsq
= Sqr(dE
) - Sqr(dL
) - Sqr(dC
);
184 c12
= sqrt(LCh1
.C
* LCh2
.C
);
186 sc
= 1.0 + (0.048 * c12
);
187 sh
= 1.0 + (0.014 * c12
);
189 return sqrt(Sqr(dL
) + Sqr(dC
) / Sqr(sc
) + Sqr(dh
) / Sqr(sh
));
196 double ComputeLBFD(LPcmsCIELab Lab
)
200 if (Lab
->L
> 7.996969)
201 yt
= (Sqr((Lab
->L
+16)/116)*((Lab
->L
+16)/116))*100;
203 yt
= 100 * (Lab
->L
/ 903.3);
205 return (54.6 * (LOGE
* (log(yt
+ 1.5))) - 9.6);
210 // bfd - gets BFD(1:1) difference between Lab1, Lab2
211 double LCMSEXPORT
cmsBFDdeltaE(LPcmsCIELab Lab1
, LPcmsCIELab Lab2
)
213 double lbfd1
,lbfd2
,AveC
,Aveh
,dE
,deltaL
,
214 deltaC
,deltah
,dc
,t
,g
,dh
,rh
,rc
,rt
,bfd
;
215 cmsCIELCh LCh1
, LCh2
;
218 if (Lab1
->L
== 0 && Lab2
->L
== 0) return 0;
220 lbfd1
= ComputeLBFD(Lab1
);
221 lbfd2
= ComputeLBFD(Lab2
);
222 deltaL
= lbfd2
- lbfd1
;
224 cmsLab2LCh(&LCh1
, Lab1
);
225 cmsLab2LCh(&LCh2
, Lab2
);
227 deltaC
= LCh2
.C
- LCh1
.C
;
228 AveC
= (LCh1
.C
+LCh2
.C
)/2;
229 Aveh
= (LCh1
.h
+LCh2
.h
)/2;
231 dE
= cmsDeltaE(Lab1
, Lab2
);
233 if (Sqr(dE
)>(Sqr(Lab2
->L
-Lab1
->L
)+Sqr(deltaC
)))
234 deltah
= sqrt(Sqr(dE
)-Sqr(Lab2
->L
-Lab1
->L
)-Sqr(deltaC
));
239 dc
= 0.035 * AveC
/ (1 + 0.00365 * AveC
)+0.521;
240 g
= sqrt(Sqr(Sqr(AveC
))/(Sqr(Sqr(AveC
))+14000));
241 t
= 0.627+(0.055*cos((Aveh
-254)/(180/M_PI
))-
242 0.040*cos((2*Aveh
-136)/(180/M_PI
))+
243 0.070*cos((3*Aveh
-31)/(180/M_PI
))+
244 0.049*cos((4*Aveh
+114)/(180/M_PI
))-
245 0.015*cos((5*Aveh
-103)/(180/M_PI
)));
248 rh
= -0.260*cos((Aveh
-308)/(180/M_PI
))-
249 0.379*cos((2*Aveh
-160)/(180/M_PI
))-
250 0.636*cos((3*Aveh
+254)/(180/M_PI
))+
251 0.226*cos((4*Aveh
+140)/(180/M_PI
))-
252 0.194*cos((5*Aveh
+280)/(180/M_PI
));
254 rc
= sqrt((AveC
*AveC
*AveC
*AveC
*AveC
*AveC
)/((AveC
*AveC
*AveC
*AveC
*AveC
*AveC
)+70000000));
257 bfd
= sqrt(Sqr(deltaL
)+Sqr(deltaC
/dc
)+Sqr(deltah
/dh
)+(rt
*(deltaC
/dc
)*(deltah
/dh
)));
263 // cmc - CMC(1:1) difference between Lab1, Lab2
264 double LCMSEXPORT
cmsCMCdeltaE(LPcmsCIELab Lab1
, LPcmsCIELab Lab2
)
266 double dE
,dL
,dC
,dh
,sl
,sc
,sh
,t
,f
,cmc
;
267 cmsCIELCh LCh1
, LCh2
;
269 if (Lab1
->L
== 0 && Lab2
->L
== 0) return 0;
271 cmsLab2LCh(&LCh1
, Lab1
);
272 cmsLab2LCh(&LCh2
, Lab2
);
275 dL
= Lab2
->L
-Lab1
->L
;
278 dE
= cmsDeltaE(Lab1
, Lab2
);
279 if (Sqr(dE
)>(Sqr(dL
)+Sqr(dC
)))
280 dh
= sqrt(Sqr(dE
)-Sqr(dL
)-Sqr(dC
));
284 if ((LCh1
.h
> 164) && (LCh1
.h
<345))
285 t
= 0.56 + fabs(0.2 * cos(((LCh1
.h
+ 168)/(180/M_PI
))));
287 t
= 0.36 + fabs(0.4 * cos(((LCh1
.h
+ 35 )/(180/M_PI
))));
289 sc
= 0.0638 * LCh1
.C
/ (1 + 0.0131 * LCh1
.C
) + 0.638;
290 sl
= 0.040975 * Lab1
->L
/(1 + 0.01765 * Lab1
->L
);
295 f
= sqrt((LCh1
.C
* LCh1
.C
* LCh1
.C
* LCh1
.C
)/((LCh1
.C
* LCh1
.C
* LCh1
.C
* LCh1
.C
)+1900));
297 cmc
= sqrt(Sqr(dL
/sl
)+Sqr(dC
/sc
)+Sqr(dh
/sh
));
305 double atan2deg(double b
, double a
)
309 if (a
== 0 && b
== 0)
328 double RADIANES(double deg
)
330 return (deg
* M_PI
) / 180.;
334 // dE2000 The weightings KL, KC and KH can be modified to reflect the relative
335 // importance of lightness, chroma and hue in different industrial applications
337 double LCMSEXPORT
cmsCIE2000DeltaE(LPcmsCIELab Lab1
, LPcmsCIELab Lab2
,
338 double Kl
, double Kc
, double Kh
)
343 double C
= sqrt( Sqr(a1
) + Sqr(b1
) );
345 double Ls
= Lab2
->L
;
346 double as
= Lab2
->a
;
347 double bs
= Lab2
->b
;
348 double Cs
= sqrt( Sqr(as
) + Sqr(bs
) );
350 double G
= 0.5 * ( 1 - sqrt(pow((C
+ Cs
) / 2 , 7.0) / (pow((C
+ Cs
) / 2, 7.0) + pow(25.0, 7.0) ) ));
352 double a_p
= (1 + G
) * a1
;
354 double C_p
= sqrt( Sqr(a_p
) + Sqr(b_p
));
355 double h_p
= atan2deg(a_p
, b_p
);
358 double a_ps
= (1 + G
) * as
;
360 double C_ps
= sqrt(Sqr(a_ps
) + Sqr(b_ps
));
361 double h_ps
= atan2deg(a_ps
, b_ps
);
363 double meanC_p
=(C_p
+ C_ps
) / 2;
365 double hps_plus_hp
= h_ps
+ h_p
;
366 double hps_minus_hp
= h_ps
- h_p
;
368 double meanh_p
= fabs(hps_minus_hp
) <= 180.000001 ? (hps_plus_hp
)/2 :
369 (hps_plus_hp
) < 360 ? (hps_plus_hp
+ 360)/2 :
370 (hps_plus_hp
- 360)/2;
372 double delta_h
= (hps_minus_hp
) <= -180.000001 ? (hps_minus_hp
+ 360) :
373 (hps_minus_hp
) > 180 ? (hps_minus_hp
- 360) :
375 double delta_L
= (Ls
- L1
);
376 double delta_C
= (C_ps
- C_p
);
379 double delta_H
=2 * sqrt(C_ps
*C_p
) * sin(RADIANES(delta_h
) / 2);
381 double T
= 1 - 0.17 * cos(RADIANES(meanh_p
-30))
382 + 0.24 * cos(RADIANES(2*meanh_p
))
383 + 0.32 * cos(RADIANES(3*meanh_p
+ 6))
384 - 0.2 * cos(RADIANES(4*meanh_p
- 63));
386 double Sl
= 1 + (0.015 * Sqr((Ls
+ L1
) /2- 50) )/ sqrt(20 + Sqr( (Ls
+L1
)/2 - 50) );
388 double Sc
= 1 + 0.045 * (C_p
+ C_ps
)/2;
389 double Sh
= 1 + 0.015 * ((C_ps
+ C_p
)/2) * T
;
391 double delta_ro
= 30 * exp( -Sqr(((meanh_p
- 275 ) / 25)));
393 double Rc
= 2 * sqrt(( pow(meanC_p
, 7.0) )/( pow(meanC_p
, 7.0) + pow(25.0, 7.0)));
395 double Rt
= -sin(2 * RADIANES(delta_ro
)) * Rc
;
397 double deltaE00
= sqrt( Sqr(delta_L
/(Sl
* Kl
)) +
398 Sqr(delta_C
/(Sc
* Kc
)) +
399 Sqr(delta_H
/(Sh
* Kh
)) +
400 Rt
*(delta_C
/(Sc
* Kc
)) * (delta_H
/ (Sh
* Kh
)));
407 // Carefully, clamp on CIELab space.
409 void LCMSEXPORT
cmsClampLab(LPcmsCIELab Lab
, double amax
, double amin
,
410 double bmax
, double bmin
)
413 // Whole Luma surface to zero
417 Lab
-> L
= Lab
->a
= Lab
-> b
= 0.0;
421 // Clamp white, DISCARD HIGHLIGHTS. This is done
422 // in such way because icc spec doesn't allow the
423 // use of L>100 as a highlight means.
428 // Check out gamut prism, on a, b faces
430 if (Lab
-> a
< amin
|| Lab
->a
> amax
||
431 Lab
-> b
< bmin
|| Lab
->b
> bmax
) {
436 // Falls outside a, b limits. Transports to LCh space,
437 // and then do the clipping
440 if (Lab
-> a
== 0.0) { // Is hue exactly 90?
442 // atan will not work, so clamp here
443 Lab
-> b
= Lab
->b
< 0 ? bmin
: bmax
;
447 cmsLab2LCh(&LCh
, Lab
);
449 slope
= Lab
-> b
/ Lab
-> a
;
454 if ((h
>= 0. && h
< 45.) ||
455 (h
>= 315 && h
<= 360.)) {
459 Lab
-> b
= amax
* slope
;
462 if (h
>= 45. && h
< 135)
466 Lab
-> a
= bmax
/ slope
;
469 if (h
>= 135 && h
< 225) {
472 Lab
-> b
= amin
* slope
;
476 if (h
>= 225 && h
< 315) {
479 Lab
-> a
= bmin
/ slope
;
482 cmsSignalError(LCMS_ERRC_ABORTED
, "Invalid angle");
487 // Several utilities -------------------------------------------------------
489 // Translate from our colorspace to ICC representation
491 icColorSpaceSignature LCMSEXPORT
_cmsICCcolorSpace(int OurNotation
)
493 switch (OurNotation
) {
496 case PT_GRAY
: return icSigGrayData
;
499 case PT_RGB
: return icSigRgbData
;
501 case PT_CMY
: return icSigCmyData
;
502 case PT_CMYK
: return icSigCmykData
;
503 case PT_YCbCr
:return icSigYCbCrData
;
504 case PT_YUV
: return icSigLuvData
;
505 case PT_XYZ
: return icSigXYZData
;
506 case PT_Lab
: return icSigLabData
;
507 case PT_YUVK
: return icSigLuvKData
;
508 case PT_HSV
: return icSigHsvData
;
509 case PT_HLS
: return icSigHlsData
;
510 case PT_Yxy
: return icSigYxyData
;
511 case PT_HiFi
: return icSigHexachromeData
;
512 case PT_HiFi7
: return icSigHeptachromeData
;
513 case PT_HiFi8
: return icSigOctachromeData
;
515 case PT_HiFi9
: return icSigMCH9Data
;
516 case PT_HiFi10
: return icSigMCHAData
;
517 case PT_HiFi11
: return icSigMCHBData
;
518 case PT_HiFi12
: return icSigMCHCData
;
519 case PT_HiFi13
: return icSigMCHDData
;
520 case PT_HiFi14
: return icSigMCHEData
;
521 case PT_HiFi15
: return icSigMCHFData
;
523 default: return icMaxEnumData
;
528 int LCMSEXPORT
_cmsLCMScolorSpace(icColorSpaceSignature ProfileSpace
)
530 switch (ProfileSpace
) {
532 case icSigGrayData
: return PT_GRAY
;
533 case icSigRgbData
: return PT_RGB
;
534 case icSigCmyData
: return PT_CMY
;
535 case icSigCmykData
: return PT_CMYK
;
536 case icSigYCbCrData
:return PT_YCbCr
;
537 case icSigLuvData
: return PT_YUV
;
538 case icSigXYZData
: return PT_XYZ
;
539 case icSigLabData
: return PT_Lab
;
540 case icSigLuvKData
: return PT_YUVK
;
541 case icSigHsvData
: return PT_HSV
;
542 case icSigHlsData
: return PT_HLS
;
543 case icSigYxyData
: return PT_Yxy
;
545 case icSig6colorData
:
546 case icSigHexachromeData
: return PT_HiFi
;
548 case icSigHeptachromeData
:
549 case icSig7colorData
: return PT_HiFi7
;
551 case icSigOctachromeData
:
552 case icSig8colorData
: return PT_HiFi8
;
555 case icSig9colorData
: return PT_HiFi9
;
558 case icSig10colorData
: return PT_HiFi10
;
561 case icSig11colorData
: return PT_HiFi11
;
564 case icSig12colorData
: return PT_HiFi12
;
567 case icSig13colorData
: return PT_HiFi13
;
570 case icSig14colorData
: return PT_HiFi14
;
573 case icSig15colorData
: return PT_HiFi15
;
575 default: return icMaxEnumData
;
580 int LCMSEXPORT
_cmsChannelsOf(icColorSpaceSignature ColorSpace
)
583 switch (ColorSpace
) {
585 case icSigGrayData
: return 1;
587 case icSig2colorData
: return 2;
598 case icSig3colorData
: return 3;
602 case icSig4colorData
: return 4;
605 case icSig5colorData
: return 5;
607 case icSigHexachromeData
:
608 case icSig6colorData
: return 6;
610 case icSigHeptachromeData
:
611 case icSig7colorData
: return 7;
613 case icSigOctachromeData
:
614 case icSig8colorData
: return 8;
617 case icSig9colorData
: return 9;
620 case icSig10colorData
: return 10;
623 case icSig11colorData
: return 11;
626 case icSig12colorData
: return 12;
629 case icSig13colorData
: return 13;
632 case icSig14colorData
: return 14;
635 case icSig15colorData
: return 15;
643 // v2 L=100 is supposed to be placed on 0xFF00. There is no reasonable
644 // number of gridpoints that would make exact match. However, a
645 // prelinearization of 258 entries, would map 0xFF00 on entry 257.
646 // This is almost what we need, unfortunately, the rest of entries
647 // should be scaled by (255*257/256) and this is not exact.
649 // An intermediate solution would be to use 257 entries. This does not
650 // map 0xFF00 exactly on a node, but so close that the dE induced is
651 // negligible. AND the rest of curve is exact.
654 void CreateLabPrelinearization(LPGAMMATABLE LabTable
[])
658 LabTable
[0] = cmsAllocGamma(257);
659 LabTable
[1] = cmsBuildGamma(257, 1.0);
660 LabTable
[2] = cmsBuildGamma(257, 1.0);
662 // L* uses 257 entries. Entry 256 holds 0xFFFF, so, the effective range
663 // is 0..0xFF00. Last entry (257) is also collapsed to 0xFFFF
666 for (i
=0; i
< 256; i
++)
667 LabTable
[0]->GammaTable
[i
] = RGB_8_TO_16(i
);
669 // Repeat last for 0xFFFF
670 LabTable
[0] ->GammaTable
[256] = 0xFFFF;
674 // Used by gamut & softproofing
678 cmsHTRANSFORM hInput
; // From whatever input color space. NULL for Lab
679 cmsHTRANSFORM hForward
, hReverse
; // Transforms going from Lab to colorant and back
680 double Thereshold
; // The thereshold after which is considered out of gamut
682 } GAMUTCHAIN
,FAR
* LPGAMUTCHAIN
;
684 // This sampler does compute gamut boundaries by comparing original
685 // values with a transform going back and forth. Values above ERR_THERESHOLD
686 // of maximum are considered out of gamut.
689 #define ERR_THERESHOLD 5
693 int GamutSampler(register WORD In
[], register WORD Out
[], register LPVOID Cargo
)
695 LPGAMUTCHAIN t
= (LPGAMUTCHAIN
) Cargo
;
696 WORD Proof
[MAXCHANNELS
], Check
[MAXCHANNELS
];
697 WORD Proof2
[MAXCHANNELS
], Check2
[MAXCHANNELS
];
698 cmsCIELab LabIn1
, LabOut1
;
699 cmsCIELab LabIn2
, LabOut2
;
700 double dE1
, dE2
, ErrorRatio
;
702 // Assume in-gamut by default.
708 // Any input space? I can use In[] no matter channels
709 // because is just one pixel
711 if (t
-> hInput
!= NULL
) cmsDoTransform(t
-> hInput
, In
, In
, 1);
713 // converts from PCS to colorant. This always
714 // does return in-gamut values,
715 cmsDoTransform(t
-> hForward
, In
, Proof
, 1);
717 // Now, do the inverse, from colorant to PCS.
718 cmsDoTransform(t
-> hReverse
, Proof
, Check
, 1);
721 // Try again, but this time taking Check as input
722 cmsDoTransform(t
-> hForward
, Check
, Proof2
, 1);
723 cmsDoTransform(t
-> hReverse
, Proof2
, Check2
, 1);
727 // Does the transform returns out-of-gamut?
728 if (Check
[0] == 0xFFFF &&
729 Check
[1] == 0xFFFF &&
732 Out
[0] = 0xFF00; // Out of gamut!
735 // Transport encoded values
736 cmsLabEncoded2Float(&LabIn1
, In
);
737 cmsLabEncoded2Float(&LabOut1
, Check
);
739 // Take difference of direct value
740 dE1
= cmsDeltaE(&LabIn1
, &LabOut1
);
742 cmsLabEncoded2Float(&LabIn2
, Check
);
743 cmsLabEncoded2Float(&LabOut2
, Check2
);
745 // Take difference of converted value
746 dE2
= cmsDeltaE(&LabIn2
, &LabOut2
);
749 // if dE1 is small and dE2 is small, value is likely to be in gamut
750 if (dE1
< t
->Thereshold
&& dE2
< t
->Thereshold
)
753 // if dE1 is small and dE2 is big, undefined. Assume in gamut
754 if (dE1
< t
->Thereshold
&& dE2
> t
->Thereshold
)
757 // dE1 is big and dE2 is small, clearly out of gamut
758 if (dE1
> t
->Thereshold
&& dE2
< t
->Thereshold
)
759 Out
[0] = (WORD
) _cmsQuickFloor((dE1
- t
->Thereshold
) + .5);
762 // dE1 is big and dE2 is also big, could be due to perceptual mapping
763 // so take error ratio
767 ErrorRatio
= dE1
/ dE2
;
769 if (ErrorRatio
> t
->Thereshold
)
770 Out
[0] = (WORD
) _cmsQuickFloor((ErrorRatio
- t
->Thereshold
) + .5);
781 // Does compute a gamut LUT going back and forth across
782 // pcs -> relativ. colorimetric intent -> pcs
783 // the dE obtained is then annotated on the LUT.
784 // values truely out of gamut, are clipped to dE = 0xFFFE
785 // and values changed are supposed to be handled by
786 // any gamut remapping, so, are out of gamut as well.
788 // **WARNING: This algorithm does assume that gamut
789 // remapping algorithms does NOT move in-gamut colors,
790 // of course, many perceptual and saturation intents does
791 // not work in such way, but relativ. ones should.
794 LPLUT
ComputeGamutWithInput(cmsHPROFILE hInput
, cmsHPROFILE hProfile
, int Intent
)
800 int nErrState
, nChannels
, nGridpoints
;
801 LPGAMMATABLE Trans
[3];
802 icColorSpaceSignature ColorSpace
;
805 ZeroMemory(&Chain
, sizeof(GAMUTCHAIN
));
807 hLab
= cmsCreateLabProfile(NULL
);
809 // Safeguard against early abortion
810 nErrState
= cmsErrorAction(LCMS_ERROR_IGNORE
);
812 // The figure of merit. On matrix-shaper profiles, should be almost zero as
813 // the conversion is pretty exact. On LUT based profiles, different resolutions
814 // of input and output CLUT may result in differences.
816 if (!cmsIsIntentSupported(hProfile
, Intent
, LCMS_USED_AS_INPUT
) &&
817 !cmsIsIntentSupported(hProfile
, Intent
, LCMS_USED_AS_OUTPUT
))
819 Chain
.Thereshold
= 1.0;
821 Chain
.Thereshold
= ERR_THERESHOLD
;
823 ColorSpace
= cmsGetColorSpace(hProfile
);
825 // If input profile specified, create a transform from such profile to Lab
826 if (hInput
!= NULL
) {
828 nChannels
= _cmsChannelsOf(ColorSpace
);
829 nGridpoints
= _cmsReasonableGridpointsByColorspace(ColorSpace
, cmsFLAGS_HIGHRESPRECALC
);
830 dwFormat
= (CHANNELS_SH(nChannels
)|BYTES_SH(2));
832 Chain
.hInput
= cmsCreateTransform(hInput
, dwFormat
,
835 cmsFLAGS_NOTPRECALC
);
838 // Input transform=NULL (Lab) Used to compute the gamut tag
839 // This table will take 53 points to give some accurancy,
840 // 53 * 53 * 53 * 2 = 291K
842 nChannels
= 3; // For Lab
845 dwFormat
= (CHANNELS_SH(_cmsChannelsOf(ColorSpace
))|BYTES_SH(2));
849 // Does create the forward step
850 Chain
.hForward
= cmsCreateTransform(hLab
, TYPE_Lab_16
,
852 INTENT_RELATIVE_COLORIMETRIC
,
853 cmsFLAGS_NOTPRECALC
);
855 // Does create the backwards step
856 Chain
.hReverse
= cmsCreateTransform(hProfile
, dwFormat
,
858 INTENT_RELATIVE_COLORIMETRIC
,
859 cmsFLAGS_NOTPRECALC
);
861 // Restores error handler previous state
862 cmsErrorAction(nErrState
);
866 if (Chain
.hForward
&& Chain
.hReverse
) {
868 // Go on, try to compute gamut LUT from PCS.
869 // This consist on a single channel containing
870 // dE when doing a transform back and forth on
871 // the colorimetric intent.
873 Gamut
= cmsAllocLUT();
874 Gamut
= cmsAlloc3DGrid(Gamut
, nGridpoints
, nChannels
, 1);
876 // If no input, then this is a gamut tag operated by Lab,
877 // so include pertinent prelinearization
878 if (hInput
== NULL
) {
880 CreateLabPrelinearization(Trans
);
881 cmsAllocLinearTable(Gamut
, Trans
, 1);
882 cmsFreeGammaTriple(Trans
);
886 cmsSample3DGrid(Gamut
, GamutSampler
, (LPVOID
) &Chain
, Gamut
->wFlags
);
889 Gamut
= NULL
; // Didn't work...
891 // Free all needed stuff.
892 if (Chain
.hInput
) cmsDeleteTransform(Chain
.hInput
);
893 if (Chain
.hForward
) cmsDeleteTransform(Chain
.hForward
);
894 if (Chain
.hReverse
) cmsDeleteTransform(Chain
.hReverse
);
896 cmsCloseProfile(hLab
);
898 // And return computed hull
905 LPLUT
_cmsComputeGamutLUT(cmsHPROFILE hProfile
, int Intent
)
907 return ComputeGamutWithInput(NULL
, hProfile
, Intent
);
911 // This routine does compute the gamut check CLUT. This CLUT goes from whatever
912 // input space to the 0 or != 0 gamut check.
914 LPLUT
_cmsPrecalculateGamutCheck(cmsHTRANSFORM h
)
916 _LPcmsTRANSFORM p
= (_LPcmsTRANSFORM
) h
;
918 return ComputeGamutWithInput(p
->InputProfile
, p
->PreviewProfile
, p
->Intent
);
922 // SoftProofing. Convert from Lab to device, then back to Lab,
923 // any gamut remapping is applied
926 int SoftProofSampler(register WORD In
[], register WORD Out
[], register LPVOID Cargo
)
928 LPGAMUTCHAIN t
= (LPGAMUTCHAIN
) Cargo
;
929 WORD Colorant
[MAXCHANNELS
];
931 // From pcs to colorant
932 cmsDoTransform(t
-> hForward
, In
, Colorant
, 1);
934 // Now, do the inverse, from colorant to pcs.
935 cmsDoTransform(t
-> hReverse
, Colorant
, Out
, 1);
940 // Does return Softproofing LUT on desired intent
942 LPLUT
_cmsComputeSoftProofLUT(cmsHPROFILE hProfile
, int nIntent
)
949 LPGAMMATABLE Trans
[3];
952 // LUTs are never abs. colorimetric, is the transform who
953 // is responsible of generating white point displacement
954 if (nIntent
== INTENT_ABSOLUTE_COLORIMETRIC
)
955 nIntent
= INTENT_RELATIVE_COLORIMETRIC
;
957 ZeroMemory(&Chain
, sizeof(GAMUTCHAIN
));
959 hLab
= cmsCreateLabProfile(NULL
);
962 dwFormat
= (CHANNELS_SH(4)|BYTES_SH(2));
964 // Safeguard against early abortion
965 nErrState
= cmsErrorAction(LCMS_ERROR_IGNORE
);
967 // Does create the first step
968 Chain
.hForward
= cmsCreateTransform(hLab
, TYPE_Lab_16
,
971 cmsFLAGS_NOTPRECALC
);
973 // Does create the last step
974 Chain
.hReverse
= cmsCreateTransform(hProfile
, dwFormat
,
976 INTENT_RELATIVE_COLORIMETRIC
,
977 cmsFLAGS_NOTPRECALC
);
979 // Restores error handler previous state
980 cmsErrorAction(nErrState
);
983 if (Chain
.hForward
&& Chain
.hReverse
) {
985 // This is Lab -> Lab, so 33 point should hold anything
986 SoftProof
= cmsAllocLUT();
987 SoftProof
= cmsAlloc3DGrid(SoftProof
, 33, 3, 3);
989 CreateLabPrelinearization(Trans
);
990 cmsAllocLinearTable(SoftProof
, Trans
, 1);
991 cmsFreeGammaTriple(Trans
);
993 cmsSample3DGrid(SoftProof
, SoftProofSampler
, (LPVOID
) &Chain
, SoftProof
->wFlags
);
996 SoftProof
= NULL
; // Didn't work...
998 // Free all needed stuff.
999 if (Chain
.hForward
) cmsDeleteTransform(Chain
.hForward
);
1000 if (Chain
.hReverse
) cmsDeleteTransform(Chain
.hReverse
);
1002 cmsCloseProfile(hLab
);
1009 int MostlyLinear(WORD Table
[], int nEntries
)
1014 for (i
=5; i
< nEntries
; i
++) {
1016 diff
= abs((int) Table
[i
] - (int) _cmsQuantizeVal(i
, nEntries
));
1026 void SlopeLimiting(WORD Table
[], int nEntries
)
1028 int At
= (int) floor((double) nEntries
* 0.02 + 0.5); // Cutoff at 2%
1035 for (i
=0; i
< At
; i
++)
1036 Table
[i
] = (WORD
) floor(i
* Slope
+ 0.5);
1041 // Check for monotonicity.
1044 LCMSBOOL
IsMonotonic(LPGAMMATABLE t
)
1046 int n
= t
-> nEntries
;
1049 last
= t
->GammaTable
[n
-1];
1051 for (i
= n
-2; i
>= 0; --i
) {
1053 if (t
->GammaTable
[i
] > last
)
1057 last
= t
->GammaTable
[i
];
1064 // Check for endpoints
1067 LCMSBOOL
HasProperEndpoints(LPGAMMATABLE t
)
1069 if (t
->GammaTable
[0] != 0) return FALSE
;
1070 if (t
->GammaTable
[t
->nEntries
-1] != 0xFFFF) return FALSE
;
1077 #define PRELINEARIZATION_POINTS 4096
1079 // Fixes the gamma balancing of transform. Thanks to Mike Chaney
1080 // for pointing this subtle bug.
1082 void _cmsComputePrelinearizationTablesFromXFORM(cmsHTRANSFORM h
[], int nTransforms
, LPLUT Grid
)
1084 LPGAMMATABLE Trans
[MAXCHANNELS
];
1085 unsigned int t
, i
, v
;
1087 WORD In
[MAXCHANNELS
], Out
[MAXCHANNELS
];
1088 LCMSBOOL lIsSuitable
;
1089 _LPcmsTRANSFORM InputXForm
= (_LPcmsTRANSFORM
) h
[0];
1090 _LPcmsTRANSFORM OutputXForm
= (_LPcmsTRANSFORM
) h
[nTransforms
-1];
1093 // First space is *Lab, use our specialized curves for v2 Lab
1095 if (InputXForm
->EntryColorSpace
== icSigLabData
&&
1096 OutputXForm
->ExitColorSpace
!= icSigLabData
) {
1098 CreateLabPrelinearization(Trans
);
1099 cmsAllocLinearTable(Grid
, Trans
, 1);
1100 cmsFreeGammaTriple(Trans
);
1105 // Do nothing on all but Gray/RGB to Gray/RGB transforms
1107 if (((InputXForm
->EntryColorSpace
!= icSigRgbData
) && (InputXForm
->EntryColorSpace
!= icSigGrayData
)) ||
1108 ((OutputXForm
->ExitColorSpace
!= icSigRgbData
) && (OutputXForm
->ExitColorSpace
!= icSigGrayData
))) return;
1111 for (t
= 0; t
< Grid
-> InputChan
; t
++)
1112 Trans
[t
] = cmsAllocGamma(PRELINEARIZATION_POINTS
);
1114 for (i
=0; i
< PRELINEARIZATION_POINTS
; i
++) {
1116 v
= _cmsQuantizeVal(i
, PRELINEARIZATION_POINTS
);
1118 for (t
=0; t
< Grid
-> InputChan
; t
++)
1121 cmsDoTransform(h
[0], In
, Out
, 1);
1122 for (j
=1; j
< nTransforms
; j
++)
1123 cmsDoTransform(h
[j
], Out
, Out
, 1);
1125 for (t
=0; t
< Grid
-> InputChan
; t
++)
1126 Trans
[t
] ->GammaTable
[i
] = Out
[t
];
1131 // Check transfer curves
1133 for (t
=0; (lIsSuitable
&& (t
< Grid
->InputChan
)); t
++) {
1136 // Exclude if already linear
1137 if (MostlyLinear(Trans
[t
]->GammaTable
, PRELINEARIZATION_POINTS
))
1138 lIsSuitable
= FALSE
;
1140 // Exclude if non-monotonic
1141 if (!IsMonotonic(Trans
[t
]))
1142 lIsSuitable
= FALSE
;
1144 // Exclude if weird endpoints
1145 if (!HasProperEndpoints(Trans
[t
]))
1146 lIsSuitable
= FALSE
;
1149 // Exclude if transfer function is not smooth enough
1150 // to be modelled as a gamma function, or the gamma is reversed
1152 if (cmsEstimateGamma(Trans[t]) < 1.0)
1153 lIsSuitable = FALSE;
1160 for (t
= 0; t
< Grid
->InputChan
; t
++)
1161 SlopeLimiting(Trans
[t
]->GammaTable
, Trans
[t
]->nEntries
);
1164 if (lIsSuitable
) cmsAllocLinearTable(Grid
, Trans
, 1);
1167 for (t
= 0; t
< Grid
->InputChan
; t
++)
1168 cmsFreeGamma(Trans
[t
]);
1174 // Compute K -> L* relationship. Flags may include black point compensation. In this case,
1175 // the relationship is assumed from the profile with BPC to a black point zero.
1177 LPGAMMATABLE
ComputeKToLstar(cmsHPROFILE hProfile
, int nPoints
, int Intent
, DWORD dwFlags
)
1181 WORD cmyk
[4], wLab
[3];
1182 cmsHPROFILE hLab
= cmsCreateLabProfile(NULL
);
1183 cmsHTRANSFORM xform
= cmsCreateTransform(hProfile
, TYPE_CMYK_16
,
1185 Intent
, (dwFlags
|cmsFLAGS_NOTPRECALC
));
1188 out
= cmsAllocGamma(nPoints
);
1189 for (i
=0; i
< nPoints
; i
++) {
1194 cmyk
[3] = _cmsQuantizeVal(i
, nPoints
);
1196 cmsDoTransform(xform
, cmyk
, wLab
, 1);
1197 out
->GammaTable
[i
] = (WORD
) (0xFFFF - wLab
[0]);
1200 cmsDeleteTransform(xform
);
1201 cmsCloseProfile(hLab
);
1208 // Compute Black tone curve on a CMYK -> CMYK transform. This is done by
1209 // using the proof direction on both profiles to find K->L* relationship
1210 // then joining both curves. dwFlags may include black point compensation.
1212 LPGAMMATABLE
_cmsBuildKToneCurve(cmsHTRANSFORM hCMYK2CMYK
, int nPoints
)
1214 LPGAMMATABLE in
, out
;
1216 _LPcmsTRANSFORM p
= (_LPcmsTRANSFORM
) hCMYK2CMYK
;
1219 // Make sure CMYK -> CMYK
1220 if (p
-> EntryColorSpace
!= icSigCmykData
||
1221 p
-> ExitColorSpace
!= icSigCmykData
) return NULL
;
1223 // Create individual curves. BPC works also as each K to L* is
1224 // computed as a BPC to zero black point in case of L*
1225 in
= ComputeKToLstar(p
->InputProfile
, nPoints
, p
->Intent
, p
-> dwOriginalFlags
);
1226 out
= ComputeKToLstar(p
->OutputProfile
, nPoints
, p
->Intent
, p
-> dwOriginalFlags
);
1228 // Build the relationship
1229 KTone
= cmsJoinGamma(in
, out
);
1231 cmsFreeGamma(in
); cmsFreeGamma(out
);
1233 // Make sure it is monotonic
1235 if (!IsMonotonic(KTone
)) {
1237 cmsFreeGamma(KTone
);