/src/apps/ociobakelut/main.cpp

http://github.com/imageworks/OpenColorIO · C++ · 539 lines · 445 code · 72 blank · 22 comment · 91 complexity · f9f9a9b3e6f720b79d6c8358de03fd4a MD5 · raw file

  1. // SPDX-License-Identifier: BSD-3-Clause
  2. // Copyright Contributors to the OpenColorIO Project.
  3. #include <cmath>
  4. #include <cstdlib>
  5. #include <iostream>
  6. #include <sstream>
  7. #include <fstream>
  8. #include <vector>
  9. #include <OpenColorIO/OpenColorIO.h>
  10. namespace OCIO = OCIO_NAMESPACE;
  11. #include "apputils/argparse.h"
  12. #include "ocioicc.h"
  13. static std::string outputfile;
  14. static int
  15. parse_end_args(int argc, const char *argv[])
  16. {
  17. if(argc>0)
  18. {
  19. outputfile = argv[0];
  20. }
  21. return 0;
  22. }
  23. OCIO::GroupTransformRcPtr
  24. parse_luts(int argc, const char *argv[]);
  25. int main (int argc, const char* argv[])
  26. {
  27. bool help = false;
  28. int cubesize = -1;
  29. int shapersize = -1; // cubsize^2
  30. std::string format;
  31. std::string inputconfig;
  32. std::string inputspace;
  33. std::string shaperspace;
  34. std::string looks;
  35. std::string outputspace;
  36. bool usestdout = false;
  37. bool verbose = false;
  38. int whitepointtemp = 6505;
  39. std::string displayicc;
  40. std::string description;
  41. std::string copyright = "No copyright. Use freely.";
  42. // What are the allowed baker output formats?
  43. std::ostringstream formats;
  44. formats << "the LUT format to bake: ";
  45. for(int i=0; i<OCIO::Baker::getNumFormats(); ++i)
  46. {
  47. if(i!=0) formats << ", ";
  48. formats << OCIO::Baker::getFormatNameByIndex(i);
  49. formats << " (." << OCIO::Baker::getFormatExtensionByIndex(i) << ")";
  50. }
  51. formats << ", icc (.icc)";
  52. std::string formatstr = formats.str();
  53. std::string dummystr;
  54. float dummyf1, dummyf2, dummyf3;
  55. ArgParse ap;
  56. ap.options("ociobakelut -- create a new LUT or ICC profile from an OCIO config or LUT file(s)\n\n"
  57. "usage: ociobakelut [options] <OUTPUTFILE.LUT>\n\n"
  58. "example: ociobakelut --inputspace lg10 --outputspace srgb8 --format flame lg_to_srgb.3dl\n"
  59. "example: ociobakelut --lut filmlut.3dl --lut calibration.3dl --format flame display.3dl\n"
  60. "example: ociobakelut --cccid 0 --lut cdlgrade.ccc --lut calibration.3dl --format flame graded_display.3dl\n"
  61. "example: ociobakelut --lut look.3dl --offset 0.01 -0.02 0.03 --lut display.3dl --format flame display_with_look.3dl\n"
  62. "example: ociobakelut --inputspace lg10 --outputspace srgb8 --format icc ~/Library/ColorSync/Profiles/test.icc\n"
  63. "example: ociobakelut --lut filmlut.3dl --lut calibration.3dl --format icc ~/Library/ColorSync/Profiles/test.icc\n\n",
  64. "%*", parse_end_args, "",
  65. "<SEPARATOR>", "Using Existing OCIO Configurations",
  66. "--inputspace %s", &inputspace, "Input OCIO ColorSpace (or Role)",
  67. "--outputspace %s", &outputspace, "Output OCIO ColorSpace (or Role)",
  68. "--shaperspace %s", &shaperspace, "the OCIO ColorSpace or Role, for the shaper",
  69. "--looks %s", &looks, "the OCIO looks to apply",
  70. "--iconfig %s", &inputconfig, "Input .ocio configuration file (default: $OCIO)\n",
  71. "<SEPARATOR>", "Config-Free LUT Baking",
  72. "<SEPARATOR>", " (all options can be specified multiple times, each is applied in order)",
  73. "--cccid %s", &dummystr, "Specify a CCCId for any following LUTs",
  74. "--lut %s", &dummystr, "Specify a LUT (forward direction)",
  75. "--invlut %s", &dummystr, "Specify a LUT (inverse direction)",
  76. "--slope %f %f %f", &dummyf1, &dummyf2, &dummyf3, "slope",
  77. "--offset %f %f %f", &dummyf1, &dummyf2, &dummyf3, "offset (float)",
  78. "--offset10 %f %f %f", &dummyf1, &dummyf2, &dummyf3, "offset (10-bit)",
  79. "--power %f %f %f", &dummyf1, &dummyf2, &dummyf3, "power",
  80. "--sat %f", &dummyf1, "saturation (ASC-CDL luma coefficients)\n",
  81. "<SEPARATOR>", "Baking Options",
  82. "--format %s", &format, formatstr.c_str(),
  83. "--shapersize %d", &shapersize, "size of the shaper (default: format specific)",
  84. "--cubesize %d", &cubesize, "size of the cube (default: format specific)",
  85. "--stdout", &usestdout, "Write to stdout (rather than file)",
  86. "--v", &verbose, "Verbose",
  87. "--help", &help, "Print help message\n",
  88. "<SEPARATOR>", "ICC Options",
  89. //"--cubesize %d", &cubesize, "size of the ICC CLUT cube (default: 32)",
  90. "--whitepoint %d", &whitepointtemp, "whitepoint for the profile (default: 6505)",
  91. "--displayicc %s", &displayicc , "an ICC profile which matches the OCIO profiles target display",
  92. "--description %s", &description , "a meaningful description, this will show up in UI like photoshop (defaults to \"filename.icc\")",
  93. "--copyright %s", &copyright , "a copyright field added in the file (default: \"No copyright. Use freely.\")\n",
  94. // TODO: add --metadata option
  95. NULL);
  96. if (ap.parse(argc, argv) < 0)
  97. {
  98. std::cout << ap.geterror() << std::endl;
  99. ap.usage();
  100. std::cout << "\n";
  101. return 1;
  102. }
  103. if (help || (argc == 1 ))
  104. {
  105. ap.usage();
  106. std::cout << "\n";
  107. return 1;
  108. }
  109. // If we're printing to stdout, disable verbose printouts
  110. if(usestdout)
  111. {
  112. verbose = false;
  113. }
  114. // Create the OCIO processor for the specified transform.
  115. OCIO::ConstConfigRcPtr config;
  116. OCIO::GroupTransformRcPtr groupTransform;
  117. try
  118. {
  119. groupTransform = parse_luts(argc, argv);
  120. }
  121. catch(const OCIO::Exception & e)
  122. {
  123. std::cerr << "\nERROR: " << e.what() << std::endl;
  124. std::cerr << "See --help for more info." << std::endl;
  125. return 1;
  126. }
  127. catch(...)
  128. {
  129. std::cerr << "\nERROR: An unknown error occurred in parse_luts" << std::endl;
  130. std::cerr << "See --help for more info." << std::endl;
  131. return 1;
  132. }
  133. if(!groupTransform)
  134. {
  135. std::cerr << "\nERROR: parse_luts returned null transform" << std::endl;
  136. std::cerr << "See --help for more info." << std::endl;
  137. return 1;
  138. }
  139. // If --luts have been specified, synthesize a new (temporary) configuration
  140. // with the transformation embedded in a colorspace.
  141. if(groupTransform->getNumTransforms() > 0)
  142. {
  143. if(!inputspace.empty())
  144. {
  145. std::cerr << "\nERROR: --inputspace is not allowed when using --lut\n\n";
  146. std::cerr << "See --help for more info." << std::endl;
  147. return 1;
  148. }
  149. if(!outputspace.empty())
  150. {
  151. std::cerr << "\nERROR: --outputspace is not allowed when using --lut\n\n";
  152. std::cerr << "See --help for more info." << std::endl;
  153. return 1;
  154. }
  155. if(!looks.empty())
  156. {
  157. std::cerr << "\nERROR: --looks is not allowed when using --lut\n\n";
  158. std::cerr << "See --help for more info." << std::endl;
  159. return 1;
  160. }
  161. if(!shaperspace.empty())
  162. {
  163. std::cerr << "\nERROR: --shaperspace is not allowed when using --lut\n\n";
  164. std::cerr << "See --help for more info." << std::endl;
  165. return 1;
  166. }
  167. OCIO::ConfigRcPtr editableConfig = OCIO::Config::Create();
  168. OCIO::ColorSpaceRcPtr inputColorSpace = OCIO::ColorSpace::Create();
  169. inputspace = "RawInput";
  170. inputColorSpace->setName(inputspace.c_str());
  171. editableConfig->addColorSpace(inputColorSpace);
  172. OCIO::ColorSpaceRcPtr outputColorSpace = OCIO::ColorSpace::Create();
  173. outputspace = "ProcessedOutput";
  174. outputColorSpace->setName(outputspace.c_str());
  175. outputColorSpace->setTransform(groupTransform,
  176. OCIO::COLORSPACE_DIR_FROM_REFERENCE);
  177. if(verbose)
  178. {
  179. std::cout << "[OpenColorIO DEBUG]: Specified Transform:";
  180. std::cout << *groupTransform;
  181. std::cout << "\n";
  182. }
  183. editableConfig->addColorSpace(outputColorSpace);
  184. config = editableConfig;
  185. }
  186. else
  187. {
  188. if(inputspace.empty())
  189. {
  190. std::cerr << "\nERROR: You must specify the --inputspace.\n\n";
  191. std::cerr << "See --help for more info." << std::endl;
  192. return 1;
  193. }
  194. if(outputspace.empty())
  195. {
  196. std::cerr << "\nERROR: You must specify the --outputspace.\n\n";
  197. std::cerr << "See --help for more info." << std::endl;
  198. return 1;
  199. }
  200. if(format.empty())
  201. {
  202. std::cerr << "\nERROR: You must specify the LUT format using --format.\n\n";
  203. std::cerr << "See --help for more info." << std::endl;
  204. return 1;
  205. }
  206. if(!inputconfig.empty())
  207. {
  208. if(!usestdout && verbose)
  209. std::cout << "[OpenColorIO INFO]: Loading " << inputconfig << std::endl;
  210. config = OCIO::Config::CreateFromFile(inputconfig.c_str());
  211. }
  212. else if(OCIO::GetEnvVariable("OCIO"))
  213. {
  214. if(!usestdout && verbose)
  215. {
  216. std::cout << "[OpenColorIO INFO]: Loading $OCIO "
  217. << OCIO::GetEnvVariable("OCIO") << std::endl;
  218. }
  219. config = OCIO::Config::CreateFromEnv();
  220. }
  221. else
  222. {
  223. std::cerr << "ERROR: You must specify an input OCIO configuration ";
  224. std::cerr << "(either with --iconfig or $OCIO).\n\n";
  225. ap.usage ();
  226. return 1;
  227. }
  228. }
  229. if(outputfile.empty() && !usestdout)
  230. {
  231. std::cerr << "\nERROR: You must specify the outputfile or --stdout.\n\n";
  232. std::cerr << "See --help for more info." << std::endl;
  233. return 1;
  234. }
  235. try
  236. {
  237. if(format == "icc")
  238. {
  239. if(description.empty())
  240. {
  241. description = outputfile;
  242. if(verbose)
  243. std::cout << "[OpenColorIO INFO]: \"--description\" set to default value of filename.icc: " << outputfile << "" << std::endl;
  244. }
  245. if(usestdout)
  246. {
  247. std::cerr << "\nERROR: --stdout not supported when writing ICC profiles.\n\n";
  248. std::cerr << "See --help for more info." << std::endl;
  249. return 1;
  250. }
  251. if(outputfile.empty())
  252. {
  253. std::cerr << "ERROR: you need to specify a output ICC path\n";
  254. std::cerr << "See --help for more info." << std::endl;
  255. return 1;
  256. }
  257. if(cubesize<2) cubesize = 32; // default
  258. OCIO::ConstCPUProcessorRcPtr processor;
  259. if (!looks.empty())
  260. {
  261. OCIO::LookTransformRcPtr transform =
  262. OCIO::LookTransform::Create();
  263. transform->setLooks(looks.c_str());
  264. transform->setSrc(inputspace.c_str());
  265. transform->setDst(outputspace.c_str());
  266. processor = config->getProcessor(transform,
  267. OCIO::TRANSFORM_DIR_FORWARD)->getDefaultCPUProcessor();
  268. }
  269. else
  270. {
  271. processor = config->getProcessor(inputspace.c_str(),
  272. outputspace.c_str())->getDefaultCPUProcessor();
  273. }
  274. SaveICCProfileToFile(outputfile,
  275. processor,
  276. cubesize,
  277. whitepointtemp,
  278. displayicc,
  279. description,
  280. copyright,
  281. verbose);
  282. }
  283. else
  284. {
  285. OCIO::BakerRcPtr baker = OCIO::Baker::Create();
  286. // setup the baker for our LUT type
  287. baker->setConfig(config);
  288. baker->setFormat(format.c_str());
  289. baker->setInputSpace(inputspace.c_str());
  290. baker->setShaperSpace(shaperspace.c_str());
  291. baker->setLooks(looks.c_str());
  292. baker->setTargetSpace(outputspace.c_str());
  293. if(shapersize!=-1) baker->setShaperSize(shapersize);
  294. if(cubesize!=-1) baker->setCubeSize(cubesize);
  295. // output LUT
  296. std::ostringstream output;
  297. if(!usestdout && verbose)
  298. std::cout << "[OpenColorIO INFO]: Baking '" << format << "' LUT" << std::endl;
  299. if(usestdout)
  300. {
  301. baker->bake(std::cout);
  302. }
  303. else
  304. {
  305. std::ofstream f(outputfile.c_str());
  306. if(f.fail())
  307. {
  308. std::cerr << "ERROR: Non-writable file path " << outputfile << " specified." << std::endl;
  309. return 1;
  310. }
  311. baker->bake(f);
  312. if(verbose)
  313. std::cout << "[OpenColorIO INFO]: Wrote '" << outputfile << "'" << std::endl;
  314. }
  315. }
  316. }
  317. catch(OCIO::Exception & exception)
  318. {
  319. std::cerr << "OCIO Error: " << exception.what() << std::endl;
  320. std::cerr << "See --help for more info." << std::endl;
  321. return 1;
  322. }
  323. catch (std::exception& exception)
  324. {
  325. std::cerr << "Error: " << exception.what() << "\n";
  326. std::cerr << "See --help for more info." << std::endl;
  327. return 1;
  328. }
  329. catch(...)
  330. {
  331. std::cerr << "Unknown OCIO error encountered." << std::endl;
  332. std::cerr << "See --help for more info." << std::endl;
  333. return 1;
  334. }
  335. return 0;
  336. }
  337. // TODO: Replace this dirty argument parsing code with a clean version
  338. // that leverages the same codepath for the standard arguments. If
  339. // the OIIO derived argparse does not suffice, options we may want to consider
  340. // include simpleopt, tclap, ultraopt
  341. // TODO: Use better input validation, instead of atof.
  342. // If too few arguments are provides for scale (let's say only two) and
  343. // the following argument is the start of another flag (let's say "--invlut")
  344. // then atof() will likely try to convert "--invlut" to its double equivalent,
  345. // resulting in an invalid (or at least undesired) scale value.
  346. OCIO::GroupTransformRcPtr
  347. parse_luts(int argc, const char *argv[])
  348. {
  349. OCIO::GroupTransformRcPtr groupTransform = OCIO::GroupTransform::Create();
  350. const char *lastCCCId = NULL; // Ugly to use this but using GroupTransform::getTransform()
  351. // returns a const object so we must set this
  352. // prior to using --lut for now.
  353. for(int i=0; i<argc; ++i)
  354. {
  355. std::string arg(argv[i]);
  356. if(arg == "--lut" || arg == "-lut")
  357. {
  358. if(i+1>=argc)
  359. {
  360. throw OCIO::Exception("Error parsing --lut. Invalid num args");
  361. }
  362. OCIO::FileTransformRcPtr t = OCIO::FileTransform::Create();
  363. t->setSrc(argv[i+1]);
  364. t->setInterpolation(OCIO::INTERP_BEST);
  365. if (lastCCCId)
  366. {
  367. t->setCCCId(lastCCCId);
  368. }
  369. groupTransform->appendTransform(t);
  370. i += 1;
  371. }
  372. else if(arg == "--cccid" || arg == "-cccid")
  373. {
  374. if(i+1>=argc)
  375. {
  376. throw OCIO::Exception("Error parsing --cccid. Invalid num args");
  377. }
  378. lastCCCId = argv[i+1];
  379. i += 1;
  380. }
  381. else if(arg == "--invlut" || arg == "-invlut")
  382. {
  383. if(i+1>=argc)
  384. {
  385. throw OCIO::Exception("Error parsing --invlut. Invalid num args");
  386. }
  387. OCIO::FileTransformRcPtr t = OCIO::FileTransform::Create();
  388. t->setSrc(argv[i+1]);
  389. t->setInterpolation(OCIO::INTERP_BEST);
  390. t->setDirection(OCIO::TRANSFORM_DIR_INVERSE);
  391. groupTransform->appendTransform(t);
  392. i += 1;
  393. }
  394. else if(arg == "--slope" || arg == "-slope")
  395. {
  396. if(i+3>=argc)
  397. {
  398. throw OCIO::Exception("Error parsing --slope. Invalid num args");
  399. }
  400. OCIO::CDLTransformRcPtr t = OCIO::CDLTransform::Create();
  401. double scale[3];
  402. scale[0] = atof(argv[i+1]);
  403. scale[1] = atof(argv[i+2]);
  404. scale[2] = atof(argv[i+3]);
  405. t->setSlope(scale);
  406. groupTransform->appendTransform(t);
  407. i += 3;
  408. }
  409. else if(arg == "--offset" || arg == "-offset")
  410. {
  411. if(i+3>=argc)
  412. {
  413. throw OCIO::Exception("Error parsing --offset. Invalid num args");
  414. }
  415. OCIO::CDLTransformRcPtr t = OCIO::CDLTransform::Create();
  416. double offset[3];
  417. offset[0] = atof(argv[i+1]);
  418. offset[1] = atof(argv[i+2]);
  419. offset[2] = atof(argv[i+3]);
  420. t->setOffset(offset);
  421. groupTransform->appendTransform(t);
  422. i += 3;
  423. }
  424. else if(arg == "--offset10" || arg == "-offset10")
  425. {
  426. if(i+3>=argc)
  427. {
  428. throw OCIO::Exception("Error parsing --offset10. Invalid num args");
  429. }
  430. OCIO::CDLTransformRcPtr t = OCIO::CDLTransform::Create();
  431. double offset[3];
  432. offset[0] = atof(argv[i+1]) / 1023.0;
  433. offset[1] = atof(argv[i+2]) / 1023.0;
  434. offset[2] = atof(argv[i+3]) / 1023.0;
  435. t->setOffset(offset);
  436. groupTransform->appendTransform(t);
  437. i += 3;
  438. }
  439. else if(arg == "--power" || arg == "-power")
  440. {
  441. if(i+3>=argc)
  442. {
  443. throw OCIO::Exception("Error parsing --power. Invalid num args");
  444. }
  445. OCIO::CDLTransformRcPtr t = OCIO::CDLTransform::Create();
  446. double power[3];
  447. power[0] = atof(argv[i+1]);
  448. power[1] = atof(argv[i+2]);
  449. power[2] = atof(argv[i+3]);
  450. t->setPower(power);
  451. groupTransform->appendTransform(t);
  452. i += 3;
  453. }
  454. else if(arg == "--sat" || arg == "-sat")
  455. {
  456. if(i+1>=argc)
  457. {
  458. throw OCIO::Exception("Error parsing --sat. Invalid num args");
  459. }
  460. OCIO::CDLTransformRcPtr t = OCIO::CDLTransform::Create();
  461. t->setSat(atof(argv[i+1]));
  462. groupTransform->appendTransform(t);
  463. i += 1;
  464. }
  465. }
  466. return groupTransform;
  467. }