/src/apps/ocioconvert/main.cpp

http://github.com/imageworks/OpenColorIO · C++ · 699 lines · 578 code · 96 blank · 25 comment · 88 complexity · 0ee7fb62e83220117e0592d252a5ae5e MD5 · raw file

  1. // SPDX-License-Identifier: BSD-3-Clause
  2. // Copyright Contributors to the OpenColorIO Project.
  3. #include <chrono>
  4. #include <cstdlib>
  5. #include <iostream>
  6. #include <sstream>
  7. #include <utility>
  8. #include <vector>
  9. #include <OpenColorIO/OpenColorIO.h>
  10. namespace OCIO = OCIO_NAMESPACE;
  11. #include <OpenImageIO/imageio.h>
  12. #include <OpenImageIO/typedesc.h>
  13. #if (OIIO_VERSION < 10100)
  14. namespace OIIO = OIIO_NAMESPACE;
  15. #endif
  16. #include "apputils/argparse.h"
  17. #ifdef OCIO_GPU_ENABLED
  18. #include "oglapp.h"
  19. #endif // OCIO_GPU_ENABLED
  20. #include "oiiohelpers.h"
  21. #include "OpenEXR/half.h"
  22. // Array of non OpenColorIO arguments.
  23. static std::vector<std::string> args;
  24. // Fill 'args' array with OpenColorIO arguments.
  25. static int
  26. parse_end_args(int argc, const char *argv[])
  27. {
  28. while(argc>0)
  29. {
  30. args.push_back(argv[0]);
  31. argc--;
  32. argv++;
  33. }
  34. return 0;
  35. }
  36. bool ParseNameValuePair(std::string& name, std::string& value,
  37. const std::string& input);
  38. bool StringToFloat(float * fval, const char * str);
  39. bool StringToInt(int * ival, const char * str);
  40. bool StringToVector(std::vector<int> * ivector, const char * str);
  41. int main(int argc, const char **argv)
  42. {
  43. ArgParse ap;
  44. std::vector<std::string> floatAttrs;
  45. std::vector<std::string> intAttrs;
  46. std::vector<std::string> stringAttrs;
  47. std::string keepChannels;
  48. bool croptofull = false;
  49. bool usegpu = false;
  50. bool usegpuLegacy = false;
  51. bool outputgpuInfo = false;
  52. bool verbose = false;
  53. bool useLut = false;
  54. bool useDisplayView = false;
  55. ap.options("ocioconvert -- apply colorspace transform to an image \n\n"
  56. "usage: ocioconvert [options] inputimage inputcolorspace outputimage outputcolorspace\n"
  57. " or: ocioconvert [options] --lut lutfile inputimage outputimage\n"
  58. " or: ocioconvert [options] --view inputimage inputcolorspace outputimage displayname viewname\n\n",
  59. "%*", parse_end_args, "",
  60. "<SEPARATOR>", "Options:",
  61. "--lut", &useLut, "Convert using a LUT rather than a config file",
  62. "--view", &useDisplayView, "Convert to a (display,view) pair rather than to "
  63. "an output color space",
  64. "--gpu", &usegpu, "Use GPU color processing instead of CPU (CPU is the default)",
  65. "--gpulegacy", &usegpuLegacy, "Use the legacy (i.e. baked) GPU color processing "
  66. "instead of the CPU one (--gpu is ignored)",
  67. "--gpuinfo", &outputgpuInfo, "Output the OCIO shader program",
  68. "--v", &verbose, "Display general information",
  69. "<SEPARATOR>", "\nOpenImageIO options:",
  70. "--float-attribute %L", &floatAttrs, "\"name=float\" pair defining OIIO float attribute "
  71. "for outputimage",
  72. "--int-attribute %L", &intAttrs, "\"name=int\" pair defining OIIO int attribute "
  73. "for outputimage",
  74. "--string-attribute %L", &stringAttrs, "\"name=string\" pair defining OIIO string attribute "
  75. "for outputimage",
  76. "--croptofull", &croptofull, "Crop or pad to make pixel data region match the "
  77. "\"full\" region",
  78. "--ch %s", &keepChannels, "Select channels (e.g., \"2,3,4\")",
  79. NULL
  80. );
  81. if (ap.parse (argc, argv) < 0)
  82. {
  83. std::cerr << ap.geterror() << std::endl;
  84. ap.usage ();
  85. exit(1);
  86. }
  87. #ifndef OCIO_GPU_ENABLED
  88. if (usegpu || outputgpuInfo || usegpuLegacy)
  89. {
  90. std::cerr << "Compiled without OpenGL support, GPU options are not available.";
  91. std::cerr << std::endl;
  92. exit(1);
  93. }
  94. #endif // OCIO_GPU_ENABLED
  95. const char * inputimage = nullptr;
  96. const char * inputcolorspace = nullptr;
  97. const char * outputimage = nullptr;
  98. const char * outputcolorspace = nullptr;
  99. const char * lutFile = nullptr;
  100. const char * display = nullptr;
  101. const char * view = nullptr;
  102. if (!useLut && !useDisplayView)
  103. {
  104. if (args.size() != 4)
  105. {
  106. std::cerr << "ERROR: Expecting 4 arguments, found " << args.size() << std::endl;
  107. ap.usage();
  108. exit(1);
  109. }
  110. inputimage = args[0].c_str();
  111. inputcolorspace = args[1].c_str();
  112. outputimage = args[2].c_str();
  113. outputcolorspace = args[3].c_str();
  114. }
  115. else if (useLut && useDisplayView)
  116. {
  117. std::cerr << "ERROR: Options lut & view can't be used at the same time." << std::endl;
  118. ap.usage();
  119. exit(1);
  120. }
  121. else if (useLut)
  122. {
  123. if (args.size() != 3)
  124. {
  125. std::cerr << "ERROR: Expecting 3 arguments for --lut option, found "
  126. << args.size() << std::endl;
  127. ap.usage();
  128. exit(1);
  129. }
  130. lutFile = args[0].c_str();
  131. inputimage = args[1].c_str();
  132. outputimage = args[2].c_str();
  133. }
  134. else if (useDisplayView)
  135. {
  136. if (args.size() != 5)
  137. {
  138. std::cerr << "ERROR: Expecting 5 arguments for --view option, found "
  139. << args.size() << std::endl;
  140. ap.usage();
  141. exit(1);
  142. }
  143. inputimage = args[0].c_str();
  144. inputcolorspace = args[1].c_str();
  145. outputimage = args[2].c_str();
  146. display = args[3].c_str();
  147. view = args[4].c_str();
  148. }
  149. if(verbose)
  150. {
  151. std::cout << std::endl;
  152. std::cout << "OIIO Version: " << OIIO_VERSION_STRING << std::endl;
  153. std::cout << "OCIO Version: " << OCIO::GetVersion() << std::endl;
  154. const char * env = getenv("OCIO");
  155. if(env && *env)
  156. {
  157. try
  158. {
  159. std::cout << std::endl;
  160. std::cout << "OCIO Configuration: '" << env << "'" << std::endl;
  161. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  162. std::cout << "OCIO search_path: " << config->getSearchPath() << std::endl;
  163. }
  164. catch (const OCIO::Exception & e)
  165. {
  166. std::cout << "ERROR loading config file: " << e.what() << std::endl;
  167. exit(1);
  168. }
  169. catch(...)
  170. {
  171. std::cerr << "ERROR loading the config file: '" << env << "'";
  172. exit(1);
  173. }
  174. }
  175. }
  176. if (usegpuLegacy)
  177. {
  178. std::cout << std::endl;
  179. std::cout << "Using legacy OCIO v1 GPU color processing." << std::endl;
  180. }
  181. else if (usegpu)
  182. {
  183. std::cout << std::endl;
  184. std::cout << "Using GPU color processing." << std::endl;
  185. }
  186. OIIO::ImageSpec spec;
  187. OCIO::ImgBuffer img;
  188. int imgwidth = 0;
  189. int imgheight = 0;
  190. int components = 0;
  191. // Load the image.
  192. std::cout << std::endl;
  193. std::cout << "Loading " << inputimage << std::endl;
  194. try
  195. {
  196. #if OIIO_VERSION < 10903
  197. OIIO::ImageInput* f = OIIO::ImageInput::create(inputimage);
  198. #else
  199. auto f = OIIO::ImageInput::create(inputimage);
  200. #endif
  201. if(!f)
  202. {
  203. std::cerr << "ERROR: Could not create image input." << std::endl;
  204. exit(1);
  205. }
  206. f->open(inputimage, spec);
  207. std::string error = f->geterror();
  208. if(!error.empty())
  209. {
  210. std::cerr << "ERROR: Could not load image: " << error << std::endl;
  211. exit(1);
  212. }
  213. OCIO::PrintImageSpec(spec, verbose);
  214. imgwidth = spec.width;
  215. imgheight = spec.height;
  216. components = spec.nchannels;
  217. if (usegpu || usegpuLegacy)
  218. {
  219. spec.format = OIIO::TypeDesc::FLOAT;
  220. img.allocate(spec);
  221. const bool ok = f->read_image(spec.format, img.getBuffer());
  222. if(!ok)
  223. {
  224. std::cerr << "ERROR: Reading \"" << inputimage << "\" failed with: "
  225. << f->geterror() << std::endl;
  226. exit(1);
  227. }
  228. if(croptofull)
  229. {
  230. std::cerr << "ERROR: Crop disabled in GPU mode" << std::endl;
  231. exit(1);
  232. }
  233. }
  234. else
  235. {
  236. img.allocate(spec);
  237. const bool ok = f->read_image(spec.format, img.getBuffer());
  238. if(!ok)
  239. {
  240. std::cerr << "ERROR: Reading \"" << inputimage << "\" failed with: "
  241. << f->geterror() << std::endl;
  242. exit(1);
  243. }
  244. }
  245. #if OIIO_VERSION < 10903
  246. OIIO::ImageInput::destroy(f);
  247. #endif
  248. std::vector<int> kchannels;
  249. //parse --ch argument.
  250. if (keepChannels != "" && !StringToVector(&kchannels, keepChannels.c_str()))
  251. {
  252. std::cerr << "ERROR: --ch: '" << keepChannels
  253. << "' should be comma-seperated integers" << std::endl;
  254. exit(1);
  255. }
  256. //if kchannels not specified, then keep all channels.
  257. if (kchannels.size() == 0)
  258. {
  259. kchannels.resize(components);
  260. for (int channel=0; channel < components; channel++)
  261. {
  262. kchannels[channel] = channel;
  263. }
  264. }
  265. if (croptofull)
  266. {
  267. imgwidth = spec.full_width;
  268. imgheight = spec.full_height;
  269. std::cout << "cropping to " << imgwidth
  270. << "x" << imgheight << std::endl;
  271. }
  272. if (croptofull || (int)kchannels.size() < spec.nchannels)
  273. {
  274. // Redefine the spec so it matches the new bounding box.
  275. OIIO::ImageSpec croppedSpec = spec;
  276. croppedSpec.x = 0;
  277. croppedSpec.y = 0;
  278. croppedSpec.height = imgheight;
  279. croppedSpec.width = imgwidth;
  280. croppedSpec.nchannels = (int)(kchannels.size());
  281. OCIO::ImgBuffer croppedImg(croppedSpec);
  282. void * croppedBuf = croppedImg.getBuffer();
  283. void * imgBuf = img.getBuffer();
  284. // crop down bounding box and ditch all but n channels.
  285. // img is a flattened 3 dimensional matrix heightxwidthxchannels.
  286. // fill croppedimg with only the needed pixels.
  287. for (int y=0 ; y < spec.height ; y++)
  288. {
  289. for (int x=0 ; x < spec.width; x++)
  290. {
  291. for (int k=0; k < (int)kchannels.size(); k++)
  292. {
  293. int channel = kchannels[k];
  294. int current_pixel_y = y + spec.y;
  295. int current_pixel_x = x + spec.x;
  296. if (current_pixel_y >= 0 &&
  297. current_pixel_x >= 0 &&
  298. current_pixel_y < imgheight &&
  299. current_pixel_x < imgwidth)
  300. {
  301. const size_t imgIdx = (y * spec.width * components)
  302. + (x * components) + channel;
  303. const size_t cropIdx = (current_pixel_y * imgwidth * kchannels.size())
  304. + (current_pixel_x * kchannels.size())
  305. + channel;
  306. if(spec.format==OIIO::TypeDesc::FLOAT)
  307. {
  308. ((float*)croppedBuf)[cropIdx] = ((float*)imgBuf)[imgIdx];
  309. }
  310. else if(spec.format==OIIO::TypeDesc::HALF)
  311. {
  312. ((half*)croppedBuf)[cropIdx] = ((half*)imgBuf)[imgIdx];
  313. }
  314. else if(spec.format==OIIO::TypeDesc::UINT16)
  315. {
  316. ((uint16_t*)croppedBuf)[cropIdx] = ((uint16_t*)imgBuf)[imgIdx];
  317. }
  318. else if(spec.format==OIIO::TypeDesc::UINT8)
  319. {
  320. ((uint8_t*)croppedBuf)[cropIdx] = ((uint8_t*)imgBuf)[imgIdx];
  321. }
  322. else
  323. {
  324. std::cerr << "ERROR: Unsupported image type: "
  325. << spec.format << std::endl;
  326. exit(1);
  327. }
  328. }
  329. }
  330. }
  331. }
  332. components = (int)(kchannels.size());
  333. img = std::move(croppedImg);
  334. }
  335. }
  336. catch(...)
  337. {
  338. std::cerr << "ERROR: Loading file failed" << std::endl;
  339. exit(1);
  340. }
  341. #ifdef OCIO_GPU_ENABLED
  342. // Initialize GPU.
  343. OCIO::OglAppRcPtr oglApp;
  344. if (usegpu || usegpuLegacy)
  345. {
  346. OCIO::OglApp::Components comp = OCIO::OglApp::COMPONENTS_RGBA;
  347. if (components == 4)
  348. {
  349. comp = OCIO::OglApp::COMPONENTS_RGBA;
  350. }
  351. else if (components == 3)
  352. {
  353. comp = OCIO::OglApp::COMPONENTS_RGB;
  354. }
  355. else
  356. {
  357. std::cerr << "Cannot convert image with " << components << " components." << std::endl;
  358. exit(1);
  359. }
  360. try
  361. {
  362. oglApp = std::make_shared<OCIO::OglApp>("ocioconvert", 256, 20);
  363. }
  364. catch (const OCIO::Exception & e)
  365. {
  366. std::cerr << std::endl << e.what() << std::endl;
  367. exit(1);
  368. }
  369. if (outputgpuInfo)
  370. {
  371. oglApp->printGLInfo();
  372. }
  373. oglApp->initImage(imgwidth, imgheight, comp, (float *)img.getBuffer());
  374. oglApp->createGLBuffers();
  375. }
  376. #endif // OCIO_GPU_ENABLED
  377. // Process the image.
  378. try
  379. {
  380. // Load the current config.
  381. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  382. // Get the processor.
  383. OCIO::ConstProcessorRcPtr processor;
  384. try
  385. {
  386. if (useLut)
  387. {
  388. // Create the OCIO processor for the specified transform.
  389. OCIO::FileTransformRcPtr t = OCIO::FileTransform::Create();
  390. t->setSrc(lutFile);
  391. t->setInterpolation(OCIO::INTERP_BEST);
  392. processor = config->getProcessor(t);
  393. }
  394. else if (useDisplayView)
  395. {
  396. OCIO::DisplayTransformRcPtr t = OCIO::DisplayTransform::Create();
  397. t->setInputColorSpaceName(inputcolorspace);
  398. t->setDisplay(display);
  399. t->setView(view);
  400. processor = config->getProcessor(t);
  401. }
  402. else
  403. {
  404. processor = config->getProcessor(inputcolorspace, outputcolorspace);
  405. }
  406. }
  407. catch (const OCIO::Exception & e)
  408. {
  409. std::cout << "ERROR: OCIO failed with: " << e.what() << std::endl;
  410. exit(1);
  411. }
  412. catch (...)
  413. {
  414. std::cout << "ERROR: Creating processor unknown failure" << std::endl;
  415. exit(1);
  416. }
  417. #ifdef OCIO_GPU_ENABLED
  418. if (usegpu || usegpuLegacy)
  419. {
  420. // Get the GPU shader program from the processor and set oglApp to use it.
  421. OCIO::GpuShaderDescRcPtr shaderDesc;
  422. if (usegpuLegacy)
  423. {
  424. shaderDesc = OCIO::GpuShaderDesc::CreateLegacyShaderDesc(32);
  425. }
  426. else
  427. {
  428. shaderDesc = OCIO::GpuShaderDesc::CreateShaderDesc();
  429. }
  430. shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3);
  431. processor->getDefaultGPUProcessor()->extractGpuShaderInfo(shaderDesc);
  432. oglApp->setShader(shaderDesc);
  433. oglApp->reshape(imgwidth, imgheight);
  434. oglApp->redisplay();
  435. oglApp->readImage((float *)img.getBuffer());
  436. }
  437. else
  438. #endif // OCIO_GPU_ENABLED
  439. {
  440. const OCIO::BitDepth bitDepth = OCIO::GetBitDepth(spec);
  441. OCIO::ConstCPUProcessorRcPtr cpuProcessor
  442. = processor->getOptimizedCPUProcessor(bitDepth, bitDepth,
  443. OCIO::OPTIMIZATION_DEFAULT);
  444. const std::chrono::high_resolution_clock::time_point start
  445. = std::chrono::high_resolution_clock::now();
  446. OCIO::ImageDescRcPtr imgDesc = OCIO::CreateImageDesc(spec, img);
  447. cpuProcessor->apply(*imgDesc);
  448. if(verbose)
  449. {
  450. const std::chrono::high_resolution_clock::time_point end
  451. = std::chrono::high_resolution_clock::now();
  452. std::chrono::duration<float, std::milli> duration = end - start;
  453. std::cout << std::endl;
  454. std::cout << "CPU processing took: "
  455. << duration.count()
  456. << " ms" << std::endl;
  457. }
  458. }
  459. }
  460. catch(OCIO::Exception & exception)
  461. {
  462. std::cerr << "ERROR: OCIO failed with: " << exception.what() << std::endl;
  463. exit(1);
  464. }
  465. catch(...)
  466. {
  467. std::cerr << "ERROR: Unknown error processing the image" << std::endl;
  468. exit(1);
  469. }
  470. //
  471. // set the provided OpenImageIO attributes.
  472. //
  473. bool parseerror = false;
  474. for(unsigned int i=0; i<floatAttrs.size(); ++i)
  475. {
  476. std::string name, value;
  477. float fval = 0.0f;
  478. if(!ParseNameValuePair(name, value, floatAttrs[i]) ||
  479. !StringToFloat(&fval,value.c_str()))
  480. {
  481. std::cerr << "ERROR: Attribute string '" << floatAttrs[i]
  482. << "' should be in the form name=floatvalue" << std::endl;
  483. parseerror = true;
  484. continue;
  485. }
  486. spec.attribute(name, fval);
  487. }
  488. for(unsigned int i=0; i<intAttrs.size(); ++i)
  489. {
  490. std::string name, value;
  491. int ival = 0;
  492. if(!ParseNameValuePair(name, value, intAttrs[i]) ||
  493. !StringToInt(&ival,value.c_str()))
  494. {
  495. std::cerr << "ERROR: Attribute string '" << intAttrs[i]
  496. << "' should be in the form name=intvalue" << std::endl;
  497. parseerror = true;
  498. continue;
  499. }
  500. spec.attribute(name, ival);
  501. }
  502. for(unsigned int i=0; i<stringAttrs.size(); ++i)
  503. {
  504. std::string name, value;
  505. if(!ParseNameValuePair(name, value, stringAttrs[i]))
  506. {
  507. std::cerr << "ERROR: Attribute string '" << stringAttrs[i]
  508. << "' should be in the form name=value" << std::endl;
  509. parseerror = true;
  510. continue;
  511. }
  512. spec.attribute(name, value);
  513. }
  514. if(parseerror)
  515. {
  516. exit(1);
  517. }
  518. // Write out the result.
  519. try
  520. {
  521. #if OIIO_VERSION < 10903
  522. OIIO::ImageOutput* f = OIIO::ImageOutput::create(outputimage);
  523. #else
  524. auto f = OIIO::ImageOutput::create(outputimage);
  525. #endif
  526. if(!f)
  527. {
  528. std::cerr << "ERROR: Could not create output input" << std::endl;
  529. exit(1);
  530. }
  531. f->open(outputimage, spec);
  532. if(!f->write_image(spec.format, img.getBuffer()))
  533. {
  534. std::cerr << "ERROR: Writing \"" << outputimage << "\" failed with: "
  535. << f->geterror() << std::endl;
  536. exit(1);
  537. }
  538. f->close();
  539. #if OIIO_VERSION < 10903
  540. OIIO::ImageOutput::destroy(f);
  541. #endif
  542. }
  543. catch(...)
  544. {
  545. std::cerr << "ERROR: Writing file \"" << outputimage << "\"" << std::endl;
  546. exit(1);
  547. }
  548. std::cout << std::endl;
  549. std::cout << "Wrote " << outputimage << std::endl;
  550. return 0;
  551. }
  552. // Parse name=value parts.
  553. // return true on success.
  554. bool ParseNameValuePair(std::string& name,
  555. std::string& value,
  556. const std::string& input)
  557. {
  558. // split string into name=value.
  559. size_t pos = input.find('=');
  560. if(pos==std::string::npos) return false;
  561. name = input.substr(0,pos);
  562. value = input.substr(pos+1);
  563. return true;
  564. }
  565. // return true on success.
  566. bool StringToFloat(float * fval, const char * str)
  567. {
  568. if(!str) return false;
  569. std::istringstream inputStringstream(str);
  570. float x;
  571. if(!(inputStringstream >> x))
  572. {
  573. return false;
  574. }
  575. if(fval) *fval = x;
  576. return true;
  577. }
  578. bool StringToInt(int * ival, const char * str)
  579. {
  580. if(!str) return false;
  581. std::istringstream inputStringstream(str);
  582. int x;
  583. if(!(inputStringstream >> x))
  584. {
  585. return false;
  586. }
  587. if(ival) *ival = x;
  588. return true;
  589. }
  590. bool StringToVector(std::vector<int> * ivector, const char * str)
  591. {
  592. std::stringstream ss(str);
  593. int i;
  594. while (ss >> i)
  595. {
  596. ivector->push_back(i);
  597. if (ss.peek() == ',')
  598. {
  599. ss.ignore();
  600. }
  601. }
  602. return ivector->size() != 0;
  603. }