/src/apps/ociodisplay/main.cpp

http://github.com/imageworks/OpenColorIO · C++ · 729 lines · 616 code · 104 blank · 9 comment · 111 complexity · 1d18c0b269b461b2a51d4f5df9be3f30 MD5 · raw file

  1. // SPDX-License-Identifier: BSD-3-Clause
  2. // Copyright Contributors to the OpenColorIO Project.
  3. #include <array>
  4. #include <cstdlib>
  5. #include <cmath>
  6. #include <cstdio>
  7. #include <cstring>
  8. #include <iostream>
  9. #include <fstream>
  10. #include <sstream>
  11. #include <utility>
  12. #include <vector>
  13. #include <OpenColorIO/OpenColorIO.h>
  14. namespace OCIO = OCIO_NAMESPACE;
  15. #include <OpenImageIO/imageio.h>
  16. #include <OpenImageIO/typedesc.h>
  17. #ifdef __APPLE__
  18. #include <OpenGL/gl.h>
  19. #include <OpenGL/glext.h>
  20. #include <GLUT/glut.h>
  21. #elif _WIN32
  22. #include <GL/glew.h>
  23. #include <GL/glut.h>
  24. #else
  25. #include <GL/glew.h>
  26. #include <GL/gl.h>
  27. #include <GL/glut.h>
  28. #endif
  29. #include "glsl.h"
  30. #include "oglapp.h"
  31. bool g_verbose = false;
  32. bool g_gpulegacy = false;
  33. bool g_gpuinfo = false;
  34. std::string g_filename;
  35. float g_imageAspect;
  36. std::string g_inputColorSpace;
  37. std::string g_display;
  38. std::string g_transformName;
  39. std::string g_look;
  40. OCIO::OptimizationFlags g_optimization{ OCIO::OPTIMIZATION_DEFAULT };
  41. static const std::array<std::pair<const char*, OCIO::OptimizationFlags>, 5> OptmizationMenu = { {
  42. { "None", OCIO::OPTIMIZATION_NONE },
  43. { "Lossless", OCIO::OPTIMIZATION_LOSSLESS },
  44. { "Very good", OCIO::OPTIMIZATION_VERY_GOOD },
  45. { "Good", OCIO::OPTIMIZATION_GOOD },
  46. { "Draft", OCIO::OPTIMIZATION_DRAFT } } };
  47. float g_exposure_fstop{ 0.0f };
  48. float g_display_gamma{ 1.0f };
  49. int g_channelHot[4]{ 1, 1, 1, 1 }; // show rgb
  50. OCIO::OglAppRcPtr g_oglApp;
  51. void UpdateOCIOGLState();
  52. static void InitImageTexture(const char * filename)
  53. {
  54. std::vector<float> img;
  55. int texWidth = 512;
  56. int texHeight = 512;
  57. int components = 4;
  58. if(filename && *filename)
  59. {
  60. std::cout << "loading: " << filename << std::endl;
  61. try
  62. {
  63. #if OIIO_VERSION < 10903
  64. OIIO::ImageInput* f = OIIO::ImageInput::create(filename);
  65. #else
  66. auto f = OIIO::ImageInput::create(filename);
  67. #endif
  68. if(!f)
  69. {
  70. std::cerr << "Could not create image input." << std::endl;
  71. exit(1);
  72. }
  73. OIIO::ImageSpec spec;
  74. f->open(filename, spec);
  75. std::string error = f->geterror();
  76. if(!error.empty())
  77. {
  78. std::cerr << "Error loading image " << error << std::endl;
  79. exit(1);
  80. }
  81. texWidth = spec.width;
  82. texHeight = spec.height;
  83. components = spec.nchannels;
  84. img.resize(texWidth*texHeight*components);
  85. memset(&img[0], 0, texWidth*texHeight*components*sizeof(float));
  86. const bool ok = f->read_image(OIIO::TypeDesc::FLOAT, &img[0]);
  87. if(!ok)
  88. {
  89. std::cerr << "Error reading \"" << filename << "\" : " << f->geterror() << "\n";
  90. exit(1);
  91. }
  92. #if OIIO_VERSION < 10903
  93. OIIO::ImageInput::destroy(f);
  94. #endif
  95. }
  96. catch(...)
  97. {
  98. std::cerr << "Error loading file.";
  99. exit(1);
  100. }
  101. }
  102. // If no file is provided, use a default gradient texture
  103. else
  104. {
  105. std::cout << "No image specified, loading gradient." << std::endl;
  106. img.resize(texWidth*texHeight*components);
  107. memset(&img[0], 0, texWidth*texHeight*components*sizeof(float));
  108. for(int y=0; y<texHeight; ++y)
  109. {
  110. for(int x=0; x<texWidth; ++x)
  111. {
  112. float c = (float)x/((float)texWidth-1.0f);
  113. img[components*(texWidth*y+x) + 0] = c;
  114. img[components*(texWidth*y+x) + 1] = c;
  115. img[components*(texWidth*y+x) + 2] = c;
  116. img[components*(texWidth*y+x) + 3] = 1.0f;
  117. }
  118. }
  119. }
  120. OCIO::OglApp::Components comp = OCIO::OglApp::COMPONENTS_RGBA;
  121. if (components == 4)
  122. {
  123. comp = OCIO::OglApp::COMPONENTS_RGBA;
  124. }
  125. else if (components == 3)
  126. {
  127. comp = OCIO::OglApp::COMPONENTS_RGB;
  128. }
  129. else
  130. {
  131. std::cerr << "Cannot load image with " << components << " components." << std::endl;
  132. exit(1);
  133. }
  134. g_imageAspect = 1.0;
  135. if(texHeight!=0)
  136. {
  137. g_imageAspect = (float) texWidth / (float) texHeight;
  138. }
  139. if (g_oglApp)
  140. {
  141. g_oglApp->initImage(texWidth, texHeight, comp, &img[0]);
  142. }
  143. }
  144. void InitOCIO(const char * filename)
  145. {
  146. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  147. g_display = config->getDefaultDisplay();
  148. g_transformName = config->getDefaultView(g_display.c_str());
  149. g_look = config->getDisplayLooks(g_display.c_str(), g_transformName.c_str());
  150. g_inputColorSpace = OCIO::ROLE_SCENE_LINEAR;
  151. if(filename && *filename)
  152. {
  153. std::string cs = config->parseColorSpaceFromString(filename);
  154. if(!cs.empty())
  155. {
  156. g_inputColorSpace = cs;
  157. std::cout << "colorspace: " << cs << std::endl;
  158. }
  159. else
  160. {
  161. std::cout << "colorspace: " << g_inputColorSpace
  162. << " \t(could not determine from filename, using default)"
  163. << std::endl;
  164. }
  165. }
  166. }
  167. void Redisplay(void)
  168. {
  169. if (g_oglApp)
  170. {
  171. g_oglApp->redisplay();
  172. }
  173. }
  174. static void Reshape(int width, int height)
  175. {
  176. if (g_oglApp)
  177. {
  178. g_oglApp->reshape(width, height);
  179. }
  180. }
  181. static void CleanUp(void)
  182. {
  183. g_oglApp.reset();
  184. }
  185. static void Key(unsigned char key, int /*x*/, int /*y*/)
  186. {
  187. if(key == 'c' || key == 'C')
  188. {
  189. g_channelHot[0] = 1;
  190. g_channelHot[1] = 1;
  191. g_channelHot[2] = 1;
  192. g_channelHot[3] = 1;
  193. }
  194. else if(key == 'r' || key == 'R')
  195. {
  196. g_channelHot[0] = 1;
  197. g_channelHot[1] = 0;
  198. g_channelHot[2] = 0;
  199. g_channelHot[3] = 0;
  200. }
  201. else if(key == 'g' || key == 'G')
  202. {
  203. g_channelHot[0] = 0;
  204. g_channelHot[1] = 1;
  205. g_channelHot[2] = 0;
  206. g_channelHot[3] = 0;
  207. }
  208. else if(key == 'b' || key == 'B')
  209. {
  210. g_channelHot[0] = 0;
  211. g_channelHot[1] = 0;
  212. g_channelHot[2] = 1;
  213. g_channelHot[3] = 0;
  214. }
  215. else if(key == 'a' || key == 'A')
  216. {
  217. g_channelHot[0] = 0;
  218. g_channelHot[1] = 0;
  219. g_channelHot[2] = 0;
  220. g_channelHot[3] = 1;
  221. }
  222. else if(key == 'l' || key == 'L')
  223. {
  224. g_channelHot[0] = 1;
  225. g_channelHot[1] = 1;
  226. g_channelHot[2] = 1;
  227. g_channelHot[3] = 0;
  228. }
  229. else if(key == 27)
  230. {
  231. CleanUp();
  232. exit(0);
  233. }
  234. UpdateOCIOGLState();
  235. glutPostRedisplay();
  236. }
  237. static void SpecialKey(int key, int x, int y)
  238. {
  239. (void) x;
  240. (void) y;
  241. int mod = glutGetModifiers();
  242. if(key == GLUT_KEY_UP && (mod & GLUT_ACTIVE_CTRL))
  243. {
  244. g_exposure_fstop += 0.25f;
  245. }
  246. else if(key == GLUT_KEY_DOWN && (mod & GLUT_ACTIVE_CTRL))
  247. {
  248. g_exposure_fstop -= 0.25f;
  249. }
  250. else if(key == GLUT_KEY_HOME && (mod & GLUT_ACTIVE_CTRL))
  251. {
  252. g_exposure_fstop = 0.0f;
  253. g_display_gamma = 1.0f;
  254. }
  255. else if(key == GLUT_KEY_UP && (mod & GLUT_ACTIVE_ALT))
  256. {
  257. g_display_gamma *= 1.1f;
  258. }
  259. else if(key == GLUT_KEY_DOWN && (mod & GLUT_ACTIVE_ALT))
  260. {
  261. g_display_gamma /= 1.1f;
  262. }
  263. else if(key == GLUT_KEY_HOME && (mod & GLUT_ACTIVE_ALT))
  264. {
  265. g_exposure_fstop = 0.0f;
  266. g_display_gamma = 1.0f;
  267. }
  268. UpdateOCIOGLState();
  269. glutPostRedisplay();
  270. }
  271. void UpdateOCIOGLState()
  272. {
  273. if (!g_oglApp)
  274. {
  275. return;
  276. }
  277. // Step 0: Get the processor using any of the pipelines mentioned above.
  278. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  279. OCIO::DisplayTransformRcPtr transform = OCIO::DisplayTransform::Create();
  280. transform->setInputColorSpaceName( g_inputColorSpace.c_str() );
  281. transform->setDisplay( g_display.c_str() );
  282. transform->setView( g_transformName.c_str() );
  283. transform->setLooksOverride( g_look.c_str() );
  284. if(g_verbose)
  285. {
  286. std::cout << std::endl;
  287. std::cout << "Color transformation composed of:" << std::endl;
  288. std::cout << " Image ColorSpace is:\t" << g_inputColorSpace << std::endl;
  289. std::cout << " Transform is:\t\t" << g_transformName << std::endl;
  290. std::cout << " Device is:\t\t" << g_display << std::endl;
  291. std::cout << " Looks Override is:\t'" << g_look << "'" << std::endl;
  292. std::cout << " with:" << std::endl;
  293. std::cout << " exposure_fstop = " << g_exposure_fstop << std::endl;
  294. std::cout << " display_gamma = " << g_display_gamma << std::endl;
  295. std::cout << " channels = "
  296. << (g_channelHot[0] ? "R" : "")
  297. << (g_channelHot[1] ? "G" : "")
  298. << (g_channelHot[2] ? "B" : "")
  299. << (g_channelHot[3] ? "A" : "") << std::endl;
  300. for (const auto & opt : OptmizationMenu)
  301. {
  302. if (opt.second == g_optimization)
  303. {
  304. std::cout << std::endl << "Optimization: " << opt.first << std::endl;
  305. }
  306. }
  307. }
  308. // Add optional transforms to create a full-featured, "canonical" display pipeline
  309. // Fstop exposure control (in SCENE_LINEAR)
  310. {
  311. double gain = powf(2.0f, g_exposure_fstop);
  312. const double slope4f[] = { gain, gain, gain, gain };
  313. double m44[16];
  314. double offset4[4];
  315. OCIO::MatrixTransform::Scale(m44, offset4, slope4f);
  316. OCIO::MatrixTransformRcPtr mtx = OCIO::MatrixTransform::Create();
  317. mtx->setMatrix(m44);
  318. mtx->setOffset(offset4);
  319. transform->setLinearCC(mtx);
  320. }
  321. // Channel swizzling
  322. {
  323. double lumacoef[3];
  324. config->getDefaultLumaCoefs(lumacoef);
  325. double m44[16];
  326. double offset[4];
  327. OCIO::MatrixTransform::View(m44, offset, g_channelHot, lumacoef);
  328. OCIO::MatrixTransformRcPtr swizzle = OCIO::MatrixTransform::Create();
  329. swizzle->setMatrix(m44);
  330. swizzle->setOffset(offset);
  331. transform->setChannelView(swizzle);
  332. }
  333. // Post-display transform gamma
  334. {
  335. double exponent = 1.0/std::max(1e-6, static_cast<double>(g_display_gamma));
  336. const double exponent4f[4] = { exponent, exponent, exponent, exponent };
  337. OCIO::ExponentTransformRcPtr expTransform = OCIO::ExponentTransform::Create();
  338. expTransform->setValue(exponent4f);
  339. transform->setDisplayCC(expTransform);
  340. }
  341. OCIO::ConstProcessorRcPtr processor;
  342. try
  343. {
  344. processor = config->getProcessor(transform);
  345. }
  346. catch(OCIO::Exception & e)
  347. {
  348. std::cerr << e.what() << std::endl;
  349. return;
  350. }
  351. catch(...)
  352. {
  353. return;
  354. }
  355. // Set shader.
  356. OCIO::GpuShaderDescRcPtr shaderDesc;
  357. if (g_gpulegacy)
  358. {
  359. shaderDesc = OCIO::GpuShaderDesc::CreateLegacyShaderDesc(32);
  360. }
  361. else
  362. {
  363. shaderDesc = OCIO::GpuShaderDesc::CreateShaderDesc();
  364. }
  365. shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3);
  366. shaderDesc->setFunctionName("OCIODisplay");
  367. shaderDesc->setResourcePrefix("ocio_");
  368. processor->getOptimizedGPUProcessor(g_optimization)->extractGpuShaderInfo(shaderDesc);
  369. g_oglApp->setShader(shaderDesc);
  370. }
  371. void menuCallback(int /*id*/)
  372. {
  373. glutPostRedisplay();
  374. }
  375. void imageColorSpace_CB(int id)
  376. {
  377. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  378. const char * name = config->getColorSpaceNameByIndex(id);
  379. if(!name) return;
  380. g_inputColorSpace = name;
  381. UpdateOCIOGLState();
  382. glutPostRedisplay();
  383. }
  384. void displayDevice_CB(int id)
  385. {
  386. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  387. const char * display = config->getDisplay(id);
  388. if(!display) return;
  389. g_display = display;
  390. const char * csname = config->getDisplayColorSpaceName(g_display.c_str(), g_transformName.c_str());
  391. if(!csname)
  392. {
  393. g_transformName = config->getDefaultView(g_display.c_str());
  394. }
  395. g_look = config->getDisplayLooks(g_display.c_str(), g_transformName.c_str());
  396. UpdateOCIOGLState();
  397. glutPostRedisplay();
  398. }
  399. void transform_CB(int id)
  400. {
  401. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  402. const char * transform = config->getView(g_display.c_str(), id);
  403. if(!transform) return;
  404. g_transformName = transform;
  405. g_look = config->getDisplayLooks(g_display.c_str(), g_transformName.c_str());
  406. UpdateOCIOGLState();
  407. glutPostRedisplay();
  408. }
  409. void look_CB(int id)
  410. {
  411. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  412. const char * look = config->getLookNameByIndex(id);
  413. if(!look || !*look) return;
  414. g_look = look;
  415. UpdateOCIOGLState();
  416. glutPostRedisplay();
  417. }
  418. void optimization_CB(int id)
  419. {
  420. g_optimization = OptmizationMenu[id].second;
  421. UpdateOCIOGLState();
  422. glutPostRedisplay();
  423. }
  424. static void PopulateOCIOMenus()
  425. {
  426. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  427. int csMenuID = glutCreateMenu(imageColorSpace_CB);
  428. std::map<std::string, int> families;
  429. for(int i=0; i<config->getNumColorSpaces(); ++i)
  430. {
  431. const char * csName = config->getColorSpaceNameByIndex(i);
  432. if(csName && *csName)
  433. {
  434. OCIO::ConstColorSpaceRcPtr cs = config->getColorSpace(csName);
  435. if(cs)
  436. {
  437. const char * family = cs->getFamily();
  438. if(family && *family)
  439. {
  440. if(families.find(family)==families.end())
  441. {
  442. families[family] = glutCreateMenu(imageColorSpace_CB);
  443. glutAddMenuEntry(csName, i);
  444. glutSetMenu(csMenuID);
  445. glutAddSubMenu(family, families[family]);
  446. }
  447. else
  448. {
  449. glutSetMenu(families[family]);
  450. glutAddMenuEntry(csName, i);
  451. }
  452. }
  453. else
  454. {
  455. glutSetMenu(csMenuID);
  456. glutAddMenuEntry(csName, i);
  457. }
  458. }
  459. }
  460. }
  461. int deviceMenuID = glutCreateMenu(displayDevice_CB);
  462. for(int i=0; i<config->getNumDisplays(); ++i)
  463. {
  464. glutAddMenuEntry(config->getDisplay(i), i);
  465. }
  466. int transformMenuID = glutCreateMenu(transform_CB);
  467. const char * defaultDisplay = config->getDefaultDisplay();
  468. for(int i=0; i<config->getNumViews(defaultDisplay); ++i)
  469. {
  470. glutAddMenuEntry(config->getView(defaultDisplay, i), i);
  471. }
  472. int lookMenuID = glutCreateMenu(look_CB);
  473. for(int i=0; i<config->getNumLooks(); ++i)
  474. {
  475. glutAddMenuEntry(config->getLookNameByIndex(i), i);
  476. }
  477. int optimizationMenuID = glutCreateMenu(optimization_CB);
  478. for (size_t i = 0; i<OptmizationMenu.size(); ++i)
  479. {
  480. glutAddMenuEntry(OptmizationMenu[i].first, static_cast<int>(i));
  481. }
  482. glutCreateMenu(menuCallback);
  483. glutAddSubMenu("Image ColorSpace", csMenuID);
  484. glutAddSubMenu("Transform", transformMenuID);
  485. glutAddSubMenu("Device", deviceMenuID);
  486. glutAddSubMenu("Looks Override", lookMenuID);
  487. glutAddSubMenu("Optimization", optimizationMenuID);
  488. glutAttachMenu(GLUT_RIGHT_BUTTON);
  489. }
  490. const char * USAGE_TEXT = "\n"
  491. "Keys:\n"
  492. "\tCtrl+Up: Exposure +1/4 stop (in scene linear)\n"
  493. "\tCtrl+Down: Exposure -1/4 stop (in scene linear)\n"
  494. "\tCtrl+Home: Reset Exposure + Gamma\n"
  495. "\n"
  496. "\tAlt+Up: Gamma up (post display transform)\n"
  497. "\tAlt+Down: Gamma down (post display transform)\n"
  498. "\tAlt+Home: Reset Exposure + Gamma\n"
  499. "\n"
  500. "\tC: View Color\n"
  501. "\tR: View Red\n"
  502. "\tG: View Green\n"
  503. "\tB: View Blue\n"
  504. "\tA: View Alpha\n"
  505. "\tL: View Luma\n"
  506. "\n"
  507. "\tRight-Mouse Button: Configure Display / Transform / ColorSpace / Looks / Optimization\n"
  508. "\n"
  509. "\tEsc: Quit\n";
  510. void parseArguments(int argc, char **argv)
  511. {
  512. for(int i=1; i<argc; ++i)
  513. {
  514. if(0==strcmp(argv[i], "-v"))
  515. {
  516. g_verbose = true;
  517. }
  518. else if(0==strcmp(argv[i], "-gpulegacy"))
  519. {
  520. g_gpulegacy = true;
  521. }
  522. else if(0==strcmp(argv[i], "-gpuinfo"))
  523. {
  524. g_gpuinfo = true;
  525. }
  526. else if(0==strcmp(argv[i], "-h"))
  527. {
  528. std::cout << std::endl;
  529. std::cout << "help:" << std::endl;
  530. std::cout << " ociodisplay [OPTIONS] [image] where" << std::endl;
  531. std::cout << std::endl;
  532. std::cout << " OPTIONS:" << std::endl;
  533. std::cout << " -h : displays the help and exit" << std::endl;
  534. std::cout << " -v : displays the color space information" << std::endl;
  535. std::cout << " -gpulegacy : use the legacy (i.e. baked) GPU color processing" << std::endl;
  536. std::cout << " -gpuinfo : output the OCIO shader program" << std::endl;
  537. std::cout << std::endl;
  538. exit(0);
  539. }
  540. else
  541. {
  542. g_filename = argv[i];
  543. }
  544. }
  545. }
  546. int main(int argc, char **argv)
  547. {
  548. parseArguments(argc, argv);
  549. try
  550. {
  551. g_oglApp = std::make_shared<OCIO::OglApp>("ociodisplay", 512, 512);
  552. }
  553. catch (const OCIO::Exception & e)
  554. {
  555. std::cerr << e.what() << std::endl;
  556. return 1;
  557. }
  558. if (g_verbose)
  559. {
  560. g_oglApp->printGLInfo();
  561. }
  562. g_oglApp->setYMirror();
  563. g_oglApp->setPrintShader(g_gpuinfo);
  564. glutReshapeFunc(Reshape);
  565. glutKeyboardFunc(Key);
  566. glutSpecialFunc(SpecialKey);
  567. glutDisplayFunc(Redisplay);
  568. if(g_verbose)
  569. {
  570. if(!g_filename.empty())
  571. {
  572. std::cout << std::endl;
  573. std::cout << "Image: " << g_filename << std::endl;
  574. }
  575. std::cout << std::endl;
  576. std::cout << "OIIO Version: " << OIIO_VERSION_STRING << std::endl;
  577. std::cout << "OCIO Version: " << OCIO::GetVersion() << std::endl;
  578. }
  579. OCIO::ConstConfigRcPtr config;
  580. try
  581. {
  582. config = OCIO::GetCurrentConfig();
  583. }
  584. catch(...)
  585. {
  586. const char * env = OCIO::GetEnvVariable("OCIO");
  587. std::cerr << "Error loading the config file: '" << (env ? env : "") << "'";
  588. exit(1);
  589. }
  590. if(g_verbose)
  591. {
  592. const char * env = OCIO::GetEnvVariable("OCIO");
  593. if(env && *env)
  594. {
  595. std::cout << std::endl;
  596. std::cout << "OCIO Configuration: '" << env << "'" << std::endl;
  597. std::cout << "OCIO search_path: " << config->getSearchPath() << std::endl;
  598. }
  599. }
  600. std::cout << std::endl;
  601. std::cout << USAGE_TEXT << std::endl;
  602. InitImageTexture(g_filename.c_str());
  603. try
  604. {
  605. InitOCIO(g_filename.c_str());
  606. }
  607. catch(OCIO::Exception & e)
  608. {
  609. std::cerr << e.what() << std::endl;
  610. exit(1);
  611. }
  612. PopulateOCIOMenus();
  613. try
  614. {
  615. UpdateOCIOGLState();
  616. }
  617. catch(OCIO::Exception & e)
  618. {
  619. std::cerr << e.what() << std::endl;
  620. exit(1);
  621. }
  622. Redisplay();
  623. glutMainLoop();
  624. return 0;
  625. }