/docs/developers/usage_examples.rst
http://github.com/imageworks/OpenColorIO · ReStructuredText · 363 lines · 274 code · 89 blank · 0 comment · 0 complexity · 66f32deab9ff97cd2f5b93bad1a58fa3 MD5 · raw file
- ..
- SPDX-License-Identifier: CC-BY-4.0
- Copyright Contributors to the OpenColorIO Project.
- .. _developers-usageexamples:
- Usage Examples
- ==============
- Some examples of using the OCIO API, via both C++ and the Python bindings.
- For further examples, see the ``src/apps/`` directory in the git repository
- .. _usage_applybasic:
- Applying a basic ColorSpace transform, using the CPU
- ****************************************************
- This describes what code is used to convert from a specified source
- :cpp:class:`ColorSpace` to a specified destination :cpp:class:`ColorSpace`.
- If you are using the OCIO Nuke plugins, the OCIOColorSpace node performs these
- steps internally.
- #. **Get the Config.**
- This represents the entirety of the current color "universe". It can either
- be initialized by your app at startup or created explicitly. In common
- usage, you can just query :cpp:func:`GetCurrentConfig`, which will auto
- initialize on first use using the :envvar:`OCIO` environment variable.
- #. **Get Processor from the Config.**
- A processor corresponds to a 'baked' color transformation. You specify two
- arguments when querying a processor: the :ref:`colorspace_section` you are
- coming from, and the :ref:`colorspace_section` you are going to.
- :ref:`cfgcolorspaces_section` ColorSpaces can be either explicitly named
- strings (defined by the current configuration) or can be
- :ref:`cfgroles_section` (essentially :ref:`colorspace_section` aliases)
- which are consistent across configurations. Constructing a
- :cpp:class:`Processor` object is likely a blocking operation (thread-wise)
- so care should be taken to do this as infrequently as is sensible. Once per
- render 'setup' would be appropriate, once per scanline would be
- inappropriate.
- #. **Convert your image, using the Processor.**
- Once you have the processor, you can apply the color transformation using
- the "apply" function. In :ref:`usage_applybasic_cpp`, you apply the
- processing in-place, by first wrapping your image in an
- :cpp:class:`ImageDesc` class. This approach is intended to be used in high
- performance applications, and can be used on multiple threads (per scanline,
- per tile, etc). In :ref:`usage_applybasic_python` you call
- "applyRGB" / "applyRGBA" on your sequence of pixels. Note that in both
- languages, it is far more efficient to call "apply" on batches of pixels at
- a time.
- .. _usage_applybasic_cpp:
- C++
- +++
- .. code-block:: cpp
-
- #include <OpenColorIO/OpenColorIO.h>
- namespace OCIO = OCIO_NAMESPACE;
-
- try
- {
- OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
- ConstProcessorRcPtr processor = config->getProcessor(OCIO::ROLE_COMPOSITING_LOG,
- OCIO::ROLE_SCENE_LINEAR);
-
- OCIO::PackedImageDesc img(imageData, w, h, 4);
- processor->apply(img);
- }
- catch(OCIO::Exception & exception)
- {
- std::cerr << "OpenColorIO Error: " << exception.what() << std::endl;
- }
- .. _usage_applybasic_python:
- Python
- ++++++
- .. code-block:: py
-
- import PyOpenColorIO as OCIO
-
- try:
- config = OCIO.GetCurrentConfig()
- processor = config.getProcessor(OCIO.Constants.ROLE_COMPOSITING_LOG,
- OCIO.Constants.ROLE_SCENE_LINEAR)
-
- # Apply the color transform to the existing RGBA pixel data
- img = processor.applyRGBA(img)
- except Exception, e:
- print "OpenColorIO Error",e
- .. _usage_displayimage:
- Displaying an image, using the CPU (simple ColorSpace conversion)
- *****************************************************************
- Converting an image for display is similar to a normal color space conversion.
- The only difference is that one has to first determine the name of the display
- (destination) ColorSpace by querying the config with the device name and
- transform name.
- #. **Get the Config.**
- See :ref:`usage_applybasic` for details.
-
- #. **Lookup the display ColorSpace.**
- The display :cpp:class:`ColorSpace` is queried from the configuration using
- :cpp:func:`Config::getDisplayColorSpaceName`. If the user has specified
- value for the ``device`` or the ``displayTransformName``, use them. If these
- values are unknown default values can be queried (as shown below).
- #. **Get the processor from the Config.**
- See :ref:`usage_applybasic` for details.
- #. **Convert your image, using the processor.**
- See :ref:`usage_applybasic` for details.
- C++
- +++
- .. code-block:: cpp
-
- #include <OpenColorIO/OpenColorIO.h>
- namespace OCIO = OCIO_NAMESPACE;
-
- OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
-
- // If the user hasn't picked a display, use the defaults...
- const char * device = config->getDefaultDisplayDeviceName();
- const char * transformName = config->getDefaultDisplayTransformName(device);
- const char * displayColorSpace = config->getDisplayColorSpaceName(device, transformName);
-
- ConstProcessorRcPtr processor = config->getProcessor(OCIO::ROLE_SCENE_LINEAR,
- displayColorSpace);
-
- OCIO::PackedImageDesc img(imageData, w, h, 4);
- processor->apply(img);
- Python
- ++++++
- .. code-block:: python
- import PyOpenColorIO as OCIO
- config = OCIO.GetCurrentConfig()
- device = config.getDefaultDisplayDeviceName()
- transformName = config.getDefaultDisplayTransformName(device)
- displayColorSpace = config.getDisplayColorSpaceName(device, transformName)
- processor = config.getProcessor(OCIO.Constants.ROLE_SCENE_LINEAR, displayColorSpace)
- processor.applyRGB(imageData)
- Displaying an image, using the CPU (Full Display Pipeline)
- **********************************************************
- This alternative version allows for a more complex displayTransform, allowing
- for all of the controls typically added to real-world viewer interfaces. For
- example, options are allowed to control which channels (red, green, blue,
- alpha, luma) are visible, as well as allowing for optional color corrections
- (such as an exposure offset in scene linear). If you are using the OCIO Nuke
- plugins, the OCIODisplay node performs these steps internally.
- #. **Get the Config.**
- See :ref:`usage_applybasic` for details.
- #. **Lookup the display ColorSpace.**
- See :ref:`usage_displayimage` for details
- #. **Create a new DisplayTransform.**
- This transform will embody the full 'display' pipeline you wish to control.
- The user is required to call
- :cpp:func:`DisplayTransform::setInputColorSpaceName` to set the input
- ColorSpace, as well as
- :cpp:func:`DisplayTransform::setDisplayColorSpaceName` (with the results of
- :cpp:func:`Config::getDisplayColorSpaceName`).
- #. **Set any additional DisplayTransform options.**
- If the user wants to specify a channel swizzle, a scene-linear exposure
- offset, an artistic look, this is the place to add it. See below for an
- example. Note that although we provide recommendations for display, any
- transforms are allowed to be added into any of the slots. So if for your app
- you want to add 3 transforms into a particular slot (chained together), you
- are free to wrap them in a :cpp:class:`GroupTransform` and set it
- accordingly!
- #. **Get the processor from the Config.**
- The processor is then queried from the config passing the new
- :cpp:class:`DisplayTransform` as the argument. Once the processor has been
- returned, the original :cpp:class:`DisplayTransform` is no longer necessary
- to hold onto. (Though if you'd like to for re-use, there is no problem doing
- so).
- #. **Convert your image, using the processor.**
- See :ref:`usage_applybasic` for details.
- C++
- +++
- .. code-block:: cpp
-
- // Step 1: Get the config
- OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
-
- // Step 2: Lookup the display ColorSpace
- const char * device = config->getDefaultDisplayDeviceName();
- const char * transformName = config->getDefaultDisplayTransformName(device);
- const char * displayColorSpace = config->getDisplayColorSpaceName(device, transformName);
-
- // Step 3: Create a DisplayTransform, and set the input and display ColorSpaces
- // (This example assumes the input is scene linear. Adapt as needed.)
-
- OCIO::DisplayTransformRcPtr transform = OCIO::DisplayTransform::Create();
- transform->setInputColorSpaceName( OCIO::ROLE_SCENE_LINEAR );
- transform->setDisplayColorSpaceName( displayColorSpace );
-
- // Step 4: Add custom transforms for a 'canonical' Display Pipeline
-
- // Add an fstop exposure control (in SCENE_LINEAR)
- float gain = powf(2.0f, exposure_in_stops);
- const float slope3f[] = { gain, gain, gain };
- OCIO::CDLTransformRcPtr cc = OCIO::CDLTransform::Create();
- cc->setSlope(slope3f);
- transform->setLinearCC(cc);
-
- // Add a Channel view 'swizzle'
-
- // 'channelHot' controls which channels are viewed.
- int channelHot[4] = { 1, 1, 1, 1 }; // show rgb
- //int channelHot[4] = { 1, 0, 0, 0 }; // show red
- //int channelHot[4] = { 0, 0, 0, 1 }; // show alpha
- //int channelHot[4] = { 1, 1, 1, 0 }; // show luma
-
- float lumacoef[3];
- config.getDefaultLumaCoefs(lumacoef);
-
- float m44[16];
- float offset[4];
- OCIO::MatrixTransform::View(m44, offset, channelHot, lumacoef);
- OCIO::MatrixTransformRcPtr swizzle = OCIO::MatrixTransform::Create();
- swizzle->setValue(m44, offset);
- transform->setChannelView(swizzle);
-
- // And then process the image normally.
- OCIO::ConstProcessorRcPtr processor = config->getProcessor(transform);
-
- OCIO::PackedImageDesc img(imageData, w, h, 4);
- processor->apply(img);
- Python
- ++++++
- .. code-block:: python
- import PyOpenColorIO as OCIO
- # Step 1: Get the config
- config = OCIO.GetCurrentConfig()
- # Step 2: Lookup the display ColorSpace
- display = config.getDefaultDisplay()
- view = config.getDefaultView(display)
- # Step 3: Create a DisplayTransform, and set the input, display, and view
- # (This example assumes the input is scene linear. Adapt as needed.)
- transform = OCIO.DisplayTransform()
- transform.setInputColorSpaceName(OCIO.Constants.ROLE_SCENE_LINEAR)
- transform.setDisplay(display)
- transform.setView(view)
- # Step 4: Add custom transforms for a 'canonical' Display Pipeline
- # Add an fstop exposure control (in SCENE_LINEAR)
- exposure = 0 # Example data: zero exposure adjustment
- gain = 2**exposure
- slope3f = (gain, gain, gain)
- cc = OCIO.CDLTransform()
- cc.setSlope(slope3f)
- transform.setLinearCC(cc)
- # Add a Channel view 'swizzle'
- channelHot = (1, 1, 1, 1) # show rgb
- # channelHot = (1, 0, 0, 0) # show red
- # channelHot = (0, 0, 0, 1) # show alpha
- # channelHot = (1, 1, 1, 0) # show luma
- lumacoef = config.getDefaultLumaCoefs()
- m44, offset = OCIO.MatrixTransform.View(channelHot, lumacoef)
- swizzle = OCIO.MatrixTransform()
- swizzle.setValue(m44, offset)
- transform.setChannelView(swizzle)
- # And then process the image normally.
- processor = config.getProcessor(transform)
- imageData = [0,0,0, 1,0,0] # Example data: A black and a red pixel
- print processor.applyRGB(imageData)
- Displaying an image, using the GPU
- **********************************
- Applying OpenColorIO's color processing using GPU processing is
- straightforward, provided you have the capability to upload custom shader code
- and a custom 3D Lookup Table (3DLUT).
- #. **Get the Processor.**
- This portion of the pipeline is identical to the CPU approach. Just get the
- processor as you normally would have, see above for details.
- #. **Create a GpuShaderDesc.**
- #. **Query the GPU Shader Text + 3D LUT.**
- #. **Configure the GPU State.**
- #. **Draw your image.**
- C++
- +++
- This example is available as a working app in the OCIO source: src/apps/ociodisplay.
- .. code-block:: cpp
-
- // Step 0: Get the processor using any of the pipelines mentioned above.
- OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
- const char * device = config->getDefaultDisplayDeviceName();
- const char * transformName = config->getDefaultDisplayTransformName(device);
- const char * displayColorSpace = config->getDisplayColorSpaceName(device, transformName);
- ConstProcessorRcPtr processor = config->getProcessor(OCIO::ROLE_SCENE_LINEAR,
- displayColorSpace);
-
- // Step 1: Create a GPU Shader Description
- GpuShaderDesc shaderDesc;
- shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_0);
- shaderDesc.setFunctionName("OCIODisplay");
- const int LUT3D_EDGE_SIZE = 32;
- shaderDesc.setLut3DEdgeLen(LUT3D_EDGE_SIZE);
-
- // Step 2: Compute and the 3D LUT
- // Optional Optimization:
- // Only do this the 3D LUT's contents
- // are different from the last drawn frame.
- // Use getGpuLut3DCacheID to compute the cacheID.
- // cheaply.
- //
- // std::string lut3dCacheID = processor->getGpuLut3DCacheID(shaderDesc);
- int num3Dentries = 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE;
- std::vector<float> g_lut3d;
- g_lut3d.resize(num3Dentries);
- processor->getGpuLut3D(&g_lut3d[0], shaderDesc);
-
- // Load the data into an OpenGL 3D Texture
- glGenTextures(1, &g_lut3d_textureID);
- glBindTexture(GL_TEXTURE_3D, g_lut3d_textureID);
- glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB,
- LUT3D_EDGE_SIZE, LUT3D_EDGE_SIZE, LUT3D_EDGE_SIZE,
- 0, GL_RGB,GL_FLOAT, &g_lut3d[0]);
-
- // Step 3: Query