Tizen 2.0 Release
[external/lcms.git] / utils / matlab / icctrans.c
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 "lcms2.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
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
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
94 void xoptinit()
95 {   
96         xoptind = 1;
97         xopterr = 0;
98         letP = NULL;
99 }
100
101
102 static
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
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
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
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
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
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
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
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
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
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
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
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"\r
615         "\t*Lab4  -- D50-based v4 CIEL*a*b\n"\r
616         "\t*Lab   -- D50-based v4 CIEL*a*b\n"\r
617         "\t*XYZ   -- CIE XYZ (PCS)\n"\r
618         "\t*sRGB  -- IEC6 1996-2.1 sRGB color space\n" \r
619         "\t*Gray22 - Monochrome of Gamma 2.2\n"\r
620         "\t*Gray30 - Monochrome of Gamma 3.0\n"\r
621         "\t*null   - Monochrome black for all input\n"\r
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
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