Skip to content

Commit

Permalink
Merge pull request GafferHQ#5324 from johnhaddon/ocioContextVariables
Browse files Browse the repository at this point in the history
OpenColorIO : Define config using Gaffer context
  • Loading branch information
johnhaddon authored Jun 6, 2023
2 parents adf9398 + a034065 commit 25b9c7a
Show file tree
Hide file tree
Showing 36 changed files with 1,470 additions and 138 deletions.
13 changes: 11 additions & 2 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Improvements
- Removed "Default" view.
- Added menu options for changing the current Display.
- Allowed the available views to be filtered using an `openColorIO:activeViews` metadata value registered on the View's `displayTransform.name` plug. Values should be space-separated names, optionally containing wildcards.
- OpenColorIOContext : Added a new node that creates Gaffer context variables to define the OpenColorIO config used by upstream nodes. This allows different OpenColorIO configs to be used in different Gaffer contexts.
- OpenColorIOTransform :
- Improved performance.
- Improved detection of no-op transforms, such as when converting between colorspace aliases like `scene_linear` and `ACEScg`.
Expand Down Expand Up @@ -64,11 +65,16 @@ API
- OpenColorIOTransformUI :
- Added `noneLabel` argument to `colorSpacePresetNames()`.
- Added support for `openColorIO:categories` and `openColorIO:includeRoles` metadata to `colorSpacePresetNames()`. These may be registered on a per-plug basis to control the colorspaces shown for that plug.
- OpenColorIOAlgo : Added a new namespace that allows the OpenColorIO config to be defined via the Gaffer context.
- ImageReader/ImageWriter : Added a `config` argument to the `DefaultColorSpaceFunction` definition. This is passed the OpenColorIO config currently being used by the node.
- ValuePlugs : Added Python bindings for `ValueType` type alias.
- Color4fVectorDataPlug : Added a plug type for storing arrays of `Color4f`.
- TypedObjectPlug : Added default value for `direction` and `defaultValue` constructor arguments.
- VectorDataWidget : Added `setErrored()` and `getErrored()` methods to control an error state. Errors are reflected by a red background colour.
- PlugLayout : Added support for `layout:minimumWidth` metadata.
- PlugLayout :
- Added support for `layout:minimumWidth` metadata.
- Tabs are now hidden if all their child plugs are hidden by `layout:visibilityActivator` metadata.
- TabbedContainer : Added `setTabVisible()` and `getTabVisible()` methods.
- Removed use of `RTLD_GLOBAL` for loading Python modules.
- SceneAlgo : Added `validateName()` function.

Expand All @@ -89,7 +95,10 @@ Breaking Changes
- ViewportGadget : Changed function signature for `setPostProcessShader()` and `getPostProcessShader()`.
- UVInspector : Moved the `displayTransform` plug to `displayTransform.name`.
- ImageReader : Renamed `None` preset to `Automatic`.
- OpenColorIOTransform : Removed `availableColorSpaces()` and `availableRoles()` methods.
- ImageReader/ImageWriter : Added a `config` argument to `DefaultColorSpaceFunction`. If implementing such a function in Python, it can be compatible with both Gaffer 1.2 and 1.3 if the argument is declared as `config = PyOpenColorIO.GetCurrentConfig()`.
- OpenColorIOTransform :
- Removed `availableColorSpaces()` and `availableRoles()` methods.
- Deprecated `context` plug.
- OpenColorIO : Changed default config.
- Subprocess32 : Removed Python module.
- Six : Removed Python module.
Expand Down
4 changes: 3 additions & 1 deletion include/GafferImage/ImageReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@

#include "Gaffer/CompoundNumericPlug.h"

#include "OpenColorIO/OpenColorTypes.h"

#include <functional>

namespace Gaffer
Expand Down Expand Up @@ -131,7 +133,7 @@ class GAFFERIMAGE_API ImageReader : public ImageNode
/// A function which can take information about a file being read, and return the colorspace
/// of the data within the file. This is used whenever the colorSpace plug is at its default
/// value.
using DefaultColorSpaceFunction = std::function<const std::string ( const std::string &fileName, const std::string &fileFormat, const std::string &dataType, const IECore::CompoundData *metadata )>;
using DefaultColorSpaceFunction = std::function<const std::string ( const std::string &fileName, const std::string &fileFormat, const std::string &dataType, const IECore::CompoundData *metadata, const OCIO_NAMESPACE::ConstConfigRcPtr &config )>;
static void setDefaultColorSpaceFunction( DefaultColorSpaceFunction f );
static DefaultColorSpaceFunction getDefaultColorSpaceFunction();

Expand Down
4 changes: 3 additions & 1 deletion include/GafferImage/ImageWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@

#include "IECore/CompoundData.h"

#include "OpenColorIO/OpenColorTypes.h"

#include <functional>

namespace Gaffer
Expand Down Expand Up @@ -105,7 +107,7 @@ class GAFFERIMAGE_API ImageWriter : public GafferDispatch::TaskNode

/// Note that this is intentionally identical to the ImageReader's DefaultColorSpaceFunction
/// definition, so that the same function can be used with both nodes.
using DefaultColorSpaceFunction = std::function<const std::string ( const std::string &fileName, const std::string &fileFormat, const std::string &dataType, const IECore::CompoundData *metadata )>;
using DefaultColorSpaceFunction = std::function<const std::string ( const std::string &fileName, const std::string &fileFormat, const std::string &dataType, const IECore::CompoundData *metadata, const OCIO_NAMESPACE::ConstConfigRcPtr &config )>;
static void setDefaultColorSpaceFunction( DefaultColorSpaceFunction f );
static DefaultColorSpaceFunction getDefaultColorSpaceFunction();

Expand Down
89 changes: 89 additions & 0 deletions include/GafferImage/OpenColorIOAlgo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2023, Cinesite VFX Ltd. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above
// copyright notice, this list of conditions and the following
// disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided with
// the distribution.
//
// * Neither the name of John Haddon nor the names of
// any other contributors to this software may be used to endorse or
// promote products derived from this software without specific prior
// written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////

#pragma once

#include "GafferImage/Export.h"

#include "Gaffer/Context.h"

#include "OpenColorIO/OpenColorTypes.h"

namespace GafferImage
{

namespace OpenColorIOAlgo
{

/// Specifying an OpenColorIO Config
/// ================================
///
/// The OpenColorIO config used by Gaffer is specified using the Gaffer context.
/// These functions are used to define that config.

/// Sets the config to use in `context`. If the filename is empty, the config
/// specified by the `$OCIO` environment variable will be used instead.
GAFFERIMAGE_API void setConfig( Gaffer::Context *context, const std::string &configFileName );
GAFFERIMAGE_API void setConfig( Gaffer::Context::EditableScope &context, const std::string *configFileName );
GAFFERIMAGE_API const std::string &getConfig( const Gaffer::Context *context );

/// Adds an OCIO "string var" to be used in `context`. Note that OCIO also calls these
/// "environment vars" and "context vars" but they're all the same thing.
GAFFERIMAGE_API void addVariable( Gaffer::Context *context, const std::string &name, const std::string &value );
GAFFERIMAGE_API void addVariable( Gaffer::Context::EditableScope &context, const std::string &name, const std::string *value );
/// Gets the value of an OCIO "string var".
GAFFERIMAGE_API const std::string &getVariable( const Gaffer::Context *context, const std::string &name );
/// Removes an OCIO "string var".
GAFFERIMAGE_API void removeVariable( Gaffer::Context *context, const std::string &name );
GAFFERIMAGE_API void removeVariable( Gaffer::Context::EditableScope &context, const std::string &name );
/// Returns a list of the OCIO variables in the context.
GAFFERIMAGE_API std::vector<std::string> variables( const Gaffer::Context *context );

/// Acquiring an OpenColorIO Config
/// ===============================
///
/// These functions return OCIO Configs and Contexts as defined by the current Gaffer context.

using ConfigAndContext = std::pair<OCIO_NAMESPACE::ConstConfigRcPtr, OCIO_NAMESPACE::ConstContextRcPtr>;

GAFFERIMAGE_API OCIO_NAMESPACE::ConstConfigRcPtr currentConfig();
GAFFERIMAGE_API IECore::MurmurHash currentConfigHash();
GAFFERIMAGE_API ConfigAndContext currentConfigAndContext();
GAFFERIMAGE_API IECore::MurmurHash currentConfigAndContextHash();

} // namespace OpenColorIOAlgo

} // namespace GafferImageU
98 changes: 98 additions & 0 deletions include/GafferImage/OpenColorIOContext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2023, Cinesite VFX Ltd. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above
// copyright notice, this list of conditions and the following
// disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided with
// the distribution.
//
// * Neither the name of John Haddon nor the names of
// any other contributors to this software may be used to endorse or
// promote products derived from this software without specific prior
// written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////

#pragma once

#include "GafferImage/Export.h"
#include "GafferImage/TypeIds.h"

#include "Gaffer/CompoundDataPlug.h"
#include "Gaffer/ContextProcessor.h"
#include "Gaffer/TypedObjectPlug.h"

namespace GafferImage
{

class GAFFERIMAGE_API OpenColorIOContext : public Gaffer::ContextProcessor
{

public :

GAFFER_NODE_DECLARE_TYPE( GafferImage::OpenColorIOContext, OpenColorIOContextTypeId, Gaffer::ContextProcessor );

explicit OpenColorIOContext( const std::string &name=GraphComponent::defaultName<OpenColorIOContext>() );
~OpenColorIOContext() override;

Gaffer::ValuePlug *configPlug();
const Gaffer::ValuePlug *configPlug() const;

Gaffer::BoolPlug *configEnabledPlug();
const Gaffer::BoolPlug *configEnabledPlug() const;

Gaffer::StringPlug *configValuePlug();
const Gaffer::StringPlug *configValuePlug() const;

Gaffer::ValuePlug *variablesPlug();
const Gaffer::ValuePlug *variablesPlug() const;

Gaffer::AtomicCompoundDataPlug *extraVariablesPlug();
const Gaffer::AtomicCompoundDataPlug *extraVariablesPlug() const;

void affects( const Gaffer::Plug *input, DependencyNode::AffectedPlugsContainer &outputs ) const override;

protected :

void hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const override;
void compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const override;

bool affectsContext( const Gaffer::Plug *input ) const override;
void processContext( Gaffer::Context::EditableScope &context, IECore::ConstRefCountedPtr &storage ) const override;

private :

// We combine everything into this plug, so that we have all variables
// cached and can push them into the context without needing to perform
// any allocations.
Gaffer::AtomicCompoundDataPlug *combinedVariablesPlug();
const Gaffer::AtomicCompoundDataPlug *combinedVariablesPlug() const;

static size_t g_firstPlugIndex;

};

IE_CORE_DECLAREPTR( OpenColorIOContext );

} // namespace GafferImage
3 changes: 2 additions & 1 deletion include/GafferImage/OpenColorIOTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class GAFFERIMAGE_API OpenColorIOTransform : public ColorProcessor

/// May return null if the derived class does not
/// request OCIO context variable support.
/// \deprecated Use the OpenColorIOContext node instead.
Gaffer::CompoundDataPlug *contextPlug();
const Gaffer::CompoundDataPlug *contextPlug() const;

Expand Down Expand Up @@ -96,7 +97,7 @@ class GAFFERIMAGE_API OpenColorIOTransform : public ColorProcessor
void hashColorProcessor( const Gaffer::Context *context, IECore::MurmurHash &h ) const final;
ColorProcessorFunction colorProcessor( const Gaffer::Context *context ) const final;

OCIO_NAMESPACE::ConstContextRcPtr ocioContext( OCIO_NAMESPACE::ConstConfigRcPtr config ) const;
OCIO_NAMESPACE::ConstContextRcPtr modifiedOCIOContext( OCIO_NAMESPACE::ConstContextRcPtr context ) const;

static size_t g_firstPlugIndex;
bool m_hasContextPlug;
Expand Down
2 changes: 1 addition & 1 deletion include/GafferImage/TypeIds.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ enum TypeId
ImagePlugTypeId = 110750,
ImageNodeTypeId = 110751,
ImageReaderTypeId = 110752,
ImagePrimitiveNodeTypeId = 110753, // Obsolete - available for reuse
OpenColorIOContextTypeId = 110753,
DisplayTypeId = 110754,
GafferDisplayDriverTypeId = 110755,
ImageProcessorTypeId = 110756,
Expand Down
60 changes: 36 additions & 24 deletions python/GafferImageTest/ColorSpaceTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,11 @@ def testPassThrough( self ) :
self.assertEqual( i["out"]["dataWindow"].getValue(), o["out"]["dataWindow"].getValue() )
self.assertEqual( i["out"]["channelNames"].getValue(), o["out"]["channelNames"].getValue() )

def testContext( self ) :
def testContextPlugs( self ) :

scriptFileName = self.temporaryDirectory() / "script.gfr"
contextImageFile = self.temporaryDirectory() / "context.#.exr"
contextOverrideImageFile = self.temporaryDirectory() / "context_override.#.exr"
contextImageFile = self.temporaryDirectory() / "context.exr"
contextOverrideImageFile = self.temporaryDirectory() / "context_override.exr"

s = Gaffer.ScriptNode()

Expand All @@ -185,11 +185,11 @@ def testContext( self ) :
s["cs"]["inputSpace"].setValue( "linear" )
s["cs"]["outputSpace"].setValue( "context" )


s["writer"] = GafferImage.ImageWriter()
s["writer"]["fileName"].setValue( contextImageFile )
s["writer"]["in"].setInput( s["cs"]["out"] )
s["writer"]["channels"].setValue( "R G B A" )
s["writer"]["openexr"]["dataType"].setValue( "float" )

s["fileName"].setValue( scriptFileName )
s.save()
Expand All @@ -200,46 +200,58 @@ def testContext( self ) :
env["CDL"] = "cineon.spi1d"

subprocess.check_call(
[ str( Gaffer.executablePath() ), "execute", str( scriptFileName ),"-frames", "1" ],
[ str( Gaffer.executablePath() ), "execute", str( scriptFileName ), "-frames", "1" ],
stderr = subprocess.PIPE,
env = env,
)

i = GafferImage.ImageReader()
i["fileName"].setValue( self.imagesPath() / "checker_ocio_context.exr" )

o = GafferImage.ImageReader()
o["fileName"].setValue( contextImageFile )
expected = GafferImage.ImageReader()
expected["fileName"].setValue( self.imagesPath() / "checker_ocio_context.exr" )

expected = i["out"]
context = o["out"]
actual = GafferImage.ImageReader()
actual["fileName"].setValue( contextImageFile )

# check against expected output
self.assertImagesEqual( expected, context, ignoreMetadata = True )
self.assertImagesEqual( actual["out"], expected["out"], ignoreMetadata = True )

# override context
s["writer"]["fileName"].setValue( contextOverrideImageFile )
s["cs"]["context"].addChild( Gaffer.NameValuePlug("LUT", "cineon.spi1d", True, "LUT", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) )
s["cs"]["context"].addChild( Gaffer.NameValuePlug("CDL", "rec709.spi1d", True, "CDL", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) )
s["cs"]["context"].addChild( Gaffer.NameValuePlug( "LUT", "cineon.spi1d", True, "LUT", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) )
s["cs"]["context"].addChild( Gaffer.NameValuePlug( "CDL", "rec709.spi1d", True, "CDL", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) )
s.save()

subprocess.check_call(
[ str( Gaffer.executablePath() ), "execute", str( scriptFileName ),"-frames", "1" ],
[ str( Gaffer.executablePath() ), "execute", str( scriptFileName ), "-frames", "1" ],
stderr = subprocess.PIPE,
env = env
)

i = GafferImage.ImageReader()
i["fileName"].setValue( self.imagesPath() / "checker_ocio_context_override.exr" )
expected["fileName"].setValue( self.imagesPath() / "checker_ocio_context_override.exr" )
actual["fileName"].setValue( contextOverrideImageFile )

o = GafferImage.ImageReader()
o["fileName"].setValue( contextOverrideImageFile )
# check override produce expected output
self.assertImagesEqual( actual["out"], expected["out"], ignoreMetadata = True )

expected = i["out"]
context = o["out"]
def testConfigFromGafferContext( self ) :

# check override produce expected output
self.assertImagesEqual( expected, context, ignoreMetadata = True )
reader = GafferImage.ImageReader()
reader["fileName"].setValue( self.fileName )

colorSpace = GafferImage.ColorSpace()
colorSpace["in"].setInput( reader["out"] )
colorSpace["inputSpace"].setValue( "linear" )
colorSpace["outputSpace"].setValue( "context" )

expected = GafferImage.ImageReader()
expected["fileName"].setValue( self.imagesPath() / "checker_ocio_context.exr" )

with Gaffer.Context() as c :

GafferImage.OpenColorIOAlgo.setConfig( c, str( self.openColorIOPath() / "context.ocio" ) )
GafferImage.OpenColorIOAlgo.addVariable( c, "LUT", "srgb.spi1d" )
GafferImage.OpenColorIOAlgo.addVariable( c, "CDL", "cineon.spi1d" )

self.assertImagesEqual( expected["out"], colorSpace["out"], maxDifference = 0.0002, ignoreMetadata = True )

def testSingleChannelImage( self ) :

Expand Down
Loading

0 comments on commit 25b9c7a

Please sign in to comment.