icctrans.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  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. #include "mex.h"
  23. #include "lcms2.h"
  24. #include "string.h"
  25. #include "stdarg.h"
  26. // xgetopt() interface -----------------------------------------------------
  27. static int xoptind;
  28. static char *xoptarg;
  29. static int xopterr;
  30. static char *letP;
  31. static char SW = '-';
  32. // ------------------------------------------------------------------------
  33. static int Verbose ; // Print some statistics
  34. static char *cInProf; // Input profile
  35. static char *cOutProf; // Output profile
  36. static char *cProofing; // Softproofing profile
  37. static int Intent; // Rendering Intent
  38. static int ProofingIntent; // RI for proof
  39. static int PrecalcMode; // 0 = Not, 1=Normal, 2=Accurate, 3=Fast
  40. static cmsBool BlackPointCompensation;
  41. static cmsBool lIsDeviceLink;
  42. static cmsBool lMultiProfileChain; // Multiple profile chain
  43. static cmsHPROFILE hInput, hOutput, hProof;
  44. static cmsHTRANSFORM hColorTransform;
  45. static cmsHPROFILE hProfiles[255];
  46. static int nProfiles;
  47. static cmsColorSpaceSignature InputColorSpace, OutputColorSpace;
  48. static int OutputChannels, InputChannels, nBytesDepth;
  49. // Error. Print error message and abort
  50. static
  51. cmsBool FatalError(const char *frm, ...)
  52. {
  53. va_list args;
  54. char Buffer[1024];
  55. va_start(args, frm);
  56. vsprintf(Buffer, frm, args);
  57. mexErrMsgTxt(Buffer);
  58. va_end(args);
  59. return FALSE;
  60. }
  61. // This is the handler passed to lcms
  62. static
  63. void MatLabErrorHandler(cmsContext ContextID, cmsUInt32Number ErrorCode,
  64. const char *Text)
  65. {
  66. mexErrMsgTxt(Text);
  67. }
  68. //
  69. // Parse the command line options, System V style.
  70. //
  71. static
  72. void xoptinit()
  73. {
  74. xoptind = 1;
  75. xopterr = 0;
  76. letP = NULL;
  77. }
  78. static
  79. int xgetopt(int argc, char *argv[], char *optionS)
  80. {
  81. unsigned char ch;
  82. char *optP;
  83. if (SW == 0) {
  84. SW = '/';
  85. }
  86. if (argc > xoptind) {
  87. if (letP == NULL) {
  88. if ((letP = argv[xoptind]) == NULL ||
  89. *(letP++) != SW) goto gopEOF;
  90. if (*letP == SW) {
  91. xoptind++; goto gopEOF;
  92. }
  93. }
  94. if (0 == (ch = *(letP++))) {
  95. xoptind++; goto gopEOF;
  96. }
  97. if (':' == ch || (optP = strchr(optionS, ch)) == NULL)
  98. goto gopError;
  99. if (':' == *(++optP)) {
  100. xoptind++;
  101. if (0 == *letP) {
  102. if (argc <= xoptind) goto gopError;
  103. letP = argv[xoptind++];
  104. }
  105. xoptarg = letP;
  106. letP = NULL;
  107. } else {
  108. if (0 == *letP) {
  109. xoptind++;
  110. letP = NULL;
  111. }
  112. xoptarg = NULL;
  113. }
  114. return ch;
  115. }
  116. gopEOF:
  117. xoptarg = letP = NULL;
  118. return EOF;
  119. gopError:
  120. xoptarg = NULL;
  121. if (xopterr)
  122. FatalError ("get command line option");
  123. return ('?');
  124. }
  125. // Return Mathlab type by depth
  126. static
  127. size_t SizeOfArrayType(const mxArray *Array)
  128. {
  129. switch (mxGetClassID(Array)) {
  130. case mxINT8_CLASS: return 1;
  131. case mxUINT8_CLASS: return 1;
  132. case mxINT16_CLASS: return 2;
  133. case mxUINT16_CLASS: return 2;
  134. case mxSINGLE_CLASS: return 4;
  135. case mxDOUBLE_CLASS: return 0; // Special case -- lcms handles double as size=0
  136. default:
  137. FatalError("Unsupported data type");
  138. return 0;
  139. }
  140. }
  141. // Get number of pixels of input array. Supported arrays are
  142. // organized as NxMxD, being N and M the size of image and D the
  143. // number of components.
  144. static
  145. size_t GetNumberOfPixels(const mxArray* In)
  146. {
  147. int nDimensions = mxGetNumberOfDimensions(In);
  148. const int *Dimensions = mxGetDimensions(In);
  149. switch (nDimensions) {
  150. case 1: return 1; // It is just a spot color
  151. case 2: return Dimensions[0]; // A scanline
  152. case 3: return Dimensions[0]*Dimensions[1]; // A image
  153. default:
  154. FatalError("Unsupported array of %d dimensions", nDimensions);
  155. return 0;
  156. }
  157. }
  158. // Allocates the output array. Copies the input array modifying the pixel
  159. // definition to match "OutputChannels".
  160. static
  161. mxArray* AllocateOutputArray(const mxArray* In, int OutputChannels)
  162. {
  163. mxArray* Out = mxDuplicateArray(In); // Make a "deep copy" of Input array
  164. int nDimensions = mxGetNumberOfDimensions(In);
  165. const int* Dimensions = mxGetDimensions(In);
  166. int InputChannels = Dimensions[nDimensions-1];
  167. // Modify pixel size only if needed
  168. if (InputChannels != OutputChannels) {
  169. int i, NewSize;
  170. int *ModifiedDimensions = (int*) mxMalloc(nDimensions * sizeof(int));
  171. memmove(ModifiedDimensions, Dimensions, nDimensions * sizeof(int));
  172. ModifiedDimensions[nDimensions - 1] = OutputChannels;
  173. switch (mxGetClassID(In)) {
  174. case mxINT8_CLASS: NewSize = sizeof(char); break;
  175. case mxUINT8_CLASS: NewSize = sizeof(unsigned char); break;
  176. case mxINT16_CLASS: NewSize = sizeof(short); break;
  177. case mxUINT16_CLASS: NewSize = sizeof(unsigned short); break;
  178. default:
  179. case mxDOUBLE_CLASS: NewSize = sizeof(double); break;
  180. }
  181. // NewSize = 1;
  182. for (i=0; i < nDimensions; i++)
  183. NewSize *= ModifiedDimensions[i];
  184. mxSetDimensions(Out, ModifiedDimensions, nDimensions);
  185. mxFree(ModifiedDimensions);
  186. mxSetPr(Out, mxRealloc(mxGetPr(Out), NewSize));
  187. }
  188. return Out;
  189. }
  190. // Does create a format descriptor. "Bytes" is the sizeof type in bytes
  191. //
  192. // Bytes Meaning
  193. // ------ --------
  194. // 0 Floating point (double)
  195. // 1 8-bit samples
  196. // 2 16-bit samples
  197. static
  198. cmsUInt32Number MakeFormatDescriptor(cmsColorSpaceSignature ColorSpace, int Bytes)
  199. {
  200. int IsFloat = (Bytes == 0 || Bytes == 4) ? 1 : 0;
  201. int Channels = cmsChannelsOf(ColorSpace);
  202. return FLOAT_SH(IsFloat)|COLORSPACE_SH(_cmsLCMScolorSpace(ColorSpace))|BYTES_SH(Bytes)|CHANNELS_SH(Channels)|PLANAR_SH(1);
  203. }
  204. // Opens a profile or proper built-in
  205. static
  206. cmsHPROFILE OpenProfile(const char* File)
  207. {
  208. cmsContext ContextID = 0;
  209. if (!File)
  210. return cmsCreate_sRGBProfileTHR(ContextID);
  211. if (cmsstrcasecmp(File, "*Lab2") == 0)
  212. return cmsCreateLab2ProfileTHR(ContextID, NULL);
  213. if (cmsstrcasecmp(File, "*Lab4") == 0)
  214. return cmsCreateLab4ProfileTHR(ContextID, NULL);
  215. if (cmsstrcasecmp(File, "*Lab") == 0)
  216. return cmsCreateLab4ProfileTHR(ContextID, NULL);
  217. if (cmsstrcasecmp(File, "*LabD65") == 0) {
  218. cmsCIExyY D65xyY;
  219. cmsWhitePointFromTemp( &D65xyY, 6504);
  220. return cmsCreateLab4ProfileTHR(ContextID, &D65xyY);
  221. }
  222. if (cmsstrcasecmp(File, "*XYZ") == 0)
  223. return cmsCreateXYZProfileTHR(ContextID);
  224. if (cmsstrcasecmp(File, "*Gray22") == 0) {
  225. cmsToneCurve* Curve = cmsBuildGamma(ContextID, 2.2);
  226. cmsHPROFILE hProfile = cmsCreateGrayProfileTHR(ContextID, cmsD50_xyY(), Curve);
  227. cmsFreeToneCurve(Curve);
  228. return hProfile;
  229. }
  230. if (cmsstrcasecmp(File, "*Gray30") == 0) {
  231. cmsToneCurve* Curve = cmsBuildGamma(ContextID, 3.0);
  232. cmsHPROFILE hProfile = cmsCreateGrayProfileTHR(ContextID, cmsD50_xyY(), Curve);
  233. cmsFreeToneCurve(Curve);
  234. return hProfile;
  235. }
  236. if (cmsstrcasecmp(File, "*srgb") == 0)
  237. return cmsCreate_sRGBProfileTHR(ContextID);
  238. if (cmsstrcasecmp(File, "*null") == 0)
  239. return cmsCreateNULLProfileTHR(ContextID);
  240. if (cmsstrcasecmp(File, "*Lin2222") == 0) {
  241. cmsToneCurve* Gamma = cmsBuildGamma(0, 2.2);
  242. cmsToneCurve* Gamma4[4];
  243. cmsHPROFILE hProfile;
  244. Gamma4[0] = Gamma4[1] = Gamma4[2] = Gamma4[3] = Gamma;
  245. hProfile = cmsCreateLinearizationDeviceLink(cmsSigCmykData, Gamma4);
  246. cmsFreeToneCurve(Gamma);
  247. return hProfile;
  248. }
  249. return cmsOpenProfileFromFileTHR(ContextID, File, "r");
  250. }
  251. static
  252. cmsUInt32Number GetFlags()
  253. {
  254. cmsUInt32Number dwFlags = 0;
  255. switch (PrecalcMode) {
  256. case 0: dwFlags = cmsFLAGS_NOOPTIMIZE; break;
  257. case 2: dwFlags = cmsFLAGS_HIGHRESPRECALC; break;
  258. case 3: dwFlags = cmsFLAGS_LOWRESPRECALC; break;
  259. case 1: break;
  260. default: FatalError("Unknown precalculation mode '%d'", PrecalcMode);
  261. }
  262. if (BlackPointCompensation)
  263. dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
  264. return dwFlags;
  265. }
  266. // Create transforms
  267. static
  268. void OpenTransforms(int argc, char *argv[])
  269. {
  270. cmsUInt32Number dwIn, dwOut, dwFlags;
  271. if (lMultiProfileChain) {
  272. int i;
  273. cmsHTRANSFORM hTmp;
  274. nProfiles = argc - xoptind;
  275. for (i=0; i < nProfiles; i++) {
  276. hProfiles[i] = OpenProfile(argv[i+xoptind]);
  277. }
  278. // Create a temporary devicelink
  279. hTmp = cmsCreateMultiprofileTransform(hProfiles, nProfiles,
  280. 0, 0, Intent, GetFlags());
  281. hInput = cmsTransform2DeviceLink(hTmp, 4.2, 0);
  282. hOutput = NULL;
  283. cmsDeleteTransform(hTmp);
  284. InputColorSpace = cmsGetColorSpace(hInput);
  285. OutputColorSpace = cmsGetPCS(hInput);
  286. lIsDeviceLink = TRUE;
  287. }
  288. else
  289. if (lIsDeviceLink) {
  290. hInput = cmsOpenProfileFromFile(cInProf, "r");
  291. hOutput = NULL;
  292. InputColorSpace = cmsGetColorSpace(hInput);
  293. OutputColorSpace = cmsGetPCS(hInput);
  294. }
  295. else {
  296. hInput = OpenProfile(cInProf);
  297. hOutput = OpenProfile(cOutProf);
  298. InputColorSpace = cmsGetColorSpace(hInput);
  299. OutputColorSpace = cmsGetColorSpace(hOutput);
  300. if (cmsGetDeviceClass(hInput) == cmsSigLinkClass ||
  301. cmsGetDeviceClass(hOutput) == cmsSigLinkClass)
  302. FatalError("Use %cl flag for devicelink profiles!\n", SW);
  303. }
  304. /*
  305. if (Verbose) {
  306. mexPrintf("From: %s\n", cmsTakeProductName(hInput));
  307. if (hOutput) mexPrintf("To : %s\n\n", cmsTakeProductName(hOutput));
  308. }
  309. */
  310. OutputChannels = cmsChannelsOf(OutputColorSpace);
  311. InputChannels = cmsChannelsOf(InputColorSpace);
  312. dwIn = MakeFormatDescriptor(InputColorSpace, nBytesDepth);
  313. dwOut = MakeFormatDescriptor(OutputColorSpace, nBytesDepth);
  314. dwFlags = GetFlags();
  315. if (cProofing != NULL) {
  316. hProof = OpenProfile(cProofing);
  317. dwFlags |= cmsFLAGS_SOFTPROOFING;
  318. }
  319. hColorTransform = cmsCreateProofingTransform(hInput, dwIn,
  320. hOutput, dwOut,
  321. hProof, Intent,
  322. ProofingIntent,
  323. dwFlags);
  324. }
  325. static
  326. void ApplyTransforms(const mxArray *In, mxArray *Out)
  327. {
  328. double *Input = mxGetPr(In);
  329. double *Output = mxGetPr(Out);
  330. size_t nPixels = GetNumberOfPixels(In);;
  331. cmsDoTransform(hColorTransform, Input, Output, nPixels );
  332. }
  333. static
  334. void CloseTransforms(void)
  335. {
  336. int i;
  337. if (hColorTransform) cmsDeleteTransform(hColorTransform);
  338. if (hInput) cmsCloseProfile(hInput);
  339. if (hOutput) cmsCloseProfile(hOutput);
  340. if (hProof) cmsCloseProfile(hProof);
  341. for (i=0; i < nProfiles; i++)
  342. cmsCloseProfile(hProfiles[i]);
  343. hColorTransform = NULL; hInput = NULL; hOutput = NULL; hProof = NULL;
  344. }
  345. static
  346. void HandleSwitches(int argc, char *argv[])
  347. {
  348. int s;
  349. xoptinit();
  350. while ((s = xgetopt(argc, argv,"C:c:VvbBI:i:O:o:T:t:L:l:r:r:P:p:Mm")) != EOF) {
  351. switch (s){
  352. case 'b':
  353. case 'B':
  354. BlackPointCompensation = TRUE;
  355. break;
  356. case 'c':
  357. case 'C':
  358. PrecalcMode = atoi(xoptarg);
  359. if (PrecalcMode < 0 || PrecalcMode > 3)
  360. FatalError("Unknown precalc mode '%d'", PrecalcMode);
  361. break;
  362. case 'v':
  363. case 'V':
  364. Verbose = TRUE;
  365. break;
  366. case 'i':
  367. case 'I':
  368. if (lIsDeviceLink)
  369. FatalError("Device-link already specified");
  370. cInProf = xoptarg;
  371. break;
  372. case 'o':
  373. case 'O':
  374. if (lIsDeviceLink)
  375. FatalError("Device-link already specified");
  376. cOutProf = xoptarg;
  377. break;
  378. case 't':
  379. case 'T':
  380. Intent = atoi(xoptarg);
  381. // if (Intent > 3) Intent = 3;
  382. if (Intent < 0) Intent = 0;
  383. break;
  384. case 'l':
  385. case 'L':
  386. cInProf = xoptarg;
  387. lIsDeviceLink = TRUE;
  388. break;
  389. case 'p':
  390. case 'P':
  391. cProofing = xoptarg;
  392. break;
  393. case 'r':
  394. case 'R':
  395. ProofingIntent = atoi(xoptarg);
  396. // if (ProofingIntent > 3) ProofingIntent = 3;
  397. if (ProofingIntent < 0) ProofingIntent = 0;
  398. break;
  399. case 'm':
  400. case 'M':
  401. lMultiProfileChain = TRUE;
  402. break;
  403. default:
  404. FatalError("Unknown option.");
  405. }
  406. }
  407. // For multiprofile, need to specify -m
  408. if (xoptind < argc) {
  409. if (!lMultiProfileChain)
  410. FatalError("Use %cm for multiprofile transforms", SW);
  411. }
  412. }
  413. // -------------------------------------------------- Print some fancy help
  414. static
  415. void PrintHelp(void)
  416. {
  417. mexPrintf("(MX) little cms ColorSpace conversion tool - v2.0\n\n");
  418. mexPrintf("usage: icctrans (mVar, flags)\n\n");
  419. mexPrintf("mVar : Matlab array.\n");
  420. mexPrintf("flags: a string containing one or more of following options.\n\n");
  421. mexPrintf("\t%cv - Verbose\n", SW);
  422. mexPrintf("\t%ci<profile> - Input profile (defaults to sRGB)\n", SW);
  423. mexPrintf("\t%co<profile> - Output profile (defaults to sRGB)\n", SW);
  424. mexPrintf("\t%cl<profile> - Transform by device-link profile\n", SW);
  425. mexPrintf("\t%cm<profiles> - Apply multiprofile chain\n", SW);
  426. mexPrintf("\t%ct<n> - Rendering intent\n", SW);
  427. mexPrintf("\t%cb - Black point compensation\n", SW);
  428. mexPrintf("\t%cc<0,1,2,3> - Optimize transform (0=Off, 1=Normal, 2=Hi-res, 3=Lo-Res) [defaults to 1]\n", SW);
  429. mexPrintf("\t%cp<profile> - Soft proof profile\n", SW);
  430. mexPrintf("\t%cr<0,1,2,3> - Soft proof intent\n", SW);
  431. mexPrintf("\nYou can use following built-ins as profiles:\n\n");
  432. mexPrintf("\t*Lab2 -- D50-based v2 CIEL*a*b\n"
  433. "\t*Lab4 -- D50-based v4 CIEL*a*b\n"
  434. "\t*Lab -- D50-based v4 CIEL*a*b\n"
  435. "\t*XYZ -- CIE XYZ (PCS)\n"
  436. "\t*sRGB -- IEC6 1996-2.1 sRGB color space\n"
  437. "\t*Gray22 - Monochrome of Gamma 2.2\n"
  438. "\t*Gray30 - Monochrome of Gamma 3.0\n"
  439. "\t*null - Monochrome black for all input\n"
  440. "\t*Lin2222- CMYK linearization of gamma 2.2 on each channel\n\n");
  441. mexPrintf("For suggestions, comments, bug reports etc. send mail to [email protected]\n\n");
  442. }
  443. // Main entry point
  444. void mexFunction(
  445. int nlhs, // Number of left hand side (output) arguments
  446. mxArray *plhs[], // Array of left hand side arguments
  447. int nrhs, // Number of right hand side (input) arguments
  448. const mxArray *prhs[] // Array of right hand side arguments
  449. )
  450. {
  451. char CommandLine[4096+1];
  452. char *pt, *argv[128];
  453. int argc = 1;
  454. if (nrhs != 2) {
  455. PrintHelp();
  456. return;
  457. }
  458. if(nlhs > 1) {
  459. FatalError("Too many output arguments.");
  460. }
  461. // Setup error handler
  462. cmsSetLogErrorHandler(MatLabErrorHandler);
  463. // Defaults
  464. Verbose = 0;
  465. cInProf = NULL;
  466. cOutProf = NULL;
  467. cProofing = NULL;
  468. lMultiProfileChain = FALSE;
  469. nProfiles = 0;
  470. Intent = INTENT_PERCEPTUAL;
  471. ProofingIntent = INTENT_ABSOLUTE_COLORIMETRIC;
  472. PrecalcMode = 1;
  473. BlackPointCompensation = FALSE;
  474. lIsDeviceLink = FALSE;
  475. // Check types. Fist parameter is array of values, second parameter is command line
  476. if (!mxIsNumeric(prhs[0]))
  477. FatalError("Type mismatch on argument 1 -- Must be numeric");
  478. if (!mxIsChar(prhs[1]))
  479. FatalError("Type mismatch on argument 2 -- Must be string");
  480. // Unpack string to command line buffer
  481. if (mxGetString(prhs[1], CommandLine, 4096))
  482. FatalError("Cannot unpack command string");
  483. // Separate to argv[] convention
  484. argv[0] = NULL;
  485. for (pt = strtok(CommandLine, " ");
  486. pt;
  487. pt = strtok(NULL, " ")) {
  488. argv[argc++] = pt;
  489. }
  490. // Parse arguments
  491. HandleSwitches(argc, argv);
  492. nBytesDepth = SizeOfArrayType(prhs[0]);
  493. OpenTransforms(argc, argv);
  494. plhs[0] = AllocateOutputArray(prhs[0], OutputChannels);
  495. ApplyTransforms(prhs[0], plhs[0]);
  496. CloseTransforms();
  497. // Done!
  498. }