PageRenderTime 91ms CodeModel.GetById 15ms app.highlight 70ms RepoModel.GetById 1ms app.codeStats 0ms

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