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]);