linkicc.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. //---------------------------------------------------------------------------------
  2. //
  3. // Little Color Management System
  4. // Copyright (c) 1998-2023 Marti Maria Saguer
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining
  7. // a copy of this software and associated documentation files (the "Software"),
  8. // to deal in the Software without restriction, including without limitation
  9. // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  10. // and/or sell copies of the Software, and to permit persons to whom the Software
  11. // is furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
  18. // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  20. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  21. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  22. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23. //
  24. //---------------------------------------------------------------------------------
  25. #include "utils.h"
  26. // ---------------------------------------------------------------------------------
  27. static char* Description = "Devicelink profile";
  28. static char* Copyright = "No copyright, use freely";
  29. static int Intent = INTENT_PERCEPTUAL;
  30. static char* cOutProf = "devicelink.icc";
  31. static int PrecalcMode = 1;
  32. static int NumOfGridPoints = 0;
  33. static cmsFloat64Number ObserverAdaptationState = 1.0; // According ICC 4.2 this is the default
  34. static cmsBool BlackPointCompensation = FALSE;
  35. static cmsFloat64Number InkLimit = 400;
  36. static cmsBool lUse8bits = FALSE;
  37. static cmsBool TagResult = FALSE;
  38. static cmsBool KeepLinearization = FALSE;
  39. static cmsFloat64Number Version = 4.3;
  40. // The manual
  41. static
  42. int Help(int level)
  43. {
  44. UTILS_UNUSED_PARAMETER(level);
  45. fprintf(stderr, "\nlinkicc: Links profiles into a single devicelink.\n");
  46. fprintf(stderr, "\n");
  47. fprintf(stderr, "usage: linkicc [flags] <profiles>\n\n");
  48. fprintf(stderr, "flags:\n\n");
  49. fprintf(stderr, "-o<profile> - Output devicelink profile. [defaults to 'devicelink.icc']\n");
  50. PrintRenderingIntents();
  51. fprintf(stderr, "-c<0,1,2> - Precision (0=LowRes, 1=Normal, 2=Hi-res) [defaults to 1]\n");
  52. fprintf(stderr, "-n<gridpoints> - Alternate way to set precision, number of CLUT points\n");
  53. fprintf(stderr, "-d<description> - description text (quotes can be used)\n");
  54. fprintf(stderr, "-y<copyright> - copyright notice (quotes can be used)\n");
  55. fprintf(stderr, "\n-k<0..400> - Ink-limiting in %% (CMYK only)\n");
  56. fprintf(stderr, "-8 - Creates 8-bit devicelink\n");
  57. fprintf(stderr, "-x - Creatively, guess deviceclass of resulting profile.\n");
  58. fprintf(stderr, "-b - Black point compensation\n");
  59. fprintf(stderr, "-a<0..1> - Observer adaptation state (abs.col. only)\n\n");
  60. fprintf(stderr, "-l - Use linearization curves (may affect accuracy)\n");
  61. fprintf(stderr, "-r<v.r> - Profile version. (CAUTION: may change the profile implementation)\n");
  62. fprintf(stderr, "\n");
  63. fprintf(stderr, "Colorspaces must be paired except Lab/XYZ, that can be interchanged.\n\n");
  64. PrintBuiltins();
  65. fprintf(stderr, "\nExamples:\n\n"
  66. "To create 'devicelink.icm' from a.icc to b.icc:\n"
  67. "\tlinkicc a.icc b.icc\n\n"
  68. "To create 'out.icc' from sRGB to cmyk.icc:\n"
  69. "\tlinkicc -o out.icc *sRGB cmyk.icc\n\n"
  70. "To create a sRGB input profile working in Lab:\n"
  71. "\tlinkicc -x -o sRGBLab.icc *sRGB *Lab\n\n"
  72. "To create a XYZ -> sRGB output profile:\n"
  73. "\tlinkicc -x -o sRGBLab.icc *XYZ *sRGB\n\n"
  74. "To create a abstract profile doing softproof for cmyk.icc:\n"
  75. "\tlinkicc -t1 -x -o softproof.icc *Lab cmyk.icc cmyk.icc *Lab\n\n"
  76. "To create a 'grayer' sRGB input profile:\n"
  77. "\tlinkicc -x -o grayer.icc *sRGB gray.icc gray.icc *Lab\n\n"
  78. "To embed ink limiting into a cmyk output profile:\n"
  79. "\tlinkicc -x -o cmyklimited.icc -k 250 cmyk.icc *Lab\n\n");
  80. fprintf(stderr, "This program is intended to be a demo of the Little CMS\n"
  81. "color engine. Both lcms and this program are open source.\n"
  82. "You can obtain both in source code at https://www.littlecms.com\n"
  83. "For suggestions, comments, bug reports etc. send mail to\n"
  84. "info@littlecms.com\n\n");
  85. exit(0);
  86. }
  87. // The toggles stuff
  88. static
  89. void HandleSwitches(int argc, char *argv[])
  90. {
  91. int s;
  92. while ((s = xgetopt(argc,argv,"a:A:BbC:c:D:d:h:H:k:K:lLn:N:O:o:r:R:T:t:V:v:xX8y:Y:-:")) != EOF) {
  93. switch (s) {
  94. case '-':
  95. if (strcmp(xoptarg, "help") == 0)
  96. {
  97. Help(0);
  98. }
  99. else
  100. {
  101. FatalError("Unknown option - run without args to see valid ones.\n");
  102. }
  103. break;
  104. case 'a':
  105. case 'A':
  106. ObserverAdaptationState = atof(xoptarg);
  107. if (ObserverAdaptationState < 0 ||
  108. ObserverAdaptationState > 1.0)
  109. FatalError("Adaptation state should be 0..1");
  110. break;
  111. case 'b':
  112. case 'B':
  113. BlackPointCompensation = TRUE;
  114. break;
  115. case 'c':
  116. case 'C':
  117. PrecalcMode = atoi(xoptarg);
  118. if (PrecalcMode < 0 || PrecalcMode > 2) {
  119. FatalError("Unknown precalc mode '%d'", PrecalcMode);
  120. }
  121. break;
  122. case 'd':
  123. case 'D':
  124. // Doing that is correct and safe: Description points to memory allocated in the command line.
  125. // same for Copyright and output devicelink.
  126. Description = xoptarg;
  127. break;
  128. case 'h':
  129. case 'H':
  130. Help(atoi(xoptarg));
  131. return;
  132. case 'k':
  133. case 'K':
  134. InkLimit = atof(xoptarg);
  135. if (InkLimit < 0.0 || InkLimit > 400.0) {
  136. FatalError("Ink limit must be 0%%..400%%");
  137. }
  138. break;
  139. case 'l':
  140. case 'L': KeepLinearization = TRUE;
  141. break;
  142. case 'n':
  143. case 'N':
  144. if (PrecalcMode != 1) {
  145. FatalError("Precalc mode already specified");
  146. }
  147. NumOfGridPoints = atoi(xoptarg);
  148. break;
  149. case 'o':
  150. case 'O':
  151. cOutProf = xoptarg;
  152. break;
  153. case 'r':
  154. case 'R':
  155. Version = atof(xoptarg);
  156. if (Version < 2.0 || Version > 4.3) {
  157. fprintf(stderr, "WARNING: lcms was not aware of this version, tag types may be wrong!\n");
  158. }
  159. break;
  160. case 't':
  161. case 'T':
  162. Intent = atoi(xoptarg); // Will be validated latter on
  163. break;
  164. case 'V':
  165. case 'v':
  166. Verbose = atoi(xoptarg);
  167. if (Verbose < 0 || Verbose > 3) {
  168. FatalError("Unknown verbosity level '%d'", Verbose);
  169. }
  170. break;
  171. case '8':
  172. lUse8bits = TRUE;
  173. break;
  174. case 'y':
  175. case 'Y':
  176. Copyright = xoptarg;
  177. break;
  178. case 'x':
  179. case 'X': TagResult = TRUE;
  180. break;
  181. default:
  182. FatalError("Unknown option - run without args to see valid ones.\n");
  183. }
  184. }
  185. }
  186. // Set the copyright and description
  187. static
  188. cmsBool SetTextTags(cmsHPROFILE hProfile)
  189. {
  190. cmsMLU *DescriptionMLU, *CopyrightMLU;
  191. cmsBool rc = FALSE;
  192. cmsContext ContextID = cmsGetProfileContextID(hProfile);
  193. DescriptionMLU = cmsMLUalloc(ContextID, 1);
  194. CopyrightMLU = cmsMLUalloc(ContextID, 1);
  195. if (DescriptionMLU == NULL || CopyrightMLU == NULL) goto Error;
  196. if (!cmsMLUsetASCII(DescriptionMLU, "en", "US", Description)) goto Error;
  197. if (!cmsMLUsetASCII(CopyrightMLU, "en", "US", Copyright)) goto Error;
  198. if (!cmsWriteTag(hProfile, cmsSigProfileDescriptionTag, DescriptionMLU)) goto Error;
  199. if (!cmsWriteTag(hProfile, cmsSigCopyrightTag, CopyrightMLU)) goto Error;
  200. rc = TRUE;
  201. Error:
  202. if (DescriptionMLU)
  203. cmsMLUfree(DescriptionMLU);
  204. if (CopyrightMLU)
  205. cmsMLUfree(CopyrightMLU);
  206. return rc;
  207. }
  208. int main(int argc, char *argv[])
  209. {
  210. int i, nargs, rc;
  211. cmsHPROFILE Profiles[257];
  212. cmsHPROFILE hProfile;
  213. cmsUInt32Number dwFlags;
  214. cmsHTRANSFORM hTransform = NULL;
  215. // Here we are
  216. fprintf(stderr, "Little CMS ICC device link generator - v3.2 [LittleCMS %2.2f]\n", cmsGetEncodedCMMversion() / 1000.0);
  217. fprintf(stderr, "Copyright (c) 1998-2023 Marti Maria Saguer. See COPYING file for details.\n");
  218. fflush(stderr);
  219. // Initialize
  220. InitUtils("linkicc");
  221. rc = 0;
  222. // Get the options
  223. HandleSwitches(argc, argv);
  224. // How many profiles to link?
  225. nargs = (argc - xoptind);
  226. if (nargs < 1)
  227. return Help(0);
  228. if (nargs > 255) {
  229. FatalError("Holy profile! what are you trying to do with so many profiles!?");
  230. goto Cleanup;
  231. }
  232. // Open all profiles
  233. memset(Profiles, 0, sizeof(Profiles));
  234. for (i=0; i < nargs; i++) {
  235. Profiles[i] = OpenStockProfile(0, argv[i + xoptind]);
  236. if (Profiles[i] == NULL) goto Cleanup;
  237. if (Verbose >= 1) {
  238. PrintProfileInformation(Profiles[i]);
  239. }
  240. }
  241. // Ink limiting
  242. if (InkLimit != 400.0) {
  243. cmsColorSpaceSignature EndingColorSpace = cmsGetColorSpace(Profiles[nargs-1]);
  244. Profiles[nargs++] = cmsCreateInkLimitingDeviceLink(EndingColorSpace, InkLimit);
  245. }
  246. // Set the flags
  247. dwFlags = cmsFLAGS_KEEP_SEQUENCE;
  248. switch (PrecalcMode) {
  249. case 0: dwFlags |= cmsFLAGS_LOWRESPRECALC; break;
  250. case 2: dwFlags |= cmsFLAGS_HIGHRESPRECALC; break;
  251. case 1:
  252. if (NumOfGridPoints > 0)
  253. dwFlags |= cmsFLAGS_GRIDPOINTS(NumOfGridPoints);
  254. break;
  255. default:
  256. {
  257. FatalError("Unknown precalculation mode '%d'", PrecalcMode);
  258. goto Cleanup;
  259. }
  260. }
  261. if (BlackPointCompensation)
  262. dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
  263. if (TagResult)
  264. dwFlags |= cmsFLAGS_GUESSDEVICECLASS;
  265. if (KeepLinearization)
  266. dwFlags |= cmsFLAGS_CLUT_PRE_LINEARIZATION|cmsFLAGS_CLUT_POST_LINEARIZATION;
  267. if (lUse8bits) dwFlags |= cmsFLAGS_8BITS_DEVICELINK;
  268. cmsSetAdaptationState(ObserverAdaptationState);
  269. // Create the color transform. Specify 0 for the format is safe as the transform
  270. // is intended to be used only for the devicelink.
  271. hTransform = cmsCreateMultiprofileTransform(Profiles, nargs, 0, 0, Intent, dwFlags|cmsFLAGS_NOOPTIMIZE);
  272. if (hTransform == NULL) {
  273. FatalError("Transform creation failed");
  274. goto Cleanup;
  275. }
  276. hProfile = cmsTransform2DeviceLink(hTransform, Version, dwFlags);
  277. if (hProfile == NULL) {
  278. FatalError("Devicelink creation failed");
  279. goto Cleanup;
  280. }
  281. SetTextTags(hProfile);
  282. cmsSetHeaderRenderingIntent(hProfile, Intent);
  283. if (cmsSaveProfileToFile(hProfile, cOutProf)) {
  284. if (Verbose > 0)
  285. fprintf(stderr, "Ok");
  286. }
  287. else
  288. FatalError("Error saving file!");
  289. cmsCloseProfile(hProfile);
  290. Cleanup:
  291. if (hTransform != NULL) cmsDeleteTransform(hTransform);
  292. for (i=0; i < nargs; i++) {
  293. if (Profiles[i] != NULL) cmsCloseProfile(Profiles[i]);
  294. }
  295. return rc;
  296. }