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.
24 // Postscript level 2 operators
32 // PostScript ColorRenderingDictionary and ColorSpaceArray
34 LCMSAPI DWORD LCMSEXPORT
cmsGetPostScriptCSA(cmsHPROFILE hProfile
, int Intent
, LPVOID Buffer
, DWORD dwBufferLen
);
35 LCMSAPI DWORD LCMSEXPORT
cmsGetPostScriptCRD(cmsHPROFILE hProfile
, int Intent
, LPVOID Buffer
, DWORD dwBufferLen
);
36 LCMSAPI DWORD LCMSEXPORT
cmsGetPostScriptCRDEx(cmsHPROFILE hProfile
, int Intent
, DWORD dwFlags
, LPVOID Buffer
, DWORD dwBufferLen
);
37 // -------------------------------------------------------------------- Implementation
39 #define MAXPSCOLS 60 // Columns on tables
45 PostScript does use XYZ as its internal PCS. But since PostScript
46 interpolation tables are limited to 8 bits, I use Lab as a way to
47 improve the accuracy, favoring perceptual results. So, for the creation
48 of each CRD, CSA the profiles are converted to Lab via a device
49 link between profile -> Lab or Lab -> profile. The PS code necessary to
50 convert Lab <-> XYZ is also included.
54 Color Space Arrays (CSA)
55 ==================================================================================
57 In order to obtain precission, code chooses between three ways to implement
58 the device -> XYZ transform. These cases identifies monochrome profiles (often
59 implemented as a set of curves), matrix-shaper and LUT-based.
64 This is implemented as /CIEBasedA CSA. The prelinearization curve is
65 placed into /DecodeA section, and matrix equals to D50. Since here is
66 no interpolation tables, I do the conversion directly to XYZ
68 NOTE: CLUT-based monochrome profiles are NOT supported. So, cmsFLAGS_MATRIXINPUT
69 flag is forced on such profiles.
73 /DecodeA { transfer function } bind
75 /RangeLMN [ 0.0 D50X 0.0 D50Y 0.0 D50Z ]
78 /RenderingIntent (intent)
82 On simpler profiles, the PCS is already XYZ, so no conversion is required.
88 This is implemented both with /CIEBasedABC or /CIEBasedDEF on dependig
89 of profile implementation. Since here is no interpolation tables, I do
90 the conversion directly to XYZ
96 /DecodeABC [ {transfer1} {transfer2} {transfer3} ]
98 /RangeLMN [ 0.0 D50X 0.0 D50Y 0.0 D50Z ]
99 /DecodeLMN [ { / 2} dup dup ]
102 /RenderingIntent (intent)
110 Lab is used in such cases.
114 /DecodeDEF [ <prelinearization> ]
115 /Table [ p p p [<...>]]
116 /RangeABC [ 0 1 0 1 0 1]
117 /DecodeABC[ <postlinearization> ]
118 /RangeLMN [ -0.236 1.254 0 1 -0.635 1.640 ]
119 % -128/500 1+127/500 0 1 -127/200 1+128/200
120 /MatrixABC [ 1 1 1 1 0 0 0 0 -1]
123 /RenderingIntent (intent)
127 Color Rendering Dictionaries (CRD)
128 ==================================
129 These are always implemented as CLUT, and always are using Lab. Since CRD are expected to
130 be used as resources, the code adds the definition as well.
133 /ColorRenderingType 1
136 /MatrixPQR [ Bradford ]
137 /RangePQR [-0.125 1.375 -0.125 1.375 -0.125 1.375 ]
139 {4 index 3 get div 2 index 3 get mul exch pop exch pop exch pop exch pop } bind
140 {4 index 4 get div 2 index 4 get mul exch pop exch pop exch pop exch pop } bind
141 {4 index 5 get div 2 index 5 get mul exch pop exch pop exch pop exch pop } bind
145 /RangeABC <.. used for XYZ -> Lab>
147 /RenderTable [ p p p [<...>]]
149 /RenderingIntent (Perceptual)
151 /Current exch /ColorRendering defineresource pop
154 The following stages are used to convert from XYZ to Lab
155 --------------------------------------------------------
157 Input is given at LMN stage on X, Y, Z
159 Encode LMN gives us f(X/Xn), f(Y/Yn), f(Z/Zn)
163 { 0.964200 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind
164 { 1.000000 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind
165 { 0.824900 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind
170 MatrixABC is used to compute f(Y/Yn), f(X/Xn) - f(Y/Yn), f(Y/Yn) - f(Z/Zn)
176 /MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]
178 EncodeABC finally gives Lab values.
181 { 116 mul 16 sub 100 div } bind
182 { 500 mul 128 add 255 div } bind
183 { 200 mul 128 add 255 div } bind
186 The following stages are used to convert Lab to XYZ
187 ----------------------------------------------------
189 /RangeABC [ 0 1 0 1 0 1]
190 /DecodeABC [ { 100 mul 16 add 116 div } bind
191 { 255 mul 128 sub 500 div } bind
192 { 255 mul 128 sub 200 div } bind
195 /MatrixABC [ 1 1 1 1 0 0 0 0 -1]
197 {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.964200 mul} bind
198 {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse } bind
199 {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.824900 mul} bind
207 PostScript algorithms discussion.
208 =========================================================================================================
210 1D interpolation algorithm
213 1D interpolation (float)
214 ------------------------
216 val2 = Domain * Value;
218 cell0 = (int) floor(val2);
219 cell1 = (int) ceil(val2);
223 y0 = LutTable[cell0] ;
224 y1 = LutTable[cell1] ;
226 y = y0 + (y1 - y0) * rest;
230 PostScript code Stack
231 ================================================
237 length 1 sub % v tab dom
239 3 -1 roll % tab dom v
243 dup % tab val2 val2 val2
244 floor cvi % tab val2 val2 cell0
245 exch % tab val2 cell0 val2
246 ceiling cvi % tab val2 cell0 cell1
248 3 index % tab val2 cell0 cell1 tab
249 exch % tab val2 cell0 tab cell1
250 get % tab val2 cell0 y1
252 4 -1 roll % val2 cell0 y1 tab
253 3 -1 roll % val2 y1 tab cell0
257 3 1 roll % val2 y0 y1 y0
259 sub % val2 y0 (y1-y0)
260 3 -1 roll % y0 (y1-y0) val2
261 dup % y0 (y1-y0) val2 val2
262 floor cvi % y0 (y1-y0) val2 floor(val2)
263 sub % y0 (y1-y0) rest
273 static icTagSignature Device2PCSTab
[] = {icSigAToB0Tag
, // Perceptual
274 icSigAToB1Tag
, // Relative colorimetric
275 icSigAToB2Tag
, // Saturation
276 icSigAToB1Tag
}; // Absolute colorimetric
277 // (Relative/WhitePoint)
280 // --------------------------------------------------------------- Memory Stream
282 // This struct holds the memory block currently being write
294 } MEMSTREAM
, FAR
* LPMEMSTREAM
;
310 int lIsInput
; // Handle L* encoding
311 int FixWhite
; // Force mapping of pure white
313 icColorSpaceSignature ColorSpace
; // ColorSpace of profile
316 } SAMPLERCARGO
, FAR
* LPSAMPLERCARGO
;
319 // Creates a ready to use memory stream
321 LPMEMSTREAM
CreateMemStream(LPBYTE Buffer
, DWORD dwMax
, int MaxCols
)
323 LPMEMSTREAM m
= (LPMEMSTREAM
) _cmsMalloc(sizeof(MEMSTREAM
));
324 if (m
== NULL
) return NULL
;
326 ZeroMemory(m
, sizeof(MEMSTREAM
));
328 m
-> Block
= m
-> Ptr
= Buffer
;
331 m
-> MaxCols
= MaxCols
;
342 BYTE
Word2Byte(WORD w
)
344 return (BYTE
) floor((double) w
/ 257.0 + 0.5);
348 // Convert to byte (using ICC2 notation)
355 if (ww
> 0xFFFF) return 0xFF;
357 return (BYTE
) ((WORD
) (ww
>> 8) & 0xFF);
360 // Write a raw, uncooked byte. Check for space
362 void WriteRawByte(LPMEMSTREAM m
, BYTE b
)
364 if (m
-> dwUsed
+ 1 > m
-> dwMax
) {
368 if (!m
->HasError
&& m
->Block
) {
375 // Write a cooked byte
377 void WriteByte(LPMEMSTREAM m
, BYTE b
)
379 static const BYTE Hex
[] = "0123456789ABCDEF";
382 c
= Hex
[(b
>> 4) & 0x0f];
390 if (m
-> Col
> m
-> MaxCols
) {
392 WriteRawByte(m
, '\n');
398 // Does write a formatted string. Guaranteed to be 2048 bytes at most.
400 void Writef(LPMEMSTREAM m
, const char *frm
, ...)
408 vsnprintf((char*) Buffer
, 2048, frm
, args
);
410 for (pt
= Buffer
; *pt
; pt
++) {
412 WriteRawByte(m
, *pt
);
420 // ----------------------------------------------------------------- PostScript generation
423 // Removes offending Carriage returns
425 char* RemoveCR(const char* txt
)
427 static char Buffer
[2048];
430 strncpy(Buffer
, txt
, 2047);
432 for (pt
= Buffer
; *pt
; pt
++)
433 if (*pt
== '\n' || *pt
== '\r') *pt
= ' ';
440 void EmitHeader(LPMEMSTREAM m
, const char* Title
, cmsHPROFILE hProfile
)
447 Writef(m
, "%%!PS-Adobe-3.0\n");
449 Writef(m
, "%% %s\n", Title
);
450 Writef(m
, "%% Source: %s\n", RemoveCR(cmsTakeProductName(hProfile
)));
451 Writef(m
, "%% Description: %s\n", RemoveCR(cmsTakeProductDesc(hProfile
)));
452 Writef(m
, "%% Created: %s", ctime(&timer
)); // ctime appends a \n!!!
454 Writef(m
, "%%%%BeginResource\n");
459 // Emits White & Black point. White point is always D50, Black point is the device
460 // Black point adapted to D50.
463 void EmitWhiteBlackD50(LPMEMSTREAM m
, LPcmsCIEXYZ BlackPoint
)
466 Writef(m
, "/BlackPoint [%f %f %f]\n", BlackPoint
-> X
,
470 Writef(m
, "/WhitePoint [%f %f %f]\n", cmsD50_XYZ()->X
,
477 void EmitRangeCheck(LPMEMSTREAM m
)
479 Writef(m
, "dup 0.0 lt { pop 0.0 } if "
480 "dup 1.0 gt { pop 1.0 } if ");
484 // Does write the intent
487 void EmitIntent(LPMEMSTREAM m
, int RenderingIntent
)
491 switch (RenderingIntent
) {
493 case INTENT_PERCEPTUAL
: intent
= "Perceptual"; break;
494 case INTENT_RELATIVE_COLORIMETRIC
: intent
= "RelativeColorimetric"; break;
495 case INTENT_ABSOLUTE_COLORIMETRIC
: intent
= "AbsoluteColorimetric"; break;
496 case INTENT_SATURATION
: intent
= "Saturation"; break;
498 default: intent
= "Undefined"; break;
501 Writef(m
, "/RenderingIntent (%s)\n", intent
);
507 // Y = Yn*[ (L* + 16) / 116] ^ 3 if (L*) >= 6 / 29
508 // = Yn*( L* / 116) / 7.787 if (L*) < 6 / 29
513 void EmitL2Y(LPMEMSTREAM m)
517 "100 mul 16 add 116 div " // (L * 100 + 16) / 116
518 "dup 6 29 div ge " // >= 6 / 29 ?
519 "{ dup dup mul mul } " // yes, ^3 and done
520 "{ 4 29 div sub 108 841 div mul } " // no, slope limiting
526 // Lab -> XYZ, see the discussion above
529 void EmitLab2XYZ(LPMEMSTREAM m
)
531 Writef(m
, "/RangeABC [ 0 1 0 1 0 1]\n");
532 Writef(m
, "/DecodeABC [\n");
533 Writef(m
, "{100 mul 16 add 116 div } bind\n");
534 Writef(m
, "{255 mul 128 sub 500 div } bind\n");
535 Writef(m
, "{255 mul 128 sub 200 div } bind\n");
537 Writef(m
, "/MatrixABC [ 1 1 1 1 0 0 0 0 -1]\n");
538 Writef(m
, "/RangeLMN [ -0.236 1.254 0 1 -0.635 1.640 ]\n");
539 Writef(m
, "/DecodeLMN [\n");
540 Writef(m
, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.964200 mul} bind\n");
541 Writef(m
, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse } bind\n");
542 Writef(m
, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.824900 mul} bind\n");
548 // Outputs a table of words. It does use 16 bits
551 void Emit1Gamma(LPMEMSTREAM m
, LPWORD Table
, int nEntries
)
557 if (nEntries
<= 0) return; // Empty table
559 // Suppress whole if identity
560 if (cmsIsLinear(Table
, nEntries
)) return;
562 // Check if is really an exponential. If so, emit "exp"
563 gamma
= cmsEstimateGammaEx(Table
, nEntries
, 0.001);
565 Writef(m
, "{ %g exp } bind ", gamma
);
574 // Emit intepolation code
576 // PostScript code Stack
577 // =============== ========================
581 // TODO: Check for endianess!!!
583 for (i
=0; i
< nEntries
; i
++) {
584 Writef(m
, "%d ", Table
[i
]);
587 Writef(m
, "] "); // v tab
589 Writef(m
, "dup "); // v tab tab
590 Writef(m
, "length 1 sub "); // v tab dom
591 Writef(m
, "3 -1 roll "); // tab dom v
592 Writef(m
, "mul "); // tab val2
593 Writef(m
, "dup "); // tab val2 val2
594 Writef(m
, "dup "); // tab val2 val2 val2
595 Writef(m
, "floor cvi "); // tab val2 val2 cell0
596 Writef(m
, "exch "); // tab val2 cell0 val2
597 Writef(m
, "ceiling cvi "); // tab val2 cell0 cell1
598 Writef(m
, "3 index "); // tab val2 cell0 cell1 tab
599 Writef(m
, "exch "); // tab val2 cell0 tab cell1
600 Writef(m
, "get "); // tab val2 cell0 y1
601 Writef(m
, "4 -1 roll "); // val2 cell0 y1 tab
602 Writef(m
, "3 -1 roll "); // val2 y1 tab cell0
603 Writef(m
, "get "); // val2 y1 y0
604 Writef(m
, "dup "); // val2 y1 y0 y0
605 Writef(m
, "3 1 roll "); // val2 y0 y1 y0
606 Writef(m
, "sub "); // val2 y0 (y1-y0)
607 Writef(m
, "3 -1 roll "); // y0 (y1-y0) val2
608 Writef(m
, "dup "); // y0 (y1-y0) val2 val2
609 Writef(m
, "floor cvi "); // y0 (y1-y0) val2 floor(val2)
610 Writef(m
, "sub "); // y0 (y1-y0) rest
611 Writef(m
, "mul "); // y0 t1
612 Writef(m
, "add "); // y
613 Writef(m
, "65535 div "); // result
615 Writef(m
, " } bind ");
619 // Compare gamma table
622 LCMSBOOL
GammaTableEquals(LPWORD g1
, LPWORD g2
, int nEntries
)
624 return memcmp(g1
, g2
, nEntries
* sizeof(WORD
)) == 0;
628 // Does write a set of gamma curves
631 void EmitNGamma(LPMEMSTREAM m
, int n
, LPWORD g
[], int nEntries
)
635 for( i
=0; i
< n
; i
++ )
637 if (i
> 0 && GammaTableEquals(g
[i
-1], g
[i
], nEntries
)) {
642 Emit1Gamma(m
, g
[i
], nEntries
);
649 // Check whatever a profile has CLUT tables (only on input)
652 LCMSBOOL
IsLUTbased(cmsHPROFILE hProfile
, int Intent
)
656 // Check if adequate tag is present
657 Tag
= Device2PCSTab
[Intent
];
659 if (cmsIsTag(hProfile
, Tag
)) return 1;
661 // If not present, revert to default (perceptual)
664 // If no tag present, try matrix-shaper
665 return cmsIsTag(hProfile
, Tag
);
670 // Following code dumps a LUT onto memory stream
673 // This is the sampler. Intended to work in SAMPLER_INSPECT mode,
674 // that is, the callback will be called for each knot with
676 // In[] The grid location coordinates, normalized to 0..ffff
677 // Out[] The LUT values, normalized to 0..ffff
679 // Returning a value other than 0 does terminate the sampling process
681 // Each row contains LUT values for all but first component. So, I
682 // detect row changing by keeping a copy of last value of first
683 // component. -1 is used to mark begining of whole block.
686 int OutputValueSampler(register WORD In
[], register WORD Out
[], register LPVOID Cargo
)
688 LPSAMPLERCARGO sc
= (LPSAMPLERCARGO
) Cargo
;
692 if (sc
-> FixWhite
) {
694 if (In
[0] == 0xFFFF) { // Only in L* = 100, ab = [-8..8]
696 if ((In
[1] >= 0x7800 && In
[1] <= 0x8800) &&
697 (In
[2] >= 0x7800 && In
[2] <= 0x8800)) {
703 if (!_cmsEndPointsBySpace(sc
->ColorSpace
, &White
, &Black
, &nOutputs
))
706 for (i
=0; i
< (unsigned int) nOutputs
; i
++)
715 // Hadle the parenthesis on rows
717 if (In
[0] != sc
->FirstComponent
) {
719 if (sc
->FirstComponent
!= -1) {
721 Writef(sc
->m
, sc
->PostMin
);
722 sc
->SecondComponent
= -1;
723 Writef(sc
->m
, sc
->PostMaj
);
729 Writef(sc
->m
, sc
->PreMaj
);
730 sc
->FirstComponent
= In
[0];
734 if (In
[1] != sc
->SecondComponent
) {
736 if (sc
->SecondComponent
!= -1) {
738 Writef(sc
->m
, sc
->PostMin
);
741 Writef(sc
->m
, sc
->PreMin
);
742 sc
->SecondComponent
= In
[1];
747 // Dump table. Could be Word or byte based on
748 // depending on bps member (16 bps mode is not currently
749 // being used at all, but is here for future ampliations)
751 for (i
=0; i
< sc
-> Lut
->OutputChan
; i
++) {
753 WORD wWordOut
= Out
[i
];
760 // If is input, convert from Lab2 to Lab4 (just divide by 256)
765 wByteOut
= L2Byte(wWordOut
);
768 wByteOut
= Word2Byte(wWordOut
);
770 WriteByte(sc
-> m
, wByteOut
);
775 WriteByte(sc
-> m
, (BYTE
) (wWordOut
& 0xFF));
776 WriteByte(sc
-> m
, (BYTE
) ((wWordOut
>> 8) & 0xFF));
783 // Writes a LUT on memstream. Could be 8 or 16 bits based
786 void WriteCLUT(LPMEMSTREAM m
, LPLUT Lut
, int bps
, const char* PreMaj
,
792 icColorSpaceSignature ColorSpace
)
797 sc
.FirstComponent
= -1;
798 sc
.SecondComponent
= -1;
806 sc
.PostMin
= PostMin
;
807 sc
.lIsInput
= lIsInput
;
808 sc
.FixWhite
= FixWhite
;
809 sc
.ColorSpace
= ColorSpace
;
813 for (i
=0; i
< Lut
->InputChan
; i
++)
814 Writef(m
, " %d ", Lut
->cLutPoints
);
820 cmsSample3DGrid(Lut
, OutputValueSampler
, (LPVOID
) &sc
, SAMPLER_INSPECT
);
832 // Dumps CIEBasedA Color Space Array
835 int EmitCIEBasedA(LPMEMSTREAM m
, LPWORD Tab
, int nEntries
, LPcmsCIEXYZ BlackPoint
)
838 Writef(m
, "[ /CIEBasedA\n");
841 Writef(m
, "/DecodeA ");
843 Emit1Gamma(m
,Tab
, nEntries
);
847 Writef(m
, "/MatrixA [ 0.9642 1.0000 0.8249 ]\n");
848 Writef(m
, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n");
850 EmitWhiteBlackD50(m
, BlackPoint
);
851 EmitIntent(m
, INTENT_PERCEPTUAL
);
860 // Dumps CIEBasedABC Color Space Array
863 int EmitCIEBasedABC(LPMEMSTREAM m
, LPWORD L
[], int nEntries
, LPWMAT3 Matrix
, LPcmsCIEXYZ BlackPoint
)
867 Writef(m
, "[ /CIEBasedABC\n");
869 Writef(m
, "/DecodeABC [ ");
871 EmitNGamma(m
, 3, L
, nEntries
);
875 Writef(m
, "/MatrixABC [ " );
877 for( i
=0; i
< 3; i
++ ) {
879 Writef(m
, "%.6f %.6f %.6f ",
880 FIXED_TO_DOUBLE(Matrix
->v
[0].n
[i
]),
881 FIXED_TO_DOUBLE(Matrix
->v
[1].n
[i
]),
882 FIXED_TO_DOUBLE(Matrix
->v
[2].n
[i
]));
888 Writef(m
, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n");
890 EmitWhiteBlackD50(m
, BlackPoint
);
891 EmitIntent(m
, INTENT_PERCEPTUAL
);
902 int EmitCIEBasedDEF(LPMEMSTREAM m
, LPLUT Lut
, int Intent
, LPcmsCIEXYZ BlackPoint
)
906 const char* PreMin
, *PostMin
;
908 switch (Lut
->InputChan
) {
911 Writef(m
, "[ /CIEBasedDEF\n");
914 PreMin
= PostMin
= "";
917 Writef(m
, "[ /CIEBasedDEFG\n");
930 if (Lut
->wFlags
& LUT_HASTL1
) {
932 Writef(m
, "/DecodeDEF [ ");
933 EmitNGamma(m
, Lut
->InputChan
, Lut
->L1
, Lut
->CLut16params
.nSamples
);
939 if (Lut
->wFlags
& LUT_HAS3DGRID
) {
941 Writef(m
, "/Table ");
942 WriteCLUT(m
, Lut
, 8, PreMaj
, PostMaj
, PreMin
, PostMin
, TRUE
, FALSE
, (icColorSpaceSignature
) 0);
947 EmitWhiteBlackD50(m
, BlackPoint
);
948 EmitIntent(m
, Intent
);
957 // Generates a curve from a gray profile
960 LPGAMMATABLE
ExtractGray2Y(cmsHPROFILE hProfile
, int Intent
)
962 LPGAMMATABLE Out
= cmsAllocGamma(256);
963 cmsHPROFILE hXYZ
= cmsCreateXYZProfile();
964 cmsHTRANSFORM xform
= cmsCreateTransform(hProfile
, TYPE_GRAY_8
, hXYZ
, TYPE_XYZ_DBL
, Intent
, cmsFLAGS_NOTPRECALC
);
967 for (i
=0; i
< 256; i
++) {
969 BYTE Gray
= (BYTE
) i
;
972 cmsDoTransform(xform
, &Gray
, &XYZ
, 1);
974 Out
->GammaTable
[i
] =_cmsClampWord((int) floor(XYZ
.Y
* 65535.0 + 0.5));
977 cmsDeleteTransform(xform
);
978 cmsCloseProfile(hXYZ
);
984 // Because PostScrip has only 8 bits in /Table, we should use
985 // a more perceptually uniform space... I do choose Lab.
988 int WriteInputLUT(LPMEMSTREAM m
, cmsHPROFILE hProfile
, int Intent
)
992 icColorSpaceSignature ColorSpace
;
996 cmsHPROFILE Profiles
[2];
997 cmsCIEXYZ BlackPointAdaptedToD50
;
999 // Does create a device-link based transform.
1000 // The DeviceLink is next dumped as working CSA.
1002 hLab
= cmsCreateLabProfile(NULL
);
1003 ColorSpace
= cmsGetColorSpace(hProfile
);
1004 nChannels
= _cmsChannelsOf(ColorSpace
);
1005 InputFormat
= CHANNELS_SH(nChannels
) | BYTES_SH(2);
1007 cmsDetectBlackPoint(&BlackPointAdaptedToD50
, hProfile
, Intent
,LCMS_BPFLAGS_D50_ADAPTED
);
1009 // Is a devicelink profile?
1010 if (cmsGetDeviceClass(hProfile
) == icSigLinkClass
) {
1012 // if devicelink output already Lab, use it directly
1014 if (cmsGetPCS(hProfile
) == icSigLabData
) {
1016 xform
= cmsCreateTransform(hProfile
, InputFormat
, NULL
,
1017 TYPE_Lab_DBL
, Intent
, 0);
1021 // Nope, adjust output to Lab if possible
1023 Profiles
[0] = hProfile
;
1026 xform
= cmsCreateMultiprofileTransform(Profiles
, 2, InputFormat
,
1027 TYPE_Lab_DBL
, Intent
, 0);
1034 // This is a normal profile
1035 xform
= cmsCreateTransform(hProfile
, InputFormat
, hLab
,
1036 TYPE_Lab_DBL
, Intent
, 0);
1041 if (xform
== NULL
) {
1043 cmsSignalError(LCMS_ERRC_ABORTED
, "Cannot create transform Profile -> Lab");
1047 // Only 1, 3 and 4 channels are allowed
1049 switch (nChannels
) {
1052 LPGAMMATABLE Gray2Y
= ExtractGray2Y(hProfile
, Intent
);
1053 EmitCIEBasedA(m
, Gray2Y
->GammaTable
, Gray2Y
->nEntries
, &BlackPointAdaptedToD50
);
1054 cmsFreeGamma(Gray2Y
);
1061 _LPcmsTRANSFORM v
= (_LPcmsTRANSFORM
) xform
;
1064 rc
= EmitCIEBasedDEF(m
, v
->DeviceLink
, Intent
, &BlackPointAdaptedToD50
);
1066 DeviceLink
= _cmsPrecalculateDeviceLink(xform
, 0);
1067 rc
= EmitCIEBasedDEF(m
, DeviceLink
, Intent
, &BlackPointAdaptedToD50
);
1068 cmsFreeLUT(DeviceLink
);
1075 cmsSignalError(LCMS_ERRC_ABORTED
, "Only 3, 4 channels supported for CSA. This profile has %d channels.", nChannels
);
1080 cmsDeleteTransform(xform
);
1081 cmsCloseProfile(hLab
);
1087 // Does create CSA based on matrix-shaper. Allowed types are gray and RGB based
1090 int WriteInputMatrixShaper(LPMEMSTREAM m
, cmsHPROFILE hProfile
)
1092 icColorSpaceSignature ColorSpace
;
1093 LPMATSHAPER MatShaper
;
1095 cmsCIEXYZ BlackPointAdaptedToD50
;
1098 ColorSpace
= cmsGetColorSpace(hProfile
);
1099 MatShaper
= cmsBuildInputMatrixShaper(hProfile
);
1101 cmsDetectBlackPoint(&BlackPointAdaptedToD50
, hProfile
, INTENT_RELATIVE_COLORIMETRIC
, LCMS_BPFLAGS_D50_ADAPTED
);
1103 if (MatShaper
== NULL
) {
1105 cmsSignalError(LCMS_ERRC_ABORTED
, "This profile is not suitable for input");
1109 if (ColorSpace
== icSigGrayData
) {
1111 rc
= EmitCIEBasedA(m
, MatShaper
->L
[0],
1112 MatShaper
->p16
.nSamples
,
1113 &BlackPointAdaptedToD50
);
1117 if (ColorSpace
== icSigRgbData
) {
1120 rc
= EmitCIEBasedABC(m
, MatShaper
->L
,
1121 MatShaper
->p16
.nSamples
,
1122 &MatShaper
->Matrix
,
1123 &BlackPointAdaptedToD50
);
1127 cmsSignalError(LCMS_ERRC_ABORTED
, "Profile is not suitable for CSA. Unsupported colorspace.");
1131 cmsFreeMatShaper(MatShaper
);
1137 // Creates a PostScript color list from a named profile data.
1138 // This is a HP extension, and it works in Lab instead of XYZ
1141 int WriteNamedColorCSA(LPMEMSTREAM m
, cmsHPROFILE hNamedColor
, int Intent
)
1143 cmsHTRANSFORM xform
;
1149 hLab
= cmsCreateLabProfile(NULL
);
1150 xform
= cmsCreateTransform(hNamedColor
, TYPE_NAMED_COLOR_INDEX
,
1151 hLab
, TYPE_Lab_DBL
, Intent
, cmsFLAGS_NOTPRECALC
);
1152 if (xform
== NULL
) return 0;
1156 Writef(m
, "(colorlistcomment) (%s)\n", "Named color CSA");
1157 Writef(m
, "(Prefix) [ (Pantone ) (PANTONE ) ]\n");
1158 Writef(m
, "(Suffix) [ ( CV) ( CVC) ( C) ]\n");
1160 nColors
= cmsNamedColorCount(xform
);
1163 for (i
=0; i
< nColors
; i
++) {
1170 if (!cmsNamedColorInfo(xform
, i
, ColorName
, NULL
, NULL
))
1173 cmsDoTransform(xform
, In
, &Lab
, 1);
1174 Writef(m
, " (%s) [ %.3f %.3f %.3f ]\n", ColorName
, Lab
.L
, Lab
.a
, Lab
.b
);
1181 cmsDeleteTransform(xform
);
1182 cmsCloseProfile(hLab
);
1187 // Does create a Color Space Array on XYZ colorspace for PostScript usage
1189 DWORD LCMSEXPORT
cmsGetPostScriptCSA(cmsHPROFILE hProfile
,
1191 LPVOID Buffer
, DWORD dwBufferLen
)
1197 // Set up the serialization engine
1198 mem
= CreateMemStream((LPBYTE
) Buffer
, dwBufferLen
, MAXPSCOLS
);
1202 // Is a named color profile?
1203 if (cmsGetDeviceClass(hProfile
) == icSigNamedColorClass
) {
1205 if (!WriteNamedColorCSA(mem
, hProfile
, Intent
)) {
1207 _cmsFree((void*) mem
);
1214 // Any profile class are allowed (including devicelink), but
1215 // output (PCS) colorspace must be XYZ or Lab
1216 icColorSpaceSignature ColorSpace
= cmsGetPCS(hProfile
);
1218 if (ColorSpace
!= icSigXYZData
&&
1219 ColorSpace
!= icSigLabData
) {
1221 cmsSignalError(LCMS_ERRC_ABORTED
, "Invalid output color space");
1222 _cmsFree((void*) mem
);
1226 // Is there any CLUT?
1227 if (IsLUTbased(hProfile
, Intent
)) {
1229 // Yes, so handle as LUT-based
1230 if (!WriteInputLUT(mem
, hProfile
, Intent
)) {
1232 _cmsFree((void*) mem
);
1238 // No, try Matrix-shaper (this only works on XYZ)
1240 if (!WriteInputMatrixShaper(mem
, hProfile
)) {
1242 _cmsFree((void*) mem
); // Something went wrong
1249 // Done, keep memory usage
1250 dwBytesUsed
= mem
->dwUsed
;
1252 // Get rid of memory stream
1253 _cmsFree((void*) mem
);
1255 // Finally, return used byte count
1259 // ------------------------------------------------------ Color Rendering Dictionary (CRD)
1265 Black point compensation plus chromatic adaptation:
1267 Step 1 - Chromatic adaptation
1268 =============================
1274 Step 2 - Black point compensation
1275 =================================
1277 (WPout - BPout)*X - WPout*(BPin - BPout)
1278 out = ---------------------------------------
1282 Algorithm discussion
1283 ====================
1285 TransformPQR(WPin, BPin, WPout, BPout, PQR)
1287 Wpin,etc= { Xws Yws Zws Pws Qws Rws }
1290 Algorithm Stack 0...n
1291 ===========================================================
1292 PQR BPout WPout BPin WPin
1293 4 index 3 get WPin PQR BPout WPout BPin WPin
1294 div (PQR/WPin) BPout WPout BPin WPin
1295 2 index 3 get WPout (PQR/WPin) BPout WPout BPin WPin
1296 mult WPout*(PQR/WPin) BPout WPout BPin WPin
1298 2 index 3 get WPout WPout*(PQR/WPin) BPout WPout BPin WPin
1299 2 index 3 get BPout WPout WPout*(PQR/WPin) BPout WPout BPin WPin
1300 sub (WPout-BPout) WPout*(PQR/WPin) BPout WPout BPin WPin
1301 mult (WPout-BPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1303 2 index 3 get WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1304 4 index 3 get BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1305 3 index 3 get BPout BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1307 sub (BPin-BPout) WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1308 mult (BPin-BPout)*WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1309 sub (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1311 3 index 3 get BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1312 3 index 3 get WPout BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1314 sub (WPout-BPin) (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1326 void EmitPQRStage(LPMEMSTREAM m
, cmsHPROFILE hProfile
, int DoBPC
, int lIsAbsolute
)
1332 // For absolute colorimetric intent, encode back to relative
1333 // and generate a relative LUT
1335 // Relative encoding is obtained across XYZpcs*(D50/WhitePoint)
1339 cmsTakeMediaWhitePoint(&White
, hProfile
);
1341 Writef(m
,"/MatrixPQR [1 0 0 0 1 0 0 0 1 ]\n");
1342 Writef(m
,"/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n");
1344 Writef(m
, "%% Absolute colorimetric -- encode to relative to maximize LUT usage\n"
1346 "{0.9642 mul %g div exch pop exch pop exch pop exch pop} bind\n"
1347 "{1.0000 mul %g div exch pop exch pop exch pop exch pop} bind\n"
1348 "{0.8249 mul %g div exch pop exch pop exch pop exch pop} bind\n]\n",
1349 White
.X
, White
.Y
, White
.Z
);
1354 Writef(m
,"%% Bradford Cone Space\n"
1355 "/MatrixPQR [0.8951 -0.7502 0.0389 0.2664 1.7135 -0.0685 -0.1614 0.0367 1.0296 ] \n");
1357 Writef(m
, "/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n");
1364 Writef(m
, "%% VonKries-like transform in Bradford Cone Space\n"
1366 "{exch pop exch 3 get mul exch pop exch 3 get div} bind\n"
1367 "{exch pop exch 4 get mul exch pop exch 4 get div} bind\n"
1368 "{exch pop exch 5 get mul exch pop exch 5 get div} bind\n]\n");
1373 Writef(m
, "%% VonKries-like transform in Bradford Cone Space plus BPC\n"
1374 "/TransformPQR [\n");
1376 Writef(m
, "{4 index 3 get div 2 index 3 get mul "
1377 "2 index 3 get 2 index 3 get sub mul "
1378 "2 index 3 get 4 index 3 get 3 index 3 get sub mul sub "
1379 "3 index 3 get 3 index 3 get exch sub div "
1380 "exch pop exch pop exch pop exch pop } bind\n");
1382 Writef(m
, "{4 index 4 get div 2 index 4 get mul "
1383 "2 index 4 get 2 index 4 get sub mul "
1384 "2 index 4 get 4 index 4 get 3 index 4 get sub mul sub "
1385 "3 index 4 get 3 index 4 get exch sub div "
1386 "exch pop exch pop exch pop exch pop } bind\n");
1388 Writef(m
, "{4 index 5 get div 2 index 5 get mul "
1389 "2 index 5 get 2 index 5 get sub mul "
1390 "2 index 5 get 4 index 5 get 3 index 5 get sub mul sub "
1391 "3 index 5 get 3 index 5 get exch sub div "
1392 "exch pop exch pop exch pop exch pop } bind\n]\n");
1401 void EmitXYZ2Lab(LPMEMSTREAM m
)
1403 Writef(m
, "/RangeLMN [ -0.635 2.0 0 2 -0.635 2.0 ]\n");
1404 Writef(m
, "/EncodeLMN [\n");
1405 Writef(m
, "{ 0.964200 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
1406 Writef(m
, "{ 1.000000 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
1407 Writef(m
, "{ 0.824900 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
1409 Writef(m
, "/MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]\n");
1410 Writef(m
, "/EncodeABC [\n");
1413 Writef(m
, "{ 116 mul 16 sub 100 div } bind\n");
1414 Writef(m
, "{ 500 mul 128 add 256 div } bind\n");
1415 Writef(m
, "{ 200 mul 128 add 256 div } bind\n");
1423 // Due to impedance mismatch between XYZ and almost all RGB and CMYK spaces
1424 // I choose to dump LUTS in Lab instead of XYZ. There is still a lot of wasted
1425 // space on 3D CLUT, but since space seems not to be a problem here, 33 points
1426 // would give a reasonable accurancy. Note also that CRD tables must operate in
1430 int WriteOutputLUT(LPMEMSTREAM m
, cmsHPROFILE hProfile
, int Intent
, DWORD dwFlags
)
1433 cmsHTRANSFORM xform
;
1434 icColorSpaceSignature ColorSpace
;
1439 cmsHPROFILE Profiles
[3];
1440 cmsCIEXYZ BlackPointAdaptedToD50
;
1441 LCMSBOOL lFreeDeviceLink
= FALSE
;
1442 LCMSBOOL lDoBPC
= (dwFlags
& cmsFLAGS_BLACKPOINTCOMPENSATION
);
1443 LCMSBOOL lFixWhite
= !(dwFlags
& cmsFLAGS_NOWHITEONWHITEFIXUP
);
1444 int RelativeEncodingIntent
;
1448 hLab
= cmsCreateLabProfile(NULL
);
1450 ColorSpace
= cmsGetColorSpace(hProfile
);
1451 nChannels
= _cmsChannelsOf(ColorSpace
);
1452 OutputFormat
= CHANNELS_SH(nChannels
) | BYTES_SH(2);
1454 // For absolute colorimetric, the LUT is encoded as relative
1455 // in order to preserve precission.
1457 RelativeEncodingIntent
= Intent
;
1458 if (RelativeEncodingIntent
== INTENT_ABSOLUTE_COLORIMETRIC
)
1459 RelativeEncodingIntent
= INTENT_RELATIVE_COLORIMETRIC
;
1462 // Is a devicelink profile?
1463 if (cmsGetDeviceClass(hProfile
) == icSigLinkClass
) {
1465 // if devicelink input already in Lab
1467 if (ColorSpace
== icSigLabData
) {
1469 // adjust input to Lab to our v4
1472 Profiles
[1] = hProfile
;
1474 xform
= cmsCreateMultiprofileTransform(Profiles
, 2, TYPE_Lab_DBL
,
1475 OutputFormat
, RelativeEncodingIntent
,
1476 dwFlags
|cmsFLAGS_NOWHITEONWHITEFIXUP
|cmsFLAGS_NOPRELINEARIZATION
);
1480 cmsSignalError(LCMS_ERRC_ABORTED
, "Cannot use devicelink profile for CRD creation");
1488 // This is a normal profile
1489 xform
= cmsCreateTransform(hLab
, TYPE_Lab_DBL
, hProfile
,
1490 OutputFormat
, RelativeEncodingIntent
, dwFlags
|cmsFLAGS_NOWHITEONWHITEFIXUP
|cmsFLAGS_NOPRELINEARIZATION
);
1493 if (xform
== NULL
) {
1495 cmsSignalError(LCMS_ERRC_ABORTED
, "Cannot create transform Lab -> Profile in CRD creation");
1499 // Get the internal precalculated devicelink
1501 v
= (_LPcmsTRANSFORM
) xform
;
1502 DeviceLink
= v
->DeviceLink
;
1506 DeviceLink
= _cmsPrecalculateDeviceLink(xform
, cmsFLAGS_NOPRELINEARIZATION
);
1507 lFreeDeviceLink
= TRUE
;
1511 Writef(m
, "/ColorRenderingType 1\n");
1514 cmsDetectBlackPoint(&BlackPointAdaptedToD50
, hProfile
, Intent
, LCMS_BPFLAGS_D50_ADAPTED
);
1516 // Emit headers, etc.
1517 EmitWhiteBlackD50(m
, &BlackPointAdaptedToD50
);
1518 EmitPQRStage(m
, hProfile
, lDoBPC
, Intent
== INTENT_ABSOLUTE_COLORIMETRIC
);
1521 if (DeviceLink
->wFlags
& LUT_HASTL1
) {
1524 cmsSignalError(LCMS_ERRC_ABORTED
, "Internal error (prelinearization on CRD)");
1529 // FIXUP: map Lab (100, 0, 0) to perfect white, because the particular encoding for Lab
1530 // does map a=b=0 not falling into any specific node. Since range a,b goes -128..127,
1531 // zero is slightly moved towards right, so assure next node (in L=100 slice) is mapped to
1532 // zero. This would sacrifice a bit of highlights, but failure to do so would cause
1535 if (Intent
== INTENT_ABSOLUTE_COLORIMETRIC
)
1538 Writef(m
, "/RenderTable ");
1540 WriteCLUT(m
, DeviceLink
, 8, "<", ">\n", "", "", FALSE
,
1541 lFixWhite
, ColorSpace
);
1543 Writef(m
, " %d {} bind ", nChannels
);
1545 for (i
=1; i
< nChannels
; i
++)
1551 EmitIntent(m
, Intent
);
1555 if (!(dwFlags
& cmsFLAGS_NODEFAULTRESOURCEDEF
)) {
1557 Writef(m
, "/Current exch /ColorRendering defineresource pop\n");
1560 if (lFreeDeviceLink
) cmsFreeLUT(DeviceLink
);
1561 cmsDeleteTransform(xform
);
1562 cmsCloseProfile(hLab
);
1568 // Builds a ASCII string containing colorant list in 0..1.0 range
1570 void BuildColorantList(char *Colorant
, int nColorant
, WORD Out
[])
1576 if (nColorant
> MAXCHANNELS
)
1577 nColorant
= MAXCHANNELS
;
1579 for (j
=0; j
< nColorant
; j
++) {
1581 sprintf(Buff
, "%.3f", Out
[j
] / 65535.0);
1582 strcat(Colorant
, Buff
);
1583 if (j
< nColorant
-1)
1584 strcat(Colorant
, " ");
1590 // Creates a PostScript color list from a named profile data.
1591 // This is a HP extension.
1594 int WriteNamedColorCRD(LPMEMSTREAM m
, cmsHPROFILE hNamedColor
, int Intent
, DWORD dwFlags
)
1596 cmsHTRANSFORM xform
;
1597 int i
, nColors
, nColorant
;
1602 nColorant
= _cmsChannelsOf(cmsGetColorSpace(hNamedColor
));
1603 OutputFormat
= CHANNELS_SH(nColorant
) | BYTES_SH(2);
1605 xform
= cmsCreateTransform(hNamedColor
, TYPE_NAMED_COLOR_INDEX
,
1606 NULL
, OutputFormat
, Intent
, cmsFLAGS_NOTPRECALC
);
1607 if (xform
== NULL
) return 0;
1611 Writef(m
, "(colorlistcomment) (%s) \n", "Named profile");
1612 Writef(m
, "(Prefix) [ (Pantone ) (PANTONE ) ]\n");
1613 Writef(m
, "(Suffix) [ ( CV) ( CVC) ( C) ]\n");
1615 nColors
= cmsNamedColorCount(xform
);
1618 for (i
=0; i
< nColors
; i
++) {
1621 WORD Out
[MAXCHANNELS
];
1625 if (!cmsNamedColorInfo(xform
, i
, ColorName
, NULL
, NULL
))
1628 cmsDoTransform(xform
, In
, Out
, 1);
1629 BuildColorantList(Colorant
, nColorant
, Out
);
1630 Writef(m
, " (%s) [ %s ]\n", ColorName
, Colorant
);
1635 if (!(dwFlags
& cmsFLAGS_NODEFAULTRESOURCEDEF
)) {
1637 Writef(m
, " /Current exch /HPSpotTable defineresource pop\n");
1640 cmsDeleteTransform(xform
);
1646 // This one does create a Color Rendering Dictionary.
1647 // CRD are always LUT-Based, no matter if profile is
1648 // implemented as matrix-shaper.
1650 DWORD LCMSEXPORT
cmsGetPostScriptCRDEx(cmsHPROFILE hProfile
,
1651 int Intent
, DWORD dwFlags
,
1652 LPVOID Buffer
, DWORD dwBufferLen
)
1658 // Set up the serialization artifact
1659 mem
= CreateMemStream((LPBYTE
) Buffer
, dwBufferLen
, MAXPSCOLS
);
1663 if (!(dwFlags
& cmsFLAGS_NODEFAULTRESOURCEDEF
)) {
1665 EmitHeader(mem
, "Color Rendering Dictionary (CRD)", hProfile
);
1669 // Is a named color profile?
1670 if (cmsGetDeviceClass(hProfile
) == icSigNamedColorClass
) {
1672 if (!WriteNamedColorCRD(mem
, hProfile
, Intent
, dwFlags
)) {
1674 _cmsFree((void*) mem
);
1680 // CRD are always implemented as LUT.
1683 if (!WriteOutputLUT(mem
, hProfile
, Intent
, dwFlags
)) {
1684 _cmsFree((void*) mem
);
1689 if (!(dwFlags
& cmsFLAGS_NODEFAULTRESOURCEDEF
)) {
1691 Writef(mem
, "%%%%EndResource\n");
1692 Writef(mem
, "\n%% CRD End\n");
1695 // Done, keep memory usage
1696 dwBytesUsed
= mem
->dwUsed
;
1698 // Get rid of memory stream
1699 _cmsFree((void*) mem
);
1701 // Finally, return used byte count
1706 // For compatibility with previous versions
1708 DWORD LCMSEXPORT
cmsGetPostScriptCRD(cmsHPROFILE hProfile
,
1710 LPVOID Buffer
, DWORD dwBufferLen
)
1712 return cmsGetPostScriptCRDEx(hProfile
, Intent
, 0, Buffer
, dwBufferLen
);