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 // ---------------------------------------------------------------------------------
29 static volatile int GlobalBlackPreservationStrategy
= 0;
31 // Quantize a value 0 <= i < MaxSamples
33 WORD
_cmsQuantizeVal(double i
, int MaxSamples
)
37 x
= ((double) i
* 65535.) / (double) (MaxSamples
- 1);
39 return (WORD
) floor(x
+ .5);
45 int cmsIsLinear(WORD Table
[], int nEntries
)
50 for (i
=0; i
< nEntries
; i
++) {
52 diff
= abs((int) Table
[i
] - (int) _cmsQuantizeVal(i
, nEntries
));
62 // pow() restricted to integer
65 int ipow(int base
, int exp
)
76 // Given n, 0<=n<=clut^dim, returns the colorant.
79 int ComponentOf(int n
, int clut
, int nColorant
)
84 n
/= ipow(clut
, nColorant
);
91 // This routine does a sweep on whole input space, and calls its callback
92 // function on knots. returns TRUE if all ok, FALSE otherwise.
94 LCMSBOOL LCMSEXPORT
cmsSample3DGrid(LPLUT Lut
, _cmsSAMPLER Sampler
, LPVOID Cargo
, DWORD dwFlags
)
96 int i
, t
, nTotalPoints
, Colorant
, index
;
97 WORD In
[MAXCHANNELS
], Out
[MAXCHANNELS
];
99 nTotalPoints
= ipow(Lut
->cLutPoints
, Lut
-> InputChan
);
102 for (i
= 0; i
< nTotalPoints
; i
++) {
104 for (t
=0; t
< (int) Lut
-> InputChan
; t
++) {
106 Colorant
= ComponentOf(i
, Lut
-> cLutPoints
, (Lut
-> InputChan
- t
- 1 ));
107 In
[t
] = _cmsQuantizeVal(Colorant
, Lut
-> cLutPoints
);
111 if (dwFlags
& SAMPLER_HASTL1
) {
113 for (t
=0; t
< (int) Lut
-> InputChan
; t
++)
114 In
[t
] = cmsReverseLinearInterpLUT16(In
[t
],
119 for (t
=0; t
< (int) Lut
-> OutputChan
; t
++)
120 Out
[t
] = Lut
->T
[index
+ t
];
122 if (dwFlags
& SAMPLER_HASTL2
) {
124 for (t
=0; t
< (int) Lut
-> OutputChan
; t
++)
125 Out
[t
] = cmsLinearInterpLUT16(Out
[t
],
127 &Lut
-> Out16params
);
131 if (!Sampler(In
, Out
, Cargo
))
134 if (!(dwFlags
& SAMPLER_INSPECT
)) {
136 if (dwFlags
& SAMPLER_HASTL2
) {
138 for (t
=0; t
< (int) Lut
-> OutputChan
; t
++)
139 Out
[t
] = cmsReverseLinearInterpLUT16(Out
[t
],
141 &Lut
-> Out16params
);
145 for (t
=0; t
< (int) Lut
-> OutputChan
; t
++)
146 Lut
->T
[index
+ t
] = Out
[t
];
150 index
+= Lut
-> OutputChan
;
162 // choose reasonable resolution
163 int _cmsReasonableGridpointsByColorspace(icColorSpaceSignature Colorspace
, DWORD dwFlags
)
167 // Already specified?
168 if (dwFlags
& 0x00FF0000) {
170 return (dwFlags
>> 16) & 0xFF;
173 nChannels
= _cmsChannelsOf(Colorspace
);
175 // HighResPrecalc is maximum resolution
177 if (dwFlags
& cmsFLAGS_HIGHRESPRECALC
) {
180 return 7; // 7 for Hifi
182 if (nChannels
== 4) // 23 for CMYK
185 return 49; // 49 for RGB and others
189 // LowResPrecal is stripped resolution
191 if (dwFlags
& cmsFLAGS_LOWRESPRECALC
) {
194 return 6; // 6 for Hifi
197 return 33; // For monochrome
199 return 17; // 17 for remaining
205 return 7; // 7 for Hifi
208 return 17; // 17 for CMYK
210 return 33; // 33 for RGB
214 // Sampler implemented by another transform. This is a clean way to
215 // precalculate the devicelink 3D CLUT for almost any transform
218 int XFormSampler(register WORD In
[], register WORD Out
[], register LPVOID Cargo
)
220 cmsDoTransform((cmsHTRANSFORM
) Cargo
, In
, Out
, 1);
224 // This routine does compute the devicelink CLUT containing whole
225 // transform. Handles any channel number.
227 LPLUT
_cmsPrecalculateDeviceLink(cmsHTRANSFORM h
, DWORD dwFlags
)
229 _LPcmsTRANSFORM p
= (_LPcmsTRANSFORM
) h
;
232 DWORD dwFormatIn
, dwFormatOut
;
233 DWORD SaveFormatIn
, SaveFormatOut
;
234 int ChannelsIn
, ChannelsOut
;
238 // Remove any gamut checking
239 SaveGamutLUT
= p
->Gamut
;
242 ChannelsIn
= _cmsChannelsOf(p
-> EntryColorSpace
);
243 ChannelsOut
= _cmsChannelsOf(p
-> ExitColorSpace
);
245 nGridPoints
= _cmsReasonableGridpointsByColorspace(p
-> EntryColorSpace
, dwFlags
);
247 Grid
= cmsAllocLUT();
248 if (!Grid
) return NULL
;
250 Grid
= cmsAlloc3DGrid(Grid
, nGridPoints
, ChannelsIn
, ChannelsOut
);
252 // Compute device link on 16-bit basis
253 dwFormatIn
= (CHANNELS_SH(ChannelsIn
)|BYTES_SH(2));
254 dwFormatOut
= (CHANNELS_SH(ChannelsOut
)|BYTES_SH(2));
256 SaveFormatIn
= p
->InputFormat
;
257 SaveFormatOut
= p
->OutputFormat
;
259 p
-> InputFormat
= dwFormatIn
;
260 p
-> OutputFormat
= dwFormatOut
;
261 p
-> FromInput
= _cmsIdentifyInputFormat(p
, dwFormatIn
);
262 p
-> ToOutput
= _cmsIdentifyOutputFormat(p
, dwFormatOut
);
264 // Fix gamut & gamma possible mismatches.
266 if (!(dwFlags
& cmsFLAGS_NOPRELINEARIZATION
)) {
268 cmsHTRANSFORM hOne
[1];
271 _cmsComputePrelinearizationTablesFromXFORM(hOne
, 1, Grid
);
274 // Attention to this typecast! we can take the luxury to
275 // do this since cmsHTRANSFORM is only an alias to a pointer
276 // to the transform struct.
278 if (!cmsSample3DGrid(Grid
, XFormSampler
, (LPVOID
) p
, Grid
-> wFlags
)) {
284 p
->Gamut
= SaveGamutLUT
;
285 p
->InputFormat
= SaveFormatIn
;
286 p
->OutputFormat
= SaveFormatOut
;
293 // Sampler for Black-preserving CMYK->CMYK transforms
296 cmsHTRANSFORM cmyk2cmyk
;
297 cmsHTRANSFORM cmyk2Lab
;
299 L16PARAMS KToneParams
;
303 cmsHTRANSFORM hRoundTrip
;
306 cmsHTRANSFORM hProofOutput
;
308 } BPCARGO
, *LPBPCARGO
;
312 // Preserve black only if that is the only ink used
314 int BlackPreservingGrayOnlySampler(register WORD In
[], register WORD Out
[], register LPVOID Cargo
)
316 BPCARGO
* bp
= (LPBPCARGO
) Cargo
;
318 // If going across black only, keep black only
319 if (In
[0] == 0 && In
[1] == 0 && In
[2] == 0) {
321 // TAC does not apply because it is black ink!
322 Out
[0] = Out
[1] = Out
[2] = 0;
323 Out
[3] = cmsLinearInterpLUT16(In
[3], bp
->KTone
->GammaTable
, &bp
->KToneParams
);
327 // Keep normal transform for other colors
328 cmsDoTransform(bp
->cmyk2cmyk
, In
, Out
, 1);
334 // Preserve all K plane.
336 int BlackPreservingSampler(register WORD In
[], register WORD Out
[], register LPVOID Cargo
)
340 double SumCMY
, SumCMYK
, Error
;
341 cmsCIELab ColorimetricLab
, BlackPreservingLab
;
342 BPCARGO
* bp
= (LPBPCARGO
) Cargo
;
344 // Get the K across Tone curve
345 LabK
[3] = cmsLinearInterpLUT16(In
[3], bp
->KTone
->GammaTable
, &bp
->KToneParams
);
347 // If going across black only, keep black only
348 if (In
[0] == 0 && In
[1] == 0 && In
[2] == 0) {
350 Out
[0] = Out
[1] = Out
[2] = 0;
355 // Try the original transform, maybe K is already ok (valid on K=0)
356 cmsDoTransform(bp
->cmyk2cmyk
, In
, Out
, 1);
357 if (Out
[3] == LabK
[3]) return 1;
360 // No, mesure and keep Lab measurement for further usage
361 cmsDoTransform(bp
->hProofOutput
, Out
, &ColorimetricLab
, 1);
363 // Is not black only and the transform doesn't keep black.
364 // Obtain the Lab of CMYK. After that we have Lab + K
365 cmsDoTransform(bp
->cmyk2Lab
, In
, LabK
, 1);
367 // Obtain the corresponding CMY using reverse interpolation.
368 // As a seed, we use the colorimetric CMY
369 cmsEvalLUTreverse(bp
->LabK2cmyk
, LabK
, Out
, Out
);
371 // Estimate the error
372 cmsDoTransform(bp
->hProofOutput
, Out
, &BlackPreservingLab
, 1);
373 Error
= cmsDeltaE(&ColorimetricLab
, &BlackPreservingLab
);
376 // Apply TAC if needed
378 SumCMY
= Out
[0] + Out
[1] + Out
[2];
379 SumCMYK
= SumCMY
+ Out
[3];
381 if (SumCMYK
> bp
->MaxTAC
) {
383 double Ratio
= 1 - ((SumCMYK
- bp
->MaxTAC
) / SumCMY
);
387 Out
[0] = (WORD
) floor(Out
[0] * Ratio
+ 0.5); // C
388 Out
[1] = (WORD
) floor(Out
[1] * Ratio
+ 0.5); // M
389 Out
[2] = (WORD
) floor(Out
[2] * Ratio
+ 0.5); // Y
396 // Sample whole gamut to estimate maximum TAC
399 #pragma warning(disable : 4100)
403 int EstimateTAC(register WORD In
[], register WORD Out
[], register LPVOID Cargo
)
405 BPCARGO
* bp
= (LPBPCARGO
) Cargo
;
409 cmsDoTransform(bp
->hRoundTrip
, In
, RoundTrip
, 1);
411 Sum
= RoundTrip
[0] + RoundTrip
[1] + RoundTrip
[2] + RoundTrip
[3];
413 if (Sum
> bp
->MaxTAC
)
420 // Estimate the maximum error
422 int BlackPreservingEstimateErrorSampler(register WORD In
[], register WORD Out
[], register LPVOID Cargo
)
424 BPCARGO
* bp
= (LPBPCARGO
) Cargo
;
425 WORD ColorimetricOut
[4];
426 cmsCIELab ColorimetricLab
, BlackPreservingLab
;
429 if (In
[0] == 0 && In
[1] == 0 && In
[2] == 0) return 1;
431 cmsDoTransform(bp
->cmyk2cmyk
, In
, ColorimetricOut
, 1);
433 cmsDoTransform(bp
->hProofOutput
, ColorimetricOut
, &ColorimetricLab
, 1);
434 cmsDoTransform(bp
->hProofOutput
, Out
, &BlackPreservingLab
, 1);
436 Error
= cmsDeltaE(&ColorimetricLab
, &BlackPreservingLab
);
438 if (Error
> bp
->MaxError
)
439 bp
->MaxError
= Error
;
444 // Setup the K preservation strategy
445 int LCMSEXPORT
cmsSetCMYKPreservationStrategy(int n
)
447 int OldVal
= GlobalBlackPreservationStrategy
;
450 GlobalBlackPreservationStrategy
= n
;
455 #pragma warning(disable: 4550)
457 // Get a pointer to callback on depending of strategy
459 _cmsSAMPLER
_cmsGetBlackPreservationSampler(void)
461 switch (GlobalBlackPreservationStrategy
) {
463 case 0: return BlackPreservingGrayOnlySampler
;
464 default: return BlackPreservingSampler
;
469 // This is the black-preserving devicelink generator
470 LPLUT
_cmsPrecalculateBlackPreservingDeviceLink(cmsHTRANSFORM hCMYK2CMYK
, DWORD dwFlags
)
472 _LPcmsTRANSFORM p
= (_LPcmsTRANSFORM
) hCMYK2CMYK
;
476 cmsHPROFILE hLab
= cmsCreateLabProfile(NULL
);
478 icTagSignature Device2PCS
[] = {icSigAToB0Tag
, // Perceptual
479 icSigAToB1Tag
, // Relative colorimetric
480 icSigAToB2Tag
, // Saturation
481 icSigAToB1Tag
}; // Absolute colorimetric
482 // (Relative/WhitePoint)
484 nGridPoints
= _cmsReasonableGridpointsByColorspace(p
-> EntryColorSpace
, dwFlags
);
486 // Get a copy of inteserting flags for this kind of xform
487 LocalFlags
= cmsFLAGS_NOTPRECALC
;
488 if (p
-> dwOriginalFlags
& cmsFLAGS_BLACKPOINTCOMPENSATION
)
489 LocalFlags
|= cmsFLAGS_BLACKPOINTCOMPENSATION
;
491 // Fill in cargo struct
492 Cargo
.cmyk2cmyk
= hCMYK2CMYK
;
494 // Compute tone curve.
495 Cargo
.KTone
= _cmsBuildKToneCurve(hCMYK2CMYK
, 256);
496 if (Cargo
.KTone
== NULL
) return NULL
;
497 cmsCalcL16Params(Cargo
.KTone
->nEntries
, &Cargo
.KToneParams
);
500 // Create a CMYK->Lab "normal" transform on input, without K-preservation
501 Cargo
.cmyk2Lab
= cmsCreateTransform(p
->InputProfile
, TYPE_CMYK_16
,
502 hLab
, TYPE_Lab_16
, p
->Intent
, LocalFlags
);
504 // We are going to use the reverse of proof direction
505 Cargo
.LabK2cmyk
= cmsReadICCLut(p
->OutputProfile
, Device2PCS
[p
->Intent
]);
507 // Is there any table available?
508 if (Cargo
.LabK2cmyk
== NULL
) {
514 // Setup a roundtrip on output profile for TAC estimation
515 Cargo
.hRoundTrip
= cmsCreateTransform(p
->OutputProfile
, TYPE_CMYK_16
,
516 p
->OutputProfile
, TYPE_CMYK_16
, p
->Intent
, cmsFLAGS_NOTPRECALC
);
519 // Setup a proof CMYK->Lab on output
520 Cargo
.hProofOutput
= cmsCreateTransform(p
->OutputProfile
, TYPE_CMYK_16
,
521 hLab
, TYPE_Lab_DBL
, p
->Intent
, LocalFlags
);
524 // Create an empty LUT for holding K-preserving xform
525 Grid
= cmsAllocLUT();
526 if (!Grid
) goto Cleanup
;
528 Grid
= cmsAlloc3DGrid(Grid
, nGridPoints
, 4, 4);
531 p
-> FromInput
= _cmsIdentifyInputFormat(p
, TYPE_CMYK_16
);
532 p
-> ToOutput
= _cmsIdentifyOutputFormat(p
, TYPE_CMYK_16
);
536 // Step #1, estimate TAC
538 if (!cmsSample3DGrid(Grid
, EstimateTAC
, (LPVOID
) &Cargo
, 0)) {
546 // Step #2, compute approximation
547 if (!cmsSample3DGrid(Grid
, _cmsGetBlackPreservationSampler(), (LPVOID
) &Cargo
, 0)) {
554 // Step #3, estimate error
556 cmsSample3DGrid(Grid
, BlackPreservingEstimateErrorSampler
, (LPVOID
) &Cargo
, SAMPLER_INSPECT
);
561 if (Cargo
.cmyk2Lab
) cmsDeleteTransform(Cargo
.cmyk2Lab
);
562 if (Cargo
.hRoundTrip
) cmsDeleteTransform(Cargo
.hRoundTrip
);
563 if (Cargo
.hProofOutput
) cmsDeleteTransform(Cargo
.hProofOutput
);
565 if (hLab
) cmsCloseProfile(hLab
);
566 if (Cargo
.KTone
) cmsFreeGamma(Cargo
.KTone
);
567 if (Cargo
.LabK2cmyk
) cmsFreeLUT(Cargo
.LabK2cmyk
);
574 // Fix broken LUT. just to obtain other CMS compatibility
577 void PatchLUT(LPLUT Grid
, WORD At
[], WORD Value
[],
578 int nChannelsOut
, int nChannelsIn
)
580 LPL16PARAMS p16
= &Grid
-> CLut16params
;
581 double px
, py
, pz
, pw
;
586 if (Grid
->wFlags
& LUT_HASTL1
) return; // There is a prelinearization
588 px
= ((double) At
[0] * (p16
->Domain
)) / 65535.0;
589 py
= ((double) At
[1] * (p16
->Domain
)) / 65535.0;
590 pz
= ((double) At
[2] * (p16
->Domain
)) / 65535.0;
591 pw
= ((double) At
[3] * (p16
->Domain
)) / 65535.0;
593 x0
= (int) floor(px
);
594 y0
= (int) floor(py
);
595 z0
= (int) floor(pz
);
596 w0
= (int) floor(pw
);
598 if (nChannelsIn
== 4) {
600 if (((px
- x0
) != 0) ||
603 ((pw
- w0
) != 0)) return; // Not on exact node
605 index
= p16
-> opta4
* x0
+
611 if (nChannelsIn
== 3) {
613 if (((px
- x0
) != 0) ||
615 ((pz
- z0
) != 0)) return; // Not on exact node
617 index
= p16
-> opta3
* x0
+
622 if (nChannelsIn
== 1) {
624 if (((px
- x0
) != 0)) return; // Not on exact node
626 index
= p16
-> opta1
* x0
;
629 cmsSignalError(LCMS_ERRC_ABORTED
, "(internal) %d Channels are not supported on PatchLUT", nChannelsIn
);
633 for (i
=0; i
< nChannelsOut
; i
++)
634 Grid
-> T
[index
+ i
] = Value
[i
];
640 LCMSBOOL
_cmsFixWhiteMisalignment(_LPcmsTRANSFORM p
)
643 WORD
*WhitePointIn
, *WhitePointOut
, *BlackPointIn
, *BlackPointOut
;
647 if (!p
-> DeviceLink
) return FALSE
;
649 if (p
->Intent
== INTENT_ABSOLUTE_COLORIMETRIC
) return FALSE
;
650 if ((p
->PreviewProfile
!= NULL
) &&
651 (p
->ProofIntent
== INTENT_ABSOLUTE_COLORIMETRIC
)) return FALSE
;
654 if (!_cmsEndPointsBySpace(p
-> EntryColorSpace
,
655 &WhitePointIn
, &BlackPointIn
, &nIns
)) return FALSE
;
658 if (!_cmsEndPointsBySpace(p
-> ExitColorSpace
,
659 &WhitePointOut
, &BlackPointOut
, &nOuts
)) return FALSE
;
663 PatchLUT(p
-> DeviceLink
, WhitePointIn
, WhitePointOut
, nOuts
, nIns
);
664 // PatchLUT(p -> DeviceLink, BlackPointIn, BlackPointOut, nOuts, nIns);