3 // Copyright (C) 1998-2010 Marti Maria, Ignacio Ruiz de Conejo
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the "Software"),
7 // to deal in the Software without restriction, including without limitation
8 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 // and/or sell copies of the Software, and to permit persons to whom the Software
10 // is furnished to do so, subject to the following conditions:
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
17 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 // xgetopt() interface -----------------------------------------------------
38 // ------------------------------------------------------------------------
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
47 static int Intent
; // Rendering Intent
48 static int ProofingIntent
; // RI for proof
50 static int PrecalcMode
; // 0 = Not, 1=Normal, 2=Accurate, 3=Fast
52 static cmsBool BlackPointCompensation
;
53 static cmsBool lIsDeviceLink
;
54 static cmsBool lMultiProfileChain
; // Multiple profile chain
56 static cmsHPROFILE hInput
, hOutput
, hProof
;
57 static cmsHTRANSFORM hColorTransform
;
58 static cmsHPROFILE hProfiles
[255];
61 static cmsColorSpaceSignature InputColorSpace
, OutputColorSpace
;
62 static int OutputChannels
, InputChannels
, nBytesDepth
;
65 // Error. Print error message and abort
68 cmsBool
FatalError(const char *frm
, ...)
74 vsprintf(Buffer
, frm
, args
);
81 // This is the handler passed to lcms
84 void MatLabErrorHandler(cmsContext ContextID
, cmsUInt32Number ErrorCode
,
90 // Parse the command line options, System V style.
103 int xgetopt(int argc
, char *argv
[], char *optionS
)
112 if (argc
> xoptind
) {
114 if ((letP
= argv
[xoptind
]) == NULL
||
115 *(letP
++) != SW
) goto gopEOF
;
117 xoptind
++; goto gopEOF
;
120 if (0 == (ch
= *(letP
++))) {
121 xoptind
++; goto gopEOF
;
123 if (':' == ch
|| (optP
= strchr(optionS
, ch
)) == NULL
)
125 if (':' == *(++optP
)) {
128 if (argc
<= xoptind
) goto gopError
;
129 letP
= argv
[xoptind
++];
143 xoptarg
= letP
= NULL
;
149 FatalError ("get command line option");
154 // Return Mathlab type by depth
157 size_t SizeOfArrayType(const mxArray
*Array
)
160 switch (mxGetClassID(Array
)) {
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
171 FatalError("Unsupported data type");
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.
182 size_t GetNumberOfPixels(const mxArray
* In
)
184 int nDimensions
= mxGetNumberOfDimensions(In
);
185 const int *Dimensions
= mxGetDimensions(In
);
187 switch (nDimensions
) {
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
194 FatalError("Unsupported array of %d dimensions", nDimensions
);
200 // Allocates the output array. Copies the input array modifying the pixel
201 // definition to match "OutputChannels".
204 mxArray
* AllocateOutputArray(const mxArray
* In
, int OutputChannels
)
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];
213 // Modify pixel size only if needed
215 if (InputChannels
!= OutputChannels
) {
219 int *ModifiedDimensions
= (int*) mxMalloc(nDimensions
* sizeof(int));
222 memmove(ModifiedDimensions
, Dimensions
, nDimensions
* sizeof(int));
223 ModifiedDimensions
[nDimensions
- 1] = OutputChannels
;
225 switch (mxGetClassID(In
)) {
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;
233 case mxDOUBLE_CLASS
: NewSize
= sizeof(double); break;
238 for (i
=0; i
< nDimensions
; i
++)
239 NewSize
*= ModifiedDimensions
[i
];
242 mxSetDimensions(Out
, ModifiedDimensions
, nDimensions
);
243 mxFree(ModifiedDimensions
);
245 mxSetPr(Out
, mxRealloc(mxGetPr(Out
), NewSize
));
255 // Does create a format descriptor. "Bytes" is the sizeof type in bytes
259 // 0 Floating point (double)
264 cmsUInt32Number
MakeFormatDescriptor(cmsColorSpaceSignature ColorSpace
, int Bytes
)
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);
272 // Opens a profile or proper built-in
275 cmsHPROFILE
OpenProfile(const char* File
)
278 cmsContext ContextID
= 0;
281 return cmsCreate_sRGBProfileTHR(ContextID
);
283 if (cmsstrcasecmp(File
, "*Lab2") == 0)
284 return cmsCreateLab2ProfileTHR(ContextID
, NULL
);
286 if (cmsstrcasecmp(File
, "*Lab4") == 0)
287 return cmsCreateLab4ProfileTHR(ContextID
, NULL
);
289 if (cmsstrcasecmp(File
, "*Lab") == 0)
290 return cmsCreateLab4ProfileTHR(ContextID
, NULL
);
292 if (cmsstrcasecmp(File
, "*LabD65") == 0) {
296 cmsWhitePointFromTemp( &D65xyY
, 6504);
297 return cmsCreateLab4ProfileTHR(ContextID
, &D65xyY
);
300 if (cmsstrcasecmp(File
, "*XYZ") == 0)
301 return cmsCreateXYZProfileTHR(ContextID
);
303 if (cmsstrcasecmp(File
, "*Gray22") == 0) {
305 cmsToneCurve
* Curve
= cmsBuildGamma(ContextID
, 2.2);
306 cmsHPROFILE hProfile
= cmsCreateGrayProfileTHR(ContextID
, cmsD50_xyY(), Curve
);
307 cmsFreeToneCurve(Curve
);
311 if (cmsstrcasecmp(File
, "*Gray30") == 0) {
313 cmsToneCurve
* Curve
= cmsBuildGamma(ContextID
, 3.0);
314 cmsHPROFILE hProfile
= cmsCreateGrayProfileTHR(ContextID
, cmsD50_xyY(), Curve
);
315 cmsFreeToneCurve(Curve
);
319 if (cmsstrcasecmp(File
, "*srgb") == 0)
320 return cmsCreate_sRGBProfileTHR(ContextID
);
322 if (cmsstrcasecmp(File
, "*null") == 0)
323 return cmsCreateNULLProfileTHR(ContextID
);
326 if (cmsstrcasecmp(File
, "*Lin2222") == 0) {
328 cmsToneCurve
* Gamma
= cmsBuildGamma(0, 2.2);
329 cmsToneCurve
* Gamma4
[4];
330 cmsHPROFILE hProfile
;
332 Gamma4
[0] = Gamma4
[1] = Gamma4
[2] = Gamma4
[3] = Gamma
;
333 hProfile
= cmsCreateLinearizationDeviceLink(cmsSigCmykData
, Gamma4
);
334 cmsFreeToneCurve(Gamma
);
339 return cmsOpenProfileFromFileTHR(ContextID
, File
, "r");
344 cmsUInt32Number
GetFlags()
346 cmsUInt32Number dwFlags
= 0;
348 switch (PrecalcMode
) {
350 case 0: dwFlags
= cmsFLAGS_NOOPTIMIZE
; break;
351 case 2: dwFlags
= cmsFLAGS_HIGHRESPRECALC
; break;
352 case 3: dwFlags
= cmsFLAGS_LOWRESPRECALC
; break;
355 default: FatalError("Unknown precalculation mode '%d'", PrecalcMode
);
358 if (BlackPointCompensation
)
359 dwFlags
|= cmsFLAGS_BLACKPOINTCOMPENSATION
;
367 void OpenTransforms(int argc
, char *argv
[])
370 cmsUInt32Number dwIn
, dwOut
, dwFlags
;
373 if (lMultiProfileChain
) {
379 nProfiles
= argc
- xoptind
;
380 for (i
=0; i
< nProfiles
; i
++) {
382 hProfiles
[i
] = OpenProfile(argv
[i
+xoptind
]);
386 // Create a temporary devicelink
388 hTmp
= cmsCreateMultiprofileTransform(hProfiles
, nProfiles
,
389 0, 0, Intent
, GetFlags());
391 hInput
= cmsTransform2DeviceLink(hTmp
, 4.2, 0);
393 cmsDeleteTransform(hTmp
);
395 InputColorSpace
= cmsGetColorSpace(hInput
);
396 OutputColorSpace
= cmsGetPCS(hInput
);
397 lIsDeviceLink
= TRUE
;
403 hInput
= cmsOpenProfileFromFile(cInProf
, "r");
405 InputColorSpace
= cmsGetColorSpace(hInput
);
406 OutputColorSpace
= cmsGetPCS(hInput
);
412 hInput
= OpenProfile(cInProf
);
413 hOutput
= OpenProfile(cOutProf
);
415 InputColorSpace
= cmsGetColorSpace(hInput
);
416 OutputColorSpace
= cmsGetColorSpace(hOutput
);
418 if (cmsGetDeviceClass(hInput
) == cmsSigLinkClass
||
419 cmsGetDeviceClass(hOutput
) == cmsSigLinkClass
)
420 FatalError("Use %cl flag for devicelink profiles!\n", SW
);
429 mexPrintf("From: %s\n", cmsTakeProductName(hInput));
430 if (hOutput) mexPrintf("To : %s\n\n", cmsTakeProductName(hOutput));
436 OutputChannels
= cmsChannelsOf(OutputColorSpace
);
437 InputChannels
= cmsChannelsOf(InputColorSpace
);
440 dwIn
= MakeFormatDescriptor(InputColorSpace
, nBytesDepth
);
441 dwOut
= MakeFormatDescriptor(OutputColorSpace
, nBytesDepth
);
444 dwFlags
= GetFlags();
446 if (cProofing
!= NULL
) {
448 hProof
= OpenProfile(cProofing
);
449 dwFlags
|= cmsFLAGS_SOFTPROOFING
;
455 hColorTransform
= cmsCreateProofingTransform(hInput
, dwIn
,
466 void ApplyTransforms(const mxArray
*In
, mxArray
*Out
)
468 double *Input
= mxGetPr(In
);
469 double *Output
= mxGetPr(Out
);
470 size_t nPixels
= GetNumberOfPixels(In
);;
472 cmsDoTransform(hColorTransform
, Input
, Output
, nPixels
);
478 void CloseTransforms(void)
482 if (hColorTransform
) cmsDeleteTransform(hColorTransform
);
483 if (hInput
) cmsCloseProfile(hInput
);
484 if (hOutput
) cmsCloseProfile(hOutput
);
485 if (hProof
) cmsCloseProfile(hProof
);
487 for (i
=0; i
< nProfiles
; i
++)
488 cmsCloseProfile(hProfiles
[i
]);
490 hColorTransform
= NULL
; hInput
= NULL
; hOutput
= NULL
; hProof
= NULL
;
495 void HandleSwitches(int argc
, char *argv
[])
501 while ((s
= xgetopt(argc
, argv
,"C:c:VvbBI:i:O:o:T:t:L:l:r:r:P:p:Mm")) != EOF
) {
508 BlackPointCompensation
= TRUE
;
513 PrecalcMode
= atoi(xoptarg
);
514 if (PrecalcMode
< 0 || PrecalcMode
> 3)
515 FatalError("Unknown precalc mode '%d'", PrecalcMode
);
526 FatalError("Device-link already specified");
533 FatalError("Device-link already specified");
539 Intent
= atoi(xoptarg
);
540 // if (Intent > 3) Intent = 3;
541 if (Intent
< 0) Intent
= 0;
548 lIsDeviceLink
= TRUE
;
560 ProofingIntent
= atoi(xoptarg
);
561 // if (ProofingIntent > 3) ProofingIntent = 3;
562 if (ProofingIntent
< 0) ProofingIntent
= 0;
568 lMultiProfileChain
= TRUE
;
572 FatalError("Unknown option.");
576 // For multiprofile, need to specify -m
578 if (xoptind
< argc
) {
580 if (!lMultiProfileChain
)
581 FatalError("Use %cm for multiprofile transforms", SW
);
588 // -------------------------------------------------- Print some fancy help
592 mexPrintf("(MX) little cms ColorSpace conversion tool - v2.0\n\n");
594 mexPrintf("usage: icctrans (mVar, flags)\n\n");
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
);
604 mexPrintf("\t%ct<n> - Rendering intent\n", SW
);
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
);
609 mexPrintf("\t%cp<profile> - Soft proof profile\n", SW
);
610 mexPrintf("\t%cr<0,1,2,3> - Soft proof intent\n", SW
);
612 mexPrintf("\nYou can use following built-ins as profiles:\n\n");
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");
624 mexPrintf("For suggestions, comments, bug reports etc. send mail to info@littlecms.com\n\n");
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
640 char CommandLine
[4096+1];
641 char *pt
, *argv
[128];
653 FatalError("Too many output arguments.");
657 // Setup error handler
659 cmsSetLogErrorHandler(MatLabErrorHandler
);
668 lMultiProfileChain
= FALSE
;
671 Intent
= INTENT_PERCEPTUAL
;
672 ProofingIntent
= INTENT_ABSOLUTE_COLORIMETRIC
;
674 BlackPointCompensation
= FALSE
;
675 lIsDeviceLink
= FALSE
;
677 // Check types. Fist parameter is array of values, second parameter is command line
679 if (!mxIsNumeric(prhs
[0]))
680 FatalError("Type mismatch on argument 1 -- Must be numeric");
682 if (!mxIsChar(prhs
[1]))
683 FatalError("Type mismatch on argument 2 -- Must be string");
688 // Unpack string to command line buffer
690 if (mxGetString(prhs
[1], CommandLine
, 4096))
691 FatalError("Cannot unpack command string");
693 // Separate to argv[] convention
696 for (pt
= strtok(CommandLine
, " ");
698 pt
= strtok(NULL
, " ")) {
706 HandleSwitches(argc
, argv
);
709 nBytesDepth
= SizeOfArrayType(prhs
[0]);
711 OpenTransforms(argc
, argv
);
714 plhs
[0] = AllocateOutputArray(prhs
[0], OutputChannels
);
717 ApplyTransforms(prhs
[0], plhs
[0]);