diff --git a/include/GafferImageUI/ImageView.h b/include/GafferImageUI/ImageView.h index 00235d264ef..8634eb549c0 100644 --- a/include/GafferImageUI/ImageView.h +++ b/include/GafferImageUI/ImageView.h @@ -38,12 +38,21 @@ #ifndef GAFFERIMAGEUI_IMAGEVIEW_H #define GAFFERIMAGEUI_IMAGEVIEW_H -#include "GafferUI/View.h" +#include "Gaffer/NumericPlug.h" -#include "GafferImage/ImagePlug.h" -#include "GafferImage/ImageStats.h" +#include "GafferUI/View.h" #include "GafferImageUI/TypeIds.h" +#include "GafferImageUIBindings/ImageViewBinding.h" // to enable friend declaration for bindImageView(). + +namespace GafferImage +{ + +IE_CORE_FORWARDDECLARE( Grade ) +IE_CORE_FORWARDDECLARE( ImageStats ) +IE_CORE_FORWARDDECLARE( ImagePlug ) + +} // namespace GafferImage namespace GafferImageUI { @@ -58,22 +67,35 @@ class ImageView : public GafferUI::View IE_CORE_DECLARERUNTIMETYPEDEXTENSION( GafferImageUI::ImageView, ImageViewTypeId, GafferUI::View ); - protected : - - /// This constructor is for classes which derive from ImageView, but - /// don't necessarily accept an ImagePlug. Instead they should create - /// a preprocessor node that accepts the input plug and outputs an - /// image plug, and enable it using setPreprocessor(). - ImageView( const std::string &name, Gaffer::PlugPtr input ); + Gaffer::FloatPlug *exposurePlug(); + const Gaffer::FloatPlug *exposurePlug() const; + Gaffer::FloatPlug *gammaPlug(); + const Gaffer::FloatPlug *gammaPlug() const; + + protected : + + /// May be called from a subclass constructor to add a converter + /// from non-image input types, allowing them to be viewed as images. + /// The converter must have an "in" Plug (of any desired type), and + /// convert the incoming data to an image to view on an "out" ImagePlug. + /// \note If the necessary conversion requires several nodes, a Box + /// provides a means of packaging them to meet these requirements. + /// \note Subclasses are not allowed to call setProcessor() as the + /// preprocessor is managed by the ImageView base class. + void insertConverter( Gaffer::NodePtr converter ); + virtual void update(); + private: + GafferImage::ImageStats *imageStatsNode(); const GafferImage::ImageStats *imageStatsNode() const; - static ViewDescription g_viewDescription; - - private: + GafferImage::Grade *gradeNode(); + const GafferImage::Grade *gradeNode() const; + + void plugSet( Gaffer::Plug *plug ); int m_channelToView; Imath::V2f m_mousePos; @@ -81,6 +103,11 @@ class ImageView : public GafferUI::View Imath::Color4f m_minColor; Imath::Color4f m_maxColor; Imath::Color4f m_averageColor; + + static ViewDescription g_viewDescription; + + friend void GafferImageUIBindings::bindImageView(); + }; IE_CORE_DECLAREPTR( ImageView ); diff --git a/include/GafferUI/View.inl b/include/GafferUI/View.inl index 9e61f4996a5..83755cf0681 100644 --- a/include/GafferUI/View.inl +++ b/include/GafferUI/View.inl @@ -46,13 +46,13 @@ namespace GafferUI template T *View::inPlug() { - return getChild( g_firstPlugIndex ); + return getChild( "in" ); } template const T *View::inPlug() const { - return getChild( g_firstPlugIndex ); + return getChild( "in" ); } template diff --git a/python/GafferImageUITest/ImageViewTest.py b/python/GafferImageUITest/ImageViewTest.py index 9dec201ecf7..3143a543422 100644 --- a/python/GafferImageUITest/ImageViewTest.py +++ b/python/GafferImageUITest/ImageViewTest.py @@ -55,7 +55,7 @@ def testFactory( self ) : self.assertTrue( isinstance( view, GafferImageUI.ImageView ) ) self.assertTrue( view["in"].getInput().isSame( image["out"] ) ) - def testDeriving( self ) : + def testDeprecatedDeriving( self ) : class MyView( GafferImageUI.ImageView ) : @@ -82,9 +82,43 @@ def __init__( self, viewedPlug = None ) : self.assertTrue( isinstance( view, MyView ) ) self.assertTrue( view["in"].getInput().isSame( sphere["out"] ) ) self.assertTrue( isinstance( view["in"], Gaffer.ObjectPlug ) ) + view["exposure"].setValue( 1 ) + view["gamma"].setValue( 0.5 ) view._update() + def testDeriving( self ) : + + class MyView( GafferImageUI.ImageView ) : + + def __init__( self, viewedPlug = None ) : + + GafferImageUI.ImageView.__init__( self, "MyView" ) + + converter = Gaffer.Node() + converter["in"] = Gaffer.ObjectPlug( defaultValue = IECore.NullObject.defaultNullObject() ) + converter["out"] = GafferImage.ImagePlug( direction = Gaffer.Plug.Direction.Out ) + converter["constant"] = GafferImage.Constant() + converter["constant"]["format"].setValue( GafferImage.Format( 20, 20, 1 ) ) + converter["out"].setInput( converter["constant"]["out"] ) + + self._insertConverter( converter ) + + self["in"].setInput( viewedPlug ) + + GafferUI.View.registerView( GafferTest.SphereNode.staticTypeId(), "out", MyView ) + + sphere = GafferTest.SphereNode() + + view = GafferUI.View.create( sphere["out"] ) + self.assertTrue( isinstance( view, MyView ) ) + self.assertTrue( view["in"].getInput().isSame( sphere["out"] ) ) + self.assertTrue( isinstance( view["in"], Gaffer.ObjectPlug ) ) + view["exposure"].setValue( 1 ) + view["gamma"].setValue( 0.5 ) + + view._update() + if __name__ == "__main__": unittest.main() diff --git a/src/GafferImageUI/ImageView.cpp b/src/GafferImageUI/ImageView.cpp index 11e3a26646e..9516e196255 100644 --- a/src/GafferImageUI/ImageView.cpp +++ b/src/GafferImageUI/ImageView.cpp @@ -62,6 +62,9 @@ #include "GafferUI/Pointer.h" #include "GafferImage/Format.h" +#include "GafferImage/Grade.h" +#include "GafferImage/ImagePlug.h" +#include "GafferImage/ImageStats.h" #include "GafferImageUI/ImageView.h" @@ -80,6 +83,14 @@ namespace GafferImageUI namespace Detail { +/// \todo Refactor all the colour sampling and swatch drawing out of here. +/// Sampled colours should be available on the ImageView as output plugs, +/// and a new FooterToolbar type thing in the Viewer should be used for drawing +/// them using the standard Widget classes. The ImageViewGadget itself should be +/// responsible only for the setting of the input plugs on the samplers during +/// mouse move and drag and drop operations. This way the Widgets will give us all +/// the colour correction (using GafferUI.DisplayTransform), text selection and drag +/// and drop we could desire for free. class ImageViewGadget : public GafferUI::Gadget { @@ -909,42 +920,129 @@ ImageView::ViewDescription ImageView::g_viewDescription( GafferImage: ImageView::ImageView( const std::string &name ) : View( name, new GafferImage::ImagePlug() ), - m_channelToView(0), - m_mousePos(0.), - m_sampleColor(0.), - m_minColor(0.), - m_maxColor(0.), - m_averageColor(0.) + m_channelToView( 0 ), + m_mousePos( Imath::V2f( 0.0f ) ), + m_sampleColor( Imath::Color4f( 0.0f ) ), + m_minColor( Imath::Color4f( 0.0f ) ), + m_maxColor( Imath::Color4f( 0.0f ) ), + m_averageColor( Imath::Color4f( 0.0f ) ) { - // Create an internal ImageStats node - addChild( new GafferImage::ImageStats( "imageStats" ) ); + + // build the preprocessor we use for applying colour + // transforms, and the stats node we use for displaying stats. + + NodePtr preprocessor = new Node; + ImagePlugPtr preprocessorInput = new ImagePlug( "in" ); + preprocessor->addChild( preprocessorInput ); + + ImageStatsPtr statsNode = new ImageStats( "__imageStats" ); + addChild( statsNode ); /// \todo Store this in the preprocessor when we've disallowed the changing of it by subclasses + statsNode->inPlug()->setInput( preprocessorInput ); + statsNode->channelsPlug()->setInput( preprocessorInput->channelNamesPlug() ); + + GradePtr gradeNode = new Grade; + preprocessor->setChild( "__grade", gradeNode ); + gradeNode->inPlug()->setInput( preprocessorInput ); + + FloatPlugPtr exposurePlug = new FloatPlug( "exposure" ); + exposurePlug->setFlags( Plug::AcceptsInputs, false ); + addChild( exposurePlug ); // dealt with in plugSet() + + PlugPtr gammaPlug = gradeNode->gammaPlug()->getChild( 0 )->createCounterpart( "gamma", Plug::In ); + gammaPlug->setFlags( Plug::AcceptsInputs, false ); + addChild( gammaPlug ); + gradeNode->gammaPlug()->getChild( 0 )->setInput( gammaPlug ); + gradeNode->gammaPlug()->getChild( 1 )->setInput( gammaPlug ); + gradeNode->gammaPlug()->getChild( 2 )->setInput( gammaPlug ); + + ImagePlugPtr preprocessorOutput = new ImagePlug( "out", Plug::Out ); + preprocessor->addChild( preprocessorOutput ); + preprocessorOutput->setInput( gradeNode->outPlug() ); + + // tell the base class about all the preprocessing we want to do + + setPreprocessor( preprocessor ); + + // connect up to some signals + + plugSetSignal().connect( boost::bind( &ImageView::plugSet, this, ::_1 ) ); + } -ImageView::ImageView( const std::string &name, Gaffer::PlugPtr input ) - : View( name, input ), - m_channelToView(0), - m_mousePos( 0. ), - m_sampleColor(0.), - m_minColor(0.), - m_maxColor(0.), - m_averageColor(0.) +void ImageView::insertConverter( Gaffer::NodePtr converter ) { - // Create an internal ImageStats node - addChild( new GafferImage::ImageStats( "imageStats" ) ); + PlugPtr converterInput = converter->getChild( "in" ); + if( !converterInput ) + { + throw IECore::Exception( "Converter has no Plug named \"in\"" ); + } + ImagePlugPtr converterOutput = converter->getChild( "out" ); + if( !converterOutput ) + { + throw IECore::Exception( "Converter has no ImagePlug named \"out\"" ); + } + + PlugPtr newInput = converterInput->createCounterpart( "in", Plug::In ); + setChild( "in", newInput ); + + NodePtr preprocessor = getPreprocessor(); + Plug::OutputContainer outputsToRestore = preprocessor->getChild( "in" )->outputs(); + + PlugPtr newPreprocessorInput = converterInput->createCounterpart( "in", Plug::In ); + preprocessor->setChild( "in", newPreprocessorInput ); + newPreprocessorInput->setInput( newInput ); + + preprocessor->setChild( "__converter", converter ); + converterInput->setInput( newPreprocessorInput ); + + for( Plug::OutputContainer::const_iterator it = outputsToRestore.begin(), eIt = outputsToRestore.end(); it != eIt; ++it ) + { + (*it)->setInput( converterOutput ); + } } ImageView::~ImageView() { } +Gaffer::FloatPlug *ImageView::exposurePlug() +{ + return getChild( "exposure" ); +} + +const Gaffer::FloatPlug *ImageView::exposurePlug() const +{ + return getChild( "exposure" ); +} + +Gaffer::FloatPlug *ImageView::gammaPlug() +{ + return getChild( "gamma" ); +} + +const Gaffer::FloatPlug *ImageView::gammaPlug() const +{ + return getChild( "gamma" ); +} + GafferImage::ImageStats *ImageView::imageStatsNode() { - return getChild( "imageStats" ); + return getChild( "__imageStats" ); } const GafferImage::ImageStats *ImageView::imageStatsNode() const { - return getChild( "imageStats" ); + return getChild( "__imageStats" ); +} + +GafferImage::Grade *ImageView::gradeNode() +{ + return getPreprocessor()->getChild( "__grade" ); +} + +const GafferImage::Grade *ImageView::gradeNode() const +{ + return getPreprocessor()->getChild( "__grade" ); } void ImageView::update() @@ -968,6 +1066,8 @@ void ImageView::update() throw IECore::Exception("ImageView: Failed to find an input ImagePlug"); } + /// \todo We should be able to remove this when we remove the python binding + /// which takes an input plug and allows a derived class to call setPreprocessor itself. imageStatsNode()->inPlug()->setInput( imagePlug ); imageStatsNode()->channelsPlug()->setInput( imagePlug->channelNamesPlug() ); @@ -985,3 +1085,20 @@ void ImageView::update() } } +void ImageView::plugSet( Gaffer::Plug *plug ) +{ + if( plug == exposurePlug() ) + { + Grade *g = gradeNode(); + // we have to guard against g not existing until we've removed + // the deprecated constructor, after which no subclasses should + // be calling setPreprocessor(). + /// \todo Remove guard when removing constructor + if( g ) + { + const float m = pow( 2.0f, exposurePlug()->getValue() ); + g->multiplyPlug()->setValue( Color3f( m ) ); + } + } +} + diff --git a/src/GafferImageUIBindings/ImageViewBinding.cpp b/src/GafferImageUIBindings/ImageViewBinding.cpp index 212f57605a2..2698faf9ecc 100644 --- a/src/GafferImageUIBindings/ImageViewBinding.cpp +++ b/src/GafferImageUIBindings/ImageViewBinding.cpp @@ -51,6 +51,7 @@ class ImageViewWrapper : public NodeWrapper public : + /// \todo Remove deprecated input argument. ImageViewWrapper( PyObject *self, const std::string &name, Gaffer::PlugPtr input = 0 ) : NodeWrapper( self, name ) { @@ -68,7 +69,9 @@ void GafferImageUIBindings::bindImageView() { GafferBindings::NodeClass() + /// \todo Remove deprecated input argument. .def( init() ) + .def( "_insertConverter", &ImageView::insertConverter ) ; }