1 //
2 // Little cms
3 // Copyright (C) 1998-2010 Marti Maria, Ignacio Ruiz de Conejo
4 //
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:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
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.
22
23
24 #include "mex.h"
25
26 #include "lcms2mt.h"
27 #include "string.h"
28 #include "stdarg.h"
29
30 // xgetopt() interface -----------------------------------------------------
31
32 static int xoptind;
33 static char *xoptarg;
34 static int xopterr;
35 static char *letP;
36 static char SW = '-';
37
38 // ------------------------------------------------------------------------
39
40
41 static int Verbose ; // Print some statistics
42 static char *cInProf; // Input profile
43 static char *cOutProf; // Output profile
44 static char *cProofing; // Softproofing profile
45
46
47 static int Intent; // Rendering Intent
48 static int ProofingIntent; // RI for proof
49
50 static int PrecalcMode; // 0 = Not, 1=Normal, 2=Accurate, 3=Fast
51
52 static cmsBool BlackPointCompensation;
53 static cmsBool lIsDeviceLink;
54 static cmsBool lMultiProfileChain; // Multiple profile chain
55
56 static cmsHPROFILE hInput, hOutput, hProof;
57 static cmsHTRANSFORM hColorTransform;
58 static cmsHPROFILE hProfiles[255];
59 static int nProfiles;
60
61 static cmsColorSpaceSignature InputColorSpace, OutputColorSpace;
62 static int OutputChannels, InputChannels, nBytesDepth;
63
64
65 // Error. Print error message and abort
66
67 static
FatalError(const char * frm,...)68 cmsBool FatalError(const char *frm, ...)
69 {
70 va_list args;
71 char Buffer[1024];
72
73 va_start(args, frm);
74 vsprintf(Buffer, frm, args);
75 mexErrMsgTxt(Buffer);
76 va_end(args);
77
78 return FALSE;
79 }
80
81 // This is the handler passed to lcms
82
83 static
MatLabErrorHandler(cmsContext ContextID,cmsUInt32Number ErrorCode,const char * Text)84 void MatLabErrorHandler(cmsContext ContextID, cmsUInt32Number ErrorCode,
85 const char *Text)
86 {
87 mexErrMsgTxt(Text);
88 }
89 //
90 // Parse the command line options, System V style.
91 //
92
93 static
xoptinit()94 void xoptinit()
95 {
96 xoptind = 1;
97 xopterr = 0;
98 letP = NULL;
99 }
100
101
102 static
xgetopt(int argc,char * argv[],char * optionS)103 int xgetopt(int argc, char *argv[], char *optionS)
104 {
105 unsigned char ch;
106 char *optP;
107
108 if (SW == 0) {
109 SW = '/';
110 }
111
112 if (argc > xoptind) {
113 if (letP == NULL) {
114 if ((letP = argv[xoptind]) == NULL ||
115 *(letP++) != SW) goto gopEOF;
116 if (*letP == SW) {
117 xoptind++; goto gopEOF;
118 }
119 }
120 if (0 == (ch = *(letP++))) {
121 xoptind++; goto gopEOF;
122 }
123 if (':' == ch || (optP = strchr(optionS, ch)) == NULL)
124 goto gopError;
125 if (':' == *(++optP)) {
126 xoptind++;
127 if (0 == *letP) {
128 if (argc <= xoptind) goto gopError;
129 letP = argv[xoptind++];
130 }
131 xoptarg = letP;
132 letP = NULL;
133 } else {
134 if (0 == *letP) {
135 xoptind++;
136 letP = NULL;
137 }
138 xoptarg = NULL;
139 }
140 return ch;
141 }
142 gopEOF:
143 xoptarg = letP = NULL;
144 return EOF;
145
146 gopError:
147 xoptarg = NULL;
148 if (xopterr)
149 FatalError ("get command line option");
150 return ('?');
151 }
152
153
154 // Return Mathlab type by depth
155
156 static
SizeOfArrayType(const mxArray * Array)157 size_t SizeOfArrayType(const mxArray *Array)
158 {
159
160 switch (mxGetClassID(Array)) {
161
162 case mxINT8_CLASS: return 1;
163 case mxUINT8_CLASS: return 1;
164 case mxINT16_CLASS: return 2;
165 case mxUINT16_CLASS: return 2;
166 case mxSINGLE_CLASS: return 4;
167 case mxDOUBLE_CLASS: return 0; // Special case -- lcms handles double as size=0
168
169
170 default:
171 FatalError("Unsupported data type");
172 return 0;
173 }
174 }
175
176
177 // Get number of pixels of input array. Supported arrays are
178 // organized as NxMxD, being N and M the size of image and D the
179 // number of components.
180
181 static
GetNumberOfPixels(const mxArray * In)182 size_t GetNumberOfPixels(const mxArray* In)
183 {
184 int nDimensions = mxGetNumberOfDimensions(In);
185 const int *Dimensions = mxGetDimensions(In);
186
187 switch (nDimensions) {
188
189 case 1: return 1; // It is just a spot color
190 case 2: return Dimensions[0]; // A scanline
191 case 3: return Dimensions[0]*Dimensions[1]; // A image
192
193 default:
194 FatalError("Unsupported array of %d dimensions", nDimensions);
195 return 0;
196 }
197 }
198
199
200 // Allocates the output array. Copies the input array modifying the pixel
201 // definition to match "OutputChannels".
202
203 static
AllocateOutputArray(const mxArray * In,int OutputChannels)204 mxArray* AllocateOutputArray(const mxArray* In, int OutputChannels)
205 {
206
207 mxArray* Out = mxDuplicateArray(In); // Make a "deep copy" of Input array
208 int nDimensions = mxGetNumberOfDimensions(In);
209 const int* Dimensions = mxGetDimensions(In);
210 int InputChannels = Dimensions[nDimensions-1];
211
212
213 // Modify pixel size only if needed
214
215 if (InputChannels != OutputChannels) {
216
217
218 int i, NewSize;
219 int *ModifiedDimensions = (int*) mxMalloc(nDimensions * sizeof(int));
220
221
222 memmove(ModifiedDimensions, Dimensions, nDimensions * sizeof(int));
223 ModifiedDimensions[nDimensions - 1] = OutputChannels;
224
225 switch (mxGetClassID(In)) {
226
227 case mxINT8_CLASS: NewSize = sizeof(char); break;
228 case mxUINT8_CLASS: NewSize = sizeof(unsigned char); break;
229 case mxINT16_CLASS: NewSize = sizeof(short); break;
230 case mxUINT16_CLASS: NewSize = sizeof(unsigned short); break;
231
232 default:
233 case mxDOUBLE_CLASS: NewSize = sizeof(double); break;
234 }
235
236
237 // NewSize = 1;
238 for (i=0; i < nDimensions; i++)
239 NewSize *= ModifiedDimensions[i];
240
241
242 mxSetDimensions(Out, ModifiedDimensions, nDimensions);
243 mxFree(ModifiedDimensions);
244
245 mxSetPr(Out, mxRealloc(mxGetPr(Out), NewSize));
246
247 }
248
249
250 return Out;
251 }
252
253
254
255 // Does create a format descriptor. "Bytes" is the sizeof type in bytes
256 //
257 // Bytes Meaning
258 // ------ --------
259 // 0 Floating point (double)
260 // 1 8-bit samples
261 // 2 16-bit samples
262
263 static
MakeFormatDescriptor(cmsColorSpaceSignature ColorSpace,int Bytes)264 cmsUInt32Number MakeFormatDescriptor(cmsColorSpaceSignature ColorSpace, int Bytes)
265 {
266 int IsFloat = (Bytes == 0 || Bytes == 4) ? 1 : 0;
267 int Channels = cmsChannelsOf(ColorSpace);
268 return FLOAT_SH(IsFloat)|COLORSPACE_SH(_cmsLCMScolorSpace(ColorSpace))|BYTES_SH(Bytes)|CHANNELS_SH(Channels)|PLANAR_SH(1);
269 }
270
271
272 // Opens a profile or proper built-in
273
274 static
OpenProfile(const char * File)275 cmsHPROFILE OpenProfile(const char* File)
276 {
277
278 cmsContext ContextID = 0;
279
280 if (!File)
281 return cmsCreate_sRGBProfileTHR(ContextID);
282
283 if (cmsstrcasecmp(File, "*Lab2") == 0)
284 return cmsCreateLab2ProfileTHR(ContextID, NULL);
285
286 if (cmsstrcasecmp(File, "*Lab4") == 0)
287 return cmsCreateLab4ProfileTHR(ContextID, NULL);
288
289 if (cmsstrcasecmp(File, "*Lab") == 0)
290 return cmsCreateLab4ProfileTHR(ContextID, NULL);
291
292 if (cmsstrcasecmp(File, "*LabD65") == 0) {
293
294 cmsCIExyY D65xyY;
295
296 cmsWhitePointFromTemp( &D65xyY, 6504);
297 return cmsCreateLab4ProfileTHR(ContextID, &D65xyY);
298 }
299
300 if (cmsstrcasecmp(File, "*XYZ") == 0)
301 return cmsCreateXYZProfileTHR(ContextID);
302
303 if (cmsstrcasecmp(File, "*Gray22") == 0) {
304
305 cmsToneCurve* Curve = cmsBuildGamma(ContextID, 2.2);
306 cmsHPROFILE hProfile = cmsCreateGrayProfileTHR(ContextID, cmsD50_xyY(), Curve);
307 cmsFreeToneCurve(Curve);
308 return hProfile;
309 }
310
311 if (cmsstrcasecmp(File, "*Gray30") == 0) {
312
313 cmsToneCurve* Curve = cmsBuildGamma(ContextID, 3.0);
314 cmsHPROFILE hProfile = cmsCreateGrayProfileTHR(ContextID, cmsD50_xyY(), Curve);
315 cmsFreeToneCurve(Curve);
316 return hProfile;
317 }
318
319 if (cmsstrcasecmp(File, "*srgb") == 0)
320 return cmsCreate_sRGBProfileTHR(ContextID);
321
322 if (cmsstrcasecmp(File, "*null") == 0)
323 return cmsCreateNULLProfileTHR(ContextID);
324
325
326 if (cmsstrcasecmp(File, "*Lin2222") == 0) {
327
328 cmsToneCurve* Gamma = cmsBuildGamma(0, 2.2);
329 cmsToneCurve* Gamma4[4];
330 cmsHPROFILE hProfile;
331
332 Gamma4[0] = Gamma4[1] = Gamma4[2] = Gamma4[3] = Gamma;
333 hProfile = cmsCreateLinearizationDeviceLink(cmsSigCmykData, Gamma4);
334 cmsFreeToneCurve(Gamma);
335 return hProfile;
336 }
337
338
339 return cmsOpenProfileFromFileTHR(ContextID, File, "r");
340 }
341
342
343 static
GetFlags()344 cmsUInt32Number GetFlags()
345 {
346 cmsUInt32Number dwFlags = 0;
347
348 switch (PrecalcMode) {
349
350 case 0: dwFlags = cmsFLAGS_NOOPTIMIZE; break;
351 case 2: dwFlags = cmsFLAGS_HIGHRESPRECALC; break;
352 case 3: dwFlags = cmsFLAGS_LOWRESPRECALC; break;
353 case 1: break;
354
355 default: FatalError("Unknown precalculation mode '%d'", PrecalcMode);
356 }
357
358 if (BlackPointCompensation)
359 dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
360
361 return dwFlags;
362 }
363
364 // Create transforms
365
366 static
OpenTransforms(int argc,char * argv[])367 void OpenTransforms(int argc, char *argv[])
368 {
369
370 cmsUInt32Number dwIn, dwOut, dwFlags;
371
372
373 if (lMultiProfileChain) {
374
375 int i;
376 cmsHTRANSFORM hTmp;
377
378
379 nProfiles = argc - xoptind;
380 for (i=0; i < nProfiles; i++) {
381
382 hProfiles[i] = OpenProfile(argv[i+xoptind]);
383 }
384
385
386 // Create a temporary devicelink
387
388 hTmp = cmsCreateMultiprofileTransform(hProfiles, nProfiles,
389 0, 0, Intent, GetFlags());
390
391 hInput = cmsTransform2DeviceLink(hTmp, 4.2, 0);
392 hOutput = NULL;
393 cmsDeleteTransform(hTmp);
394
395 InputColorSpace = cmsGetColorSpace(hInput);
396 OutputColorSpace = cmsGetPCS(hInput);
397 lIsDeviceLink = TRUE;
398
399 }
400 else
401 if (lIsDeviceLink) {
402
403 hInput = cmsOpenProfileFromFile(cInProf, "r");
404 hOutput = NULL;
405 InputColorSpace = cmsGetColorSpace(hInput);
406 OutputColorSpace = cmsGetPCS(hInput);
407
408
409 }
410 else {
411
412 hInput = OpenProfile(cInProf);
413 hOutput = OpenProfile(cOutProf);
414
415 InputColorSpace = cmsGetColorSpace(hInput);
416 OutputColorSpace = cmsGetColorSpace(hOutput);
417
418 if (cmsGetDeviceClass(hInput) == cmsSigLinkClass ||
419 cmsGetDeviceClass(hOutput) == cmsSigLinkClass)
420 FatalError("Use %cl flag for devicelink profiles!\n", SW);
421
422 }
423
424
425 /*
426
427 if (Verbose) {
428
429 mexPrintf("From: %s\n", cmsTakeProductName(hInput));
430 if (hOutput) mexPrintf("To : %s\n\n", cmsTakeProductName(hOutput));
431
432 }
433 */
434
435
436 OutputChannels = cmsChannelsOf(OutputColorSpace);
437 InputChannels = cmsChannelsOf(InputColorSpace);
438
439
440 dwIn = MakeFormatDescriptor(InputColorSpace, nBytesDepth);
441 dwOut = MakeFormatDescriptor(OutputColorSpace, nBytesDepth);
442
443
444 dwFlags = GetFlags();
445
446 if (cProofing != NULL) {
447
448 hProof = OpenProfile(cProofing);
449 dwFlags |= cmsFLAGS_SOFTPROOFING;
450 }
451
452
453
454
455 hColorTransform = cmsCreateProofingTransform(hInput, dwIn,
456 hOutput, dwOut,
457 hProof, Intent,
458 ProofingIntent,
459 dwFlags);
460
461 }
462
463
464
465 static
ApplyTransforms(const mxArray * In,mxArray * Out)466 void ApplyTransforms(const mxArray *In, mxArray *Out)
467 {
468 double *Input = mxGetPr(In);
469 double *Output = mxGetPr(Out);
470 size_t nPixels = GetNumberOfPixels(In);;
471
472 cmsDoTransform(hColorTransform, Input, Output, nPixels );
473
474 }
475
476
477 static
CloseTransforms(void)478 void CloseTransforms(void)
479 {
480 int i;
481
482 if (hColorTransform) cmsDeleteTransform(hColorTransform);
483 if (hInput) cmsCloseProfile(hInput);
484 if (hOutput) cmsCloseProfile(hOutput);
485 if (hProof) cmsCloseProfile(hProof);
486
487 for (i=0; i < nProfiles; i++)
488 cmsCloseProfile(hProfiles[i]);
489
490 hColorTransform = NULL; hInput = NULL; hOutput = NULL; hProof = NULL;
491 }
492
493
494 static
HandleSwitches(int argc,char * argv[])495 void HandleSwitches(int argc, char *argv[])
496 {
497 int s;
498
499 xoptinit();
500
501 while ((s = xgetopt(argc, argv,"C:c:VvbBI:i:O:o:T:t:L:l:r:r:P:p:Mm")) != EOF) {
502
503
504 switch (s){
505
506 case 'b':
507 case 'B':
508 BlackPointCompensation = TRUE;
509 break;
510
511 case 'c':
512 case 'C':
513 PrecalcMode = atoi(xoptarg);
514 if (PrecalcMode < 0 || PrecalcMode > 3)
515 FatalError("Unknown precalc mode '%d'", PrecalcMode);
516 break;
517
518 case 'v':
519 case 'V':
520 Verbose = TRUE;
521 break;
522
523 case 'i':
524 case 'I':
525 if (lIsDeviceLink)
526 FatalError("Device-link already specified");
527 cInProf = xoptarg;
528 break;
529
530 case 'o':
531 case 'O':
532 if (lIsDeviceLink)
533 FatalError("Device-link already specified");
534 cOutProf = xoptarg;
535 break;
536
537 case 't':
538 case 'T':
539 Intent = atoi(xoptarg);
540 // if (Intent > 3) Intent = 3;
541 if (Intent < 0) Intent = 0;
542 break;
543
544
545 case 'l':
546 case 'L':
547 cInProf = xoptarg;
548 lIsDeviceLink = TRUE;
549 break;
550
551 case 'p':
552 case 'P':
553 cProofing = xoptarg;
554 break;
555
556
557
558 case 'r':
559 case 'R':
560 ProofingIntent = atoi(xoptarg);
561 // if (ProofingIntent > 3) ProofingIntent = 3;
562 if (ProofingIntent < 0) ProofingIntent = 0;
563 break;
564
565
566 case 'm':
567 case 'M':
568 lMultiProfileChain = TRUE;
569 break;
570
571 default:
572 FatalError("Unknown option.");
573 }
574 }
575
576 // For multiprofile, need to specify -m
577
578 if (xoptind < argc) {
579
580 if (!lMultiProfileChain)
581 FatalError("Use %cm for multiprofile transforms", SW);
582 }
583
584 }
585
586
587
588 // -------------------------------------------------- Print some fancy help
589 static
PrintHelp(void)590 void PrintHelp(void)
591 {
592 mexPrintf("(MX) little cms ColorSpace conversion tool - v2.0\n\n");
593
594 mexPrintf("usage: icctrans (mVar, flags)\n\n");
595
596 mexPrintf("mVar : Matlab array.\n");
597 mexPrintf("flags: a string containing one or more of following options.\n\n");
598 mexPrintf("\t%cv - Verbose\n", SW);
599 mexPrintf("\t%ci<profile> - Input profile (defaults to sRGB)\n", SW);
600 mexPrintf("\t%co<profile> - Output profile (defaults to sRGB)\n", SW);
601 mexPrintf("\t%cl<profile> - Transform by device-link profile\n", SW);
602 mexPrintf("\t%cm<profiles> - Apply multiprofile chain\n", SW);
603
604 mexPrintf("\t%ct<n> - Rendering intent\n", SW);
605
606 mexPrintf("\t%cb - Black point compensation\n", SW);
607 mexPrintf("\t%cc<0,1,2,3> - Optimize transform (0=Off, 1=Normal, 2=Hi-res, 3=Lo-Res) [defaults to 1]\n", SW);
608
609 mexPrintf("\t%cp<profile> - Soft proof profile\n", SW);
610 mexPrintf("\t%cr<0,1,2,3> - Soft proof intent\n", SW);
611
612 mexPrintf("\nYou can use following built-ins as profiles:\n\n");
613
614 mexPrintf("\t*Lab2 -- D50-based v2 CIEL*a*b\n"
615 "\t*Lab4 -- D50-based v4 CIEL*a*b\n"
616 "\t*Lab -- D50-based v4 CIEL*a*b\n"
617 "\t*XYZ -- CIE XYZ (PCS)\n"
618 "\t*sRGB -- IEC6 1996-2.1 sRGB color space\n"
619 "\t*Gray22 - Monochrome of Gamma 2.2\n"
620 "\t*Gray30 - Monochrome of Gamma 3.0\n"
621 "\t*null - Monochrome black for all input\n"
622 "\t*Lin2222- CMYK linearization of gamma 2.2 on each channel\n\n");
623
624 mexPrintf("For suggestions, comments, bug reports etc. send mail to info@littlecms.com\n\n");
625
626 }
627
628
629
630 // Main entry point
631
mexFunction(int nlhs,mxArray * plhs[],int nrhs,const mxArray * prhs[])632 void mexFunction(
633 int nlhs, // Number of left hand side (output) arguments
634 mxArray *plhs[], // Array of left hand side arguments
635 int nrhs, // Number of right hand side (input) arguments
636 const mxArray *prhs[] // Array of right hand side arguments
637 )
638 {
639
640 char CommandLine[4096+1];
641 char *pt, *argv[128];
642 int argc = 1;
643
644
645 if (nrhs != 2) {
646
647 PrintHelp();
648 return;
649 }
650
651
652 if(nlhs > 1) {
653 FatalError("Too many output arguments.");
654 }
655
656
657 // Setup error handler
658
659 cmsSetLogErrorHandler(MatLabErrorHandler);
660
661 // Defaults
662
663 Verbose = 0;
664 cInProf = NULL;
665 cOutProf = NULL;
666 cProofing = NULL;
667
668 lMultiProfileChain = FALSE;
669 nProfiles = 0;
670
671 Intent = INTENT_PERCEPTUAL;
672 ProofingIntent = INTENT_ABSOLUTE_COLORIMETRIC;
673 PrecalcMode = 1;
674 BlackPointCompensation = FALSE;
675 lIsDeviceLink = FALSE;
676
677 // Check types. Fist parameter is array of values, second parameter is command line
678
679 if (!mxIsNumeric(prhs[0]))
680 FatalError("Type mismatch on argument 1 -- Must be numeric");
681
682 if (!mxIsChar(prhs[1]))
683 FatalError("Type mismatch on argument 2 -- Must be string");
684
685
686
687
688 // Unpack string to command line buffer
689
690 if (mxGetString(prhs[1], CommandLine, 4096))
691 FatalError("Cannot unpack command string");
692
693 // Separate to argv[] convention
694
695 argv[0] = NULL;
696 for (pt = strtok(CommandLine, " ");
697 pt;
698 pt = strtok(NULL, " ")) {
699
700 argv[argc++] = pt;
701 }
702
703
704
705 // Parse arguments
706 HandleSwitches(argc, argv);
707
708
709 nBytesDepth = SizeOfArrayType(prhs[0]);
710
711 OpenTransforms(argc, argv);
712
713
714 plhs[0] = AllocateOutputArray(prhs[0], OutputChannels);
715
716
717 ApplyTransforms(prhs[0], plhs[0]);
718
719 CloseTransforms();
720
721 // Done!
722 }
723
724
725