From 7b0baf6e28bc272522e6198e00a90db4ed0c4cc8 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 8 Nov 2013 17:47:26 +0000 Subject: [PATCH] Added colourspace management to the image viewer. The ImageView class allows arbitrary ImageProcessors to be registered as colour transforms, and chosen using a new "displayTransform" plug. The ocio.py config file registers a processor for each display transform in the OpenColorIO config, and a Default transform which tracks the global display transform specified via the preferences. Fixes #573. --- include/GafferImageUI/ImageView.h | 20 +++++ src/GafferImageUI/ImageView.cpp | 87 ++++++++++++++++++- .../ImageViewBinding.cpp | 46 ++++++++++ startup/gui/ocio.py | 39 +++++++++ 4 files changed, 190 insertions(+), 2 deletions(-) diff --git a/include/GafferImageUI/ImageView.h b/include/GafferImageUI/ImageView.h index 8634eb549c0..2191c7dc999 100644 --- a/include/GafferImageUI/ImageView.h +++ b/include/GafferImageUI/ImageView.h @@ -48,6 +48,7 @@ namespace GafferImage { +IE_CORE_FORWARDDECLARE( ImageProcessor ) IE_CORE_FORWARDDECLARE( Grade ) IE_CORE_FORWARDDECLARE( ImageStats ) IE_CORE_FORWARDDECLARE( ImagePlug ) @@ -73,6 +74,15 @@ class ImageView : public GafferUI::View Gaffer::FloatPlug *gammaPlug(); const Gaffer::FloatPlug *gammaPlug() const; + /// Values should be names that exist in registeredDisplayTransforms(). + Gaffer::StringPlug *displayTransformPlug(); + const Gaffer::StringPlug *displayTransformPlug() const; + + typedef boost::function DisplayTransformCreator; + + static void registerDisplayTransform( const std::string &name, DisplayTransformCreator creator ); + static void registeredDisplayTransforms( std::vector &names ); + protected : /// May be called from a subclass constructor to add a converter @@ -95,7 +105,14 @@ class ImageView : public GafferUI::View GafferImage::Grade *gradeNode(); const GafferImage::Grade *gradeNode() const; + GafferImage::ImageProcessor *displayTransformNode(); + const GafferImage::ImageProcessor *displayTransformNode() const; + void plugSet( Gaffer::Plug *plug ); + void insertDisplayTransform(); + + typedef std::map DisplayTransformMap; + DisplayTransformMap m_displayTransforms; int m_channelToView; Imath::V2f m_mousePos; @@ -104,6 +121,9 @@ class ImageView : public GafferUI::View Imath::Color4f m_maxColor; Imath::Color4f m_averageColor; + typedef std::map DisplayTransformCreatorMap; + static DisplayTransformCreatorMap &displayTransformCreators(); + static ViewDescription g_viewDescription; friend void GafferImageUIBindings::bindImageView(); diff --git a/src/GafferImageUI/ImageView.cpp b/src/GafferImageUI/ImageView.cpp index 9516e196255..537f1b86250 100644 --- a/src/GafferImageUI/ImageView.cpp +++ b/src/GafferImageUI/ImageView.cpp @@ -228,7 +228,6 @@ class ImageViewGadget : public GafferUI::Gadget "void main()" "{" " OUTCOLOR = texture2D( texture, gl_TexCoord[0].xy );" - " OUTCOLOR = vec4( ieLinToSRGB( OUTCOLOR.r ), ieLinToSRGB( OUTCOLOR.g ), ieLinToSRGB( OUTCOLOR.b ), ieLinToSRGB( OUTCOLOR.a ) );" " if( channelToView==1 )" " {" " OUTCOLOR = vec4( OUTCOLOR[0], OUTCOLOR[0], OUTCOLOR[0], 1. );" @@ -955,6 +954,8 @@ ImageView::ImageView( const std::string &name ) gradeNode->gammaPlug()->getChild( 1 )->setInput( gammaPlug ); gradeNode->gammaPlug()->getChild( 2 )->setInput( gammaPlug ); + addChild( new StringPlug( "displayTransform", Plug::In, "", Plug::Default & ~Plug::AcceptsInputs ) ); + ImagePlugPtr preprocessorOutput = new ImagePlug( "out", Plug::Out ); preprocessor->addChild( preprocessorOutput ); preprocessorOutput->setInput( gradeNode->outPlug() ); @@ -967,6 +968,9 @@ ImageView::ImageView( const std::string &name ) plugSetSignal().connect( boost::bind( &ImageView::plugSet, this, ::_1 ) ); + // trigger insertDisplayTransform() + + displayTransformPlug()->setValue( "Default" ); } void ImageView::insertConverter( Gaffer::NodePtr converter ) @@ -1024,7 +1028,17 @@ const Gaffer::FloatPlug *ImageView::gammaPlug() const { return getChild( "gamma" ); } - + +Gaffer::StringPlug *ImageView::displayTransformPlug() +{ + return getChild( "displayTransform" ); +} + +const Gaffer::StringPlug *ImageView::displayTransformPlug() const +{ + return getChild( "displayTransform" ); +} + GafferImage::ImageStats *ImageView::imageStatsNode() { return getChild( "__imageStats" ); @@ -1100,5 +1114,74 @@ void ImageView::plugSet( Gaffer::Plug *plug ) g->multiplyPlug()->setValue( Color3f( m ) ); } } + else if( plug == displayTransformPlug() ) + { + insertDisplayTransform(); + } } +void ImageView::insertDisplayTransform() +{ + Grade *grade = gradeNode(); + if( !grade ) + { + /// \todo Remove this guard when we know no + /// subclasses are calling setPreprocessor. + return; + } + + const std::string name = displayTransformPlug()->getValue(); + + ImageProcessorPtr displayTransform; + DisplayTransformMap::const_iterator it = m_displayTransforms.find( name ); + if( it != m_displayTransforms.end() ) + { + displayTransform = it->second; + } + else + { + DisplayTransformCreatorMap &m = displayTransformCreators(); + DisplayTransformCreatorMap::const_iterator it = m.find( displayTransformPlug()->getValue() ); + if( it != m.end() ) + { + displayTransform = it->second(); + } + if( displayTransform ) + { + m_displayTransforms[name] = displayTransform; + displayTransform->setName( name ); + getPreprocessor()->addChild( displayTransform ); + } + } + + if( displayTransform ) + { + displayTransform->inPlug()->setInput( grade->outPlug() ); + getPreprocessor()->getChild( "out" )->setInput( displayTransform->outPlug() ); + } + else + { + getPreprocessor()->getChild( "out" )->setInput( grade->outPlug() ); + } +} + +void ImageView::registerDisplayTransform( const std::string &name, DisplayTransformCreator creator ) +{ + displayTransformCreators()[name] = creator; +} + +void ImageView::registeredDisplayTransforms( std::vector &names ) +{ + const DisplayTransformCreatorMap &m = displayTransformCreators(); + names.clear(); + for( DisplayTransformCreatorMap::const_iterator it = m.begin(), eIt = m.end(); it != eIt; ++it ) + { + names.push_back( it->first ); + } +} + +ImageView::DisplayTransformCreatorMap &ImageView::displayTransformCreators() +{ + static DisplayTransformCreatorMap g_creators; + return g_creators; +} diff --git a/src/GafferImageUIBindings/ImageViewBinding.cpp b/src/GafferImageUIBindings/ImageViewBinding.cpp index 2698faf9ecc..81542c17686 100644 --- a/src/GafferImageUIBindings/ImageViewBinding.cpp +++ b/src/GafferImageUIBindings/ImageViewBinding.cpp @@ -38,12 +38,16 @@ #include "GafferBindings/NodeBinding.h" +#include "GafferImage/ImageProcessor.h" + #include "GafferImageUI/ImageView.h" #include "GafferImageUIBindings/ImageViewBinding.h" +using namespace std; using namespace boost::python; using namespace Gaffer; using namespace GafferBindings; +using namespace GafferImage; using namespace GafferImageUI; class ImageViewWrapper : public NodeWrapper @@ -65,6 +69,44 @@ class ImageViewWrapper : public NodeWrapper IE_CORE_DECLAREPTR( ImageViewWrapper ); +struct DisplayTransformCreator +{ + DisplayTransformCreator( object fn ) + : m_fn( fn ) + { + } + + ImageProcessorPtr operator()() + { + IECorePython::ScopedGILLock gilLock; + ImageProcessorPtr result = extract( m_fn() ); + return result; + } + + private : + + object m_fn; + +}; + +static void registerDisplayTransform( const std::string &name, object creator ) +{ + ImageView::registerDisplayTransform( name, DisplayTransformCreator( creator ) ); +} + +static boost::python::list registeredDisplayTransforms() +{ + vector n; + ImageView::registeredDisplayTransforms( n ); + boost::python::list result; + for( vector::const_iterator it = n.begin(), eIt = n.end(); it != eIt; ++it ) + { + result.append( *it ); + } + + return result; +} + void GafferImageUIBindings::bindImageView() { @@ -72,6 +114,10 @@ void GafferImageUIBindings::bindImageView() /// \todo Remove deprecated input argument. .def( init() ) .def( "_insertConverter", &ImageView::insertConverter ) + .def( "registerDisplayTransform", ®isterDisplayTransform ) + .staticmethod( "registerDisplayTransform" ) + .def( "registeredDisplayTransforms", ®isteredDisplayTransforms ) + .staticmethod( "registeredDisplayTransforms" ) ; } diff --git a/startup/gui/ocio.py b/startup/gui/ocio.py index 18818f173af..ec009055851 100644 --- a/startup/gui/ocio.py +++ b/startup/gui/ocio.py @@ -40,6 +40,7 @@ import Gaffer import GafferUI +import GafferImageUI # get default display setup @@ -86,5 +87,43 @@ def __plugSet( plug ) : return __setDisplayTransform() + __updateDefaultDisplayTransforms() application.__ocioPlugSetConnection = preferences.plugSetSignal().connect( __plugSet ) + +# register display transforms with the image viewer + +def __displayTransformCreator( name ) : + + result = GafferImage.OpenColorIO() + result["inputSpace"].setValue( "linear" ) + result["outputSpace"].setValue( config.getDisplayColorSpaceName( defaultDisplay, name ) ) + + return result + +for name in config.getViews( defaultDisplay ) : + GafferImageUI.ImageView.registerDisplayTransform( name, IECore.curry( __displayTransformCreator, name ) ) + +# and register a special "Default" display transform which tracks the +# global settings from the preferences + +__defaultDisplayTransforms = [] + +def __updateDefaultDisplayTransforms() : + + view = preferences["displayColorSpace"]["view"].getValue() + colorSpace = config.getDisplayColorSpaceName( defaultDisplay, view ) + for node in __defaultDisplayTransforms : + node["outputSpace"].setValue( colorSpace ) + +def __defaultDisplayTransformCreator() : + + result = GafferImage.OpenColorIO() + result["inputSpace"].setValue( "linear" ) + + __defaultDisplayTransforms.append( result ) + __updateDefaultDisplayTransforms() + + return result + +GafferImageUI.ImageView.registerDisplayTransform( "Default", __defaultDisplayTransformCreator )