comparison mupdf-source/thirdparty/lcms2/utils/transicc/transicc.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 //---------------------------------------------------------------------------------
2 //
3 // Little Color Management System
4 // Copyright (c) 1998-2023 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 "utils.h"
28
29 #ifndef _MSC_VER
30 # include <unistd.h>
31 #endif
32
33 #ifdef CMS_IS_WINDOWS_
34 # include <io.h>
35 #endif
36
37 #define MAX_INPUT_BUFFER 4096
38
39 // Global options
40
41 static cmsBool InHexa = FALSE;
42 static cmsBool GamutCheck = FALSE;
43 static cmsBool Width16 = FALSE;
44 static cmsBool BlackPointCompensation = FALSE;
45 static cmsBool lIsDeviceLink = FALSE;
46 static cmsBool lQuantize = FALSE;
47 static cmsBool lUnbounded = TRUE;
48 static cmsBool lIsFloat = TRUE;
49
50 static cmsUInt32Number Intent = INTENT_PERCEPTUAL;
51 static cmsUInt32Number ProofingIntent = INTENT_PERCEPTUAL;
52
53 static int PrecalcMode = 0;
54
55 // --------------------------------------------------------------
56
57 static char *cInProf = NULL;
58 static char *cOutProf = NULL;
59 static char *cProofing = NULL;
60
61 static char *IncludePart = NULL;
62
63 static cmsHANDLE hIT8in = NULL; // CGATS input
64 static cmsHANDLE hIT8out = NULL; // CGATS output
65
66 static char CGATSPatch[1024]; // Actual Patch Name
67 static char CGATSoutFilename[cmsMAX_PATH];
68
69 static int nMaxPatches;
70
71 static cmsHTRANSFORM hTrans, hTransXYZ, hTransLab;
72 static cmsBool InputNamedColor = FALSE;
73
74 static cmsColorSpaceSignature InputColorSpace, OutputColorSpace;
75
76 static cmsNAMEDCOLORLIST* InputColorant = NULL;
77 static cmsNAMEDCOLORLIST* OutputColorant = NULL;
78
79 static cmsFloat64Number InputRange, OutputRange;
80
81
82 // isatty replacement
83 #ifdef _MSC_VER
84 #define xisatty(x) _isatty( _fileno( (x) ) )
85 #else
86 #define xisatty(x) isatty( fileno( (x) ) )
87 #endif
88
89 //---------------------------------------------------------------------------------------------------
90
91 // Print usage to stderr
92 static
93 void Help(void)
94 {
95
96 fprintf(stderr, "usage: transicc [flags] [CGATS input] [CGATS output]\n\n");
97
98 fprintf(stderr, "flags:\n\n");
99 fprintf(stderr, "-v<0..3> - Verbosity level\n");
100
101 fprintf(stderr, "-e[op] - Encoded representation of numbers\n");
102 fprintf(stderr, "\t-w - use 16 bits\n");
103 fprintf(stderr, "\t-x - Hexadecimal\n\n");
104
105 fprintf(stderr, "-s - bounded mode (clip negatives and highlights)\n");
106 fprintf(stderr, "-q - Quantize (round decimals)\n\n");
107
108 fprintf(stderr, "-i<profile> - Input profile (defaults to sRGB)\n");
109 fprintf(stderr, "-o<profile> - Output profile (defaults to sRGB)\n");
110 fprintf(stderr, "-l<profile> - Transform by device-link profile\n");
111
112 PrintBuiltins();
113
114 PrintRenderingIntents(NULL);
115
116 fprintf(stderr, "\n");
117
118 fprintf(stderr, "-d<0..1> - Observer adaptation state (abs.col. only)\n\n");
119
120 fprintf(stderr, "-b - Black point compensation\n");
121
122 fprintf(stderr, "-c<0,1,2,3> Precalculates transform (0=Off, 1=Normal, 2=Hi-res, 3=LoRes)\n\n");
123 fprintf(stderr, "-n - Terse output, intended for pipe usage\n");
124
125 fprintf(stderr, "-p<profile> - Soft proof profile\n");
126 fprintf(stderr, "-m<0,1,2,3> - Soft proof intent\n");
127 fprintf(stderr, "-g - Marks out-of-gamut colors on softproof\n\n");
128
129
130
131 fprintf(stderr, "This program is intended to be a demo of the Little CMS\n"
132 "color engine. Both lcms and this program are open source.\n"
133 "You can obtain both in source code at https://www.littlecms.com\n"
134 "For suggestions, comments, bug reports etc. send mail to\n"
135 "info@littlecms.com\n\n");
136
137 }
138
139
140
141 // The toggles stuff
142
143 static
144 void HandleSwitches(cmsContext ContextID, int argc, char *argv[])
145 {
146 int s;
147
148 while ((s = xgetopt(argc, argv,
149 "bBC:c:d:D:eEgGI:i:L:l:m:M:nNO:o:p:P:QqSsT:t:V:v:WwxX!:-:")) != EOF) {
150
151 switch (s){
152
153 case '-':
154 if (strcmp(xoptarg, "help") == 0)
155 {
156 Help();
157 exit(0);
158 }
159 else
160 {
161 FatalError("Unknown option - run without args to see valid ones.\n");
162 }
163 break;
164
165 case '!':
166 IncludePart = xoptarg;
167 break;
168
169 case 'b':
170 case 'B':
171 BlackPointCompensation = TRUE;
172 break;
173
174 case 'c':
175 case 'C':
176 PrecalcMode = atoi(xoptarg);
177 if (PrecalcMode < 0 || PrecalcMode > 3)
178 FatalError("Unknown precalc mode '%d'", PrecalcMode);
179 break;
180
181 case 'd':
182 case 'D': {
183 cmsFloat64Number ObserverAdaptationState = atof(xoptarg);
184 if (ObserverAdaptationState < 0 ||
185 ObserverAdaptationState > 1.0)
186 FatalError("Adaptation states should be between 0 and 1");
187
188 cmsSetAdaptationState(ContextID, ObserverAdaptationState);
189 }
190 break;
191
192 case 'e':
193 case 'E':
194 lIsFloat = FALSE;
195 break;
196
197 case 'g':
198 case 'G':
199 GamutCheck = TRUE;
200 break;
201
202 case 'i':
203 case 'I':
204 if (lIsDeviceLink)
205 FatalError("icctrans: Device-link already specified");
206
207 cInProf = xoptarg;
208 break;
209
210 case 'l':
211 case 'L':
212 cInProf = xoptarg;
213 lIsDeviceLink = TRUE;
214 break;
215
216 // No extra intents for proofing
217 case 'm':
218 case 'M':
219 ProofingIntent = atoi(xoptarg);
220 if (ProofingIntent > 3)
221 FatalError("Unknown Proofing Intent '%d'", ProofingIntent);
222 break;
223
224 // For compatibility
225 case 'n':
226 case 'N':
227 Verbose = 0;
228 break;
229
230 // Output profile
231 case 'o':
232 case 'O':
233 if (lIsDeviceLink)
234 FatalError("icctrans: Device-link already specified");
235 cOutProf = xoptarg;
236 break;
237
238 // Proofing profile
239 case 'p':
240 case 'P':
241 cProofing = xoptarg;
242 break;
243
244 // Quantize (get rid of decimals)
245 case 'q':
246 case 'Q':
247 lQuantize = TRUE;
248 break;
249
250 // Inhibit unbounded mode
251 case 's':
252 case 'S':
253 lUnbounded = FALSE;
254 break;
255
256 // The intent
257 case 't':
258 case 'T':
259 Intent = atoi(xoptarg);
260 break;
261
262 // Verbosity level
263 case 'V':
264 case 'v':
265 Verbose = atoi(xoptarg);
266 if (Verbose < 0 || Verbose > 3) {
267 FatalError("Unknown verbosity level '%d'", Verbose);
268 }
269 break;
270
271 // Wide (16 bits)
272 case 'W':
273 case 'w':
274 Width16 = TRUE;
275 break;
276
277 // Hexadecimal
278 case 'x':
279 case 'X':
280 InHexa = TRUE;
281 break;
282
283 default:
284 FatalError("Unknown option - run without args to see valid ones.\n");
285 }
286 }
287
288
289 // If output CGATS involved, switch to float
290 if ((argc - xoptind) > 2) {
291 lIsFloat = TRUE;
292 }
293 }
294
295
296
297 static
298 void SetRange(cmsFloat64Number range, cmsBool IsInput)
299 {
300 if (IsInput)
301 InputRange = range;
302 else
303 OutputRange = range;
304 }
305
306 // Populate a named color list with usual component names.
307 // I am using the first Colorant channel to store the range, but it works since
308 // this space is not used anyway.
309 static
310 cmsNAMEDCOLORLIST* ComponentNames(cmsContext ContextID, cmsColorSpaceSignature space, cmsBool IsInput)
311 {
312 cmsNAMEDCOLORLIST* out;
313 int i, n;
314 char Buffer[cmsMAX_PATH];
315
316 out = cmsAllocNamedColorList(0, 12, cmsMAXCHANNELS, "", "");
317 if (out == NULL) return NULL;
318
319 switch (space) {
320
321 case cmsSigXYZData:
322 SetRange(100, IsInput);
323 cmsAppendNamedColor(ContextID, out, "X", NULL, NULL);
324 cmsAppendNamedColor(ContextID, out, "Y", NULL, NULL);
325 cmsAppendNamedColor(ContextID, out, "Z", NULL, NULL);
326 break;
327
328 case cmsSigLabData:
329 SetRange(1, IsInput);
330 cmsAppendNamedColor(ContextID, out, "L*", NULL, NULL);
331 cmsAppendNamedColor(ContextID, out, "a*", NULL, NULL);
332 cmsAppendNamedColor(ContextID, out, "b*", NULL, NULL);
333 break;
334
335 case cmsSigLuvData:
336 SetRange(1, IsInput);
337 cmsAppendNamedColor(ContextID, out, "L", NULL, NULL);
338 cmsAppendNamedColor(ContextID, out, "u", NULL, NULL);
339 cmsAppendNamedColor(ContextID, out, "v", NULL, NULL);
340 break;
341
342 case cmsSigYCbCrData:
343 SetRange(255, IsInput);
344 cmsAppendNamedColor(ContextID, out, "Y", NULL, NULL );
345 cmsAppendNamedColor(ContextID, out, "Cb", NULL, NULL);
346 cmsAppendNamedColor(ContextID, out, "Cr", NULL, NULL);
347 break;
348
349
350 case cmsSigYxyData:
351 SetRange(1, IsInput);
352 cmsAppendNamedColor(ContextID, out, "Y", NULL, NULL);
353 cmsAppendNamedColor(ContextID, out, "x", NULL, NULL);
354 cmsAppendNamedColor(ContextID, out, "y", NULL, NULL);
355 break;
356
357 case cmsSigRgbData:
358 SetRange(255, IsInput);
359 cmsAppendNamedColor(ContextID, out, "R", NULL, NULL);
360 cmsAppendNamedColor(ContextID, out, "G", NULL, NULL);
361 cmsAppendNamedColor(ContextID, out, "B", NULL, NULL);
362 break;
363
364 case cmsSigGrayData:
365 SetRange(255, IsInput);
366 cmsAppendNamedColor(ContextID, out, "G", NULL, NULL);
367 break;
368
369 case cmsSigHsvData:
370 SetRange(255, IsInput);
371 cmsAppendNamedColor(ContextID, out, "H", NULL, NULL);
372 cmsAppendNamedColor(ContextID, out, "s", NULL, NULL);
373 cmsAppendNamedColor(ContextID, out, "v", NULL, NULL);
374 break;
375
376 case cmsSigHlsData:
377 SetRange(255, IsInput);
378 cmsAppendNamedColor(ContextID, out, "H", NULL, NULL);
379 cmsAppendNamedColor(ContextID, out, "l", NULL, NULL);
380 cmsAppendNamedColor(ContextID, out, "s", NULL, NULL);
381 break;
382
383 case cmsSigCmykData:
384 SetRange(1, IsInput);
385 cmsAppendNamedColor(ContextID, out, "C", NULL, NULL);
386 cmsAppendNamedColor(ContextID, out, "M", NULL, NULL);
387 cmsAppendNamedColor(ContextID, out, "Y", NULL, NULL);
388 cmsAppendNamedColor(ContextID, out, "K", NULL, NULL);
389 break;
390
391 case cmsSigCmyData:
392 SetRange(1, IsInput);
393 cmsAppendNamedColor(ContextID, out, "C", NULL, NULL);
394 cmsAppendNamedColor(ContextID, out, "M", NULL, NULL);
395 cmsAppendNamedColor(ContextID, out, "Y", NULL, NULL);
396 break;
397
398 default:
399
400 SetRange(1, IsInput);
401
402 n = cmsChannelsOfColorSpace(ContextID, space);
403
404 for (i=0; i < n; i++) {
405
406 sprintf(Buffer, "Channel #%d", i + 1);
407 cmsAppendNamedColor(ContextID, out, Buffer, NULL, NULL);
408 }
409 }
410
411 return out;
412
413 }
414
415
416 // Creates all needed color transforms
417 static
418 cmsBool OpenTransforms(cmsContext ContextID)
419 {
420 cmsHPROFILE hInput, hOutput, hProof;
421 cmsUInt32Number dwIn, dwOut, dwFlags;
422 cmsNAMEDCOLORLIST* List;
423 int i;
424
425 // We don't need cache
426 dwFlags = cmsFLAGS_NOCACHE;
427
428 if (lIsDeviceLink) {
429
430 hInput = OpenStockProfile(0, cInProf);
431 if (hInput == NULL) return FALSE;
432 hOutput = NULL;
433 hProof = NULL;
434
435 if (cmsGetDeviceClass(ContextID, hInput) == cmsSigNamedColorClass) {
436 OutputColorSpace = cmsGetColorSpace(ContextID, hInput);
437 InputColorSpace = cmsGetPCS(ContextID, hInput);
438 }
439 else {
440 InputColorSpace = cmsGetColorSpace(ContextID, hInput);
441 OutputColorSpace = cmsGetPCS(ContextID, hInput);
442 }
443
444 // Read colorant tables if present
445 if (cmsIsTag(ContextID, hInput, cmsSigColorantTableTag)) {
446 List = cmsReadTag(ContextID, hInput, cmsSigColorantTableTag);
447 InputColorant = cmsDupNamedColorList(ContextID, List);
448 InputRange = 1;
449 }
450 else InputColorant = ComponentNames(ContextID, InputColorSpace, TRUE);
451
452 if (cmsIsTag(ContextID, hInput, cmsSigColorantTableOutTag)){
453
454 List = cmsReadTag(ContextID, hInput, cmsSigColorantTableOutTag);
455 OutputColorant = cmsDupNamedColorList(ContextID, List);
456 OutputRange = 1;
457 }
458 else OutputColorant = ComponentNames(ContextID, OutputColorSpace, FALSE);
459
460 }
461 else {
462
463 hInput = OpenStockProfile(0, cInProf);
464 if (hInput == NULL) return FALSE;
465
466 hOutput = OpenStockProfile(0, cOutProf);
467 if (hOutput == NULL) return FALSE;
468 hProof = NULL;
469
470
471 if (cmsGetDeviceClass(ContextID, hInput) == cmsSigLinkClass ||
472 cmsGetDeviceClass(ContextID, hOutput) == cmsSigLinkClass)
473 FatalError("Use -l flag for devicelink profiles!\n");
474
475
476 InputColorSpace = cmsGetColorSpace(ContextID, hInput);
477 OutputColorSpace = cmsGetColorSpace(ContextID, hOutput);
478
479 // Read colorant tables if present
480 if (cmsIsTag(ContextID, hInput, cmsSigColorantTableTag)) {
481 List = cmsReadTag(ContextID, hInput, cmsSigColorantTableTag);
482 InputColorant = cmsDupNamedColorList(ContextID, List);
483 if (cmsNamedColorCount(ContextID, InputColorant) <= 3)
484 SetRange(255, TRUE);
485 else
486 SetRange(1, TRUE); // Inks are already divided by 100 in the formatter
487
488 }
489 else InputColorant = ComponentNames(ContextID, InputColorSpace, TRUE);
490
491 if (cmsIsTag(ContextID, hOutput, cmsSigColorantTableTag)){
492
493 List = cmsReadTag(ContextID, hOutput, cmsSigColorantTableTag);
494 OutputColorant = cmsDupNamedColorList(ContextID, List);
495 if (cmsNamedColorCount(ContextID, OutputColorant) <= 3)
496 SetRange(255, FALSE);
497 else
498 SetRange(1, FALSE); // Inks are already divided by 100 in the formatter
499 }
500 else OutputColorant = ComponentNames(ContextID, OutputColorSpace, FALSE);
501
502
503 if (cProofing != NULL) {
504
505 hProof = OpenStockProfile(0, cProofing);
506 if (hProof == NULL) return FALSE;
507 dwFlags |= cmsFLAGS_SOFTPROOFING;
508 }
509 }
510
511 // Print information on profiles
512 if (Verbose > 2) {
513
514 printf("Profile:\n");
515 PrintProfileInformation(ContextID, hInput);
516
517 if (hOutput) {
518
519 printf("Output profile:\n");
520 PrintProfileInformation(ContextID, hOutput);
521 }
522
523 if (hProof != NULL) {
524 printf("Proofing profile:\n");
525 PrintProfileInformation(ContextID, hProof);
526 }
527 }
528
529
530 // Input is always in floating point
531 dwIn = cmsFormatterForColorspaceOfProfile(ContextID, hInput, 0, TRUE);
532
533 if (lIsDeviceLink) {
534
535 dwOut = cmsFormatterForPCSOfProfile(ContextID, hInput, lIsFloat ? 0 : 2, lIsFloat);
536 }
537 else {
538
539 // 16 bits or floating point (only on output)
540 dwOut = cmsFormatterForColorspaceOfProfile(ContextID, hOutput, lIsFloat ? 0 : 2, lIsFloat);
541 }
542
543 // For named color, there is a specialized formatter
544 if (cmsGetDeviceClass(ContextID, hInput) == cmsSigNamedColorClass) {
545
546 dwIn = TYPE_NAMED_COLOR_INDEX;
547 InputNamedColor = TRUE;
548 }
549
550 // Precision mode
551 switch (PrecalcMode) {
552
553 case 0: dwFlags |= cmsFLAGS_NOOPTIMIZE; break;
554 case 2: dwFlags |= cmsFLAGS_HIGHRESPRECALC; break;
555 case 3: dwFlags |= cmsFLAGS_LOWRESPRECALC; break;
556 case 1: break;
557
558 default:
559 FatalError("Unknown precalculation mode '%d'", PrecalcMode);
560 }
561
562
563 if (BlackPointCompensation)
564 dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
565
566
567 if (GamutCheck) {
568
569 cmsUInt16Number Alarm[cmsMAXCHANNELS];
570
571 if (hProof == NULL)
572 FatalError("I need proofing profile -p for gamut checking!");
573
574 for (i=0; i < cmsMAXCHANNELS; i++)
575 Alarm[i] = 0xFFFF;
576
577 cmsSetAlarmCodes(ContextID, Alarm);
578 dwFlags |= cmsFLAGS_GAMUTCHECK;
579 }
580
581
582 // The main transform
583 hTrans = cmsCreateProofingTransform(ContextID, hInput, dwIn, hOutput, dwOut, hProof, Intent, ProofingIntent, dwFlags);
584
585 if (hProof) cmsCloseProfile(ContextID, hProof);
586
587 if (hTrans == NULL) return FALSE;
588
589
590 // PCS Dump if requested
591 hTransXYZ = NULL; hTransLab = NULL;
592
593 if (hOutput && Verbose > 1) {
594
595 cmsHPROFILE hXYZ = cmsCreateXYZProfile(ContextID);
596 cmsHPROFILE hLab = cmsCreateLab4Profile(ContextID, NULL);
597
598 hTransXYZ = cmsCreateTransform(ContextID, hInput, dwIn, hXYZ, lIsFloat ? TYPE_XYZ_DBL : TYPE_XYZ_16, Intent, cmsFLAGS_NOCACHE);
599 if (hTransXYZ == NULL) return FALSE;
600
601 hTransLab = cmsCreateTransform(ContextID, hInput, dwIn, hLab, lIsFloat? TYPE_Lab_DBL : TYPE_Lab_16, Intent, cmsFLAGS_NOCACHE);
602 if (hTransLab == NULL) return FALSE;
603
604 cmsCloseProfile(ContextID, hXYZ);
605 cmsCloseProfile(ContextID, hLab);
606 }
607
608 if (hInput) cmsCloseProfile(ContextID, hInput);
609 if (hOutput) cmsCloseProfile(ContextID, hOutput);
610
611 return TRUE;
612 }
613
614
615 // Free open resources
616 static
617 void CloseTransforms(cmsContext ContextID)
618 {
619 if (InputColorant) cmsFreeNamedColorList(ContextID, InputColorant);
620 if (OutputColorant) cmsFreeNamedColorList(ContextID, OutputColorant);
621
622 if (hTrans) cmsDeleteTransform(ContextID, hTrans);
623 if (hTransLab) cmsDeleteTransform(ContextID, hTransLab);
624 if (hTransXYZ) cmsDeleteTransform(ContextID, hTransXYZ);
625
626 }
627
628 // ---------------------------------------------------------------------------------------------------
629
630 // Get input from user
631 static
632 void GetLine(cmsContext ContextID, char* Buffer, const char* frm, ...)
633 {
634 int res;
635 va_list args;
636
637 va_start(args, frm);
638
639 do {
640 if (xisatty(stdin))
641 vfprintf(stderr, frm, args);
642
643 res = scanf("%4095s", Buffer);
644
645 if (res < 0 || toupper(Buffer[0]) == 'Q') { // Quit?
646
647 CloseTransforms(ContextID);
648
649 if (xisatty(stdin))
650 fprintf(stderr, "Done.\n");
651
652 exit(0);
653 }
654 } while (res == 0);
655
656 va_end(args);
657 }
658
659
660 // Print a value which is given in double floating point
661 static
662 void PrintFloatResults(cmsContext ContextID, cmsFloat64Number Value[])
663 {
664 cmsUInt32Number i, n;
665 char ChannelName[cmsMAX_PATH];
666 cmsFloat64Number v;
667
668 n = cmsChannelsOfColorSpace(ContextID, OutputColorSpace);
669 for (i=0; i < n; i++) {
670
671 if (OutputColorant != NULL) {
672
673 cmsNamedColorInfo(ContextID, OutputColorant, i, ChannelName, NULL, NULL, NULL, NULL);
674 }
675 else {
676 OutputRange = 1;
677 sprintf(ChannelName, "Channel #%u", i + 1);
678 }
679
680 v = (cmsFloat64Number) Value[i]* OutputRange;
681
682 if (lQuantize)
683 v = floor(v + 0.5);
684
685 if (!lUnbounded) {
686
687 if (v < 0)
688 v = 0;
689 if (v > OutputRange)
690 v = OutputRange;
691 }
692
693 if (Verbose <= 0)
694 printf("%.4f ", v);
695 else
696 printf("%s=%.4f ", ChannelName, v);
697 }
698
699 printf("\n");
700 }
701
702
703 // Get a named-color index
704 static
705 cmsUInt16Number GetIndex(cmsContext ContextID)
706 {
707 char Buffer[4096], Name[cmsMAX_PATH], Prefix[40], Suffix[40];
708 int index, max;
709 const cmsNAMEDCOLORLIST* NamedColorList;
710
711 NamedColorList = cmsGetNamedColorList(hTrans);
712 if (NamedColorList == NULL) return 0;
713
714 max = cmsNamedColorCount(ContextID, NamedColorList)-1;
715
716 GetLine(ContextID, Buffer, "Color index (0..%d)? ", max);
717 index = atoi(Buffer);
718
719 if (index > max)
720 FatalError("Named color %d out of range!", index);
721
722 cmsNamedColorInfo(ContextID, NamedColorList, index, Name, Prefix, Suffix, NULL, NULL);
723
724 printf("\n%s %s %s\n", Prefix, Name, Suffix);
725
726 return (cmsUInt16Number) index;
727 }
728
729 // Read values from a text file or terminal
730 static
731 void TakeFloatValues(cmsContext ContextID, cmsFloat64Number Float[])
732 {
733 cmsUInt32Number i, n;
734 char ChannelName[cmsMAX_PATH];
735 char Buffer[4096];
736
737 if (xisatty(stdin))
738 fprintf(stderr, "\nEnter values, 'q' to quit\n");
739
740 if (InputNamedColor) {
741
742 // This is named color index, which is always cmsUInt16Number
743 cmsUInt16Number index = GetIndex(ContextID);
744 memcpy(Float, &index, sizeof(cmsUInt16Number));
745 return;
746 }
747
748 n = cmsChannelsOfColorSpace(ContextID, InputColorSpace);
749 for (i=0; i < n; i++) {
750
751 if (InputColorant) {
752 cmsNamedColorInfo(ContextID, InputColorant, i, ChannelName, NULL, NULL, NULL, NULL);
753 }
754 else {
755 InputRange = 1;
756 sprintf(ChannelName, "Channel #%u", i+1);
757 }
758
759 GetLine(ContextID, Buffer, "%s? ", ChannelName);
760
761 Float[i] = (cmsFloat64Number) atof(Buffer) / InputRange;
762 }
763
764 if (xisatty(stdin))
765 fprintf(stderr, "\n");
766 }
767
768 static
769 void PrintPCSFloat(cmsContext ContextID, cmsFloat64Number Input[])
770 {
771 if (Verbose > 1 && hTransXYZ && hTransLab) {
772
773 cmsCIEXYZ XYZ = { 0, 0, 0 };
774 cmsCIELab Lab = { 0, 0, 0 };
775
776 if (hTransXYZ) cmsDoTransform(ContextID, hTransXYZ, Input, &XYZ, 1);
777 if (hTransLab) cmsDoTransform(ContextID, hTransLab, Input, &Lab, 1);
778
779 printf("[PCS] Lab=(%.4f,%.4f,%.4f) XYZ=(%.4f,%.4f,%.4f)\n", Lab.L, Lab.a, Lab.b,
780 XYZ.X * 100.0, XYZ.Y * 100.0, XYZ.Z * 100.0);
781
782 }
783 }
784
785
786
787
788 // -----------------------------------------------------------------------------------------------
789
790 static
791 void PrintEncodedResults(cmsContext ContextID, cmsUInt16Number Encoded[])
792 {
793 cmsUInt32Number i, n;
794 char ChannelName[cmsMAX_PATH];
795 cmsUInt32Number v;
796
797 n = cmsChannelsOfColorSpace(ContextID, OutputColorSpace);
798 for (i=0; i < n; i++) {
799
800 if (OutputColorant != NULL) {
801
802 cmsNamedColorInfo(ContextID, OutputColorant, i, ChannelName, NULL, NULL, NULL, NULL);
803 }
804 else {
805 sprintf(ChannelName, "Channel #%u", i + 1);
806 }
807
808 if (Verbose > 0)
809 printf("%s=", ChannelName);
810
811 v = Encoded[i];
812
813 if (InHexa) {
814
815 if (Width16)
816 printf("0x%04X ", (int) floor(v + .5));
817 else
818 printf("0x%02X ", (int) floor(v / 257. + .5));
819
820 } else {
821
822 if (Width16)
823 printf("%d ", (int) floor(v + .5));
824 else
825 printf("%d ", (int) floor(v / 257. + .5));
826 }
827
828 }
829
830 printf("\n");
831 }
832
833 // Print XYZ/Lab values on verbose mode
834
835 static
836 void PrintPCSEncoded(cmsContext ContextID, cmsFloat64Number Input[])
837 {
838 if (Verbose > 1 && hTransXYZ && hTransLab) {
839
840 cmsUInt16Number XYZ[3], Lab[3];
841
842 if (hTransXYZ) cmsDoTransform(ContextID, hTransXYZ, Input, XYZ, 1);
843 if (hTransLab) cmsDoTransform(ContextID, hTransLab, Input, Lab, 1);
844
845 printf("[PCS] Lab=(0x%04X,0x%04X,0x%04X) XYZ=(0x%04X,0x%04X,0x%04X)\n", Lab[0], Lab[1], Lab[2],
846 XYZ[0], XYZ[1], XYZ[2]);
847
848 }
849 }
850
851
852 // --------------------------------------------------------------------------------------
853
854
855
856 // Take a value from IT8 and scale it accordly to fill a cmsUInt16Number (0..FFFF)
857
858 static
859 cmsFloat64Number GetIT8Val(cmsContext ContextID, const char* Name, cmsFloat64Number Max)
860 {
861 const char* Val = cmsIT8GetData(ContextID, hIT8in, CGATSPatch, Name);
862
863 if (Val == NULL)
864 FatalError("Field '%s' not found", Name);
865
866 return atof(Val) / Max;
867
868 }
869
870
871 // Read input values from CGATS file.
872
873 static
874 void TakeCGATSValues(cmsContext ContextID, int nPatch, cmsFloat64Number Float[])
875 {
876
877 // At first take the name if SAMPLE_ID is present
878 if (cmsIT8GetPatchName(ContextID, hIT8in, nPatch, CGATSPatch) == NULL) {
879 FatalError("Sorry, I need 'SAMPLE_ID' on input CGATS to operate.");
880 }
881
882
883 // Special handling for named color profiles.
884 // Lookup the name in the names database (the transform)
885
886 if (InputNamedColor) {
887
888 const cmsNAMEDCOLORLIST* NamedColorList;
889 int index;
890
891 NamedColorList = cmsGetNamedColorList(hTrans);
892 if (NamedColorList == NULL)
893 FatalError("Malformed named color profile");
894
895 index = cmsNamedColorIndex(ContextID, NamedColorList, CGATSPatch);
896 if (index < 0)
897 FatalError("Named color '%s' not found in the profile", CGATSPatch);
898
899 Float[0] = index;
900 return;
901 }
902
903 // Color is not a spot color, proceed.
904
905 switch (InputColorSpace) {
906
907 // Encoding should follow CGATS specification.
908
909 case cmsSigXYZData:
910 Float[0] = cmsIT8GetDataDbl(ContextID, hIT8in, CGATSPatch, "XYZ_X") / 100.0;
911 Float[1] = cmsIT8GetDataDbl(ContextID, hIT8in, CGATSPatch, "XYZ_Y") / 100.0;
912 Float[2] = cmsIT8GetDataDbl(ContextID, hIT8in, CGATSPatch, "XYZ_Z") / 100.0;
913 break;
914
915 case cmsSigLabData:
916 Float[0] = cmsIT8GetDataDbl(ContextID, hIT8in, CGATSPatch, "LAB_L");
917 Float[1] = cmsIT8GetDataDbl(ContextID, hIT8in, CGATSPatch, "LAB_A");
918 Float[2] = cmsIT8GetDataDbl(ContextID, hIT8in, CGATSPatch, "LAB_B");
919 break;
920
921
922 case cmsSigRgbData:
923 Float[0] = GetIT8Val(ContextID, "RGB_R", 255.0);
924 Float[1] = GetIT8Val(ContextID, "RGB_G", 255.0);
925 Float[2] = GetIT8Val(ContextID, "RGB_B", 255.0);
926 break;
927
928 case cmsSigGrayData:
929 Float[0] = GetIT8Val(ContextID, "GRAY", 255.0);
930 break;
931
932 case cmsSigCmykData:
933 Float[0] = GetIT8Val(ContextID, "CMYK_C", 1.0);
934 Float[1] = GetIT8Val(ContextID, "CMYK_M", 1.0);
935 Float[2] = GetIT8Val(ContextID, "CMYK_Y", 1.0);
936 Float[3] = GetIT8Val(ContextID, "CMYK_K", 1.0);
937 break;
938
939 case cmsSigCmyData:
940 Float[0] = GetIT8Val(ContextID, "CMY_C", 1.0);
941 Float[1] = GetIT8Val(ContextID, "CMY_M", 1.0);
942 Float[2] = GetIT8Val(ContextID, "CMY_Y", 1.0);
943 break;
944
945 case cmsSig1colorData:
946 case cmsSig2colorData:
947 case cmsSig3colorData:
948 case cmsSig4colorData:
949 case cmsSig5colorData:
950 case cmsSig6colorData:
951 case cmsSig7colorData:
952 case cmsSig8colorData:
953 case cmsSig9colorData:
954 case cmsSig10colorData:
955 case cmsSig11colorData:
956 case cmsSig12colorData:
957 case cmsSig13colorData:
958 case cmsSig14colorData:
959 case cmsSig15colorData:
960 {
961 cmsUInt32Number i, n;
962
963 n = cmsChannelsOfColorSpace(ContextID, InputColorSpace);
964 for (i=0; i < n; i++) {
965
966 char Buffer[255];
967
968 sprintf(Buffer, "%uCLR_%u", n, i+1);
969 Float[i] = GetIT8Val(ContextID, Buffer, 100.0);
970 }
971
972 }
973 break;
974
975 default:
976 {
977 cmsUInt32Number i, n;
978
979 n = cmsChannelsOfColorSpace(ContextID, InputColorSpace);
980 for (i=0; i < n; i++) {
981
982 char Buffer[255];
983
984 sprintf(Buffer, "CHAN_%u", i+1);
985 Float[i] = GetIT8Val(ContextID, Buffer, 1.0);
986 }
987
988 }
989 }
990
991 }
992
993 static
994 void SetCGATSfld(cmsContext ContextID, const char* Col, cmsFloat64Number Val)
995 {
996 if (lQuantize)
997 Val = floor(Val + 0.5);
998
999 if (!cmsIT8SetDataDbl(ContextID, hIT8out, CGATSPatch, Col, Val)) {
1000 FatalError("couldn't set '%s' on output cgats '%s'", Col, CGATSoutFilename);
1001 }
1002 }
1003
1004
1005
1006 static
1007 void PutCGATSValues(cmsContext ContextID, cmsFloat64Number Float[])
1008 {
1009 cmsIT8SetData(ContextID, hIT8out, CGATSPatch, "SAMPLE_ID", CGATSPatch);
1010 switch (OutputColorSpace) {
1011
1012
1013 // Encoding should follow CGATS specification.
1014
1015 case cmsSigXYZData:
1016
1017 SetCGATSfld(ContextID, "XYZ_X", Float[0] * 100.0);
1018 SetCGATSfld(ContextID, "XYZ_Y", Float[1] * 100.0);
1019 SetCGATSfld(ContextID, "XYZ_Z", Float[2] * 100.0);
1020 break;
1021
1022 case cmsSigLabData:
1023
1024 SetCGATSfld(ContextID, "LAB_L", Float[0]);
1025 SetCGATSfld(ContextID, "LAB_A", Float[1]);
1026 SetCGATSfld(ContextID, "LAB_B", Float[2]);
1027 break;
1028
1029
1030 case cmsSigRgbData:
1031 SetCGATSfld(ContextID, "RGB_R", Float[0] * 255.0);
1032 SetCGATSfld(ContextID, "RGB_G", Float[1] * 255.0);
1033 SetCGATSfld(ContextID, "RGB_B", Float[2] * 255.0);
1034 break;
1035
1036 case cmsSigGrayData:
1037 SetCGATSfld(ContextID, "GRAY", Float[0] * 255.0);
1038 break;
1039
1040 case cmsSigCmykData:
1041 SetCGATSfld(ContextID, "CMYK_C", Float[0]);
1042 SetCGATSfld(ContextID, "CMYK_M", Float[1]);
1043 SetCGATSfld(ContextID, "CMYK_Y", Float[2]);
1044 SetCGATSfld(ContextID, "CMYK_K", Float[3]);
1045 break;
1046
1047 case cmsSigCmyData:
1048 SetCGATSfld(ContextID, "CMY_C", Float[0]);
1049 SetCGATSfld(ContextID, "CMY_M", Float[1]);
1050 SetCGATSfld(ContextID, "CMY_Y", Float[2]);
1051 break;
1052
1053 case cmsSig1colorData:
1054 case cmsSig2colorData:
1055 case cmsSig3colorData:
1056 case cmsSig4colorData:
1057 case cmsSig5colorData:
1058 case cmsSig6colorData:
1059 case cmsSig7colorData:
1060 case cmsSig8colorData:
1061 case cmsSig9colorData:
1062 case cmsSig10colorData:
1063 case cmsSig11colorData:
1064 case cmsSig12colorData:
1065 case cmsSig13colorData:
1066 case cmsSig14colorData:
1067 case cmsSig15colorData:
1068 {
1069
1070 cmsInt32Number i, n;
1071
1072 n = cmsChannelsOfColorSpace(ContextID, InputColorSpace);
1073 for (i=0; i < n; i++) {
1074
1075 char Buffer[255];
1076
1077 sprintf(Buffer, "%uCLR_%u", n, i+1);
1078
1079 SetCGATSfld(ContextID, Buffer, Float[i] * 100.0);
1080 }
1081 }
1082 break;
1083
1084 default:
1085 {
1086
1087 cmsInt32Number i, n;
1088
1089 n = cmsChannelsOfColorSpace(ContextID, InputColorSpace);
1090 for (i=0; i < n; i++) {
1091
1092 char Buffer[255];
1093
1094 sprintf(Buffer, "CHAN_%u", i+1);
1095
1096 SetCGATSfld(ContextID, Buffer, Float[i]);
1097 }
1098 }
1099 }
1100 }
1101
1102
1103
1104 // Create data format
1105 static
1106 void SetOutputDataFormat(cmsContext ContextID)
1107 {
1108 cmsIT8DefineDblFormat(ContextID, hIT8out, "%.4g");
1109 cmsIT8SetPropertyStr(ContextID, hIT8out, "ORIGINATOR", "icctrans");
1110
1111 if (IncludePart != NULL)
1112 cmsIT8SetPropertyStr(ContextID, hIT8out, ".INCLUDE", IncludePart);
1113
1114 cmsIT8SetComment(ContextID, hIT8out, "Data follows");
1115 cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_SETS", nMaxPatches);
1116
1117
1118 switch (OutputColorSpace) {
1119
1120
1121 // Encoding should follow CGATS specification.
1122
1123 case cmsSigXYZData:
1124 cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", 4);
1125 cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
1126 cmsIT8SetDataFormat(ContextID, hIT8out, 1, "XYZ_X");
1127 cmsIT8SetDataFormat(ContextID, hIT8out, 2, "XYZ_Y");
1128 cmsIT8SetDataFormat(ContextID, hIT8out, 3, "XYZ_Z");
1129 break;
1130
1131 case cmsSigLabData:
1132 cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", 4);
1133 cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
1134 cmsIT8SetDataFormat(ContextID, hIT8out, 1, "LAB_L");
1135 cmsIT8SetDataFormat(ContextID, hIT8out, 2, "LAB_A");
1136 cmsIT8SetDataFormat(ContextID, hIT8out, 3, "LAB_B");
1137 break;
1138
1139
1140 case cmsSigRgbData:
1141 cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", 4);
1142 cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
1143 cmsIT8SetDataFormat(ContextID, hIT8out, 1, "RGB_R");
1144 cmsIT8SetDataFormat(ContextID, hIT8out, 2, "RGB_G");
1145 cmsIT8SetDataFormat(ContextID, hIT8out, 3, "RGB_B");
1146 break;
1147
1148 case cmsSigGrayData:
1149 cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", 2);
1150 cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
1151 cmsIT8SetDataFormat(ContextID, hIT8out, 1, "GRAY");
1152 break;
1153
1154 case cmsSigCmykData:
1155 cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", 5);
1156 cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
1157 cmsIT8SetDataFormat(ContextID, hIT8out, 1, "CMYK_C");
1158 cmsIT8SetDataFormat(ContextID, hIT8out, 2, "CMYK_M");
1159 cmsIT8SetDataFormat(ContextID, hIT8out, 3, "CMYK_Y");
1160 cmsIT8SetDataFormat(ContextID, hIT8out, 4, "CMYK_K");
1161 break;
1162
1163 case cmsSigCmyData:
1164 cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", 4);
1165 cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
1166 cmsIT8SetDataFormat(ContextID, hIT8out, 1, "CMY_C");
1167 cmsIT8SetDataFormat(ContextID, hIT8out, 2, "CMY_M");
1168 cmsIT8SetDataFormat(ContextID, hIT8out, 3, "CMY_Y");
1169 break;
1170
1171 case cmsSig1colorData:
1172 case cmsSig2colorData:
1173 case cmsSig3colorData:
1174 case cmsSig4colorData:
1175 case cmsSig5colorData:
1176 case cmsSig6colorData:
1177 case cmsSig7colorData:
1178 case cmsSig8colorData:
1179 case cmsSig9colorData:
1180 case cmsSig10colorData:
1181 case cmsSig11colorData:
1182 case cmsSig12colorData:
1183 case cmsSig13colorData:
1184 case cmsSig14colorData:
1185 case cmsSig15colorData:
1186 {
1187 int i, n;
1188 char Buffer[255];
1189
1190 n = cmsChannelsOfColorSpace(ContextID, OutputColorSpace);
1191 cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", n+1);
1192 cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
1193
1194 for (i=1; i <= n; i++) {
1195 sprintf(Buffer, "%dCLR_%d", n, i);
1196 cmsIT8SetDataFormat(ContextID, hIT8out, i, Buffer);
1197 }
1198 }
1199 break;
1200
1201 default: {
1202
1203 int i, n;
1204 char Buffer[255];
1205
1206 n = cmsChannelsOfColorSpace(ContextID, OutputColorSpace);
1207 cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", n+1);
1208 cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
1209
1210 for (i=1; i <= n; i++) {
1211 sprintf(Buffer, "CHAN_%d", i);
1212 cmsIT8SetDataFormat(ContextID, hIT8out, i, Buffer);
1213 }
1214 }
1215 }
1216 }
1217
1218 // Open CGATS if specified
1219
1220 static
1221 void OpenCGATSFiles(cmsContext ContextID, int argc, char *argv[])
1222 {
1223 int nParams = argc - xoptind;
1224
1225 if (nParams >= 1) {
1226
1227 hIT8in = cmsIT8LoadFromFile(0, argv[xoptind]);
1228
1229 if (hIT8in == NULL)
1230 FatalError("'%s' is not recognized as a CGATS file", argv[xoptind]);
1231
1232 nMaxPatches = (int) cmsIT8GetPropertyDbl(ContextID, hIT8in, "NUMBER_OF_SETS");
1233 }
1234
1235 if (nParams == 2) {
1236
1237 hIT8out = cmsIT8Alloc(NULL);
1238 SetOutputDataFormat(ContextID);
1239 strncpy(CGATSoutFilename, argv[xoptind+1], cmsMAX_PATH-1);
1240 }
1241
1242 if (nParams > 2) FatalError("Too many CGATS files");
1243 }
1244
1245
1246
1247 // The main sink
1248 int main(int argc, char *argv[])
1249 {
1250 cmsUInt16Number Output[cmsMAXCHANNELS];
1251 cmsFloat64Number OutputFloat[cmsMAXCHANNELS];
1252 cmsFloat64Number InputFloat[cmsMAXCHANNELS];
1253 cmsContext ContextID = NULL;
1254
1255 int nPatch = 0;
1256
1257 fprintf(stderr, "LittleCMS ColorSpace conversion calculator - 5.1 [LittleCMS %2.2f]\n", cmsGetEncodedCMMversion() / 1000.0);
1258 fprintf(stderr, "Copyright (c) 1998-2023 Marti Maria Saguer. See COPYING file for details.\n");
1259 fflush(stderr);
1260
1261 InitUtils(ContextID, "transicc");
1262
1263 Verbose = 1;
1264
1265 if (argc == 1) {
1266
1267 Help();
1268 return 0;
1269 }
1270
1271 HandleSwitches(ContextID, argc, argv);
1272
1273 // Open profiles, create transforms
1274 if (!OpenTransforms(ContextID)) return 1;
1275
1276 // Open CGATS input if specified
1277 OpenCGATSFiles(ContextID, argc, argv);
1278
1279 // Main loop: read all values and convert them
1280 for(;;) {
1281
1282 if (hIT8in != NULL) {
1283
1284 if (nPatch >= nMaxPatches) break;
1285 TakeCGATSValues(ContextID, nPatch++, InputFloat);
1286
1287 } else {
1288
1289 if (feof(stdin)) break;
1290 TakeFloatValues(ContextID, InputFloat);
1291
1292 }
1293
1294 if (lIsFloat)
1295 cmsDoTransform(ContextID, hTrans, InputFloat, OutputFloat, 1);
1296 else
1297 cmsDoTransform(ContextID, hTrans, InputFloat, Output, 1);
1298
1299
1300 if (hIT8out != NULL) {
1301
1302 PutCGATSValues(ContextID, OutputFloat);
1303 }
1304 else {
1305
1306 if (lIsFloat) {
1307 PrintFloatResults(ContextID, OutputFloat); PrintPCSFloat(ContextID, InputFloat);
1308 }
1309 else {
1310 PrintEncodedResults(ContextID, Output); PrintPCSEncoded(ContextID, InputFloat);
1311 }
1312
1313 }
1314 }
1315
1316
1317 // Cleanup
1318 CloseTransforms(ContextID);
1319
1320 if (hIT8in)
1321 cmsIT8Free(ContextID, hIT8in);
1322
1323 if (hIT8out) {
1324 cmsIT8SaveToFile(ContextID, hIT8out, CGATSoutFilename);
1325 cmsIT8Free(ContextID, hIT8out);
1326 }
1327
1328 // All is ok
1329 return 0;
1330 }