PageRenderTime 95ms CodeModel.GetById 14ms app.highlight 75ms RepoModel.GetById 1ms app.codeStats 1ms

/src/apps/ocioconvert/main.cpp

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