1 | //--------------------------------------------------------------------------------- |
2 | // |
3 | // Little Color Management System |
4 | // Copyright (c) 1998-2017 Marti Maria Saguer |
5 | // |
6 | // Permission is hereby granted, free of charge, to any person obtaining |
7 | // a copy of this software and associated documentation files (the "Software"), |
8 | // to deal in the Software without restriction, including without limitation |
9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, |
10 | // and/or sell copies of the Software, and to permit persons to whom the Software |
11 | // is furnished to do so, subject to the following conditions: |
12 | // |
13 | // The above copyright notice and this permission notice shall be included in |
14 | // all copies or substantial portions of the Software. |
15 | // |
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO |
18 | // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
23 | // |
24 | //--------------------------------------------------------------------------------- |
25 | // |
26 | |
27 | #include "lcms2_internal.h" |
28 | |
29 | // PostScript ColorRenderingDictionary and ColorSpaceArray |
30 | |
31 | |
32 | #define MAXPSCOLS 60 // Columns on tables |
33 | |
34 | /* |
35 | Implementation |
36 | -------------- |
37 | |
38 | PostScript does use XYZ as its internal PCS. But since PostScript |
39 | interpolation tables are limited to 8 bits, I use Lab as a way to |
40 | improve the accuracy, favoring perceptual results. So, for the creation |
41 | of each CRD, CSA the profiles are converted to Lab via a device |
42 | link between profile -> Lab or Lab -> profile. The PS code necessary to |
43 | convert Lab <-> XYZ is also included. |
44 | |
45 | |
46 | |
47 | Color Space Arrays (CSA) |
48 | ================================================================================== |
49 | |
50 | In order to obtain precision, code chooses between three ways to implement |
51 | the device -> XYZ transform. These cases identifies monochrome profiles (often |
52 | implemented as a set of curves), matrix-shaper and Pipeline-based. |
53 | |
54 | Monochrome |
55 | ----------- |
56 | |
57 | This is implemented as /CIEBasedA CSA. The prelinearization curve is |
58 | placed into /DecodeA section, and matrix equals to D50. Since here is |
59 | no interpolation tables, I do the conversion directly to XYZ |
60 | |
61 | NOTE: CLUT-based monochrome profiles are NOT supported. So, cmsFLAGS_MATRIXINPUT |
62 | flag is forced on such profiles. |
63 | |
64 | [ /CIEBasedA |
65 | << |
66 | /DecodeA { transfer function } bind |
67 | /MatrixA [D50] |
68 | /RangeLMN [ 0.0 cmsD50X 0.0 cmsD50Y 0.0 cmsD50Z ] |
69 | /WhitePoint [D50] |
70 | /BlackPoint [BP] |
71 | /RenderingIntent (intent) |
72 | >> |
73 | ] |
74 | |
75 | On simpler profiles, the PCS is already XYZ, so no conversion is required. |
76 | |
77 | |
78 | Matrix-shaper based |
79 | ------------------- |
80 | |
81 | This is implemented both with /CIEBasedABC or /CIEBasedDEF depending on the |
82 | profile implementation. Since here there are no interpolation tables, I do |
83 | the conversion directly to XYZ |
84 | |
85 | |
86 | |
87 | [ /CIEBasedABC |
88 | << |
89 | /DecodeABC [ {transfer1} {transfer2} {transfer3} ] |
90 | /MatrixABC [Matrix] |
91 | /RangeLMN [ 0.0 cmsD50X 0.0 cmsD50Y 0.0 cmsD50Z ] |
92 | /DecodeLMN [ { / 2} dup dup ] |
93 | /WhitePoint [D50] |
94 | /BlackPoint [BP] |
95 | /RenderingIntent (intent) |
96 | >> |
97 | ] |
98 | |
99 | |
100 | CLUT based |
101 | ---------- |
102 | |
103 | Lab is used in such cases. |
104 | |
105 | [ /CIEBasedDEF |
106 | << |
107 | /DecodeDEF [ <prelinearization> ] |
108 | /Table [ p p p [<...>]] |
109 | /RangeABC [ 0 1 0 1 0 1] |
110 | /DecodeABC[ <postlinearization> ] |
111 | /RangeLMN [ -0.236 1.254 0 1 -0.635 1.640 ] |
112 | % -128/500 1+127/500 0 1 -127/200 1+128/200 |
113 | /MatrixABC [ 1 1 1 1 0 0 0 0 -1] |
114 | /WhitePoint [D50] |
115 | /BlackPoint [BP] |
116 | /RenderingIntent (intent) |
117 | ] |
118 | |
119 | |
120 | Color Rendering Dictionaries (CRD) |
121 | ================================== |
122 | These are always implemented as CLUT, and always are using Lab. Since CRD are expected to |
123 | be used as resources, the code adds the definition as well. |
124 | |
125 | << |
126 | /ColorRenderingType 1 |
127 | /WhitePoint [ D50 ] |
128 | /BlackPoint [BP] |
129 | /MatrixPQR [ Bradford ] |
130 | /RangePQR [-0.125 1.375 -0.125 1.375 -0.125 1.375 ] |
131 | /TransformPQR [ |
132 | {4 index 3 get div 2 index 3 get mul exch pop exch pop exch pop exch pop } bind |
133 | {4 index 4 get div 2 index 4 get mul exch pop exch pop exch pop exch pop } bind |
134 | {4 index 5 get div 2 index 5 get mul exch pop exch pop exch pop exch pop } bind |
135 | ] |
136 | /MatrixABC <...> |
137 | /EncodeABC <...> |
138 | /RangeABC <.. used for XYZ -> Lab> |
139 | /EncodeLMN |
140 | /RenderTable [ p p p [<...>]] |
141 | |
142 | /RenderingIntent (Perceptual) |
143 | >> |
144 | /Current exch /ColorRendering defineresource pop |
145 | |
146 | |
147 | The following stages are used to convert from XYZ to Lab |
148 | -------------------------------------------------------- |
149 | |
150 | Input is given at LMN stage on X, Y, Z |
151 | |
152 | Encode LMN gives us f(X/Xn), f(Y/Yn), f(Z/Zn) |
153 | |
154 | /EncodeLMN [ |
155 | |
156 | { 0.964200 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind |
157 | { 1.000000 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind |
158 | { 0.824900 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind |
159 | |
160 | ] |
161 | |
162 | |
163 | MatrixABC is used to compute f(Y/Yn), f(X/Xn) - f(Y/Yn), f(Y/Yn) - f(Z/Zn) |
164 | |
165 | | 0 1 0| |
166 | | 1 -1 0| |
167 | | 0 1 -1| |
168 | |
169 | /MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ] |
170 | |
171 | EncodeABC finally gives Lab values. |
172 | |
173 | /EncodeABC [ |
174 | { 116 mul 16 sub 100 div } bind |
175 | { 500 mul 128 add 255 div } bind |
176 | { 200 mul 128 add 255 div } bind |
177 | ] |
178 | |
179 | The following stages are used to convert Lab to XYZ |
180 | ---------------------------------------------------- |
181 | |
182 | /RangeABC [ 0 1 0 1 0 1] |
183 | /DecodeABC [ { 100 mul 16 add 116 div } bind |
184 | { 255 mul 128 sub 500 div } bind |
185 | { 255 mul 128 sub 200 div } bind |
186 | ] |
187 | |
188 | /MatrixABC [ 1 1 1 1 0 0 0 0 -1] |
189 | /DecodeLMN [ |
190 | {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.964200 mul} bind |
191 | {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse } bind |
192 | {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.824900 mul} bind |
193 | ] |
194 | |
195 | |
196 | */ |
197 | |
198 | /* |
199 | |
200 | PostScript algorithms discussion. |
201 | ========================================================================================================= |
202 | |
203 | 1D interpolation algorithm |
204 | |
205 | |
206 | 1D interpolation (float) |
207 | ------------------------ |
208 | |
209 | val2 = Domain * Value; |
210 | |
211 | cell0 = (int) floor(val2); |
212 | cell1 = (int) ceil(val2); |
213 | |
214 | rest = val2 - cell0; |
215 | |
216 | y0 = LutTable[cell0] ; |
217 | y1 = LutTable[cell1] ; |
218 | |
219 | y = y0 + (y1 - y0) * rest; |
220 | |
221 | |
222 | |
223 | PostScript code Stack |
224 | ================================================ |
225 | |
226 | { % v |
227 | <check 0..1.0> |
228 | [array] % v tab |
229 | dup % v tab tab |
230 | length 1 sub % v tab dom |
231 | |
232 | 3 -1 roll % tab dom v |
233 | |
234 | mul % tab val2 |
235 | dup % tab val2 val2 |
236 | dup % tab val2 val2 val2 |
237 | floor cvi % tab val2 val2 cell0 |
238 | exch % tab val2 cell0 val2 |
239 | ceiling cvi % tab val2 cell0 cell1 |
240 | |
241 | 3 index % tab val2 cell0 cell1 tab |
242 | exch % tab val2 cell0 tab cell1 |
243 | get % tab val2 cell0 y1 |
244 | |
245 | 4 -1 roll % val2 cell0 y1 tab |
246 | 3 -1 roll % val2 y1 tab cell0 |
247 | get % val2 y1 y0 |
248 | |
249 | dup % val2 y1 y0 y0 |
250 | 3 1 roll % val2 y0 y1 y0 |
251 | |
252 | sub % val2 y0 (y1-y0) |
253 | 3 -1 roll % y0 (y1-y0) val2 |
254 | dup % y0 (y1-y0) val2 val2 |
255 | floor cvi % y0 (y1-y0) val2 floor(val2) |
256 | sub % y0 (y1-y0) rest |
257 | mul % y0 t1 |
258 | add % y |
259 | 65535 div % result |
260 | |
261 | } bind |
262 | |
263 | |
264 | */ |
265 | |
266 | |
267 | // This struct holds the memory block currently being write |
268 | typedef struct { |
269 | _cmsStageCLutData* Pipeline; |
270 | cmsIOHANDLER* m; |
271 | |
272 | int FirstComponent; |
273 | int SecondComponent; |
274 | |
275 | const char* PreMaj; |
276 | const char* PostMaj; |
277 | const char* PreMin; |
278 | const char* PostMin; |
279 | |
280 | int FixWhite; // Force mapping of pure white |
281 | |
282 | cmsColorSpaceSignature ColorSpace; // ColorSpace of profile |
283 | |
284 | |
285 | } cmsPsSamplerCargo; |
286 | |
287 | static int _cmsPSActualColumn = 0; |
288 | |
289 | |
290 | // Convert to byte |
291 | static |
292 | cmsUInt8Number Word2Byte(cmsUInt16Number w) |
293 | { |
294 | return (cmsUInt8Number) floor((cmsFloat64Number) w / 257.0 + 0.5); |
295 | } |
296 | |
297 | |
298 | // Convert to byte (using ICC2 notation) |
299 | /* |
300 | static |
301 | cmsUInt8Number L2Byte(cmsUInt16Number w) |
302 | { |
303 | int ww = w + 0x0080; |
304 | |
305 | if (ww > 0xFFFF) return 0xFF; |
306 | |
307 | return (cmsUInt8Number) ((cmsUInt16Number) (ww >> 8) & 0xFF); |
308 | } |
309 | */ |
310 | |
311 | // Write a cooked byte |
312 | |
313 | static |
314 | void WriteByte(cmsContext ContextID, cmsIOHANDLER* m, cmsUInt8Number b) |
315 | { |
316 | _cmsIOPrintf(ContextID, m, "%02x" , b); |
317 | _cmsPSActualColumn += 2; |
318 | |
319 | if (_cmsPSActualColumn > MAXPSCOLS) { |
320 | |
321 | _cmsIOPrintf(ContextID, m, "\n" ); |
322 | _cmsPSActualColumn = 0; |
323 | } |
324 | } |
325 | |
326 | // ----------------------------------------------------------------- PostScript generation |
327 | |
328 | |
329 | // Removes offending Carriage returns |
330 | static |
331 | char* RemoveCR(const char* txt) |
332 | { |
333 | static char Buffer[2048]; |
334 | char* pt; |
335 | |
336 | strncpy(Buffer, txt, 2047); |
337 | Buffer[2047] = 0; |
338 | for (pt = Buffer; *pt; pt++) |
339 | if (*pt == '\n' || *pt == '\r') *pt = ' '; |
340 | |
341 | return Buffer; |
342 | |
343 | } |
344 | |
345 | static |
346 | void (cmsContext ContextID, cmsIOHANDLER* m, const char* Title, cmsHPROFILE hProfile) |
347 | { |
348 | time_t timer; |
349 | cmsMLU *Description, *Copyright; |
350 | char DescASCII[256], CopyrightASCII[256]; |
351 | |
352 | time(&timer); |
353 | |
354 | Description = (cmsMLU*) cmsReadTag(ContextID, hProfile, cmsSigProfileDescriptionTag); |
355 | Copyright = (cmsMLU*) cmsReadTag(ContextID, hProfile, cmsSigCopyrightTag); |
356 | |
357 | DescASCII[0] = DescASCII[255] = 0; |
358 | CopyrightASCII[0] = CopyrightASCII[255] = 0; |
359 | |
360 | if (Description != NULL) cmsMLUgetASCII(ContextID, Description, cmsNoLanguage, cmsNoCountry, DescASCII, 255); |
361 | if (Copyright != NULL) cmsMLUgetASCII(ContextID, Copyright, cmsNoLanguage, cmsNoCountry, CopyrightASCII, 255); |
362 | |
363 | _cmsIOPrintf(ContextID, m, "%%!PS-Adobe-3.0\n" ); |
364 | _cmsIOPrintf(ContextID, m, "%%\n" ); |
365 | _cmsIOPrintf(ContextID, m, "%% %s\n" , Title); |
366 | _cmsIOPrintf(ContextID, m, "%% Source: %s\n" , RemoveCR(DescASCII)); |
367 | _cmsIOPrintf(ContextID, m, "%% %s\n" , RemoveCR(CopyrightASCII)); |
368 | _cmsIOPrintf(ContextID, m, "%% Created: %s" , ctime(&timer)); // ctime appends a \n!!! |
369 | _cmsIOPrintf(ContextID, m, "%%\n" ); |
370 | _cmsIOPrintf(ContextID, m, "%%%%BeginResource\n" ); |
371 | |
372 | } |
373 | |
374 | |
375 | // Emits White & Black point. White point is always D50, Black point is the device |
376 | // Black point adapted to D50. |
377 | |
378 | static |
379 | void EmitWhiteBlackD50(cmsContext ContextID, cmsIOHANDLER* m, cmsCIEXYZ* BlackPoint) |
380 | { |
381 | |
382 | _cmsIOPrintf(ContextID, m, "/BlackPoint [%f %f %f]\n" , BlackPoint -> X, |
383 | BlackPoint -> Y, |
384 | BlackPoint -> Z); |
385 | |
386 | _cmsIOPrintf(ContextID, m, "/WhitePoint [%f %f %f]\n" , cmsD50_XYZ(ContextID)->X, |
387 | cmsD50_XYZ(ContextID)->Y, |
388 | cmsD50_XYZ(ContextID)->Z); |
389 | } |
390 | |
391 | |
392 | static |
393 | void EmitRangeCheck(cmsContext ContextID, cmsIOHANDLER* m) |
394 | { |
395 | _cmsIOPrintf(ContextID, m, "dup 0.0 lt { pop 0.0 } if " |
396 | "dup 1.0 gt { pop 1.0 } if " ); |
397 | |
398 | } |
399 | |
400 | // Does write the intent |
401 | |
402 | static |
403 | void EmitIntent(cmsContext ContextID, cmsIOHANDLER* m, cmsUInt32Number RenderingIntent) |
404 | { |
405 | const char *intent; |
406 | |
407 | switch (RenderingIntent) { |
408 | |
409 | case INTENT_PERCEPTUAL: intent = "Perceptual" ; break; |
410 | case INTENT_RELATIVE_COLORIMETRIC: intent = "RelativeColorimetric" ; break; |
411 | case INTENT_ABSOLUTE_COLORIMETRIC: intent = "AbsoluteColorimetric" ; break; |
412 | case INTENT_SATURATION: intent = "Saturation" ; break; |
413 | |
414 | default: intent = "Undefined" ; break; |
415 | } |
416 | |
417 | _cmsIOPrintf(ContextID, m, "/RenderingIntent (%s)\n" , intent ); |
418 | } |
419 | |
420 | // |
421 | // Convert L* to Y |
422 | // |
423 | // Y = Yn*[ (L* + 16) / 116] ^ 3 if (L*) >= 6 / 29 |
424 | // = Yn*( L* / 116) / 7.787 if (L*) < 6 / 29 |
425 | // |
426 | |
427 | /* |
428 | static |
429 | void EmitL2Y(cmsIOHANDLER* m) |
430 | { |
431 | _cmsIOPrintf(ContextID, m, |
432 | "{ " |
433 | "100 mul 16 add 116 div " // (L * 100 + 16) / 116 |
434 | "dup 6 29 div ge " // >= 6 / 29 ? |
435 | "{ dup dup mul mul } " // yes, ^3 and done |
436 | "{ 4 29 div sub 108 841 div mul } " // no, slope limiting |
437 | "ifelse } bind "); |
438 | } |
439 | */ |
440 | |
441 | |
442 | // Lab -> XYZ, see the discussion above |
443 | |
444 | static |
445 | void EmitLab2XYZ(cmsContext ContextID, cmsIOHANDLER* m) |
446 | { |
447 | _cmsIOPrintf(ContextID, m, "/RangeABC [ 0 1 0 1 0 1]\n" ); |
448 | _cmsIOPrintf(ContextID, m, "/DecodeABC [\n" ); |
449 | _cmsIOPrintf(ContextID, m, "{100 mul 16 add 116 div } bind\n" ); |
450 | _cmsIOPrintf(ContextID, m, "{255 mul 128 sub 500 div } bind\n" ); |
451 | _cmsIOPrintf(ContextID, m, "{255 mul 128 sub 200 div } bind\n" ); |
452 | _cmsIOPrintf(ContextID, m, "]\n" ); |
453 | _cmsIOPrintf(ContextID, m, "/MatrixABC [ 1 1 1 1 0 0 0 0 -1]\n" ); |
454 | _cmsIOPrintf(ContextID, m, "/RangeLMN [ -0.236 1.254 0 1 -0.635 1.640 ]\n" ); |
455 | _cmsIOPrintf(ContextID, m, "/DecodeLMN [\n" ); |
456 | _cmsIOPrintf(ContextID, m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.964200 mul} bind\n" ); |
457 | _cmsIOPrintf(ContextID, m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse } bind\n" ); |
458 | _cmsIOPrintf(ContextID, m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.824900 mul} bind\n" ); |
459 | _cmsIOPrintf(ContextID, m, "]\n" ); |
460 | } |
461 | |
462 | |
463 | |
464 | // Outputs a table of words. It does use 16 bits |
465 | |
466 | static |
467 | void Emit1Gamma(cmsContext ContextID, cmsIOHANDLER* m, cmsToneCurve* Table) |
468 | { |
469 | cmsUInt32Number i; |
470 | cmsFloat64Number gamma; |
471 | |
472 | if (Table == NULL) return; // Error |
473 | |
474 | if (Table ->nEntries <= 0) return; // Empty table |
475 | |
476 | // Suppress whole if identity |
477 | if (cmsIsToneCurveLinear(ContextID, Table)) return; |
478 | |
479 | // Check if is really an exponential. If so, emit "exp" |
480 | gamma = cmsEstimateGamma(ContextID, Table, 0.001); |
481 | if (gamma > 0) { |
482 | _cmsIOPrintf(ContextID, m, "{ %g exp } bind " , gamma); |
483 | return; |
484 | } |
485 | |
486 | _cmsIOPrintf(ContextID, m, "{ " ); |
487 | |
488 | // Bounds check |
489 | EmitRangeCheck(ContextID, m); |
490 | |
491 | // Emit intepolation code |
492 | |
493 | // PostScript code Stack |
494 | // =============== ======================== |
495 | // v |
496 | _cmsIOPrintf(ContextID, m, " [" ); |
497 | |
498 | for (i=0; i < Table->nEntries; i++) { |
499 | _cmsIOPrintf(ContextID, m, "%d " , Table->Table16[i]); |
500 | } |
501 | |
502 | _cmsIOPrintf(ContextID, m, "] " ); // v tab |
503 | |
504 | _cmsIOPrintf(ContextID, m, "dup " ); // v tab tab |
505 | _cmsIOPrintf(ContextID, m, "length 1 sub " ); // v tab dom |
506 | _cmsIOPrintf(ContextID, m, "3 -1 roll " ); // tab dom v |
507 | _cmsIOPrintf(ContextID, m, "mul " ); // tab val2 |
508 | _cmsIOPrintf(ContextID, m, "dup " ); // tab val2 val2 |
509 | _cmsIOPrintf(ContextID, m, "dup " ); // tab val2 val2 val2 |
510 | _cmsIOPrintf(ContextID, m, "floor cvi " ); // tab val2 val2 cell0 |
511 | _cmsIOPrintf(ContextID, m, "exch " ); // tab val2 cell0 val2 |
512 | _cmsIOPrintf(ContextID, m, "ceiling cvi " ); // tab val2 cell0 cell1 |
513 | _cmsIOPrintf(ContextID, m, "3 index " ); // tab val2 cell0 cell1 tab |
514 | _cmsIOPrintf(ContextID, m, "exch " ); // tab val2 cell0 tab cell1 |
515 | _cmsIOPrintf(ContextID, m, "get " ); // tab val2 cell0 y1 |
516 | _cmsIOPrintf(ContextID, m, "4 -1 roll " ); // val2 cell0 y1 tab |
517 | _cmsIOPrintf(ContextID, m, "3 -1 roll " ); // val2 y1 tab cell0 |
518 | _cmsIOPrintf(ContextID, m, "get " ); // val2 y1 y0 |
519 | _cmsIOPrintf(ContextID, m, "dup " ); // val2 y1 y0 y0 |
520 | _cmsIOPrintf(ContextID, m, "3 1 roll " ); // val2 y0 y1 y0 |
521 | _cmsIOPrintf(ContextID, m, "sub " ); // val2 y0 (y1-y0) |
522 | _cmsIOPrintf(ContextID, m, "3 -1 roll " ); // y0 (y1-y0) val2 |
523 | _cmsIOPrintf(ContextID, m, "dup " ); // y0 (y1-y0) val2 val2 |
524 | _cmsIOPrintf(ContextID, m, "floor cvi " ); // y0 (y1-y0) val2 floor(val2) |
525 | _cmsIOPrintf(ContextID, m, "sub " ); // y0 (y1-y0) rest |
526 | _cmsIOPrintf(ContextID, m, "mul " ); // y0 t1 |
527 | _cmsIOPrintf(ContextID, m, "add " ); // y |
528 | _cmsIOPrintf(ContextID, m, "65535 div " ); // result |
529 | |
530 | _cmsIOPrintf(ContextID, m, " } bind " ); |
531 | } |
532 | |
533 | |
534 | // Compare gamma table |
535 | |
536 | static |
537 | cmsBool GammaTableEquals(cmsUInt16Number* g1, cmsUInt16Number* g2, cmsUInt32Number nEntries) |
538 | { |
539 | return memcmp(g1, g2, nEntries* sizeof(cmsUInt16Number)) == 0; |
540 | } |
541 | |
542 | |
543 | // Does write a set of gamma curves |
544 | |
545 | static |
546 | void EmitNGamma(cmsContext ContextID, cmsIOHANDLER* m, cmsUInt32Number n, cmsToneCurve* g[]) |
547 | { |
548 | cmsUInt32Number i; |
549 | |
550 | for( i=0; i < n; i++ ) |
551 | { |
552 | if (g[i] == NULL) return; // Error |
553 | |
554 | if (i > 0 && GammaTableEquals(g[i-1]->Table16, g[i]->Table16, g[i]->nEntries)) { |
555 | |
556 | _cmsIOPrintf(ContextID, m, "dup " ); |
557 | } |
558 | else { |
559 | Emit1Gamma(ContextID, m, g[i]); |
560 | } |
561 | } |
562 | |
563 | } |
564 | |
565 | |
566 | |
567 | |
568 | |
569 | // Following code dumps a LUT onto memory stream |
570 | |
571 | |
572 | // This is the sampler. Intended to work in SAMPLER_INSPECT mode, |
573 | // that is, the callback will be called for each knot with |
574 | // |
575 | // In[] The grid location coordinates, normalized to 0..ffff |
576 | // Out[] The Pipeline values, normalized to 0..ffff |
577 | // |
578 | // Returning a value other than 0 does terminate the sampling process |
579 | // |
580 | // Each row contains Pipeline values for all but first component. So, I |
581 | // detect row changing by keeping a copy of last value of first |
582 | // component. -1 is used to mark beginning of whole block. |
583 | |
584 | static |
585 | int OutputValueSampler(cmsContext ContextID, register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo) |
586 | { |
587 | cmsPsSamplerCargo* sc = (cmsPsSamplerCargo*) Cargo; |
588 | cmsUInt32Number i; |
589 | |
590 | |
591 | if (sc -> FixWhite) { |
592 | |
593 | if (In[0] == 0xFFFF) { // Only in L* = 100, ab = [-8..8] |
594 | |
595 | if ((In[1] >= 0x7800 && In[1] <= 0x8800) && |
596 | (In[2] >= 0x7800 && In[2] <= 0x8800)) { |
597 | |
598 | cmsUInt16Number* Black; |
599 | cmsUInt16Number* White; |
600 | cmsUInt32Number nOutputs; |
601 | |
602 | if (!_cmsEndPointsBySpace(sc ->ColorSpace, &White, &Black, &nOutputs)) |
603 | return 0; |
604 | |
605 | for (i=0; i < nOutputs; i++) |
606 | Out[i] = White[i]; |
607 | } |
608 | |
609 | |
610 | } |
611 | } |
612 | |
613 | |
614 | // Hadle the parenthesis on rows |
615 | |
616 | if (In[0] != sc ->FirstComponent) { |
617 | |
618 | if (sc ->FirstComponent != -1) { |
619 | |
620 | _cmsIOPrintf(ContextID, sc ->m, sc ->PostMin); |
621 | sc ->SecondComponent = -1; |
622 | _cmsIOPrintf(ContextID, sc ->m, sc ->PostMaj); |
623 | } |
624 | |
625 | // Begin block |
626 | _cmsPSActualColumn = 0; |
627 | |
628 | _cmsIOPrintf(ContextID, sc ->m, sc ->PreMaj); |
629 | sc ->FirstComponent = In[0]; |
630 | } |
631 | |
632 | |
633 | if (In[1] != sc ->SecondComponent) { |
634 | |
635 | if (sc ->SecondComponent != -1) { |
636 | |
637 | _cmsIOPrintf(ContextID, sc ->m, sc ->PostMin); |
638 | } |
639 | |
640 | _cmsIOPrintf(ContextID, sc ->m, sc ->PreMin); |
641 | sc ->SecondComponent = In[1]; |
642 | } |
643 | |
644 | // Dump table. |
645 | |
646 | for (i=0; i < sc -> Pipeline ->Params->nOutputs; i++) { |
647 | |
648 | cmsUInt16Number wWordOut = Out[i]; |
649 | cmsUInt8Number wByteOut; // Value as byte |
650 | |
651 | |
652 | // We always deal with Lab4 |
653 | |
654 | wByteOut = Word2Byte(wWordOut); |
655 | WriteByte(ContextID, sc -> m, wByteOut); |
656 | } |
657 | |
658 | return 1; |
659 | } |
660 | |
661 | // Writes a Pipeline on memstream. Could be 8 or 16 bits based |
662 | |
663 | static |
664 | void WriteCLUT(cmsContext ContextID, cmsIOHANDLER* m, cmsStage* mpe, const char* PreMaj, |
665 | const char* PostMaj, |
666 | const char* PreMin, |
667 | const char* PostMin, |
668 | int FixWhite, |
669 | cmsColorSpaceSignature ColorSpace) |
670 | { |
671 | cmsUInt32Number i; |
672 | cmsPsSamplerCargo sc; |
673 | |
674 | sc.FirstComponent = -1; |
675 | sc.SecondComponent = -1; |
676 | sc.Pipeline = (_cmsStageCLutData *) mpe ->Data; |
677 | sc.m = m; |
678 | sc.PreMaj = PreMaj; |
679 | sc.PostMaj= PostMaj; |
680 | |
681 | sc.PreMin = PreMin; |
682 | sc.PostMin = PostMin; |
683 | sc.FixWhite = FixWhite; |
684 | sc.ColorSpace = ColorSpace; |
685 | |
686 | _cmsIOPrintf(ContextID, m, "[" ); |
687 | |
688 | for (i=0; i < sc.Pipeline->Params->nInputs; i++) |
689 | _cmsIOPrintf(ContextID, m, " %d " , sc.Pipeline->Params->nSamples[i]); |
690 | |
691 | _cmsIOPrintf(ContextID, m, " [\n" ); |
692 | |
693 | cmsStageSampleCLut16bit(ContextID, mpe, OutputValueSampler, (void*) &sc, SAMPLER_INSPECT); |
694 | |
695 | _cmsIOPrintf(ContextID, m, PostMin); |
696 | _cmsIOPrintf(ContextID, m, PostMaj); |
697 | _cmsIOPrintf(ContextID, m, "] " ); |
698 | |
699 | } |
700 | |
701 | |
702 | // Dumps CIEBasedA Color Space Array |
703 | |
704 | static |
705 | int EmitCIEBasedA(cmsContext ContextID, cmsIOHANDLER* m, cmsToneCurve* Curve, cmsCIEXYZ* BlackPoint) |
706 | { |
707 | |
708 | _cmsIOPrintf(ContextID, m, "[ /CIEBasedA\n" ); |
709 | _cmsIOPrintf(ContextID, m, " <<\n" ); |
710 | |
711 | _cmsIOPrintf(ContextID, m, "/DecodeA " ); |
712 | |
713 | Emit1Gamma(ContextID, m, Curve); |
714 | |
715 | _cmsIOPrintf(ContextID, m, " \n" ); |
716 | |
717 | _cmsIOPrintf(ContextID, m, "/MatrixA [ 0.9642 1.0000 0.8249 ]\n" ); |
718 | _cmsIOPrintf(ContextID, m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n" ); |
719 | |
720 | EmitWhiteBlackD50(ContextID, m, BlackPoint); |
721 | EmitIntent(ContextID, m, INTENT_PERCEPTUAL); |
722 | |
723 | _cmsIOPrintf(ContextID, m, ">>\n" ); |
724 | _cmsIOPrintf(ContextID, m, "]\n" ); |
725 | |
726 | return 1; |
727 | } |
728 | |
729 | |
730 | // Dumps CIEBasedABC Color Space Array |
731 | |
732 | static |
733 | int EmitCIEBasedABC(cmsContext ContextID, cmsIOHANDLER* m, cmsFloat64Number* Matrix, cmsToneCurve** CurveSet, cmsCIEXYZ* BlackPoint) |
734 | { |
735 | int i; |
736 | |
737 | _cmsIOPrintf(ContextID, m, "[ /CIEBasedABC\n" ); |
738 | _cmsIOPrintf(ContextID, m, "<<\n" ); |
739 | _cmsIOPrintf(ContextID, m, "/DecodeABC [ " ); |
740 | |
741 | EmitNGamma(ContextID, m, 3, CurveSet); |
742 | |
743 | _cmsIOPrintf(ContextID, m, "]\n" ); |
744 | |
745 | _cmsIOPrintf(ContextID, m, "/MatrixABC [ " ); |
746 | |
747 | for( i=0; i < 3; i++ ) { |
748 | |
749 | _cmsIOPrintf(ContextID, m, "%.6f %.6f %.6f " , Matrix[i + 3*0], |
750 | Matrix[i + 3*1], |
751 | Matrix[i + 3*2]); |
752 | } |
753 | |
754 | |
755 | _cmsIOPrintf(ContextID, m, "]\n" ); |
756 | |
757 | _cmsIOPrintf(ContextID, m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n" ); |
758 | |
759 | EmitWhiteBlackD50(ContextID, m, BlackPoint); |
760 | EmitIntent(ContextID, m, INTENT_PERCEPTUAL); |
761 | |
762 | _cmsIOPrintf(ContextID, m, ">>\n" ); |
763 | _cmsIOPrintf(ContextID, m, "]\n" ); |
764 | |
765 | |
766 | return 1; |
767 | } |
768 | |
769 | |
770 | static |
771 | int EmitCIEBasedDEF(cmsContext ContextID, cmsIOHANDLER* m, cmsPipeline* Pipeline, cmsUInt32Number Intent, cmsCIEXYZ* BlackPoint) |
772 | { |
773 | const char* PreMaj; |
774 | const char* PostMaj; |
775 | const char* PreMin, *PostMin; |
776 | cmsStage* mpe; |
777 | |
778 | mpe = Pipeline ->Elements; |
779 | |
780 | switch (cmsStageInputChannels(ContextID, mpe)) { |
781 | case 3: |
782 | |
783 | _cmsIOPrintf(ContextID, m, "[ /CIEBasedDEF\n" ); |
784 | PreMaj ="<" ; |
785 | PostMaj= ">\n" ; |
786 | PreMin = PostMin = "" ; |
787 | break; |
788 | case 4: |
789 | _cmsIOPrintf(ContextID, m, "[ /CIEBasedDEFG\n" ); |
790 | PreMaj = "[" ; |
791 | PostMaj = "]\n" ; |
792 | PreMin = "<" ; |
793 | PostMin = ">\n" ; |
794 | break; |
795 | default: |
796 | return 0; |
797 | |
798 | } |
799 | |
800 | _cmsIOPrintf(ContextID, m, "<<\n" ); |
801 | |
802 | if (cmsStageType(ContextID, mpe) == cmsSigCurveSetElemType) { |
803 | |
804 | _cmsIOPrintf(ContextID, m, "/DecodeDEF [ " ); |
805 | EmitNGamma(ContextID, m, cmsStageOutputChannels(ContextID, mpe), _cmsStageGetPtrToCurveSet(mpe)); |
806 | _cmsIOPrintf(ContextID, m, "]\n" ); |
807 | |
808 | mpe = mpe ->Next; |
809 | } |
810 | |
811 | if (cmsStageType(ContextID, mpe) == cmsSigCLutElemType) { |
812 | |
813 | _cmsIOPrintf(ContextID, m, "/Table " ); |
814 | WriteCLUT(ContextID, m, mpe, PreMaj, PostMaj, PreMin, PostMin, FALSE, (cmsColorSpaceSignature) 0); |
815 | _cmsIOPrintf(ContextID, m, "]\n" ); |
816 | } |
817 | |
818 | EmitLab2XYZ(ContextID, m); |
819 | EmitWhiteBlackD50(ContextID, m, BlackPoint); |
820 | EmitIntent(ContextID, m, Intent); |
821 | |
822 | _cmsIOPrintf(ContextID, m, " >>\n" ); |
823 | _cmsIOPrintf(ContextID, m, "]\n" ); |
824 | |
825 | return 1; |
826 | } |
827 | |
828 | // Generates a curve from a gray profile |
829 | |
830 | static |
831 | cmsToneCurve* (cmsContext ContextID, cmsHPROFILE hProfile, cmsUInt32Number Intent) |
832 | { |
833 | cmsToneCurve* Out = cmsBuildTabulatedToneCurve16(ContextID, 256, NULL); |
834 | cmsHPROFILE hXYZ = cmsCreateXYZProfile(ContextID); |
835 | cmsHTRANSFORM xform = cmsCreateTransform(ContextID, hProfile, TYPE_GRAY_8, hXYZ, TYPE_XYZ_DBL, Intent, cmsFLAGS_NOOPTIMIZE); |
836 | int i; |
837 | |
838 | if (Out != NULL && xform != NULL) { |
839 | for (i=0; i < 256; i++) { |
840 | |
841 | cmsUInt8Number Gray = (cmsUInt8Number) i; |
842 | cmsCIEXYZ XYZ; |
843 | |
844 | cmsDoTransform(ContextID, xform, &Gray, &XYZ, 1); |
845 | |
846 | Out ->Table16[i] =_cmsQuickSaturateWord(XYZ.Y * 65535.0); |
847 | } |
848 | } |
849 | |
850 | if (xform) cmsDeleteTransform(ContextID, xform); |
851 | if (hXYZ) cmsCloseProfile(ContextID, hXYZ); |
852 | return Out; |
853 | } |
854 | |
855 | |
856 | |
857 | // Because PostScript has only 8 bits in /Table, we should use |
858 | // a more perceptually uniform space... I do choose Lab. |
859 | |
860 | static |
861 | int WriteInputLUT(cmsContext ContextID, cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags) |
862 | { |
863 | cmsHPROFILE hLab; |
864 | cmsHTRANSFORM xform; |
865 | cmsUInt32Number nChannels; |
866 | cmsUInt32Number InputFormat; |
867 | int rc; |
868 | cmsHPROFILE Profiles[2]; |
869 | cmsCIEXYZ BlackPointAdaptedToD50; |
870 | |
871 | // Does create a device-link based transform. |
872 | // The DeviceLink is next dumped as working CSA. |
873 | |
874 | InputFormat = cmsFormatterForColorspaceOfProfile(ContextID, hProfile, 2, FALSE); |
875 | nChannels = T_CHANNELS(InputFormat); |
876 | |
877 | |
878 | cmsDetectBlackPoint(ContextID, &BlackPointAdaptedToD50, hProfile, Intent, 0); |
879 | |
880 | // Adjust output to Lab4 |
881 | hLab = cmsCreateLab4Profile(ContextID, NULL); |
882 | |
883 | Profiles[0] = hProfile; |
884 | Profiles[1] = hLab; |
885 | |
886 | xform = cmsCreateMultiprofileTransform(ContextID, Profiles, 2, InputFormat, TYPE_Lab_DBL, Intent, 0); |
887 | cmsCloseProfile(ContextID, hLab); |
888 | |
889 | if (xform == NULL) { |
890 | |
891 | cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Profile -> Lab" ); |
892 | return 0; |
893 | } |
894 | |
895 | // Only 1, 3 and 4 channels are allowed |
896 | |
897 | switch (nChannels) { |
898 | |
899 | case 1: { |
900 | cmsToneCurve* Gray2Y = ExtractGray2Y(ContextID, hProfile, Intent); |
901 | EmitCIEBasedA(ContextID, m, Gray2Y, &BlackPointAdaptedToD50); |
902 | cmsFreeToneCurve(ContextID, Gray2Y); |
903 | } |
904 | break; |
905 | |
906 | case 3: |
907 | case 4: { |
908 | cmsUInt32Number OutFrm = TYPE_Lab_16; |
909 | cmsPipeline* DeviceLink; |
910 | _cmsTRANSFORM* v = (_cmsTRANSFORM*) xform; |
911 | |
912 | DeviceLink = cmsPipelineDup(ContextID, v ->core->Lut); |
913 | if (DeviceLink == NULL) return 0; |
914 | |
915 | dwFlags |= cmsFLAGS_FORCE_CLUT; |
916 | _cmsOptimizePipeline(ContextID, &DeviceLink, Intent, &InputFormat, &OutFrm, &dwFlags); |
917 | |
918 | rc = EmitCIEBasedDEF(ContextID, m, DeviceLink, Intent, &BlackPointAdaptedToD50); |
919 | cmsPipelineFree(ContextID, DeviceLink); |
920 | if (rc == 0) return 0; |
921 | } |
922 | break; |
923 | |
924 | default: |
925 | |
926 | cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Only 3, 4 channels supported for CSA. This profile has %d channels." , nChannels); |
927 | return 0; |
928 | } |
929 | |
930 | |
931 | cmsDeleteTransform(ContextID, xform); |
932 | |
933 | return 1; |
934 | } |
935 | |
936 | static |
937 | cmsFloat64Number* GetPtrToMatrix(const cmsStage* mpe) |
938 | { |
939 | _cmsStageMatrixData* Data = (_cmsStageMatrixData*) mpe ->Data; |
940 | |
941 | return Data -> Double; |
942 | } |
943 | |
944 | |
945 | // Does create CSA based on matrix-shaper. Allowed types are gray and RGB based |
946 | static |
947 | int WriteInputMatrixShaper(cmsContext ContextID, cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsStage* Matrix, cmsStage* Shaper) |
948 | { |
949 | cmsColorSpaceSignature ColorSpace; |
950 | int rc; |
951 | cmsCIEXYZ BlackPointAdaptedToD50; |
952 | |
953 | ColorSpace = cmsGetColorSpace(ContextID, hProfile); |
954 | |
955 | cmsDetectBlackPoint(ContextID, &BlackPointAdaptedToD50, hProfile, INTENT_RELATIVE_COLORIMETRIC, 0); |
956 | |
957 | if (ColorSpace == cmsSigGrayData) { |
958 | |
959 | cmsToneCurve** ShaperCurve = _cmsStageGetPtrToCurveSet(Shaper); |
960 | rc = EmitCIEBasedA(ContextID, m, ShaperCurve[0], &BlackPointAdaptedToD50); |
961 | |
962 | } |
963 | else |
964 | if (ColorSpace == cmsSigRgbData) { |
965 | |
966 | cmsMAT3 Mat; |
967 | int i, j; |
968 | |
969 | memmove(&Mat, GetPtrToMatrix(Matrix), sizeof(Mat)); |
970 | |
971 | for (i = 0; i < 3; i++) |
972 | for (j = 0; j < 3; j++) |
973 | Mat.v[i].n[j] *= MAX_ENCODEABLE_XYZ; |
974 | |
975 | rc = EmitCIEBasedABC(ContextID, m, (cmsFloat64Number *) &Mat, |
976 | _cmsStageGetPtrToCurveSet(Shaper), |
977 | &BlackPointAdaptedToD50); |
978 | } |
979 | else { |
980 | |
981 | cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Profile is not suitable for CSA. Unsupported colorspace." ); |
982 | return 0; |
983 | } |
984 | |
985 | return rc; |
986 | } |
987 | |
988 | |
989 | |
990 | // Creates a PostScript color list from a named profile data. |
991 | // This is a HP extension, and it works in Lab instead of XYZ |
992 | |
993 | static |
994 | int WriteNamedColorCSA(cmsContext ContextID, cmsIOHANDLER* m, cmsHPROFILE hNamedColor, cmsUInt32Number Intent) |
995 | { |
996 | cmsHTRANSFORM xform; |
997 | cmsHPROFILE hLab; |
998 | cmsUInt32Number i, nColors; |
999 | char ColorName[cmsMAX_PATH]; |
1000 | cmsNAMEDCOLORLIST* NamedColorList; |
1001 | |
1002 | hLab = cmsCreateLab4Profile(ContextID, NULL); |
1003 | xform = cmsCreateTransform(ContextID, hNamedColor, TYPE_NAMED_COLOR_INDEX, hLab, TYPE_Lab_DBL, Intent, 0); |
1004 | if (xform == NULL) return 0; |
1005 | |
1006 | NamedColorList = cmsGetNamedColorList(xform); |
1007 | if (NamedColorList == NULL) return 0; |
1008 | |
1009 | _cmsIOPrintf(ContextID, m, "<<\n" ); |
1010 | _cmsIOPrintf(ContextID, m, "(colorlistcomment) (%s)\n" , "Named color CSA" ); |
1011 | _cmsIOPrintf(ContextID, m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n" ); |
1012 | _cmsIOPrintf(ContextID, m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n" ); |
1013 | |
1014 | nColors = cmsNamedColorCount(ContextID, NamedColorList); |
1015 | |
1016 | |
1017 | for (i=0; i < nColors; i++) { |
1018 | |
1019 | cmsUInt16Number In[1]; |
1020 | cmsCIELab Lab; |
1021 | |
1022 | In[0] = (cmsUInt16Number) i; |
1023 | |
1024 | if (!cmsNamedColorInfo(ContextID, NamedColorList, i, ColorName, NULL, NULL, NULL, NULL)) |
1025 | continue; |
1026 | |
1027 | cmsDoTransform(ContextID, xform, In, &Lab, 1); |
1028 | _cmsIOPrintf(ContextID, m, " (%s) [ %.3f %.3f %.3f ]\n" , ColorName, Lab.L, Lab.a, Lab.b); |
1029 | } |
1030 | |
1031 | |
1032 | |
1033 | _cmsIOPrintf(ContextID, m, ">>\n" ); |
1034 | |
1035 | cmsDeleteTransform(ContextID, xform); |
1036 | cmsCloseProfile(ContextID, hLab); |
1037 | return 1; |
1038 | } |
1039 | |
1040 | |
1041 | // Does create a Color Space Array on XYZ colorspace for PostScript usage |
1042 | static |
1043 | cmsUInt32Number GenerateCSA(cmsContext ContextID, |
1044 | cmsHPROFILE hProfile, |
1045 | cmsUInt32Number Intent, |
1046 | cmsUInt32Number dwFlags, |
1047 | cmsIOHANDLER* mem) |
1048 | { |
1049 | cmsUInt32Number dwBytesUsed; |
1050 | cmsPipeline* lut = NULL; |
1051 | cmsStage* Matrix, *Shaper; |
1052 | |
1053 | |
1054 | // Is a named color profile? |
1055 | if (cmsGetDeviceClass(ContextID, hProfile) == cmsSigNamedColorClass) { |
1056 | |
1057 | if (!WriteNamedColorCSA(ContextID, mem, hProfile, Intent)) goto Error; |
1058 | } |
1059 | else { |
1060 | |
1061 | |
1062 | // Any profile class are allowed (including devicelink), but |
1063 | // output (PCS) colorspace must be XYZ or Lab |
1064 | cmsColorSpaceSignature ColorSpace = cmsGetPCS(ContextID, hProfile); |
1065 | |
1066 | if (ColorSpace != cmsSigXYZData && |
1067 | ColorSpace != cmsSigLabData) { |
1068 | |
1069 | cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Invalid output color space" ); |
1070 | goto Error; |
1071 | } |
1072 | |
1073 | |
1074 | // Read the lut with all necessary conversion stages |
1075 | lut = _cmsReadInputLUT(ContextID, hProfile, Intent); |
1076 | if (lut == NULL) goto Error; |
1077 | |
1078 | |
1079 | // Tone curves + matrix can be implemented without any LUT |
1080 | if (cmsPipelineCheckAndRetreiveStages(ContextID, lut, 2, cmsSigCurveSetElemType, cmsSigMatrixElemType, &Shaper, &Matrix)) { |
1081 | |
1082 | if (!WriteInputMatrixShaper(ContextID, mem, hProfile, Matrix, Shaper)) goto Error; |
1083 | |
1084 | } |
1085 | else { |
1086 | // We need a LUT for the rest |
1087 | if (!WriteInputLUT(ContextID, mem, hProfile, Intent, dwFlags)) goto Error; |
1088 | } |
1089 | } |
1090 | |
1091 | |
1092 | // Done, keep memory usage |
1093 | dwBytesUsed = mem ->UsedSpace; |
1094 | |
1095 | // Get rid of LUT |
1096 | if (lut != NULL) cmsPipelineFree(ContextID, lut); |
1097 | |
1098 | // Finally, return used byte count |
1099 | return dwBytesUsed; |
1100 | |
1101 | Error: |
1102 | if (lut != NULL) cmsPipelineFree(ContextID, lut); |
1103 | return 0; |
1104 | } |
1105 | |
1106 | // ------------------------------------------------------ Color Rendering Dictionary (CRD) |
1107 | |
1108 | |
1109 | |
1110 | /* |
1111 | |
1112 | Black point compensation plus chromatic adaptation: |
1113 | |
1114 | Step 1 - Chromatic adaptation |
1115 | ============================= |
1116 | |
1117 | WPout |
1118 | X = ------- PQR |
1119 | Wpin |
1120 | |
1121 | Step 2 - Black point compensation |
1122 | ================================= |
1123 | |
1124 | (WPout - BPout)*X - WPout*(BPin - BPout) |
1125 | out = --------------------------------------- |
1126 | WPout - BPin |
1127 | |
1128 | |
1129 | Algorithm discussion |
1130 | ==================== |
1131 | |
1132 | TransformPQR(WPin, BPin, WPout, BPout, PQR) |
1133 | |
1134 | Wpin,etc= { Xws Yws Zws Pws Qws Rws } |
1135 | |
1136 | |
1137 | Algorithm Stack 0...n |
1138 | =========================================================== |
1139 | PQR BPout WPout BPin WPin |
1140 | 4 index 3 get WPin PQR BPout WPout BPin WPin |
1141 | div (PQR/WPin) BPout WPout BPin WPin |
1142 | 2 index 3 get WPout (PQR/WPin) BPout WPout BPin WPin |
1143 | mult WPout*(PQR/WPin) BPout WPout BPin WPin |
1144 | |
1145 | 2 index 3 get WPout WPout*(PQR/WPin) BPout WPout BPin WPin |
1146 | 2 index 3 get BPout WPout WPout*(PQR/WPin) BPout WPout BPin WPin |
1147 | sub (WPout-BPout) WPout*(PQR/WPin) BPout WPout BPin WPin |
1148 | mult (WPout-BPout)* WPout*(PQR/WPin) BPout WPout BPin WPin |
1149 | |
1150 | 2 index 3 get WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin |
1151 | 4 index 3 get BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin |
1152 | 3 index 3 get BPout BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin |
1153 | |
1154 | sub (BPin-BPout) WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin |
1155 | mult (BPin-BPout)*WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin |
1156 | sub (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin |
1157 | |
1158 | 3 index 3 get BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin |
1159 | 3 index 3 get WPout BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin |
1160 | exch |
1161 | sub (WPout-BPin) (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin |
1162 | div |
1163 | |
1164 | exch pop |
1165 | exch pop |
1166 | exch pop |
1167 | exch pop |
1168 | |
1169 | */ |
1170 | |
1171 | |
1172 | static |
1173 | void EmitPQRStage(cmsContext ContextID, cmsIOHANDLER* m, cmsHPROFILE hProfile, int DoBPC, int lIsAbsolute) |
1174 | { |
1175 | |
1176 | |
1177 | if (lIsAbsolute) { |
1178 | |
1179 | // For absolute colorimetric intent, encode back to relative |
1180 | // and generate a relative Pipeline |
1181 | |
1182 | // Relative encoding is obtained across XYZpcs*(D50/WhitePoint) |
1183 | |
1184 | cmsCIEXYZ White; |
1185 | |
1186 | _cmsReadMediaWhitePoint(ContextID, &White, hProfile); |
1187 | |
1188 | _cmsIOPrintf(ContextID, m,"/MatrixPQR [1 0 0 0 1 0 0 0 1 ]\n" ); |
1189 | _cmsIOPrintf(ContextID, m,"/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n" ); |
1190 | |
1191 | _cmsIOPrintf(ContextID, m, "%% Absolute colorimetric -- encode to relative to maximize LUT usage\n" |
1192 | "/TransformPQR [\n" |
1193 | "{0.9642 mul %g div exch pop exch pop exch pop exch pop} bind\n" |
1194 | "{1.0000 mul %g div exch pop exch pop exch pop exch pop} bind\n" |
1195 | "{0.8249 mul %g div exch pop exch pop exch pop exch pop} bind\n]\n" , |
1196 | White.X, White.Y, White.Z); |
1197 | return; |
1198 | } |
1199 | |
1200 | |
1201 | _cmsIOPrintf(ContextID, m,"%% Bradford Cone Space\n" |
1202 | "/MatrixPQR [0.8951 -0.7502 0.0389 0.2664 1.7135 -0.0685 -0.1614 0.0367 1.0296 ] \n" ); |
1203 | |
1204 | _cmsIOPrintf(ContextID, m, "/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n" ); |
1205 | |
1206 | |
1207 | // No BPC |
1208 | |
1209 | if (!DoBPC) { |
1210 | |
1211 | _cmsIOPrintf(ContextID, m, "%% VonKries-like transform in Bradford Cone Space\n" |
1212 | "/TransformPQR [\n" |
1213 | "{exch pop exch 3 get mul exch pop exch 3 get div} bind\n" |
1214 | "{exch pop exch 4 get mul exch pop exch 4 get div} bind\n" |
1215 | "{exch pop exch 5 get mul exch pop exch 5 get div} bind\n]\n" ); |
1216 | } else { |
1217 | |
1218 | // BPC |
1219 | |
1220 | _cmsIOPrintf(ContextID, m, "%% VonKries-like transform in Bradford Cone Space plus BPC\n" |
1221 | "/TransformPQR [\n" ); |
1222 | |
1223 | _cmsIOPrintf(ContextID, m, "{4 index 3 get div 2 index 3 get mul " |
1224 | "2 index 3 get 2 index 3 get sub mul " |
1225 | "2 index 3 get 4 index 3 get 3 index 3 get sub mul sub " |
1226 | "3 index 3 get 3 index 3 get exch sub div " |
1227 | "exch pop exch pop exch pop exch pop } bind\n" ); |
1228 | |
1229 | _cmsIOPrintf(ContextID, m, "{4 index 4 get div 2 index 4 get mul " |
1230 | "2 index 4 get 2 index 4 get sub mul " |
1231 | "2 index 4 get 4 index 4 get 3 index 4 get sub mul sub " |
1232 | "3 index 4 get 3 index 4 get exch sub div " |
1233 | "exch pop exch pop exch pop exch pop } bind\n" ); |
1234 | |
1235 | _cmsIOPrintf(ContextID, m, "{4 index 5 get div 2 index 5 get mul " |
1236 | "2 index 5 get 2 index 5 get sub mul " |
1237 | "2 index 5 get 4 index 5 get 3 index 5 get sub mul sub " |
1238 | "3 index 5 get 3 index 5 get exch sub div " |
1239 | "exch pop exch pop exch pop exch pop } bind\n]\n" ); |
1240 | |
1241 | } |
1242 | |
1243 | |
1244 | } |
1245 | |
1246 | |
1247 | static |
1248 | void EmitXYZ2Lab(cmsContext ContextID, cmsIOHANDLER* m) |
1249 | { |
1250 | _cmsIOPrintf(ContextID, m, "/RangeLMN [ -0.635 2.0 0 2 -0.635 2.0 ]\n" ); |
1251 | _cmsIOPrintf(ContextID, m, "/EncodeLMN [\n" ); |
1252 | _cmsIOPrintf(ContextID, m, "{ 0.964200 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n" ); |
1253 | _cmsIOPrintf(ContextID, m, "{ 1.000000 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n" ); |
1254 | _cmsIOPrintf(ContextID, m, "{ 0.824900 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n" ); |
1255 | _cmsIOPrintf(ContextID, m, "]\n" ); |
1256 | _cmsIOPrintf(ContextID, m, "/MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]\n" ); |
1257 | _cmsIOPrintf(ContextID, m, "/EncodeABC [\n" ); |
1258 | |
1259 | |
1260 | _cmsIOPrintf(ContextID, m, "{ 116 mul 16 sub 100 div } bind\n" ); |
1261 | _cmsIOPrintf(ContextID, m, "{ 500 mul 128 add 256 div } bind\n" ); |
1262 | _cmsIOPrintf(ContextID, m, "{ 200 mul 128 add 256 div } bind\n" ); |
1263 | |
1264 | |
1265 | _cmsIOPrintf(ContextID, m, "]\n" ); |
1266 | |
1267 | |
1268 | } |
1269 | |
1270 | // Due to impedance mismatch between XYZ and almost all RGB and CMYK spaces |
1271 | // I choose to dump LUTS in Lab instead of XYZ. There is still a lot of wasted |
1272 | // space on 3D CLUT, but since space seems not to be a problem here, 33 points |
1273 | // would give a reasonable accuracy. Note also that CRD tables must operate in |
1274 | // 8 bits. |
1275 | |
1276 | static |
1277 | int WriteOutputLUT(cmsContext ContextID, cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags) |
1278 | { |
1279 | cmsHPROFILE hLab; |
1280 | cmsHTRANSFORM xform; |
1281 | cmsUInt32Number i, nChannels; |
1282 | cmsUInt32Number OutputFormat; |
1283 | _cmsTRANSFORM* v; |
1284 | cmsPipeline* DeviceLink; |
1285 | cmsHPROFILE Profiles[3]; |
1286 | cmsCIEXYZ BlackPointAdaptedToD50; |
1287 | cmsBool lDoBPC = (cmsBool) (dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION); |
1288 | cmsBool lFixWhite = (cmsBool) !(dwFlags & cmsFLAGS_NOWHITEONWHITEFIXUP); |
1289 | cmsUInt32Number InFrm = TYPE_Lab_16; |
1290 | cmsUInt32Number RelativeEncodingIntent; |
1291 | cmsColorSpaceSignature ColorSpace; |
1292 | |
1293 | |
1294 | hLab = cmsCreateLab4Profile(ContextID, NULL); |
1295 | if (hLab == NULL) return 0; |
1296 | |
1297 | OutputFormat = cmsFormatterForColorspaceOfProfile(ContextID, hProfile, 2, FALSE); |
1298 | nChannels = T_CHANNELS(OutputFormat); |
1299 | |
1300 | ColorSpace = cmsGetColorSpace(ContextID, hProfile); |
1301 | |
1302 | // For absolute colorimetric, the LUT is encoded as relative in order to preserve precision. |
1303 | |
1304 | RelativeEncodingIntent = Intent; |
1305 | if (RelativeEncodingIntent == INTENT_ABSOLUTE_COLORIMETRIC) |
1306 | RelativeEncodingIntent = INTENT_RELATIVE_COLORIMETRIC; |
1307 | |
1308 | |
1309 | // Use V4 Lab always |
1310 | Profiles[0] = hLab; |
1311 | Profiles[1] = hProfile; |
1312 | |
1313 | xform = cmsCreateMultiprofileTransform(ContextID, |
1314 | Profiles, 2, TYPE_Lab_DBL, |
1315 | OutputFormat, RelativeEncodingIntent, 0); |
1316 | cmsCloseProfile(ContextID, hLab); |
1317 | |
1318 | if (xform == NULL) { |
1319 | |
1320 | cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Lab -> Profile in CRD creation" ); |
1321 | return 0; |
1322 | } |
1323 | |
1324 | // Get a copy of the internal devicelink |
1325 | v = (_cmsTRANSFORM*) xform; |
1326 | DeviceLink = cmsPipelineDup(ContextID, v ->core->Lut); |
1327 | if (DeviceLink == NULL) return 0; |
1328 | |
1329 | |
1330 | // We need a CLUT |
1331 | dwFlags |= cmsFLAGS_FORCE_CLUT; |
1332 | _cmsOptimizePipeline(ContextID, &DeviceLink, RelativeEncodingIntent, &InFrm, &OutputFormat, &dwFlags); |
1333 | |
1334 | _cmsIOPrintf(ContextID, m, "<<\n" ); |
1335 | _cmsIOPrintf(ContextID, m, "/ColorRenderingType 1\n" ); |
1336 | |
1337 | |
1338 | cmsDetectBlackPoint(ContextID, &BlackPointAdaptedToD50, hProfile, Intent, 0); |
1339 | |
1340 | // Emit headers, etc. |
1341 | EmitWhiteBlackD50(ContextID, m, &BlackPointAdaptedToD50); |
1342 | EmitPQRStage(ContextID, m, hProfile, lDoBPC, Intent == INTENT_ABSOLUTE_COLORIMETRIC); |
1343 | EmitXYZ2Lab(ContextID, m); |
1344 | |
1345 | |
1346 | // FIXUP: map Lab (100, 0, 0) to perfect white, because the particular encoding for Lab |
1347 | // does map a=b=0 not falling into any specific node. Since range a,b goes -128..127, |
1348 | // zero is slightly moved towards right, so assure next node (in L=100 slice) is mapped to |
1349 | // zero. This would sacrifice a bit of highlights, but failure to do so would cause |
1350 | // scum dot. Ouch. |
1351 | |
1352 | if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) |
1353 | lFixWhite = FALSE; |
1354 | |
1355 | _cmsIOPrintf(ContextID, m, "/RenderTable " ); |
1356 | |
1357 | |
1358 | WriteCLUT(ContextID, m, cmsPipelineGetPtrToFirstStage(ContextID, DeviceLink), "<" , ">\n" , "" , "" , lFixWhite, ColorSpace); |
1359 | |
1360 | _cmsIOPrintf(ContextID, m, " %d {} bind " , nChannels); |
1361 | |
1362 | for (i=1; i < nChannels; i++) |
1363 | _cmsIOPrintf(ContextID, m, "dup " ); |
1364 | |
1365 | _cmsIOPrintf(ContextID, m, "]\n" ); |
1366 | |
1367 | |
1368 | EmitIntent(ContextID, m, Intent); |
1369 | |
1370 | _cmsIOPrintf(ContextID, m, ">>\n" ); |
1371 | |
1372 | if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) { |
1373 | |
1374 | _cmsIOPrintf(ContextID, m, "/Current exch /ColorRendering defineresource pop\n" ); |
1375 | } |
1376 | |
1377 | cmsPipelineFree(ContextID, DeviceLink); |
1378 | cmsDeleteTransform(ContextID, xform); |
1379 | |
1380 | return 1; |
1381 | } |
1382 | |
1383 | |
1384 | // Builds a ASCII string containing colorant list in 0..1.0 range |
1385 | static |
1386 | void BuildColorantList(char *Colorant, cmsUInt32Number nColorant, cmsUInt16Number Out[]) |
1387 | { |
1388 | char Buff[32]; |
1389 | cmsUInt32Number j; |
1390 | |
1391 | Colorant[0] = 0; |
1392 | if (nColorant > cmsMAXCHANNELS) |
1393 | nColorant = cmsMAXCHANNELS; |
1394 | |
1395 | for (j = 0; j < nColorant; j++) { |
1396 | |
1397 | snprintf(Buff, 31, "%.3f" , Out[j] / 65535.0); |
1398 | Buff[31] = 0; |
1399 | strcat(Colorant, Buff); |
1400 | if (j < nColorant - 1) |
1401 | strcat(Colorant, " " ); |
1402 | |
1403 | } |
1404 | } |
1405 | |
1406 | |
1407 | // Creates a PostScript color list from a named profile data. |
1408 | // This is a HP extension. |
1409 | |
1410 | static |
1411 | int WriteNamedColorCRD(cmsContext ContextID, cmsIOHANDLER* m, cmsHPROFILE hNamedColor, cmsUInt32Number Intent, cmsUInt32Number dwFlags) |
1412 | { |
1413 | cmsHTRANSFORM xform; |
1414 | cmsUInt32Number i, nColors, nColorant; |
1415 | cmsUInt32Number OutputFormat; |
1416 | char ColorName[cmsMAX_PATH]; |
1417 | char Colorant[128]; |
1418 | cmsNAMEDCOLORLIST* NamedColorList; |
1419 | |
1420 | |
1421 | OutputFormat = cmsFormatterForColorspaceOfProfile(ContextID, hNamedColor, 2, FALSE); |
1422 | nColorant = T_CHANNELS(OutputFormat); |
1423 | |
1424 | |
1425 | xform = cmsCreateTransform(ContextID, hNamedColor, TYPE_NAMED_COLOR_INDEX, NULL, OutputFormat, Intent, dwFlags); |
1426 | if (xform == NULL) return 0; |
1427 | |
1428 | |
1429 | NamedColorList = cmsGetNamedColorList(xform); |
1430 | if (NamedColorList == NULL) return 0; |
1431 | |
1432 | _cmsIOPrintf(ContextID, m, "<<\n" ); |
1433 | _cmsIOPrintf(ContextID, m, "(colorlistcomment) (%s) \n" , "Named profile" ); |
1434 | _cmsIOPrintf(ContextID, m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n" ); |
1435 | _cmsIOPrintf(ContextID, m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n" ); |
1436 | |
1437 | nColors = cmsNamedColorCount(ContextID, NamedColorList); |
1438 | |
1439 | for (i=0; i < nColors; i++) { |
1440 | |
1441 | cmsUInt16Number In[1]; |
1442 | cmsUInt16Number Out[cmsMAXCHANNELS]; |
1443 | |
1444 | In[0] = (cmsUInt16Number) i; |
1445 | |
1446 | if (!cmsNamedColorInfo(ContextID, NamedColorList, i, ColorName, NULL, NULL, NULL, NULL)) |
1447 | continue; |
1448 | |
1449 | cmsDoTransform(ContextID, xform, In, Out, 1); |
1450 | BuildColorantList(Colorant, nColorant, Out); |
1451 | _cmsIOPrintf(ContextID, m, " (%s) [ %s ]\n" , ColorName, Colorant); |
1452 | } |
1453 | |
1454 | _cmsIOPrintf(ContextID, m, " >>" ); |
1455 | |
1456 | if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) { |
1457 | |
1458 | _cmsIOPrintf(ContextID, m, " /Current exch /HPSpotTable defineresource pop\n" ); |
1459 | } |
1460 | |
1461 | cmsDeleteTransform(ContextID, xform); |
1462 | return 1; |
1463 | } |
1464 | |
1465 | |
1466 | |
1467 | // This one does create a Color Rendering Dictionary. |
1468 | // CRD are always LUT-Based, no matter if profile is |
1469 | // implemented as matrix-shaper. |
1470 | |
1471 | static |
1472 | cmsUInt32Number GenerateCRD(cmsContext ContextID, |
1473 | cmsHPROFILE hProfile, |
1474 | cmsUInt32Number Intent, cmsUInt32Number dwFlags, |
1475 | cmsIOHANDLER* mem) |
1476 | { |
1477 | cmsUInt32Number dwBytesUsed; |
1478 | |
1479 | if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) { |
1480 | |
1481 | EmitHeader(ContextID, mem, "Color Rendering Dictionary (CRD)" , hProfile); |
1482 | } |
1483 | |
1484 | |
1485 | // Is a named color profile? |
1486 | if (cmsGetDeviceClass(ContextID, hProfile) == cmsSigNamedColorClass) { |
1487 | |
1488 | if (!WriteNamedColorCRD(ContextID, mem, hProfile, Intent, dwFlags)) { |
1489 | return 0; |
1490 | } |
1491 | } |
1492 | else { |
1493 | |
1494 | // CRD are always implemented as LUT |
1495 | |
1496 | if (!WriteOutputLUT(ContextID, mem, hProfile, Intent, dwFlags)) { |
1497 | return 0; |
1498 | } |
1499 | } |
1500 | |
1501 | if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) { |
1502 | |
1503 | _cmsIOPrintf(ContextID, mem, "%%%%EndResource\n" ); |
1504 | _cmsIOPrintf(ContextID, mem, "\n%% CRD End\n" ); |
1505 | } |
1506 | |
1507 | // Done, keep memory usage |
1508 | dwBytesUsed = mem ->UsedSpace; |
1509 | |
1510 | // Finally, return used byte count |
1511 | return dwBytesUsed; |
1512 | } |
1513 | |
1514 | |
1515 | |
1516 | |
1517 | cmsUInt32Number CMSEXPORT cmsGetPostScriptColorResource(cmsContext ContextID, |
1518 | cmsPSResourceType Type, |
1519 | cmsHPROFILE hProfile, |
1520 | cmsUInt32Number Intent, |
1521 | cmsUInt32Number dwFlags, |
1522 | cmsIOHANDLER* io) |
1523 | { |
1524 | cmsUInt32Number rc; |
1525 | |
1526 | |
1527 | switch (Type) { |
1528 | |
1529 | case cmsPS_RESOURCE_CSA: |
1530 | rc = GenerateCSA(ContextID, hProfile, Intent, dwFlags, io); |
1531 | break; |
1532 | |
1533 | default: |
1534 | case cmsPS_RESOURCE_CRD: |
1535 | rc = GenerateCRD(ContextID, hProfile, Intent, dwFlags, io); |
1536 | break; |
1537 | } |
1538 | |
1539 | return rc; |
1540 | } |
1541 | |
1542 | |
1543 | |
1544 | cmsUInt32Number CMSEXPORT cmsGetPostScriptCRD(cmsContext ContextID, |
1545 | cmsHPROFILE hProfile, |
1546 | cmsUInt32Number Intent, cmsUInt32Number dwFlags, |
1547 | void* Buffer, cmsUInt32Number dwBufferLen) |
1548 | { |
1549 | cmsIOHANDLER* mem; |
1550 | cmsUInt32Number dwBytesUsed; |
1551 | |
1552 | // Set up the serialization engine |
1553 | if (Buffer == NULL) |
1554 | mem = cmsOpenIOhandlerFromNULL(ContextID); |
1555 | else |
1556 | mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w" ); |
1557 | |
1558 | if (!mem) return 0; |
1559 | |
1560 | dwBytesUsed = cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CRD, hProfile, Intent, dwFlags, mem); |
1561 | |
1562 | // Get rid of memory stream |
1563 | cmsCloseIOhandler(ContextID, mem); |
1564 | |
1565 | return dwBytesUsed; |
1566 | } |
1567 | |
1568 | |
1569 | |
1570 | // Does create a Color Space Array on XYZ colorspace for PostScript usage |
1571 | cmsUInt32Number CMSEXPORT cmsGetPostScriptCSA(cmsContext ContextID, |
1572 | cmsHPROFILE hProfile, |
1573 | cmsUInt32Number Intent, |
1574 | cmsUInt32Number dwFlags, |
1575 | void* Buffer, |
1576 | cmsUInt32Number dwBufferLen) |
1577 | { |
1578 | cmsIOHANDLER* mem; |
1579 | cmsUInt32Number dwBytesUsed; |
1580 | |
1581 | if (Buffer == NULL) |
1582 | mem = cmsOpenIOhandlerFromNULL(ContextID); |
1583 | else |
1584 | mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w" ); |
1585 | |
1586 | if (!mem) return 0; |
1587 | |
1588 | dwBytesUsed = cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CSA, hProfile, Intent, dwFlags, mem); |
1589 | |
1590 | // Get rid of memory stream |
1591 | cmsCloseIOhandler(ContextID, mem); |
1592 | |
1593 | return dwBytesUsed; |
1594 | |
1595 | } |
1596 | |