From a96fd46fd048fe4987db0b4945e331d7dcbc9443 Mon Sep 17 00:00:00 2001 From: Andrew Kaufman Date: Tue, 3 Nov 2015 12:12:00 -0800 Subject: [PATCH 01/13] Renamed ImageReader to OpenImageIOReader. This is to make way for an ImageReader uber-node which wraps OpenImageIOReader with some more user-friendly functionality. --- .../{ImageReader.h => OpenImageIOReader.h} | 23 ++++---- include/GafferImage/TypeIds.h | 1 + ...erBinding.h => OpenImageIOReaderBinding.h} | 10 ++-- ...ReaderTest.py => OpenImageIOReaderTest.py} | 34 ++++++------ python/GafferImageTest/__init__.py | 2 +- ...mageReaderUI.py => OpenImageIOReaderUI.py} | 11 ++-- python/GafferImageUI/__init__.py | 2 +- ...{ImageReader.cpp => OpenImageIOReader.cpp} | 52 +++++++++---------- ...nding.cpp => OpenImageIOReaderBinding.cpp} | 16 +++--- src/GafferImageModule/GafferImageModule.cpp | 4 +- startup/gui/cache.py | 6 +-- 11 files changed, 81 insertions(+), 80 deletions(-) rename include/GafferImage/{ImageReader.h => OpenImageIOReader.h} (86%) rename include/GafferImageBindings/{ImageReaderBinding.h => OpenImageIOReaderBinding.h} (87%) rename python/GafferImageTest/{ImageReaderTest.py => OpenImageIOReaderTest.py} (93%) rename python/GafferImageUI/{ImageReaderUI.py => OpenImageIOReaderUI.py} (86%) rename src/GafferImage/{ImageReader.cpp => OpenImageIOReader.cpp} (86%) rename src/GafferImageBindings/{ImageReaderBinding.cpp => OpenImageIOReaderBinding.cpp} (80%) diff --git a/include/GafferImage/ImageReader.h b/include/GafferImage/OpenImageIOReader.h similarity index 86% rename from include/GafferImage/ImageReader.h rename to include/GafferImage/OpenImageIOReader.h index adb94573832..dae2dc58da6 100644 --- a/include/GafferImage/ImageReader.h +++ b/include/GafferImage/OpenImageIOReader.h @@ -1,7 +1,7 @@ ////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012, John Haddon. All rights reserved. -// Copyright (c) 2012-2014, Image Engine Design Inc. All rights reserved. +// Copyright (c) 2012-2015, Image Engine Design Inc. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -35,8 +35,8 @@ // ////////////////////////////////////////////////////////////////////////// -#ifndef GAFFERSCENE_IMAGEREADER_H -#define GAFFERSCENE_IMAGEREADER_H +#ifndef GAFFERIMAGE_OPENIMAGEIOREADER_H +#define GAFFERIMAGE_OPENIMAGEIOREADER_H #include "Gaffer/NumericPlug.h" @@ -53,19 +53,18 @@ namespace GafferImage { /// \todo Linearise images. Perhaps this should be done by a super-node which just -/// packages up an internal ImageReader and OpenColorIO node? If so then perhaps -/// we should rename this class to SimpleImageReader or something? Perhaps we could -/// also have a metaData() plug in the ImagePlug, fill it with the file metadata, +/// packages up an internal OpenImageIOReader and OpenColorIO node? Perhaps we could +/// use the metaData() plug to fill it with the file metadata, /// and use that to pass the input colorspace into the internal OpenColorIO node. -class ImageReader : public ImageNode +class OpenImageIOReader : public ImageNode { public : - ImageReader( const std::string &name=defaultName() ); - virtual ~ImageReader(); + OpenImageIOReader( const std::string &name=defaultName() ); + virtual ~OpenImageIOReader(); - IE_CORE_DECLARERUNTIMETYPEDEXTENSION( GafferImage::ImageReader, ImageReaderTypeId, ImageNode ); + IE_CORE_DECLARERUNTIMETYPEDEXTENSION( GafferImage::OpenImageIOReader, OpenImageIOReaderTypeId, ImageNode ); Gaffer::StringPlug *fileNamePlug(); const Gaffer::StringPlug *fileNamePlug() const; @@ -105,8 +104,8 @@ class ImageReader : public ImageNode }; -IE_CORE_DECLAREPTR( ImageReader ) +IE_CORE_DECLAREPTR( OpenImageIOReader ) } // namespace GafferImage -#endif // GAFFERSCENE_IMAGEREADER_H +#endif // GAFFERIMAGE_OPENIMAGEIOREADER_H diff --git a/include/GafferImage/TypeIds.h b/include/GafferImage/TypeIds.h index 59ccd6e29e3..4e478d30021 100644 --- a/include/GafferImage/TypeIds.h +++ b/include/GafferImage/TypeIds.h @@ -106,6 +106,7 @@ enum TypeId BlurTypeId = 110810, ShapeTypeId = 110811, TextTypeId = 110812, + OpenImageIOReaderTypeId = 110813, LastTypeId = 110849 }; diff --git a/include/GafferImageBindings/ImageReaderBinding.h b/include/GafferImageBindings/OpenImageIOReaderBinding.h similarity index 87% rename from include/GafferImageBindings/ImageReaderBinding.h rename to include/GafferImageBindings/OpenImageIOReaderBinding.h index 2799dcf67f2..ed4a4ed20e3 100644 --- a/include/GafferImageBindings/ImageReaderBinding.h +++ b/include/GafferImageBindings/OpenImageIOReaderBinding.h @@ -1,6 +1,6 @@ ////////////////////////////////////////////////////////////////////////// // -// Copyright (c) 2014, Image Engine Design Inc. All rights reserved. +// Copyright (c) 2014-2015, Image Engine Design Inc. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -34,14 +34,14 @@ // ////////////////////////////////////////////////////////////////////////// -#ifndef GAFFERIMAGEBINDINGS_IMAGEREADERBINDING_H -#define GAFFERIMAGEBINDINGS_IMAGEREADERBINDING_H +#ifndef GAFFERIMAGEBINDINGS_OPENIMAGEIOREADERBINDING_H +#define GAFFERIMAGEBINDINGS_OPENIMAGEIOREADERBINDING_H namespace GafferImageBindings { -void bindImageReader(); +void bindOpenImageIOReader(); } // namespace GafferImageBindings -#endif // GAFFERIMAGEBINDINGS_IMAGEREADERBINDING_H +#endif // GAFFERIMAGEBINDINGS_OPENIMAGEIOREADERBINDING_H diff --git a/python/GafferImageTest/ImageReaderTest.py b/python/GafferImageTest/OpenImageIOReaderTest.py similarity index 93% rename from python/GafferImageTest/ImageReaderTest.py rename to python/GafferImageTest/OpenImageIOReaderTest.py index 316766aa1e9..8895ab310c2 100644 --- a/python/GafferImageTest/ImageReaderTest.py +++ b/python/GafferImageTest/OpenImageIOReaderTest.py @@ -46,7 +46,7 @@ import GafferImage import GafferImageTest -class ImageReaderTest( GafferImageTest.ImageTestCase ) : +class OpenImageIOReaderTest( GafferImageTest.TestCase ) : fileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/checker.exr" ) offsetDataWindowFileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/rgb.100x100.exr" ) @@ -62,7 +62,7 @@ def testInternalImageSpaceConversion( self ) : exrDisplayWindow = image.displayWindow exrDataWindow = image.dataWindow - n = GafferImage.ImageReader() + n = GafferImage.OpenImageIOReader() n["fileName"].setValue( self.negativeDataWindowFileName ) gafferFormat = n["out"]["format"].getValue() @@ -78,7 +78,7 @@ def testInternalImageSpaceConversion( self ) : def test( self ) : - n = GafferImage.ImageReader() + n = GafferImage.OpenImageIOReader() n["fileName"].setValue( self.fileName ) self.assertEqual( n["out"]["dataWindow"].getValue(), IECore.Box2i( IECore.V2i( 0 ), IECore.V2i( 200, 150 ) ) ) @@ -110,7 +110,7 @@ def test( self ) : def testNegativeDisplayWindowRead( self ) : - n = GafferImage.ImageReader() + n = GafferImage.OpenImageIOReader() n["fileName"].setValue( self.negativeDisplayWindowFileName ) f = n["out"]["format"].getValue() d = n["out"]["dataWindow"].getValue() @@ -125,7 +125,7 @@ def testNegativeDisplayWindowRead( self ) : def testNegativeDataWindow( self ) : - n = GafferImage.ImageReader() + n = GafferImage.OpenImageIOReader() n["fileName"].setValue( self.negativeDataWindowFileName ) self.assertEqual( n["out"]["dataWindow"].getValue(), IECore.Box2i( IECore.V2i( -25, -30 ), IECore.V2i( 175, 120 ) ) ) self.assertEqual( n["out"]["format"].getValue().getDisplayWindow(), IECore.Box2i( IECore.V2i( 0 ), IECore.V2i( 200, 150 ) ) ) @@ -148,7 +148,7 @@ def testNegativeDataWindow( self ) : def testTileSize( self ) : - n = GafferImage.ImageReader() + n = GafferImage.OpenImageIOReader() n["fileName"].setValue( self.fileName ) tile = n["out"].channelData( "R", IECore.V2i( 0 ) ) @@ -156,7 +156,7 @@ def testTileSize( self ) : def testNoCaching( self ) : - n = GafferImage.ImageReader() + n = GafferImage.OpenImageIOReader() n["fileName"].setValue( self.fileName ) c = Gaffer.Context() @@ -175,13 +175,13 @@ def testNoCaching( self ) : def testUnspecifiedFilename( self ) : - n = GafferImage.ImageReader() + n = GafferImage.OpenImageIOReader() n["out"]["channelNames"].getValue() n["out"].channelData( "R", IECore.V2i( 0 ) ) def testNoOIIOErrorBufferOverflows( self ) : - n = GafferImage.ImageReader() + n = GafferImage.OpenImageIOReader() n["fileName"].setValue( "thisReallyReallyReallyReallyReallyReallyReallyReallyReallyLongFilenameDoesNotExist.tif" ) for i in range( 0, 300000 ) : @@ -190,7 +190,7 @@ def testNoOIIOErrorBufferOverflows( self ) : def testChannelDataHashes( self ) : # Test that two tiles within the same image have different hashes. - n = GafferImage.ImageReader() + n = GafferImage.OpenImageIOReader() n["fileName"].setValue( self.fileName ) h1 = n["out"].channelData( "R", IECore.V2i( 0 ) ).hash() h2 = n["out"].channelData( "R", IECore.V2i( GafferImage.ImagePlug().tileSize() ) ).hash() @@ -199,7 +199,7 @@ def testChannelDataHashes( self ) : def testDisabledChannelDataHashes( self ) : # Test that two tiles within the same image have the same hash when disabled. - n = GafferImage.ImageReader() + n = GafferImage.OpenImageIOReader() n["fileName"].setValue( self.fileName ) n["enabled"].setValue( False ) h1 = n["out"].channelData( "R", IECore.V2i( 0 ) ).hash() @@ -209,7 +209,7 @@ def testDisabledChannelDataHashes( self ) : def testOffsetDataWindowOrigin( self ) : - n = GafferImage.ImageReader() + n = GafferImage.OpenImageIOReader() n["fileName"].setValue( self.offsetDataWindowFileName ) image = n["out"].image() @@ -222,10 +222,10 @@ def testOffsetDataWindowOrigin( self ) : def testJpgRead( self ) : - exrReader = GafferImage.ImageReader() + exrReader = GafferImage.OpenImageIOReader() exrReader["fileName"].setValue( self.circlesExrFileName ) - jpgReader = GafferImage.ImageReader() + jpgReader = GafferImage.OpenImageIOReader() jpgReader["fileName"].setValue( self.circlesJpgFileName ) ## \todo This colorspace manipulation needs to be performed # automatically in the ImageReader. @@ -259,7 +259,7 @@ def testOIIOExrRead( self ) : def testSupportedExtensions( self ) : - e = GafferImage.ImageReader.supportedExtensions() + e = GafferImage.OpenImageIOReader.supportedExtensions() self.assertTrue( "exr" in e ) self.assertTrue( "jpg" in e ) @@ -273,7 +273,7 @@ def testFileRefresh( self ) : testFile = self.temporaryDirectory() + "/refresh.exr" shutil.copyfile( self.fileName, testFile ) - reader = GafferImage.ImageReader() + reader = GafferImage.OpenImageIOReader() reader["fileName"].setValue( testFile ) image1 = reader["out"].image() @@ -288,7 +288,7 @@ def testFileRefresh( self ) : def testNonexistentFiles( self ) : - reader = GafferImage.ImageReader() + reader = GafferImage.OpenImageIOReader() reader["fileName"].setValue( "wellIDontExist.exr" ) self.assertRaisesRegexp( RuntimeError, ".*wellIDontExist.exr.*", reader["out"].image ) diff --git a/python/GafferImageTest/__init__.py b/python/GafferImageTest/__init__.py index 87e647c4495..4ca4a4cf148 100644 --- a/python/GafferImageTest/__init__.py +++ b/python/GafferImageTest/__init__.py @@ -39,7 +39,7 @@ from ImageTestCase import ImageTestCase from ImagePlugTest import ImagePlugTest -from ImageReaderTest import ImageReaderTest +from OpenImageIOReaderTest import OpenImageIOReaderTest from ColorSpaceTest import ColorSpaceTest from ObjectToImageTest import ObjectToImageTest from FormatTest import FormatTest diff --git a/python/GafferImageUI/ImageReaderUI.py b/python/GafferImageUI/OpenImageIOReaderUI.py similarity index 86% rename from python/GafferImageUI/ImageReaderUI.py rename to python/GafferImageUI/OpenImageIOReaderUI.py index f49ca0a2f46..b8913d85e42 100644 --- a/python/GafferImageUI/ImageReaderUI.py +++ b/python/GafferImageUI/OpenImageIOReaderUI.py @@ -42,12 +42,13 @@ Gaffer.Metadata.registerNode( - GafferImage.ImageReader, + GafferImage.OpenImageIOReader, "description", """ - Reads image files from disk using OpenImageIO. All file - types supported by OpenImageIO are supported by the ImageReader. + Utility node which reads image files from disk using OpenImageIO. + All file types supported by OpenImageIO are supported by the + OpenImageIOReader. """, plugs = { @@ -64,7 +65,7 @@ "plugValueWidget:type", "GafferUI.FileSystemPathPlugValueWidget", "pathPlugValueWidget:leaf", True, "pathPlugValueWidget:bookmarks", "image", - "fileSystemPathPlugValueWidget:extensions", IECore.StringVectorData( GafferImage.ImageReader.supportedExtensions() ), + "fileSystemPathPlugValueWidget:extensions", IECore.StringVectorData( GafferImage.OpenImageIOReader.supportedExtensions() ), "fileSystemPathPlugValueWidget:extensionsLabel", "Show only image files", "fileSystemPathPlugValueWidget:includeSequences", True, @@ -85,4 +86,4 @@ ) -GafferUI.PlugValueWidget.registerCreator( GafferImage.ImageReader, "refreshCount", GafferUI.IncrementingPlugValueWidget, label = "Refresh", undoable = False ) +GafferUI.PlugValueWidget.registerCreator( GafferImage.OpenImageIOReader, "refreshCount", GafferUI.IncrementingPlugValueWidget, label = "Refresh", undoable = False ) diff --git a/python/GafferImageUI/__init__.py b/python/GafferImageUI/__init__.py index 34b6e085cc3..b1ddc2e1cc2 100644 --- a/python/GafferImageUI/__init__.py +++ b/python/GafferImageUI/__init__.py @@ -41,7 +41,7 @@ from FormatPlugValueWidget import FormatPlugValueWidget from ChannelMaskPlugValueWidget import ChannelMaskPlugValueWidget -import ImageReaderUI +import OpenImageIOReaderUI import ImageViewToolbar import ImageTransformUI import ConstantUI diff --git a/src/GafferImage/ImageReader.cpp b/src/GafferImage/OpenImageIOReader.cpp similarity index 86% rename from src/GafferImage/ImageReader.cpp rename to src/GafferImage/OpenImageIOReader.cpp index 4361ed1ce6a..2e079d632b4 100644 --- a/src/GafferImage/ImageReader.cpp +++ b/src/GafferImage/OpenImageIOReader.cpp @@ -45,7 +45,7 @@ OIIO_NAMESPACE_USING #include "Gaffer/Context.h" #include "Gaffer/StringPlug.h" -#include "GafferImage/ImageReader.h" +#include "GafferImage/OpenImageIOReader.h" #include "GafferImage/FormatPlug.h" using namespace std; @@ -293,14 +293,14 @@ void oiioParameterListToMetadata( const ImageIOParameterList ¶mList, Compoun } // namespace ////////////////////////////////////////////////////////////////////////// -// ImageReader implementation +// OpenImageIOReader implementation ////////////////////////////////////////////////////////////////////////// -IE_CORE_DEFINERUNTIMETYPED( ImageReader ); +IE_CORE_DEFINERUNTIMETYPED( OpenImageIOReader ); -size_t ImageReader::g_firstPlugIndex = 0; +size_t OpenImageIOReader::g_firstPlugIndex = 0; -ImageReader::ImageReader( const std::string &name ) +OpenImageIOReader::OpenImageIOReader( const std::string &name ) : ImageNode( name ) { storeIndexOfNextChild( g_firstPlugIndex ); @@ -313,34 +313,34 @@ ImageReader::ImageReader( const std::string &name ) (*it)->setFlags( Plug::Cacheable, false ); } - plugSetSignal().connect( boost::bind( &ImageReader::plugSet, this, ::_1 ) ); + plugSetSignal().connect( boost::bind( &OpenImageIOReader::plugSet, this, ::_1 ) ); } -ImageReader::~ImageReader() +OpenImageIOReader::~OpenImageIOReader() { } -Gaffer::StringPlug *ImageReader::fileNamePlug() +Gaffer::StringPlug *OpenImageIOReader::fileNamePlug() { return getChild( g_firstPlugIndex ); } -const Gaffer::StringPlug *ImageReader::fileNamePlug() const +const Gaffer::StringPlug *OpenImageIOReader::fileNamePlug() const { return getChild( g_firstPlugIndex ); } -Gaffer::IntPlug *ImageReader::refreshCountPlug() +Gaffer::IntPlug *OpenImageIOReader::refreshCountPlug() { return getChild( g_firstPlugIndex + 1 ); } -const Gaffer::IntPlug *ImageReader::refreshCountPlug() const +const Gaffer::IntPlug *OpenImageIOReader::refreshCountPlug() const { return getChild( g_firstPlugIndex + 1 ); } -size_t ImageReader::supportedExtensions( std::vector &extensions ) +size_t OpenImageIOReader::supportedExtensions( std::vector &extensions ) { std::string attr; if( !getattribute( "extension_list", attr ) ) @@ -364,7 +364,7 @@ size_t ImageReader::supportedExtensions( std::vector &extensions ) return extensions.size(); } -void ImageReader::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const +void OpenImageIOReader::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const { ImageNode::affects( input, outputs ); @@ -377,14 +377,14 @@ void ImageReader::affects( const Gaffer::Plug *input, AffectedPlugsContainer &ou } } -void ImageReader::hashFormat( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void OpenImageIOReader::hashFormat( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ImageNode::hashFormat( output, context, h ); fileNamePlug()->hash( h ); refreshCountPlug()->hash( h ); } -GafferImage::Format ImageReader::computeFormat( const Gaffer::Context *context, const ImagePlug *parent ) const +GafferImage::Format OpenImageIOReader::computeFormat( const Gaffer::Context *context, const ImagePlug *parent ) const { const ImageSpec *spec = imageSpec( ustring( fileNamePlug()->getValue() ) ); if( !spec ) @@ -401,14 +401,14 @@ GafferImage::Format ImageReader::computeFormat( const Gaffer::Context *context, ); } -void ImageReader::hashDataWindow( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void OpenImageIOReader::hashDataWindow( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ImageNode::hashDataWindow( output, context, h ); fileNamePlug()->hash( h ); refreshCountPlug()->hash( h ); } -Imath::Box2i ImageReader::computeDataWindow( const Gaffer::Context *context, const ImagePlug *parent ) const +Imath::Box2i OpenImageIOReader::computeDataWindow( const Gaffer::Context *context, const ImagePlug *parent ) const { const ImageSpec *spec = imageSpec( ustring( fileNamePlug()->getValue() ) ); if( !spec ) @@ -422,14 +422,14 @@ Imath::Box2i ImageReader::computeDataWindow( const Gaffer::Context *context, con return format.fromEXRSpace( dataWindow ); } -void ImageReader::hashMetadata( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void OpenImageIOReader::hashMetadata( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ImageNode::hashMetadata( output, context, h ); fileNamePlug()->hash( h ); refreshCountPlug()->hash( h ); } -IECore::ConstCompoundObjectPtr ImageReader::computeMetadata( const Gaffer::Context *context, const ImagePlug *parent ) const +IECore::ConstCompoundObjectPtr OpenImageIOReader::computeMetadata( const Gaffer::Context *context, const ImagePlug *parent ) const { const ImageSpec *spec = imageSpec( ustring( fileNamePlug()->getValue() ) ); if( !spec ) @@ -443,14 +443,14 @@ IECore::ConstCompoundObjectPtr ImageReader::computeMetadata( const Gaffer::Conte return result; } -void ImageReader::hashChannelNames( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void OpenImageIOReader::hashChannelNames( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ImageNode::hashChannelNames( output, context, h ); fileNamePlug()->hash( h ); refreshCountPlug()->hash( h ); } -IECore::ConstStringVectorDataPtr ImageReader::computeChannelNames( const Gaffer::Context *context, const ImagePlug *parent ) const +IECore::ConstStringVectorDataPtr OpenImageIOReader::computeChannelNames( const Gaffer::Context *context, const ImagePlug *parent ) const { const ImageSpec *spec = imageSpec( ustring( fileNamePlug()->getValue() ) ); if( !spec ) @@ -463,7 +463,7 @@ IECore::ConstStringVectorDataPtr ImageReader::computeChannelNames( const Gaffer: return result; } -void ImageReader::hashChannelData( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void OpenImageIOReader::hashChannelData( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ImageNode::hashChannelData( output, context, h ); h.append( context->get( ImagePlug::tileOriginContextName ) ); @@ -472,7 +472,7 @@ void ImageReader::hashChannelData( const GafferImage::ImagePlug *output, const G refreshCountPlug()->hash( h ); } -IECore::ConstFloatVectorDataPtr ImageReader::computeChannelData( const std::string &channelName, const Imath::V2i &tileOrigin, const Gaffer::Context *context, const ImagePlug *parent ) const +IECore::ConstFloatVectorDataPtr OpenImageIOReader::computeChannelData( const std::string &channelName, const Imath::V2i &tileOrigin, const Gaffer::Context *context, const ImagePlug *parent ) const { ustring fileName( fileNamePlug()->getValue() ); const ImageSpec *spec = imageSpec( fileName ); @@ -519,19 +519,19 @@ IECore::ConstFloatVectorDataPtr ImageReader::computeChannelData( const std::stri return resultData; } -size_t ImageReader::getCacheMemoryLimit() +size_t OpenImageIOReader::getCacheMemoryLimit() { float memoryLimit; imageCache()->getattribute( "max_memory_MB", memoryLimit ); return (size_t)memoryLimit; } -void ImageReader::setCacheMemoryLimit( size_t mb ) +void OpenImageIOReader::setCacheMemoryLimit( size_t mb ) { imageCache()->attribute( "max_memory_MB", float( mb ) ); } -void ImageReader::plugSet( Gaffer::Plug *plug ) +void OpenImageIOReader::plugSet( Gaffer::Plug *plug ) { // this clears the cache every time the refresh count is updated, so you don't get entries // from old files hanging around. diff --git a/src/GafferImageBindings/ImageReaderBinding.cpp b/src/GafferImageBindings/OpenImageIOReaderBinding.cpp similarity index 80% rename from src/GafferImageBindings/ImageReaderBinding.cpp rename to src/GafferImageBindings/OpenImageIOReaderBinding.cpp index dc7a2934f91..1691b199249 100644 --- a/src/GafferImageBindings/ImageReaderBinding.cpp +++ b/src/GafferImageBindings/OpenImageIOReaderBinding.cpp @@ -1,6 +1,6 @@ ////////////////////////////////////////////////////////////////////////// // -// Copyright (c) 2014, Image Engine Design Inc. All rights reserved. +// Copyright (c) 2014-2015, Image Engine Design Inc. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -38,16 +38,16 @@ #include "GafferBindings/DependencyNodeBinding.h" -#include "GafferImage/ImageReader.h" +#include "GafferImage/OpenImageIOReader.h" -#include "GafferImageBindings/ImageReaderBinding.h" +#include "GafferImageBindings/OpenImageIOReaderBinding.h" using namespace GafferImage; static boost::python::list supportedExtensions() { std::vector e; - ImageReader::supportedExtensions( e ); + OpenImageIOReader::supportedExtensions( e ); boost::python::list result; for( std::vector::const_iterator it = e.begin(), eIt = e.end(); it != eIt; ++it ) @@ -58,13 +58,13 @@ static boost::python::list supportedExtensions() return result; } -void GafferImageBindings::bindImageReader() +void GafferImageBindings::bindOpenImageIOReader() { - GafferBindings::DependencyNodeClass() + GafferBindings::DependencyNodeClass() .def( "supportedExtensions", &supportedExtensions ).staticmethod( "supportedExtensions" ) - .def( "getCacheMemoryLimit", &ImageReader::getCacheMemoryLimit ).staticmethod( "getCacheMemoryLimit" ) - .def( "setCacheMemoryLimit", &ImageReader::setCacheMemoryLimit ).staticmethod( "setCacheMemoryLimit" ) + .def( "getCacheMemoryLimit", &OpenImageIOReader::getCacheMemoryLimit ).staticmethod( "getCacheMemoryLimit" ) + .def( "setCacheMemoryLimit", &OpenImageIOReader::setCacheMemoryLimit ).staticmethod( "setCacheMemoryLimit" ) ; } diff --git a/src/GafferImageModule/GafferImageModule.cpp b/src/GafferImageModule/GafferImageModule.cpp index f990e8f7ce6..bd8afb730ea 100644 --- a/src/GafferImageModule/GafferImageModule.cpp +++ b/src/GafferImageModule/GafferImageModule.cpp @@ -75,7 +75,7 @@ #include "GafferImageBindings/MergeBinding.h" #include "GafferImageBindings/MixinBinding.h" #include "GafferImageBindings/FormatDataBinding.h" -#include "GafferImageBindings/ImageReaderBinding.h" +#include "GafferImageBindings/OpenImageIOReaderBinding.h" #include "GafferImageBindings/ImageWriterBinding.h" #include "GafferImageBindings/ShuffleBinding.h" #include "GafferImageBindings/CropBinding.h" @@ -132,7 +132,7 @@ BOOST_PYTHON_MODULE( _GafferImage ) GafferImageBindings::bindSampler(); GafferImageBindings::bindMixin(); GafferImageBindings::bindFormatData(); - GafferImageBindings::bindImageReader(); + GafferImageBindings::bindOpenImageIOReader(); GafferImageBindings::bindImageWriter(); GafferImageBindings::bindMerge(); GafferImageBindings::bindShuffle(); diff --git a/startup/gui/cache.py b/startup/gui/cache.py index fc02ef8d305..4a0cd16b9ad 100644 --- a/startup/gui/cache.py +++ b/startup/gui/cache.py @@ -44,7 +44,7 @@ preferences["cache"] = Gaffer.CompoundPlug() preferences["cache"]["enabled"] = Gaffer.BoolPlug( defaultValue = True ) preferences["cache"]["memoryLimit"] = Gaffer.IntPlug( defaultValue = Gaffer.ValuePlug.getCacheMemoryLimit() / ( 1024 * 1024 ) ) -preferences["cache"]["imageReaderMemoryLimit"] = Gaffer.IntPlug( defaultValue = GafferImage.ImageReader.getCacheMemoryLimit() ) +preferences["cache"]["imageReaderMemoryLimit"] = Gaffer.IntPlug( defaultValue = GafferImage.OpenImageIOReader.getCacheMemoryLimit() ) Gaffer.Metadata.registerPlugValue( preferences["cache"]["memoryLimit"], @@ -58,7 +58,7 @@ preferences["cache"]["imageReaderMemoryLimit"], "description", """ - Controls the memory limit for the OpenImageIO cache that the ImageReader node uses. + Controls the memory limit for the OpenImageIO cache that the OpenImageIOReader node uses. """ ) @@ -77,6 +77,6 @@ def __plugSet( plug ) : imageReaderMemoryLimit = 0 Gaffer.ValuePlug.setCacheMemoryLimit( memoryLimit ) - GafferImage.ImageReader.setCacheMemoryLimit( imageReaderMemoryLimit ) + GafferImage.OpenImageIOReader.setCacheMemoryLimit( imageReaderMemoryLimit ) application.__cachePlugSetConnection = preferences.plugSetSignal().connect( __plugSet ) From 11959daadbaa75345b935ebc6244ede45ad360c1 Mon Sep 17 00:00:00 2001 From: Andrew Kaufman Date: Wed, 4 Nov 2015 11:54:57 -0800 Subject: [PATCH 02/13] Added missing frame control to OpenImageIOReader. --- include/GafferImage/OpenImageIOReader.h | 20 +- .../GafferImageTest/OpenImageIOReaderTest.py | 215 ++++++++++++++++++ python/GafferImageUI/OpenImageIOReaderUI.py | 38 +++- src/GafferImage/OpenImageIOReader.cpp | 189 +++++++++++++-- .../OpenImageIOReaderBinding.cpp | 8 +- 5 files changed, 451 insertions(+), 19 deletions(-) diff --git a/include/GafferImage/OpenImageIOReader.h b/include/GafferImage/OpenImageIOReader.h index dae2dc58da6..18d69b482ab 100644 --- a/include/GafferImage/OpenImageIOReader.h +++ b/include/GafferImage/OpenImageIOReader.h @@ -66,13 +66,26 @@ class OpenImageIOReader : public ImageNode IE_CORE_DECLARERUNTIMETYPEDEXTENSION( GafferImage::OpenImageIOReader, OpenImageIOReaderTypeId, ImageNode ); + enum MissingFrameMode + { + Error = 0, + Black, + Hold, + }; + Gaffer::StringPlug *fileNamePlug(); const Gaffer::StringPlug *fileNamePlug() const; /// Number of times the node has been refreshed. Gaffer::IntPlug *refreshCountPlug(); const Gaffer::IntPlug *refreshCountPlug() const; - + + Gaffer::IntPlug *missingFrameModePlug(); + const Gaffer::IntPlug *missingFrameModePlug() const; + + Gaffer::IntVectorDataPlug *availableFramesPlug(); + const Gaffer::IntVectorDataPlug *availableFramesPlug() const; + virtual void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const; static size_t supportedExtensions( std::vector &extensions ); @@ -84,6 +97,9 @@ class OpenImageIOReader : public ImageNode protected : + virtual void hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const; + virtual void compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const; + virtual void hashFormat( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const; virtual void hashDataWindow( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const; virtual void hashMetadata( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const; @@ -98,6 +114,8 @@ class OpenImageIOReader : public ImageNode private : + void hashFileName( const Gaffer::Context *context, IECore::MurmurHash &h ) const; + void plugSet( Gaffer::Plug *plug ); static size_t g_firstPlugIndex; diff --git a/python/GafferImageTest/OpenImageIOReaderTest.py b/python/GafferImageTest/OpenImageIOReaderTest.py index 8895ab310c2..c6dfb13f4c1 100644 --- a/python/GafferImageTest/OpenImageIOReaderTest.py +++ b/python/GafferImageTest/OpenImageIOReaderTest.py @@ -297,6 +297,221 @@ def testNonexistentFiles( self ) : self.assertRaisesRegexp( RuntimeError, ".*wellIDontExist.exr.*", reader["out"]["metadata"].getValue ) self.assertRaisesRegexp( RuntimeError, ".*wellIDontExist.exr.*", reader["out"]["channelNames"].getValue ) self.assertRaisesRegexp( RuntimeError, ".*wellIDontExist.exr.*", reader["out"].channelData, "R", IECore.V2i( 0 ) ) + + def testAvailableFrames( self ) : + + testSequence = IECore.FileSequence( self.temporaryDirectory() + "/incompleteSequence.####.exr" ) + shutil.copyfile( self.fileName, testSequence.fileNameForFrame( 1 ) ) + shutil.copyfile( self.offsetDataWindowFileName, testSequence.fileNameForFrame( 3 ) ) + + reader = GafferImage.OpenImageIOReader() + reader["fileName"].setValue( testSequence.fileName ) + + self.assertEqual( reader["availableFrames"].getValue(), IECore.IntVectorData( [ 1, 3 ] ) ) + + # it doesn't update until we refresh + shutil.copyfile( self.offsetDataWindowFileName, testSequence.fileNameForFrame( 5 ) ) + self.assertEqual( reader["availableFrames"].getValue(), IECore.IntVectorData( [ 1, 3 ] ) ) + reader["refreshCount"].setValue( reader["refreshCount"].getValue() + 1 ) + self.assertEqual( reader["availableFrames"].getValue(), IECore.IntVectorData( [ 1, 3, 5 ] ) ) + + # explicit file paths aren't considered a sequence + reader["fileName"].setValue( self.fileName ) + self.assertEqual( reader["availableFrames"].getValue(), IECore.IntVectorData( [] ) ) + reader["fileName"].setValue( testSequence.fileNameForFrame( 1 ) ) + self.assertEqual( reader["availableFrames"].getValue(), IECore.IntVectorData( [] ) ) + + def testMissingFrameMode( self ) : + + testSequence = IECore.FileSequence( self.temporaryDirectory() + "/incompleteSequence.####.exr" ) + shutil.copyfile( self.fileName, testSequence.fileNameForFrame( 1 ) ) + shutil.copyfile( self.offsetDataWindowFileName, testSequence.fileNameForFrame( 3 ) ) + + reader = GafferImage.OpenImageIOReader() + reader["fileName"].setValue( testSequence.fileName ) + + context = Gaffer.Context() + + # get frame 1 data for comparison + context.setFrame( 1 ) + with context : + f1Image = reader["out"].image() + f1Format = reader["out"]["format"].getValue() + f1DataWindow = reader["out"]["dataWindow"].getValue() + f1Metadata = reader["out"]["metadata"].getValue() + f1ChannelNames = reader["out"]["channelNames"].getValue() + f1Tile = reader["out"].channelData( "R", IECore.V2i( 0 ) ) + + # make sure the tile we're comparing isn't black + # so we can tell if MissingFrameMode::Black is working. + blackTile = IECore.FloatVectorData( [ 0 ] * GafferImage.ImagePlug.tileSize() * GafferImage.ImagePlug.tileSize() ) + self.assertNotEqual( f1Tile, blackTile ) + + # set to a missing frame + context.setFrame( 2 ) + + # everything throws + reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Error ) + with context : + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].image ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["dataWindow"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["metadata"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["channelNames"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].channelData, "R", IECore.V2i( 0 ) ) + + # everything matches frame 1 + reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold ) + with context : + self.assertEqual( reader["out"].image(), f1Image ) + self.assertEqual( reader["out"]["format"].getValue(), f1Format ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), f1DataWindow ) + self.assertEqual( reader["out"]["metadata"].getValue(), f1Metadata ) + self.assertEqual( reader["out"]["channelNames"].getValue(), f1ChannelNames ) + self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), f1Tile ) + + # the windows match frame 1, but everything else is default + reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) + with context : + self.assertNotEqual( reader["out"].image(), f1Image ) + self.assertEqual( reader["out"]["format"].getValue(), f1Format ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), f1DataWindow ) + self.assertEqual( reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue() ) + self.assertEqual( reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue() ) + self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), blackTile ) + + # get frame 3 data for comparison + context.setFrame( 3 ) + with context : + f3Image = reader["out"].image() + f3Format = reader["out"]["format"].getValue() + f3DataWindow = reader["out"]["dataWindow"].getValue() + f3Metadata = reader["out"]["metadata"].getValue() + f3ChannelNames = reader["out"]["channelNames"].getValue() + f3Tile = reader["out"].channelData( "R", IECore.V2i( 0 ) ) + + # set to a different missing frame + context.setFrame( 4 ) + + # everything matches frame 3 + reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold ) + with context : + self.assertNotEqual( reader["out"].image(), f1Image ) + self.assertNotEqual( reader["out"]["format"].getValue(), f1Format ) + self.assertNotEqual( reader["out"]["dataWindow"].getValue(), f1DataWindow ) + self.assertNotEqual( reader["out"]["metadata"].getValue(), f1Metadata ) + # same channel names is fine + self.assertEqual( reader["out"]["channelNames"].getValue(), f1ChannelNames ) + self.assertNotEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), f1Tile ) + self.assertEqual( reader["out"].image(), f3Image ) + self.assertEqual( reader["out"]["format"].getValue(), f3Format ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), f3DataWindow ) + self.assertEqual( reader["out"]["metadata"].getValue(), f3Metadata ) + self.assertEqual( reader["out"]["channelNames"].getValue(), f3ChannelNames ) + self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), f3Tile ) + + # the windows match frame 3, but everything else is default + reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) + with context : + self.assertNotEqual( reader["out"]["format"].getValue(), f1Format ) + self.assertNotEqual( reader["out"]["dataWindow"].getValue(), f1DataWindow ) + self.assertEqual( reader["out"]["format"].getValue(), f3Format ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), f3DataWindow ) + self.assertEqual( reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue() ) + self.assertEqual( reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue() ) + self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), blackTile ) + + # set to a missing frame before the start of the sequence + context.setFrame( 0 ) + + # everything matches frame 1 + reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold ) + with context : + self.assertEqual( reader["out"].image(), f1Image ) + self.assertEqual( reader["out"]["format"].getValue(), f1Format ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), f1DataWindow ) + self.assertEqual( reader["out"]["metadata"].getValue(), f1Metadata ) + self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), f1Tile ) + + # the windows match frame 1, but everything else is default + reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) + with context : + self.assertEqual( reader["out"]["format"].getValue(), f1Format ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), f1DataWindow ) + self.assertEqual( reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue() ) + self.assertEqual( reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue() ) + self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), blackTile ) + + # explicit fileNames do not support MissingFrameMode + reader["fileName"].setValue( testSequence.fileNameForFrame( 0 ) ) + reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold ) + with context : + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].image ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["dataWindow"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["metadata"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["channelNames"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].channelData, "R", IECore.V2i( 0 ) ) + + reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) + with context : + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].image ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["dataWindow"].getValue ) + self.assertEqual( reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue() ) + self.assertEqual( reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue() ) + self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), blackTile ) + + def testHashesFrame( self ) : + + # the fileName excludes FrameSubstitutions, but + # the internal implementation can still rely on + # frame, so we need to check that the output + # still responds to frame changes. + + testSequence = IECore.FileSequence( self.temporaryDirectory() + "/incompleteSequence.####.exr" ) + shutil.copyfile( self.fileName, testSequence.fileNameForFrame( 0 ) ) + shutil.copyfile( self.offsetDataWindowFileName, testSequence.fileNameForFrame( 1 ) ) + + reader = GafferImage.OpenImageIOReader() + reader["fileName"].setValue( testSequence.fileName ) + + # since the reader["out"] is never cached, we need + # to go through an intermediate node which does cache + # in order to see the affects of a frame change. + + metadata = GafferImage.ImageMetadata() + metadata["in"].setInput( reader["out"] ) + + context = Gaffer.Context() + + # get frame 0 data for comparison + context.setFrame( 0 ) + with context : + sequenceMetadataHash = metadata["out"]["metadata"].hash() + sequenceMetadataValue = metadata["out"]["metadata"].getValue() + + context.setFrame( 1 ) + with context : + self.assertNotEqual( metadata["out"]["metadata"].hash(), sequenceMetadataHash ) + self.assertNotEqual( metadata["out"]["metadata"].getValue(), sequenceMetadataValue ) + + # but when we set an explicit fileName, + # we no longer re-compute per frame. + reader["fileName"].setValue( testSequence.fileNameForFrame( 0 ) ) + + # get frame 0 data for comparison + context.setFrame( 0 ) + with context : + explicitMetadataHash = metadata["out"]["metadata"].hash() + self.assertNotEqual( explicitMetadataHash, sequenceMetadataHash ) + self.assertEqual( metadata["out"]["metadata"].getValue(), sequenceMetadataValue ) + + context.setFrame( 1 ) + with context : + self.assertNotEqual( metadata["out"]["metadata"].hash(), sequenceMetadataHash ) + self.assertEqual( metadata["out"]["metadata"].hash(), explicitMetadataHash ) + self.assertEqual( metadata["out"]["metadata"].getValue(), sequenceMetadataValue ) if __name__ == "__main__": unittest.main() diff --git a/python/GafferImageUI/OpenImageIOReaderUI.py b/python/GafferImageUI/OpenImageIOReaderUI.py index b8913d85e42..e6dde7938e9 100644 --- a/python/GafferImageUI/OpenImageIOReaderUI.py +++ b/python/GafferImageUI/OpenImageIOReaderUI.py @@ -59,7 +59,8 @@ """ The name of the file to be read. File sequences with arbitrary padding may be specified using the '#' character - as a placeholder for the frame numbers. + as a placeholder for the frame numbers. If this file sequence + format is used, then missingFrameMode will be activated. """, "plugValueWidget:type", "GafferUI.FileSystemPathPlugValueWidget", @@ -82,6 +83,41 @@ ], + "missingFrameMode" : [ + + "description", + """ + Determines how missing frames are handled when the input + fileName is a file sequence (uses the '#' character). + The default behaviour is to throw an exception, but it + can also hold the last valid frame in the sequence, or + return a black image which matches the data window and + display window of the previous valid frame in the sequence. + """, + + "preset:Error", GafferImage.OpenImageIOReader.MissingFrameMode.Error, + "preset:Black", GafferImage.OpenImageIOReader.MissingFrameMode.Black, + "preset:Hold", GafferImage.OpenImageIOReader.MissingFrameMode.Hold, + + "plugValueWidget:type", "GafferUI.PresetsPlugValueWidget", + + ], + + "availableFrames" : [ + + "description", + """ + An output of the available frames for the given file sequence. + Returns an empty vector when the input fileName is not a file + sequence, even if it has a file-sequence-like structure. + """, + + ## \todo: consider making this visible using a TextWidget with + ## FrameList syntax (e.g. "1-100x5") + "plugValueWidget:type", "", + + ], + } ) diff --git a/src/GafferImage/OpenImageIOReader.cpp b/src/GafferImage/OpenImageIOReader.cpp index 2e079d632b4..793bc63de91 100644 --- a/src/GafferImage/OpenImageIOReader.cpp +++ b/src/GafferImage/OpenImageIOReader.cpp @@ -36,12 +36,17 @@ ////////////////////////////////////////////////////////////////////////// #include "boost/bind.hpp" +#include "boost/filesystem/path.hpp" +#include "boost/regex.hpp" #include "OpenEXR/half.h" #include "OpenImageIO/imagecache.h" OIIO_NAMESPACE_USING +#include "IECore/FileSequence.h" +#include "IECore/FileSequenceFunctions.h" + #include "Gaffer/Context.h" #include "Gaffer/StringPlug.h" @@ -96,18 +101,67 @@ ImageCache *imageCache() // Returns the OIIO ImageSpec for the given filename in the current // context. Throws if the file is invalid, and returns NULL if // the filename is empty. -const ImageSpec *imageSpec( ustring fileName ) +const ImageSpec *imageSpec( std::string &fileName, OpenImageIOReader::MissingFrameMode mode, const OpenImageIOReader *node, const Context *context ) { if( fileName.empty() ) { return NULL; } + + const std::string resolvedFileName = context->substitute( fileName ); + ImageCache *cache = imageCache(); - const ImageSpec *spec = cache->imagespec( ustring( fileName.c_str() ) ); + const ImageSpec *spec = cache->imagespec( ustring( resolvedFileName ) ); if( !spec ) { - throw( IECore::Exception( cache->geterror() ) ); + if( mode == OpenImageIOReader::Black ) + { + // we can simply return the null spec and rely on the + // compute methods to return default plug values. + return spec; + } + else if( mode == OpenImageIOReader::Hold ) + { + ConstIntVectorDataPtr frameData = node->availableFramesPlug()->getValue(); + const std::vector &frames = frameData->readable(); + if( frames.size() ) + { + std::vector::const_iterator fIt = std::lower_bound( frames.begin(), frames.end(), (int)context->getFrame() ); + + // decrement to get the previous frame, unless + // this is the first frame, in which case we + // hold to the beginning of the sequence + if( fIt != frames.begin() ) + { + fIt--; + } + + // clear any error from the original fileName + cache->geterror(); + + // setup a context with the new frame + ContextPtr holdContext = new Context( *context, Context::Shared ); + holdContext->setFrame( *fIt ); + + return imageSpec( fileName, OpenImageIOReader::Error, node, holdContext.get() ); + } + + // if we got here, there was no suitable file sequence + throw( IECore::Exception( cache->geterror() ) ); + } + else + { + throw( IECore::Exception( cache->geterror() ) ); + } } + + // we overwrite the incoming fileName with + // the final successful fileName because + // computeChannelData needs to know the real + // file in order to fetch the pixels, and it + // isn't available from the ImageSpec directly. + fileName = resolvedFileName; + return spec; } @@ -304,8 +358,16 @@ OpenImageIOReader::OpenImageIOReader( const std::string &name ) : ImageNode( name ) { storeIndexOfNextChild( g_firstPlugIndex ); - addChild( new StringPlug( "fileName" ) ); + addChild( + new StringPlug( + "fileName", Plug::In, "", + /* flags */ Plug::Default, + /* substitutions */ Context::AllSubstitutions & ~Context::FrameSubstitutions + ) + ); addChild( new IntPlug( "refreshCount" ) ); + addChild( new IntPlug( "missingFrameMode", Plug::In, Error, /* min */ Error, /* max */ Hold ) ); + addChild( new IntVectorDataPlug( "availableFrames", Plug::Out, new IntVectorData ) ); // disable caching on our outputs, as OIIO is already doing caching for us. for( OutputPlugIterator it( outPlug() ); it!=it.end(); it++ ) @@ -340,6 +402,26 @@ const Gaffer::IntPlug *OpenImageIOReader::refreshCountPlug() const return getChild( g_firstPlugIndex + 1 ); } +Gaffer::IntPlug *OpenImageIOReader::missingFrameModePlug() +{ + return getChild( g_firstPlugIndex + 2 ); +} + +const Gaffer::IntPlug *OpenImageIOReader::missingFrameModePlug() const +{ + return getChild( g_firstPlugIndex + 2 ); +} + +Gaffer::IntVectorDataPlug *OpenImageIOReader::availableFramesPlug() +{ + return getChild( g_firstPlugIndex + 3 ); +} + +const Gaffer::IntVectorDataPlug *OpenImageIOReader::availableFramesPlug() const +{ + return getChild( g_firstPlugIndex + 3 ); +} + size_t OpenImageIOReader::supportedExtensions( std::vector &extensions ) { std::string attr; @@ -369,6 +451,11 @@ void OpenImageIOReader::affects( const Gaffer::Plug *input, AffectedPlugsContain ImageNode::affects( input, outputs ); if( input == fileNamePlug() || input == refreshCountPlug() ) + { + outputs.push_back( availableFramesPlug() ); + } + + if( input == fileNamePlug() || input == refreshCountPlug() || input == missingFrameModePlug() ) { for( ValuePlugIterator it( outPlug() ); it != it.end(); it++ ) { @@ -377,16 +464,75 @@ void OpenImageIOReader::affects( const Gaffer::Plug *input, AffectedPlugsContain } } +void OpenImageIOReader::hash( const ValuePlug *output, const Context *context, IECore::MurmurHash &h ) const +{ + ImageNode::hash( output, context, h ); + + if( output == availableFramesPlug() ) + { + fileNamePlug()->hash( h ); + refreshCountPlug()->hash( h ); + } +} + +void OpenImageIOReader::compute( ValuePlug *output, const Context *context ) const +{ + if( output == availableFramesPlug() ) + { + FileSequencePtr fileSequence = NULL; + IECore::ls( fileNamePlug()->getValue(), fileSequence, /* minSequenceSize */ 1 ); + + if( fileSequence ) + { + IntVectorDataPtr resultData = new IntVectorData; + std::vector frames; + fileSequence->getFrameList()->asList( frames ); + std::vector &result = resultData->writable(); + result.resize( frames.size() ); + std::copy( frames.begin(), frames.end(), result.begin() ); + static_cast( output )->setValue( resultData ); + } + else + { + static_cast( output )->setToDefault(); + } + } + else + { + ImageNode::compute( output, context ); + } +} + +void OpenImageIOReader::hashFileName( const Gaffer::Context *context, IECore::MurmurHash &h ) const +{ + // since fileName excludes frame substitutions + // but we internally vary the result output by + // frame, we need to explicitly hash the frame + // when the value contains FrameSubstitutions. + const std::string &fileName = fileNamePlug()->getValue(); + h.append( fileName ); + if( Context::substitutions( fileNamePlug()->getValue() ) & Context::FrameSubstitutions ) + { + h.append( context->getFrame() ); + } +} + void OpenImageIOReader::hashFormat( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ImageNode::hashFormat( output, context, h ); - fileNamePlug()->hash( h ); + hashFileName( context, h ); refreshCountPlug()->hash( h ); + missingFrameModePlug()->hash( h ); } GafferImage::Format OpenImageIOReader::computeFormat( const Gaffer::Context *context, const ImagePlug *parent ) const { - const ImageSpec *spec = imageSpec( ustring( fileNamePlug()->getValue() ) ); + std::string fileName = fileNamePlug()->getValue(); + // when we're in MissingFrameMode::Black we still want to + // match the format of the Hold frame. + MissingFrameMode mode = (MissingFrameMode)missingFrameModePlug()->getValue(); + mode = ( mode == Black ) ? Hold : mode; + const ImageSpec *spec = imageSpec( fileName, mode, this, context ); if( !spec ) { return FormatPlug::getDefaultFormat( context ); @@ -404,13 +550,19 @@ GafferImage::Format OpenImageIOReader::computeFormat( const Gaffer::Context *con void OpenImageIOReader::hashDataWindow( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ImageNode::hashDataWindow( output, context, h ); - fileNamePlug()->hash( h ); + hashFileName( context, h ); refreshCountPlug()->hash( h ); + missingFrameModePlug()->hash( h ); } Imath::Box2i OpenImageIOReader::computeDataWindow( const Gaffer::Context *context, const ImagePlug *parent ) const { - const ImageSpec *spec = imageSpec( ustring( fileNamePlug()->getValue() ) ); + std::string fileName = fileNamePlug()->getValue(); + // when we're in MissingFrameMode::Black we still want to + // match the data window of the Hold frame. + MissingFrameMode mode = (MissingFrameMode)missingFrameModePlug()->getValue(); + mode = ( mode == Black ) ? Hold : mode; + const ImageSpec *spec = imageSpec( fileName, mode, this, context ); if( !spec ) { return Box2i(); @@ -425,13 +577,15 @@ Imath::Box2i OpenImageIOReader::computeDataWindow( const Gaffer::Context *contex void OpenImageIOReader::hashMetadata( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ImageNode::hashMetadata( output, context, h ); - fileNamePlug()->hash( h ); + hashFileName( context, h ); refreshCountPlug()->hash( h ); + missingFrameModePlug()->hash( h ); } IECore::ConstCompoundObjectPtr OpenImageIOReader::computeMetadata( const Gaffer::Context *context, const ImagePlug *parent ) const { - const ImageSpec *spec = imageSpec( ustring( fileNamePlug()->getValue() ) ); + std::string fileName = fileNamePlug()->getValue(); + const ImageSpec *spec = imageSpec( fileName, (MissingFrameMode)missingFrameModePlug()->getValue(), this, context ); if( !spec ) { return parent->metadataPlug()->defaultValue(); @@ -446,13 +600,15 @@ IECore::ConstCompoundObjectPtr OpenImageIOReader::computeMetadata( const Gaffer: void OpenImageIOReader::hashChannelNames( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ImageNode::hashChannelNames( output, context, h ); - fileNamePlug()->hash( h ); + hashFileName( context, h ); refreshCountPlug()->hash( h ); + missingFrameModePlug()->hash( h ); } IECore::ConstStringVectorDataPtr OpenImageIOReader::computeChannelNames( const Gaffer::Context *context, const ImagePlug *parent ) const { - const ImageSpec *spec = imageSpec( ustring( fileNamePlug()->getValue() ) ); + std::string fileName = fileNamePlug()->getValue(); + const ImageSpec *spec = imageSpec( fileName, (MissingFrameMode)missingFrameModePlug()->getValue(), this, context ); if( !spec ) { return parent->channelNamesPlug()->defaultValue(); @@ -468,14 +624,15 @@ void OpenImageIOReader::hashChannelData( const GafferImage::ImagePlug *output, c ImageNode::hashChannelData( output, context, h ); h.append( context->get( ImagePlug::tileOriginContextName ) ); h.append( context->get( ImagePlug::channelNameContextName ) ); - fileNamePlug()->hash( h ); + hashFileName( context, h ); refreshCountPlug()->hash( h ); + missingFrameModePlug()->hash( h ); } IECore::ConstFloatVectorDataPtr OpenImageIOReader::computeChannelData( const std::string &channelName, const Imath::V2i &tileOrigin, const Gaffer::Context *context, const ImagePlug *parent ) const { - ustring fileName( fileNamePlug()->getValue() ); - const ImageSpec *spec = imageSpec( fileName ); + std::string fileName = fileNamePlug()->getValue(); + const ImageSpec *spec = imageSpec( fileName, (MissingFrameMode)missingFrameModePlug()->getValue(), this, context ); if( !spec ) { return parent->channelDataPlug()->defaultValue(); @@ -495,7 +652,7 @@ IECore::ConstFloatVectorDataPtr OpenImageIOReader::computeChannelData( const std std::vector channelData( ImagePlug::tileSize() * ImagePlug::tileSize() ); size_t channelIndex = channelIt - spec->channelnames.begin(); imageCache()->get_pixels( - fileName, + ustring( fileName ), 0, 0, // subimage, miplevel tileOrigin.x, tileOrigin.x + ImagePlug::tileSize(), newY, newY + ImagePlug::tileSize(), diff --git a/src/GafferImageBindings/OpenImageIOReaderBinding.cpp b/src/GafferImageBindings/OpenImageIOReaderBinding.cpp index 1691b199249..a9a7142c197 100644 --- a/src/GafferImageBindings/OpenImageIOReaderBinding.cpp +++ b/src/GafferImageBindings/OpenImageIOReaderBinding.cpp @@ -61,10 +61,16 @@ static boost::python::list supportedExtensions() void GafferImageBindings::bindOpenImageIOReader() { - GafferBindings::DependencyNodeClass() + boost::python::scope s = GafferBindings::DependencyNodeClass() .def( "supportedExtensions", &supportedExtensions ).staticmethod( "supportedExtensions" ) .def( "getCacheMemoryLimit", &OpenImageIOReader::getCacheMemoryLimit ).staticmethod( "getCacheMemoryLimit" ) .def( "setCacheMemoryLimit", &OpenImageIOReader::setCacheMemoryLimit ).staticmethod( "setCacheMemoryLimit" ) ; + + boost::python::enum_( "MissingFrameMode" ) + .value( "Error", OpenImageIOReader::Error ) + .value( "Black", OpenImageIOReader::Black ) + .value( "Hold", OpenImageIOReader::Hold ) + ; } From e183b24c9d1f9a9f1c510264c31599aa9dc66a0c Mon Sep 17 00:00:00 2001 From: Andrew Kaufman Date: Tue, 10 Nov 2015 14:40:30 -0800 Subject: [PATCH 03/13] Scoping a null Context is now a no-op. --- include/Gaffer/Context.h | 4 ++++ include/GafferTest/ContextTest.h | 3 ++- src/Gaffer/Context.cpp | 16 +++++++++----- src/GafferTest/ContextTest.cpp | 26 ++++++++++++++++++++++- src/GafferTestModule/GafferTestModule.cpp | 3 ++- 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/include/Gaffer/Context.h b/include/Gaffer/Context.h index 4d7e36574ab..c2b070c0d2e 100644 --- a/include/Gaffer/Context.h +++ b/include/Gaffer/Context.h @@ -225,6 +225,10 @@ class Context : public IECore::RefCounted /// Destruction of the Scope pops the previously pushed context. ~Scope(); + private : + + const Context *m_context; + }; /// Returns the current context for the calling thread. diff --git a/include/GafferTest/ContextTest.h b/include/GafferTest/ContextTest.h index 6c4f57c8de5..d999b1ac819 100644 --- a/include/GafferTest/ContextTest.h +++ b/include/GafferTest/ContextTest.h @@ -1,6 +1,6 @@ ////////////////////////////////////////////////////////////////////////// // -// Copyright (c) 2014, Image Engine Design Inc. All rights reserved. +// Copyright (c) 2014-2015, Image Engine Design Inc. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -43,6 +43,7 @@ namespace GafferTest void testManyContexts(); void testManySubstitutions(); void testManyEnvironmentSubstitutions(); +void testScopingNullContext(); } // namespace GafferTest diff --git a/src/Gaffer/Context.cpp b/src/Gaffer/Context.cpp index e3353f35081..b4287ea8559 100644 --- a/src/Gaffer/Context.cpp +++ b/src/Gaffer/Context.cpp @@ -506,16 +506,22 @@ typedef tbb::enumerable_thread_specificset( "foodType", std::string( "kipper" ) ); + context->set( "cookingMethod", std::string( "smoke" ) ); + + const std::string phrase( "${cookingMethod} me a ${foodType}" ); + const std::string expectedResult( "smoke me a kipper" ); + + { + Context::Scope scope( context.get() ); + const std::string s = Context::current()->substitute( phrase ); + GAFFERTEST_ASSERT( s == expectedResult ); + + const Context *nullContext = NULL; + { + Context::Scope scope( nullContext ); + const std::string s = Context::current()->substitute( phrase ); + GAFFERTEST_ASSERT( s == expectedResult ); + } + } +} diff --git a/src/GafferTestModule/GafferTestModule.cpp b/src/GafferTestModule/GafferTestModule.cpp index c91f4d0fba3..fe3073efa1e 100644 --- a/src/GafferTestModule/GafferTestModule.cpp +++ b/src/GafferTestModule/GafferTestModule.cpp @@ -1,7 +1,7 @@ ////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012, John Haddon. All rights reserved. -// Copyright (c) 2013, Image Engine Design Inc. All rights reserved. +// Copyright (c) 2013-2015, Image Engine Design Inc. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -66,6 +66,7 @@ BOOST_PYTHON_MODULE( _GafferTest ) def( "testManyContexts", &testManyContexts ); def( "testManySubstitutions", &testManySubstitutions ); def( "testManyEnvironmentSubstitutions", &testManyEnvironmentSubstitutions ); + def( "testScopingNullContext", &testScopingNullContext ); def( "testComputeNodeThreading", &testComputeNodeThreading ); } From fcc88832ea09eec98183231e04c667b9bf12905d Mon Sep 17 00:00:00 2001 From: Andrew Kaufman Date: Thu, 12 Nov 2015 16:59:16 -0800 Subject: [PATCH 04/13] Exposed OpenColorIO color spaces options via OpenColorIOTransform --- include/GafferImage/OpenColorIOTransform.h | 4 + .../OpenColorIOTransformBinding.h | 47 +++++++++++ .../OpenColorIOTransformTest.py | 60 ++++++++++++++ python/GafferImageTest/__init__.py | 1 + src/GafferImage/OpenColorIOTransform.cpp | 13 +++ .../OpenColorIOTransformBinding.cpp | 79 +++++++++++++++++++ src/GafferImageModule/GafferImageModule.cpp | 13 +-- 7 files changed, 207 insertions(+), 10 deletions(-) create mode 100644 include/GafferImageBindings/OpenColorIOTransformBinding.h create mode 100644 python/GafferImageTest/OpenColorIOTransformTest.py create mode 100644 src/GafferImageBindings/OpenColorIOTransformBinding.cpp diff --git a/include/GafferImage/OpenColorIOTransform.h b/include/GafferImage/OpenColorIOTransform.h index e773a0d3274..45b43afbfe8 100644 --- a/include/GafferImage/OpenColorIOTransform.h +++ b/include/GafferImage/OpenColorIOTransform.h @@ -53,6 +53,10 @@ class OpenColorIOTransform : public ColorProcessor OpenColorIOTransform( const std::string &name=defaultName() ); virtual ~OpenColorIOTransform(); + /// Fills the vector will the available color spaces, + /// as defined by the current OpenColorIO config. + static void availableColorSpaces( std::vector &colorSpaces ); + IE_CORE_DECLARERUNTIMETYPEDEXTENSION( GafferImage::OpenColorIOTransform, OpenColorIOTransformTypeId, ColorProcessor ); protected : diff --git a/include/GafferImageBindings/OpenColorIOTransformBinding.h b/include/GafferImageBindings/OpenColorIOTransformBinding.h new file mode 100644 index 00000000000..21e38d592fe --- /dev/null +++ b/include/GafferImageBindings/OpenColorIOTransformBinding.h @@ -0,0 +1,47 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2015, Image Engine Design Inc. 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef GAFFERIMAGEBINDINGS_OPENCOLORIOTRANSFORMBINDING_H +#define GAFFERIMAGEBINDINGS_OPENCOLORIOTRANSFORMBINDING_H + +namespace GafferImageBindings +{ + +void bindOpenColorIOTransform(); + +} // namespace GafferImageBindings + +#endif // GAFFERIMAGEBINDINGS_OPENCOLORIOTRANSFORMBINDING_H diff --git a/python/GafferImageTest/OpenColorIOTransformTest.py b/python/GafferImageTest/OpenColorIOTransformTest.py new file mode 100644 index 00000000000..a21c8585604 --- /dev/null +++ b/python/GafferImageTest/OpenColorIOTransformTest.py @@ -0,0 +1,60 @@ +########################################################################## +# +# Copyright (c) 2015, Image Engine Design Inc. 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. +# +########################################################################## + +import unittest + +import IECore + +import Gaffer +import GafferTest +import GafferImage +import GafferImageTest + +import PyOpenColorIO + +class OpenColorIOTransformTest( GafferTest.TestCase ) : + + def testAvailableColorSpaces( self ) : + + config = PyOpenColorIO.GetCurrentConfig() + + self.assertEqual( + GafferImage.OpenColorIOTransform.availableColorSpaces(), + [ cs.getName() for cs in config.getColorSpaces() ] + ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferImageTest/__init__.py b/python/GafferImageTest/__init__.py index 4ca4a4cf148..d5ef0fb94ee 100644 --- a/python/GafferImageTest/__init__.py +++ b/python/GafferImageTest/__init__.py @@ -79,6 +79,7 @@ from OffsetTest import OffsetTest from BlurTest import BlurTest from TextTest import TextTest +from OpenColorIOTransformTest import OpenColorIOTransformTest if __name__ == "__main__": import unittest diff --git a/src/GafferImage/OpenColorIOTransform.cpp b/src/GafferImage/OpenColorIOTransform.cpp index ece475186dd..5726134aa9f 100644 --- a/src/GafferImage/OpenColorIOTransform.cpp +++ b/src/GafferImage/OpenColorIOTransform.cpp @@ -134,3 +134,16 @@ void OpenColorIOTransform::processColorData( const Gaffer::Context *context, IEC processor->apply( image ); } + +void OpenColorIOTransform::availableColorSpaces( std::vector &colorSpaces ) +{ + OpenColorIO::ConstConfigRcPtr config = OpenColorIO::GetCurrentConfig(); + + colorSpaces.clear(); + colorSpaces.reserve( config->getNumColorSpaces() ); + + for( int i = 0; i < config->getNumColorSpaces(); ++i ) + { + colorSpaces.push_back( config->getColorSpaceNameByIndex( i ) ); + } +} diff --git a/src/GafferImageBindings/OpenColorIOTransformBinding.cpp b/src/GafferImageBindings/OpenColorIOTransformBinding.cpp new file mode 100644 index 00000000000..6b0410019fc --- /dev/null +++ b/src/GafferImageBindings/OpenColorIOTransformBinding.cpp @@ -0,0 +1,79 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2015, Image Engine Design Inc. 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. +// +////////////////////////////////////////////////////////////////////////// + +#include "boost/python.hpp" + +#include "GafferImage/OpenColorIOTransform.h" +#include "GafferImage/ColorSpace.h" +#include "GafferImage/CDL.h" +#include "GafferImage/DisplayTransform.h" +#include "GafferImage/LUT.h" + +#include "GafferBindings/DependencyNodeBinding.h" + +#include "GafferImageBindings/OpenColorIOTransformBinding.h" +#include "GafferImageBindings/LUTBinding.h" + +using namespace GafferImage; + +static boost::python::list availableColorSpaces() +{ + std::vector e; + OpenColorIOTransform::availableColorSpaces( e ); + + boost::python::list result; + for( std::vector::const_iterator it = e.begin(), eIt = e.end(); it != eIt; ++it ) + { + result.append( *it ); + } + + return result; +} + +void GafferImageBindings::bindOpenColorIOTransform() +{ + + GafferBindings::DependencyNodeClass() + .def( "availableColorSpaces", &availableColorSpaces ).staticmethod( "availableColorSpaces" ) + ; + + GafferBindings::DependencyNodeClass(); + GafferBindings::DependencyNodeClass(); + GafferBindings::DependencyNodeClass(); + + GafferImageBindings::bindLUT(); + +} diff --git a/src/GafferImageModule/GafferImageModule.cpp b/src/GafferImageModule/GafferImageModule.cpp index bd8afb730ea..833c1e21865 100644 --- a/src/GafferImageModule/GafferImageModule.cpp +++ b/src/GafferImageModule/GafferImageModule.cpp @@ -44,8 +44,7 @@ #include "GafferImage/Display.h" #include "GafferImage/ChannelDataProcessor.h" -#include "GafferImage/OpenColorIOTransform.h" -#include "GafferImage/ColorSpace.h" +#include "GafferImage/ColorProcessor.h" #include "GafferImage/ObjectToImage.h" #include "GafferImage/Grade.h" #include "GafferImage/Clamp.h" @@ -60,8 +59,6 @@ #include "GafferImage/Premultiply.h" #include "GafferImage/Unpremultiply.h" #include "GafferImage/Crop.h" -#include "GafferImage/CDL.h" -#include "GafferImage/DisplayTransform.h" #include "GafferImageBindings/ImageNodeBinding.h" #include "GafferImageBindings/ImageProcessorBinding.h" @@ -81,12 +78,12 @@ #include "GafferImageBindings/CropBinding.h" #include "GafferImageBindings/ResampleBinding.h" #include "GafferImageBindings/ResizeBinding.h" -#include "GafferImageBindings/LUTBinding.h" #include "GafferImageBindings/ImageAlgoBinding.h" #include "GafferImageBindings/OffsetBinding.h" #include "GafferImageBindings/BlurBinding.h" #include "GafferImageBindings/ShapeBinding.h" #include "GafferImageBindings/TextBinding.h" +#include "GafferImageBindings/OpenColorIOTransformBinding.h" using namespace boost::python; using namespace GafferImage; @@ -106,10 +103,6 @@ BOOST_PYTHON_MODULE( _GafferImage ) ; GafferBindings::DependencyNodeClass(); GafferBindings::DependencyNodeClass(); - GafferBindings::DependencyNodeClass(); - GafferBindings::DependencyNodeClass(); - GafferBindings::DependencyNodeClass(); - GafferBindings::DependencyNodeClass(); GafferBindings::DependencyNodeClass(); GafferBindings::DependencyNodeClass(); GafferBindings::DependencyNodeClass(); @@ -139,12 +132,12 @@ BOOST_PYTHON_MODULE( _GafferImage ) GafferImageBindings::bindCrop(); GafferImageBindings::bindResample(); GafferImageBindings::bindResize(); - GafferImageBindings::bindLUT(); GafferImageBindings::bindImageAlgo(); GafferImageBindings::bindOffset(); GafferImageBindings::bindBlur(); GafferImageBindings::bindShape(); GafferImageBindings::bindText(); + GafferImageBindings::bindOpenColorIOTransform(); } From 130356665ad835d001a8249482978d0e0e67f141 Mon Sep 17 00:00:00 2001 From: Andrew Kaufman Date: Thu, 12 Nov 2015 17:05:19 -0800 Subject: [PATCH 05/13] Using OpenColorIOTransform::availableSpaces() to provide presets. --- python/GafferImageUI/CDLUI.py | 2 -- python/GafferImageUI/ColorSpaceUI.py | 10 ++-------- python/GafferImageUI/DisplayTransformUI.py | 8 ++------ python/GafferImageUI/LUTUI.py | 2 -- 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/python/GafferImageUI/CDLUI.py b/python/GafferImageUI/CDLUI.py index 7dac700104b..c60c6e4a07a 100644 --- a/python/GafferImageUI/CDLUI.py +++ b/python/GafferImageUI/CDLUI.py @@ -34,8 +34,6 @@ # ########################################################################## -import PyOpenColorIO - import IECore import Gaffer diff --git a/python/GafferImageUI/ColorSpaceUI.py b/python/GafferImageUI/ColorSpaceUI.py index c98a94cccb6..71b6f38662a 100644 --- a/python/GafferImageUI/ColorSpaceUI.py +++ b/python/GafferImageUI/ColorSpaceUI.py @@ -34,8 +34,6 @@ # ########################################################################## -import PyOpenColorIO - import IECore import Gaffer @@ -44,15 +42,11 @@ def __colorSpacePresetNames( plug ) : - config = PyOpenColorIO.GetCurrentConfig() - - return IECore.StringVectorData( [ "None" ] + [ cs.getName() for cs in config.getColorSpaces() ] ) + return IECore.StringVectorData( [ "None" ] + GafferImage.OpenColorIOTransform.availableColorSpaces() ) def __colorSpacePresetValues( plug ) : - config = PyOpenColorIO.GetCurrentConfig() - - return IECore.StringVectorData( [ "" ] + [ cs.getName() for cs in config.getColorSpaces() ] ) + return IECore.StringVectorData( [ "" ] + GafferImage.OpenColorIOTransform.availableColorSpaces() ) Gaffer.Metadata.registerNode( diff --git a/python/GafferImageUI/DisplayTransformUI.py b/python/GafferImageUI/DisplayTransformUI.py index 616b833ada0..bb011b1bc42 100644 --- a/python/GafferImageUI/DisplayTransformUI.py +++ b/python/GafferImageUI/DisplayTransformUI.py @@ -44,15 +44,11 @@ def __colorSpacePresetNames( plug ) : - config = PyOpenColorIO.GetCurrentConfig() - - return IECore.StringVectorData( [ "None" ] + [ cs.getName() for cs in config.getColorSpaces() ] ) + return IECore.StringVectorData( [ "None" ] + GafferImage.OpenColorIOTransform.availableColorSpaces() ) def __colorSpacePresetValues( plug ) : - config = PyOpenColorIO.GetCurrentConfig() - - return IECore.StringVectorData( [ "" ] + [ cs.getName() for cs in config.getColorSpaces() ] ) + return IECore.StringVectorData( [ "" ] + GafferImage.OpenColorIOTransform.availableColorSpaces() ) def __displayPresetNames( plug ) : diff --git a/python/GafferImageUI/LUTUI.py b/python/GafferImageUI/LUTUI.py index 3800e849610..07086ed8f28 100644 --- a/python/GafferImageUI/LUTUI.py +++ b/python/GafferImageUI/LUTUI.py @@ -34,8 +34,6 @@ # ########################################################################## -import PyOpenColorIO - import IECore import Gaffer From 1f66834f8abd512cf2734b053c590fb78ec2d431 Mon Sep 17 00:00:00 2001 From: Andrew Kaufman Date: Fri, 6 Nov 2015 12:11:16 -0800 Subject: [PATCH 06/13] Added a more user friendly ImageReader. This node embeds an OpenImageIOReader and exposes similar options, but also adds an automatic color conversion to linear, as well as range masking options. Fixes #250. --- include/GafferImage/ImageReader.h | 152 +++++++ include/GafferImage/OpenImageIOReader.h | 4 - .../GafferImageBindings/ImageReaderBinding.h | 47 +++ src/GafferImage/ImageReader.cpp | 385 ++++++++++++++++++ .../ImageReaderBinding.cpp | 81 ++++ src/GafferImageModule/GafferImageModule.cpp | 2 + 6 files changed, 667 insertions(+), 4 deletions(-) create mode 100644 include/GafferImage/ImageReader.h create mode 100644 include/GafferImageBindings/ImageReaderBinding.h create mode 100644 src/GafferImage/ImageReader.cpp create mode 100644 src/GafferImageBindings/ImageReaderBinding.cpp diff --git a/include/GafferImage/ImageReader.h b/include/GafferImage/ImageReader.h new file mode 100644 index 00000000000..6194cd9b63e --- /dev/null +++ b/include/GafferImage/ImageReader.h @@ -0,0 +1,152 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2015, Image Engine Design Inc. 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef GAFFERIMAGE_IMAGEREADER_H +#define GAFFERIMAGE_IMAGEREADER_H + +#include "Gaffer/CompoundNumericPlug.h" + +#include "GafferImage/ImageNode.h" + +namespace Gaffer +{ + +IE_CORE_FORWARDDECLARE( StringPlug ) + +} // namespace Gaffer + +namespace GafferImage +{ + +IE_CORE_FORWARDDECLARE( ColorSpace ) +IE_CORE_FORWARDDECLARE( OpenImageIOReader ) + +class ImageReader : public ImageNode +{ + + public : + + ImageReader( const std::string &name=defaultName() ); + virtual ~ImageReader(); + + IE_CORE_DECLARERUNTIMETYPEDEXTENSION( GafferImage::ImageReader, ImageReaderTypeId, ImageNode ); + + /// The MissingFrameMode controls how to handle missing images. + /// It is distinct from OpenImageIOReader::MissingFrameMode so + /// that we can provide alternate modes using higher + /// level approaches in the future (e.g interpolation). + enum MissingFrameMode + { + Error = 0, + Black, + Hold, + }; + + /// The FrameMaskMode controls how to handle images + /// outside of the values provided by frameRangeMask(). + enum FrameMaskMode + { + None = 0, + BlackOutside, + ClampToRange, + }; + + Gaffer::StringPlug *fileNamePlug(); + const Gaffer::StringPlug *fileNamePlug() const; + + /// Number of times the node has been refreshed. + Gaffer::IntPlug *refreshCountPlug(); + const Gaffer::IntPlug *refreshCountPlug() const; + + Gaffer::IntPlug *missingFrameModePlug(); + const Gaffer::IntPlug *missingFrameModePlug() const; + + Gaffer::IntPlug *frameStartMaskModePlug(); + const Gaffer::IntPlug *frameStartMaskModePlug() const; + + Gaffer::IntPlug *frameStartMaskPlug(); + const Gaffer::IntPlug *frameStartMaskPlug() const; + + Gaffer::IntPlug *frameEndMaskModePlug(); + const Gaffer::IntPlug *frameEndMaskModePlug() const; + + Gaffer::IntPlug *frameEndMaskPlug(); + const Gaffer::IntPlug *frameEndMaskPlug() const; + + virtual void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const; + + static size_t supportedExtensions( std::vector &extensions ); + + protected : + + virtual void hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const; + virtual void compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const; + + private : + + // We use internal nodes to do all the hard work, + // but we need to store intermediate results between + // those nodes in order to affect the outcome. + + OpenImageIOReader *oiioReader(); + const OpenImageIOReader *oiioReader() const; + + Gaffer::CompoundObjectPlug *intermediateMetadataPlug(); + const Gaffer::CompoundObjectPlug *intermediateMetadataPlug() const; + + Gaffer::StringPlug *intermediateColorSpacePlug(); + const Gaffer::StringPlug *intermediateColorSpacePlug() const; + + ColorSpace *colorSpace(); + const ColorSpace *colorSpace() const; + + GafferImage::ImagePlug *intermediateImagePlug(); + const GafferImage::ImagePlug *intermediateImagePlug() const; + + void hashMaskedOutput( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h, bool alwaysClampToRange = false ) const; + void computeMaskedOutput( Gaffer::ValuePlug *output, const Gaffer::Context *context, bool alwaysClampToRange = false ) const; + + bool computeFrameMask( const Gaffer::Context *context, Gaffer::ContextPtr &maskedContext ) const; + + static size_t g_firstChildIndex; + +}; + +IE_CORE_DECLAREPTR( ImageReader ) + +} // namespace GafferImage + +#endif // GAFFERIMAGE_IMAGEREADER_H diff --git a/include/GafferImage/OpenImageIOReader.h b/include/GafferImage/OpenImageIOReader.h index 18d69b482ab..28a3a8432e3 100644 --- a/include/GafferImage/OpenImageIOReader.h +++ b/include/GafferImage/OpenImageIOReader.h @@ -52,10 +52,6 @@ IE_CORE_FORWARDDECLARE( StringPlug ) namespace GafferImage { -/// \todo Linearise images. Perhaps this should be done by a super-node which just -/// packages up an internal OpenImageIOReader and OpenColorIO node? Perhaps we could -/// use the metaData() plug to fill it with the file metadata, -/// and use that to pass the input colorspace into the internal OpenColorIO node. class OpenImageIOReader : public ImageNode { diff --git a/include/GafferImageBindings/ImageReaderBinding.h b/include/GafferImageBindings/ImageReaderBinding.h new file mode 100644 index 00000000000..fac44d27e2d --- /dev/null +++ b/include/GafferImageBindings/ImageReaderBinding.h @@ -0,0 +1,47 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2014-2015, Image Engine Design Inc. 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef GAFFERIMAGEBINDINGS_IMAGEREADERBINDING_H +#define GAFFERIMAGEBINDINGS_IMAGEREADERBINDING_H + +namespace GafferImageBindings +{ + +void bindImageReader(); + +} // namespace GafferImageBindings + +#endif // GAFFERIMAGEBINDINGS_IMAGEREADERBINDING_H diff --git a/src/GafferImage/ImageReader.cpp b/src/GafferImage/ImageReader.cpp new file mode 100644 index 00000000000..766932dbc85 --- /dev/null +++ b/src/GafferImage/ImageReader.cpp @@ -0,0 +1,385 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2015, Image Engine Design Inc. 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. +// +////////////////////////////////////////////////////////////////////////// + +#include "boost/bind.hpp" + +#include "OpenColorIO/OpenColorIO.h" + +#include "Gaffer/StringPlug.h" + +#include "GafferImage/ColorSpace.h" +#include "GafferImage/ImageReader.h" +#include "GafferImage/OpenImageIOReader.h" + +using namespace std; +using namespace tbb; +using namespace Imath; +using namespace IECore; +using namespace Gaffer; +using namespace GafferImage; + +////////////////////////////////////////////////////////////////////////// +// ImageReader implementation +////////////////////////////////////////////////////////////////////////// + +IE_CORE_DEFINERUNTIMETYPED( ImageReader ); + +size_t ImageReader::g_firstChildIndex = 0; + +ImageReader::ImageReader( const std::string &name ) + : ImageNode( name ) +{ + storeIndexOfNextChild( g_firstChildIndex ); + addChild( new StringPlug( "fileName" ) ); + addChild( new IntPlug( "refreshCount" ) ); + addChild( new IntPlug( "missingFrameMode", Plug::In, Error, /* min */ Error, /* max */ Hold ) ); + + ValuePlugPtr frameStartMaskPlug = new ValuePlug( "frameStartMask", Plug::In ); + frameStartMaskPlug->addChild( new IntPlug( "mode", Plug::In, None, /* min */ None, /* max */ ClampToRange ) ); + frameStartMaskPlug->addChild( new IntPlug( "frame", Plug::In, 0 ) ); + addChild( frameStartMaskPlug ); + + ValuePlugPtr frameEndMaskPlug = new ValuePlug( "frameEndMask", Plug::In ); + frameEndMaskPlug->addChild( new IntPlug( "mode", Plug::In, None, /* min */ None, /* max */ ClampToRange ) ); + frameEndMaskPlug->addChild( new IntPlug( "frame", Plug::In, 0 ) ); + addChild( frameEndMaskPlug ); + + addChild( new CompoundObjectPlug( "__intermediateMetadata", Plug::In, new CompoundObject, Plug::Default & ~Plug::Serialisable ) ); + addChild( new StringPlug( "__intermediateColorSpace", Plug::Out, "", Plug::Default & ~Plug::Serialisable ) ); + addChild( new ImagePlug( "__intermediateImage", Plug::In, Plug::Default & ~Plug::Serialisable ) ); + + // We don't really do much work ourselves - we just + // defer to internal nodes to do the hard work. + + OpenImageIOReaderPtr oiioReader = new OpenImageIOReader( "__oiioReader" ); + addChild( oiioReader ); + oiioReader->fileNamePlug()->setInput( fileNamePlug() ); + oiioReader->refreshCountPlug()->setInput( refreshCountPlug() ); + oiioReader->missingFrameModePlug()->setInput( missingFrameModePlug() ); + intermediateMetadataPlug()->setInput( oiioReader->outPlug()->metadataPlug() ); + + ColorSpacePtr colorSpace = new ColorSpace( "__colorSpace" ); + addChild( colorSpace ); + colorSpace->inPlug()->setInput( oiioReader->outPlug() ); + colorSpace->inputSpacePlug()->setInput( intermediateColorSpacePlug() ); + colorSpace->outputSpacePlug()->setValue( OpenColorIO::ROLE_SCENE_LINEAR ); + intermediateImagePlug()->setInput( colorSpace->outPlug() ); +} + +ImageReader::~ImageReader() +{ +} + +StringPlug *ImageReader::fileNamePlug() +{ + return getChild( g_firstChildIndex ); +} + +const StringPlug *ImageReader::fileNamePlug() const +{ + return getChild( g_firstChildIndex ); +} + +IntPlug *ImageReader::refreshCountPlug() +{ + return getChild( g_firstChildIndex + 1 ); +} + +const IntPlug *ImageReader::refreshCountPlug() const +{ + return getChild( g_firstChildIndex + 1 ); +} + +IntPlug *ImageReader::missingFrameModePlug() +{ + return getChild( g_firstChildIndex + 2 ); +} + +const IntPlug *ImageReader::missingFrameModePlug() const +{ + return getChild( g_firstChildIndex + 2 ); +} + +IntPlug *ImageReader::frameStartMaskModePlug() +{ + return getChild( g_firstChildIndex + 3 )->getChild( 0 ); +} + +const IntPlug *ImageReader::frameStartMaskModePlug() const +{ + return getChild( g_firstChildIndex + 3 )->getChild( 0 ); +} + +IntPlug *ImageReader::frameStartMaskPlug() +{ + return getChild( g_firstChildIndex + 3 )->getChild( 1 ); +} + +const IntPlug *ImageReader::frameStartMaskPlug() const +{ + return getChild( g_firstChildIndex + 3 )->getChild( 1 ); +} + +IntPlug *ImageReader::frameEndMaskModePlug() +{ + return getChild( g_firstChildIndex + 4 )->getChild( 0 ); +} + +const IntPlug *ImageReader::frameEndMaskModePlug() const +{ + return getChild( g_firstChildIndex + 4 )->getChild( 0 ); +} + +IntPlug *ImageReader::frameEndMaskPlug() +{ + return getChild( g_firstChildIndex + 4 )->getChild( 1 ); +} + +const IntPlug *ImageReader::frameEndMaskPlug() const +{ + return getChild( g_firstChildIndex + 4 )->getChild( 1 ); +} + +CompoundObjectPlug *ImageReader::intermediateMetadataPlug() +{ + return getChild( g_firstChildIndex + 5 ); +} + +const CompoundObjectPlug *ImageReader::intermediateMetadataPlug() const +{ + return getChild( g_firstChildIndex + 5 ); +} + +StringPlug *ImageReader::intermediateColorSpacePlug() +{ + return getChild( g_firstChildIndex + 6 ); +} + +const StringPlug *ImageReader::intermediateColorSpacePlug() const +{ + return getChild( g_firstChildIndex + 6 ); +} + +ImagePlug *ImageReader::intermediateImagePlug() +{ + return getChild( g_firstChildIndex + 7 ); +} + +const ImagePlug *ImageReader::intermediateImagePlug() const +{ + return getChild( g_firstChildIndex + 7 ); +} + +OpenImageIOReader *ImageReader::oiioReader() +{ + return getChild( g_firstChildIndex + 8 ); +} + +const OpenImageIOReader *ImageReader::oiioReader() const +{ + return getChild( g_firstChildIndex + 8 ); +} + +ColorSpace *ImageReader::colorSpace() +{ + return getChild( g_firstChildIndex + 9 ); +} + +const ColorSpace *ImageReader::colorSpace() const +{ + return getChild( g_firstChildIndex + 9 ); +} + +size_t ImageReader::supportedExtensions( std::vector &extensions ) +{ + OpenImageIOReader::supportedExtensions( extensions ); + return extensions.size(); +} + +void ImageReader::affects( const Plug *input, AffectedPlugsContainer &outputs ) const +{ + ImageNode::affects( input, outputs ); + + if( input == intermediateMetadataPlug() ) + { + outputs.push_back( intermediateColorSpacePlug() ); + } + else if( input->parent() == intermediateImagePlug() ) + { + outputs.push_back( outPlug()->getChild( input->getName() ) ); + } + else if ( + input == frameStartMaskPlug() || + input == frameStartMaskModePlug() || + input == frameEndMaskPlug() || + input == frameEndMaskModePlug() + ) + { + for( ValuePlugIterator it( outPlug() ); it != it.end(); ++it ) + { + outputs.push_back( it->get() ); + } + } +} + +void ImageReader::hash( const ValuePlug *output, const Context *context, IECore::MurmurHash &h ) const +{ + ImageNode::hash( output, context, h ); + + if( output == intermediateColorSpacePlug() ) + { + intermediateMetadataPlug()->hash( h ); + } + else if( + output == outPlug()->formatPlug() || + output == outPlug()->dataWindowPlug() + ) + { + // we always want to match the windows + // we would get inside the frame mask + hashMaskedOutput( output, context, h, /* alwaysClampToRange */ true ); + } + else if( + output == outPlug()->metadataPlug() || + output == outPlug()->channelNamesPlug() || + output == outPlug()->channelDataPlug() + ) + { + hashMaskedOutput( output, context, h ); + } +} + +void ImageReader::compute( ValuePlug *output, const Context *context ) const +{ + if( output == intermediateColorSpacePlug() ) + { + std::string intermediateSpace = ""; + ConstCompoundObjectPtr metadata = intermediateMetadataPlug()->getValue(); + if( const StringData *intermediateSpaceData = metadata->member( "oiio:ColorSpace" ) ) + { + std::vector colorSpaces; + OpenColorIOTransform::availableColorSpaces( colorSpaces ); + if( std::find( colorSpaces.begin(), colorSpaces.end(), intermediateSpaceData->readable() ) != colorSpaces.end() ) + { + intermediateSpace = intermediateSpaceData->readable(); + } + } + + static_cast( output )->setValue( intermediateSpace ); + } + else if( + output == outPlug()->formatPlug() || + output == outPlug()->dataWindowPlug() + ) + { + // we always want to match the windows + // we would get inside the frame mask + computeMaskedOutput( output, context, /* alwaysClampToRange */ true ); + } + else if( + output == outPlug()->metadataPlug() || + output == outPlug()->channelNamesPlug() || + output == outPlug()->channelDataPlug() + ) + { + computeMaskedOutput( output, context ); + } + else + { + ImageNode::compute( output, context ); + } +} + +void ImageReader::hashMaskedOutput( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h, bool alwaysClampToRange ) const +{ + ContextPtr maskedContext = NULL; + if( !computeFrameMask( context, maskedContext ) || alwaysClampToRange ) + { + Context::Scope scope( maskedContext.get() ); + h = intermediateImagePlug()->getChild( output->getName() )->hash(); + } +} + +void ImageReader::computeMaskedOutput( Gaffer::ValuePlug *output, const Gaffer::Context *context, bool alwaysClampToRange ) const +{ + ContextPtr maskedContext = NULL; + bool blackOutside = computeFrameMask( context, maskedContext ); + if( blackOutside && !alwaysClampToRange ) + { + output->setToDefault(); + return; + } + + Context::Scope scope( maskedContext.get() ); + output->setFrom( intermediateImagePlug()->getChild( output->getName() ) ); +} + +bool ImageReader::computeFrameMask( const Context *context, ContextPtr &maskedContext ) const +{ + int frameStartMask = frameStartMaskPlug()->getValue(); + int frameEndMask = frameEndMaskPlug()->getValue(); + FrameMaskMode frameStartMaskMode = (FrameMaskMode)frameStartMaskModePlug()->getValue(); + FrameMaskMode frameEndMaskMode = (FrameMaskMode)frameEndMaskModePlug()->getValue(); + + int origFrame = (int)context->getFrame(); + int maskedFrame = std::min( frameEndMask, std::max( frameStartMask, origFrame ) ); + + if( origFrame == maskedFrame ) + { + // no need for anything special when + // we're within the mask range. + return false; + } + + FrameMaskMode maskMode = ( origFrame < maskedFrame ) ? frameStartMaskMode : frameEndMaskMode; + + if( maskMode == None ) + { + // no need for anything special when + // we're in FrameMaskMode::None + return false; + } + + // we need to create the masked context + // for both BlackOutSide and ClampToRange, + // because some plugs require valid data + // from the mask range even in either way. + + maskedContext = new Gaffer::Context( *context, Context::Shared ); + maskedContext->setFrame( maskedFrame ); + + return ( maskMode == BlackOutside ); +} diff --git a/src/GafferImageBindings/ImageReaderBinding.cpp b/src/GafferImageBindings/ImageReaderBinding.cpp new file mode 100644 index 00000000000..e745c36c176 --- /dev/null +++ b/src/GafferImageBindings/ImageReaderBinding.cpp @@ -0,0 +1,81 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2014-2015, Image Engine Design Inc. 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. +// +////////////////////////////////////////////////////////////////////////// + +#include "boost/python.hpp" + +#include "GafferBindings/DependencyNodeBinding.h" + +#include "GafferImage/ImageReader.h" + +#include "GafferImageBindings/ImageReaderBinding.h" + +using namespace GafferImage; + +static boost::python::list supportedExtensions() +{ + std::vector e; + ImageReader::supportedExtensions( e ); + + boost::python::list result; + for( std::vector::const_iterator it = e.begin(), eIt = e.end(); it != eIt; ++it ) + { + result.append( *it ); + } + + return result; +} + +void GafferImageBindings::bindImageReader() +{ + + boost::python::scope s = GafferBindings::DependencyNodeClass() + .def( "supportedExtensions", &supportedExtensions ) + .staticmethod( "supportedExtensions" ) + ; + + boost::python::enum_( "MissingFrameMode" ) + .value( "Error", ImageReader::Error ) + .value( "Black", ImageReader::Black ) + .value( "Hold", ImageReader::Hold ) + ; + + boost::python::enum_( "FrameMaskMode" ) + .value( "None", ImageReader::None ) + .value( "BlackOutside", ImageReader::BlackOutside ) + .value( "ClampToRange", ImageReader::ClampToRange ) + ; + +} diff --git a/src/GafferImageModule/GafferImageModule.cpp b/src/GafferImageModule/GafferImageModule.cpp index 833c1e21865..8beed8993c1 100644 --- a/src/GafferImageModule/GafferImageModule.cpp +++ b/src/GafferImageModule/GafferImageModule.cpp @@ -73,6 +73,7 @@ #include "GafferImageBindings/MixinBinding.h" #include "GafferImageBindings/FormatDataBinding.h" #include "GafferImageBindings/OpenImageIOReaderBinding.h" +#include "GafferImageBindings/ImageReaderBinding.h" #include "GafferImageBindings/ImageWriterBinding.h" #include "GafferImageBindings/ShuffleBinding.h" #include "GafferImageBindings/CropBinding.h" @@ -126,6 +127,7 @@ BOOST_PYTHON_MODULE( _GafferImage ) GafferImageBindings::bindMixin(); GafferImageBindings::bindFormatData(); GafferImageBindings::bindOpenImageIOReader(); + GafferImageBindings::bindImageReader(); GafferImageBindings::bindImageWriter(); GafferImageBindings::bindMerge(); GafferImageBindings::bindShuffle(); From 88c23103ad33bfa3225870602e45708cba720e37 Mon Sep 17 00:00:00 2001 From: Andrew Kaufman Date: Fri, 6 Nov 2015 12:12:27 -0800 Subject: [PATCH 07/13] Added ImageReader tests --- python/GafferImageTest/ImageReaderTest.py | 377 ++++++++++++++++++ .../GafferImageTest/OpenImageIOReaderTest.py | 2 - python/GafferImageTest/__init__.py | 1 + 3 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 python/GafferImageTest/ImageReaderTest.py diff --git a/python/GafferImageTest/ImageReaderTest.py b/python/GafferImageTest/ImageReaderTest.py new file mode 100644 index 00000000000..ba1dc8a9ff4 --- /dev/null +++ b/python/GafferImageTest/ImageReaderTest.py @@ -0,0 +1,377 @@ +########################################################################## +# +# Copyright (c) 2015, Image Engine Design Inc. 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. +# +########################################################################## + +import os +import shutil +import unittest + +import IECore + +import Gaffer +import GafferTest +import GafferImage +import GafferImageTest + +class ImageReaderTest( GafferTest.TestCase ) : + + fileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/circles.exr" ) + offsetDataWindowFileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/rgb.100x100.exr" ) + jpgFileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/circles.jpg" ) + + def test( self ) : + + n = GafferImage.ImageReader() + n["fileName"].setValue( self.fileName ) + + oiio = GafferImage.OpenImageIOReader() + oiio["fileName"].setValue( self.fileName ) + + self.assertEqual( n["out"]["format"].getValue(), oiio["out"]["format"].getValue() ) + self.assertEqual( n["out"]["dataWindow"].getValue(), oiio["out"]["dataWindow"].getValue() ) + self.assertEqual( n["out"]["metadata"].getValue(), oiio["out"]["metadata"].getValue() ) + self.assertEqual( n["out"]["channelNames"].getValue(), oiio["out"]["channelNames"].getValue() ) + self.assertEqual( n["out"].channelData( "R", IECore.V2i( 0 ) ),oiio["out"].channelData( "R", IECore.V2i( 0 ) ) ) + self.assertEqual( n["out"].image(), oiio["out"].image() ) + + def testUnspecifiedFilename( self ) : + + n = GafferImage.ImageReader() + n["out"]["channelNames"].getValue() + n["out"].channelData( "R", IECore.V2i( 0 ) ) + + def testChannelDataHashes( self ) : + # Test that two tiles within the same image have different hashes. + n = GafferImage.ImageReader() + n["fileName"].setValue( self.fileName ) + h1 = n["out"].channelData( "R", IECore.V2i( 0 ) ).hash() + h2 = n["out"].channelData( "R", IECore.V2i( GafferImage.ImagePlug().tileSize() ) ).hash() + + self.assertNotEqual( h1, h2 ) + + def testJpgRead( self ) : + + exrReader = GafferImage.ImageReader() + exrReader["fileName"].setValue( self.fileName ) + + jpgReader = GafferImage.ImageReader() + jpgReader["fileName"].setValue( self.jpgFileName ) + + exrImage = exrReader["out"].image() + jpgImage = jpgReader["out"].image() + + exrImage.blindData().clear() + jpgImage.blindData().clear() + + imageDiffOp = IECore.ImageDiffOp() + res = imageDiffOp( + imageA = exrImage, + imageB = jpgImage, + ) + self.assertFalse( res.value ) + + def testSupportedExtensions( self ) : + + self.assertEqual( GafferImage.ImageReader.supportedExtensions(), GafferImage.OpenImageIOReader.supportedExtensions() ) + + def testFileRefresh( self ) : + + testFile = self.temporaryDirectory() + "/refresh.exr" + shutil.copyfile( self.fileName, testFile ) + + reader = GafferImage.ImageReader() + reader["fileName"].setValue( testFile ) + image1 = reader["out"].image() + + # even though we've change the image on disk, gaffer will + # still have the old one in its cache. + shutil.copyfile( self.jpgFileName, testFile ) + self.assertEqual( reader["out"].image(), image1 ) + + # until we force a refresh + reader["refreshCount"].setValue( reader["refreshCount"].getValue() + 1 ) + self.assertNotEqual( reader["out"].image(), image1 ) + + def testNonexistentFiles( self ) : + + reader = GafferImage.ImageReader() + reader["fileName"].setValue( "wellIDontExist.exr" ) + + self.assertRaisesRegexp( RuntimeError, ".*wellIDontExist.exr.*", reader["out"].image ) + self.assertRaisesRegexp( RuntimeError, ".*wellIDontExist.exr.*", reader["out"]["format"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*wellIDontExist.exr.*", reader["out"]["dataWindow"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*wellIDontExist.exr.*", reader["out"]["metadata"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*wellIDontExist.exr.*", reader["out"]["channelNames"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*wellIDontExist.exr.*", reader["out"].channelData, "R", IECore.V2i( 0 ) ) + + def testMissingFrameMode( self ) : + + testSequence = IECore.FileSequence( self.temporaryDirectory() + "/incompleteSequence.####.exr" ) + shutil.copyfile( self.fileName, testSequence.fileNameForFrame( 1 ) ) + shutil.copyfile( self.offsetDataWindowFileName, testSequence.fileNameForFrame( 3 ) ) + + reader = GafferImage.ImageReader() + reader["fileName"].setValue( testSequence.fileName ) + + oiio = GafferImage.OpenImageIOReader() + oiio["fileName"].setValue( testSequence.fileName ) + + def assertMatch() : + + self.assertEqual( reader["out"]["format"].getValue(), oiio["out"]["format"].getValue() ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), oiio["out"]["dataWindow"].getValue() ) + self.assertEqual( reader["out"]["metadata"].getValue(), oiio["out"]["metadata"].getValue() ) + self.assertEqual( reader["out"]["channelNames"].getValue(), oiio["out"]["channelNames"].getValue() ) + self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), oiio["out"].channelData( "R", IECore.V2i( 0 ) ) ) + self.assertEqual( reader["out"].image(), oiio["out"].image() ) + + context = Gaffer.Context() + + # set to a missing frame + context.setFrame( 2 ) + + # everything throws + reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Error ) + with context : + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].image ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["dataWindow"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["metadata"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["channelNames"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].channelData, "R", IECore.V2i( 0 ) ) + + # Hold mode matches OpenImageIOReader + reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Hold ) + oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold ) + with context : + assertMatch() + + # Black mode matches OpenImageIOReader + reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Black ) + oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) + with context : + assertMatch() + + # set to a different missing frame + context.setFrame( 4 ) + + # Hold mode matches OpenImageIOReader + reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Hold ) + oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold ) + with context : + assertMatch() + + # Black mode matches OpenImageIOReader + reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Black ) + oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) + with context : + assertMatch() + + # set to a missing frame before the start of the sequence + context.setFrame( 0 ) + + # Hold mode matches OpenImageIOReader + reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Hold ) + oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold ) + with context : + assertMatch() + + # Black mode matches OpenImageIOReader + reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Black ) + oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) + with context : + assertMatch() + + # explicit fileNames do not support MissingFrameMode + reader["fileName"].setValue( testSequence.fileNameForFrame( 0 ) ) + reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold ) + with context : + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].image ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["dataWindow"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["metadata"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["channelNames"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].channelData, "R", IECore.V2i( 0 ) ) + + reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) + oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) + with context : + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].image ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue ) + self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["dataWindow"].getValue ) + self.assertEqual( reader["out"]["metadata"].getValue(), oiio["out"]["metadata"].getValue() ) + self.assertEqual( reader["out"]["channelNames"].getValue(), oiio["out"]["channelNames"].getValue() ) + self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), oiio["out"].channelData( "R", IECore.V2i( 0 ) ) ) + + def testFrameRangeMask( self ) : + + testSequence = IECore.FileSequence( self.temporaryDirectory() + "/incompleteSequence.####.exr" ) + shutil.copyfile( self.fileName, testSequence.fileNameForFrame( 1 ) ) + shutil.copyfile( self.fileName, testSequence.fileNameForFrame( 3 ) ) + shutil.copyfile( self.offsetDataWindowFileName, testSequence.fileNameForFrame( 5 ) ) + shutil.copyfile( self.offsetDataWindowFileName, testSequence.fileNameForFrame( 7 ) ) + + reader = GafferImage.ImageReader() + reader["fileName"].setValue( testSequence.fileName ) + reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Hold ) + + oiio = GafferImage.OpenImageIOReader() + oiio["fileName"].setValue( testSequence.fileName ) + oiio["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Hold ) + + context = Gaffer.Context() + + # make sure the tile we're comparing isn't black + # so we can tell if BlackOutside is working. + blackTile = IECore.FloatVectorData( [ 0 ] * GafferImage.ImagePlug.tileSize() * GafferImage.ImagePlug.tileSize() ) + with context : + for i in range( 1, 11 ) : + context.setFrame( i ) + self.assertNotEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), blackTile ) + + def assertBlack() : + + # format and data window still match + self.assertEqual( reader["out"]["format"].getValue(), oiio["out"]["format"].getValue() ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), oiio["out"]["dataWindow"].getValue() ) + self.assertNotEqual( reader["out"].image(), oiio["out"].image() ) + # the metadata and channel names are at the defaults + self.assertEqual( reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue() ) + self.assertEqual( reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue() ) + # channel data is black + self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), blackTile ) + + def assertMatch() : + + self.assertEqual( reader["out"]["format"].getValue(), oiio["out"]["format"].getValue() ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), oiio["out"]["dataWindow"].getValue() ) + self.assertEqual( reader["out"]["metadata"].getValue(), oiio["out"]["metadata"].getValue() ) + self.assertEqual( reader["out"]["channelNames"].getValue(), oiio["out"]["channelNames"].getValue() ) + self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), oiio["out"].channelData( "R", IECore.V2i( 0 ) ) ) + self.assertEqual( reader["out"].image(), oiio["out"].image() ) + + def assertHold( holdFrame ) : + + context = Gaffer.Context() + context.setFrame( holdFrame ) + with context : + holdImage = reader["out"].image() + holdFormat = reader["out"]["format"].getValue() + holdDataWindow = reader["out"]["dataWindow"].getValue() + holdMetadata = reader["out"]["metadata"].getValue() + holdChannelNames = reader["out"]["channelNames"].getValue() + holdTile = reader["out"].channelData( "R", IECore.V2i( 0 ) ) + + self.assertEqual( reader["out"]["format"].getValue(), holdFormat ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), holdDataWindow ) + self.assertEqual( reader["out"]["metadata"].getValue(), holdMetadata ) + self.assertEqual( reader["out"]["channelNames"].getValue(), holdChannelNames ) + self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), holdTile ) + self.assertEqual( reader["out"].image(), holdImage ) + + reader["frameStartMask"]["frame"].setValue( 4 ) + reader["frameEndMask"]["frame"].setValue( 7 ) + + # frame 0 errors, match from 1-10 + reader["frameStartMask"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.None ) + reader["frameEndMask"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.None ) + with context : + + for i in range( 0, 11 ) : + context.setFrame( i ) + assertMatch() + + # black from 0-3, match from 4-10 + reader["frameStartMask"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.BlackOutside ) + with context : + + for i in range( 0, 4 ) : + context.setFrame( i ) + assertBlack() + + for i in range( 4, 11 ) : + context.setFrame( i ) + assertMatch() + + # black from 0-3, match from 4-7, black from 8-10 + reader["frameEndMask"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.BlackOutside ) + with context : + + for i in range( 0, 4 ) : + context.setFrame( i ) + assertBlack() + + for i in range( 4, 8 ) : + context.setFrame( i ) + assertMatch() + + for i in range( 8, 11 ) : + context.setFrame( i ) + assertBlack() + + # hold frame 4 from 0-3, match from 4-7, black from 8-10 + reader["frameStartMask"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.ClampToRange ) + with context : + + for i in range( 0, 4 ) : + context.setFrame( i ) + assertHold( 4 ) + + for i in range( 4, 8 ) : + context.setFrame( i ) + assertMatch() + + for i in range( 8, 11 ) : + context.setFrame( i ) + assertBlack() + + # hold frame 4 from 0-3, match from 4-7, hold frame 7 from 8-10 + reader["frameEndMask"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.ClampToRange ) + with context : + + for i in range( 0, 4 ) : + context.setFrame( i ) + assertHold( 4 ) + + for i in range( 4, 8 ) : + context.setFrame( i ) + assertMatch() + + for i in range( 8, 11 ) : + context.setFrame( i ) + assertHold( 7 ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferImageTest/OpenImageIOReaderTest.py b/python/GafferImageTest/OpenImageIOReaderTest.py index c6dfb13f4c1..e1d2570eba9 100644 --- a/python/GafferImageTest/OpenImageIOReaderTest.py +++ b/python/GafferImageTest/OpenImageIOReaderTest.py @@ -227,8 +227,6 @@ def testJpgRead( self ) : jpgReader = GafferImage.OpenImageIOReader() jpgReader["fileName"].setValue( self.circlesJpgFileName ) - ## \todo This colorspace manipulation needs to be performed - # automatically in the ImageReader. jpgOCIO = GafferImage.OpenColorIO() jpgOCIO["in"].setInput( jpgReader["out"] ) jpgOCIO["inputSpace"].setValue( "sRGB" ) diff --git a/python/GafferImageTest/__init__.py b/python/GafferImageTest/__init__.py index d5ef0fb94ee..2cd87ad9cbc 100644 --- a/python/GafferImageTest/__init__.py +++ b/python/GafferImageTest/__init__.py @@ -40,6 +40,7 @@ from ImageTestCase import ImageTestCase from ImagePlugTest import ImagePlugTest from OpenImageIOReaderTest import OpenImageIOReaderTest +from ImageReaderTest import ImageReaderTest from ColorSpaceTest import ColorSpaceTest from ObjectToImageTest import ObjectToImageTest from FormatTest import FormatTest From e17e6039f985ab0090923295adc91b08caad58f9 Mon Sep 17 00:00:00 2001 From: Andrew Kaufman Date: Fri, 6 Nov 2015 12:12:12 -0800 Subject: [PATCH 08/13] Added ImageReaderUI --- python/GafferImageUI/ImageReaderUI.py | 247 ++++++++++++++++++++++++++ python/GafferImageUI/__init__.py | 1 + 2 files changed, 248 insertions(+) create mode 100644 python/GafferImageUI/ImageReaderUI.py diff --git a/python/GafferImageUI/ImageReaderUI.py b/python/GafferImageUI/ImageReaderUI.py new file mode 100644 index 00000000000..2ddba8973a9 --- /dev/null +++ b/python/GafferImageUI/ImageReaderUI.py @@ -0,0 +1,247 @@ +########################################################################## +# +# Copyright (c) 2014-2015, Image Engine Design Inc. 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. +# +########################################################################## + +import IECore + +import Gaffer +import GafferUI +import GafferImage + +Gaffer.Metadata.registerNode( + + GafferImage.ImageReader, + + "description", + """ + Reads image files from disk using OpenImageIO. All file + types supported by OpenImageIO are supported by the ImageReader + and all channel data will be converted to linear using OpenColorIO. + """, + + plugs = { + + "fileName" : [ + + "description", + """ + The name of the file to be read. File sequences with + arbitrary padding may be specified using the '#' character + as a placeholder for the frame numbers. If this file sequence + format is used, then missingFrameMode will be activated. + """, + + "plugValueWidget:type", "GafferUI.FileSystemPathPlugValueWidget", + "pathPlugValueWidget:leaf", True, + "pathPlugValueWidget:bookmarks", "image", + "fileSystemPathPlugValueWidget:extensions", IECore.StringVectorData( GafferImage.ImageReader.supportedExtensions() ), + "fileSystemPathPlugValueWidget:extensionsLabel", "Show only image files", + "fileSystemPathPlugValueWidget:includeSequences", True, + + ], + + "refreshCount" : [ + + "description", + """ + May be incremented to force a reload if the file has + changed on disk - otherwise old contents may still + be loaded via Gaffer's cache. + """, + + ], + + "missingFrameMode" : [ + + "description", + """ + Determines how missing frames are handled when the input + fileName is a file sequence (uses the '#' character). + The default behaviour is to throw an exception, but it + can also hold the last valid frame in the sequence, or + return a black image which matches the data window and + display window of the previous valid frame in the sequence. + """, + + "preset:Error", GafferImage.ImageReader.MissingFrameMode.Error, + "preset:Black", GafferImage.ImageReader.MissingFrameMode.Black, + "preset:Hold", GafferImage.ImageReader.MissingFrameMode.Hold, + + "plugValueWidget:type", "GafferUI.PresetsPlugValueWidget", + + ], + + "frameStartMask" : [ + + "description", + """ + Masks frames which preceed the specified start frame. + The default is to treat them based on the MissingFrameMode, + but they can also be clamped to the start frame, or + return a black image which matches the data window + and display window of the start frame. + """, + + "plugValueWidget:type", "GafferImageUI.ImageReaderUI._FrameMaskPlugValueWidget", + + ], + + "frameStartMask.mode" : [ + + "description", + """ + The mode used detemine the mask behaviour for the start frame. + """, + + "preset:None", GafferImage.ImageReader.FrameMaskMode.None, + "preset:Black Outside", GafferImage.ImageReader.FrameMaskMode.BlackOutside, + "preset:Clamp to Range", GafferImage.ImageReader.FrameMaskMode.ClampToRange, + + "plugValueWidget:type", "GafferUI.PresetsPlugValueWidget", + + ], + + "frameStartMask.frame" : [ + + "description", + """ + The start frame of the masked range. + """, + + "presetNames", lambda plug : IECore.StringVectorData( [ str(x) for x in plug.node()["__oiioReader"]["availableFrames"].getValue() ] ), + "presetValues", lambda plug : plug.node()["__oiioReader"]["availableFrames"].getValue(), + + ], + + "frameEndMask" : [ + + "description", + """ + Masks frames which follow the specified end frame. + The default is to treat them based on the MissingFrameMode, + but they can also be clamped to the end frame, or + return a black image which matches the data window + and display window of the end frame. + """, + + "plugValueWidget:type", "GafferImageUI.ImageReaderUI._FrameMaskPlugValueWidget", + + ], + + "frameEndMask.mode" : [ + + "description", + """ + The mode used detemine the mask behaviour for the end frame. + """, + + "preset:None", GafferImage.ImageReader.FrameMaskMode.None, + "preset:Black Outside", GafferImage.ImageReader.FrameMaskMode.BlackOutside, + "preset:Clamp to Range", GafferImage.ImageReader.FrameMaskMode.ClampToRange, + + "plugValueWidget:type", "GafferUI.PresetsPlugValueWidget", + + ], + + "frameEndMask.frame" : [ + + "description", + """ + The end frame of the masked range. + """, + + "presetNames", lambda plug : IECore.StringVectorData( [ str(x) for x in plug.node()["__oiioReader"]["availableFrames"].getValue() ] ), + "presetValues", lambda plug : plug.node()["__oiioReader"]["availableFrames"].getValue(), + + ], + + } + +) + +GafferUI.PlugValueWidget.registerCreator( GafferImage.ImageReader, "refreshCount", GafferUI.IncrementingPlugValueWidget, label = "Refresh", undoable = False ) + +class _FrameMaskPlugValueWidget( GafferUI.PlugValueWidget ) : + + def __init__( self, plug, **kw ) : + + with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing=4 ) as self.__row : + + GafferUI.PlugValueWidget.create( plug["mode"] ) + GafferUI.PlugValueWidget.create( plug["frame"] ) + + GafferUI.PlugValueWidget.__init__( self, self.__row, plug, **kw ) + + def setPlug( self, plug ) : + + assert( len( plug ) == len( self.getPlug() ) ) + + GafferUI.PlugValueWidget.setPlug( self, plug ) + + for index, plug in enumerate( plug.children() ) : + self.__row[index].setPlug( plug ) + + def setHighlighted( self, highlighted ) : + + GafferUI.PlugValueWidget.setHighlighted( self, highlighted ) + + for i in range( 0, len( self.getPlug() ) ) : + self.__row[i].setHighlighted( highlighted ) + + def setReadOnly( self, readOnly ) : + + if readOnly == self.getReadOnly() : + return + + GafferUI.PlugValueWidget.setReadOnly( self, readOnly ) + + for w in self.__row : + if isinstance( w, GafferUI.PlugValueWidget ) : + w.setReadOnly( readOnly ) + + def childPlugValueWidget( self, childPlug, lazy=True ) : + + for i, p in enumerate( self.getPlug().children() ) : + if p.isSame( childPlug ) : + return self.__row[i] + + return None + + def _updateFromPlug( self ) : + + with self.getContext() : + mode = self.getPlug()["mode"].getValue() + + self.childPlugValueWidget( self.getPlug()["frame"] ).setEnabled( mode != GafferImage.ImageReader.FrameMaskMode.None ) diff --git a/python/GafferImageUI/__init__.py b/python/GafferImageUI/__init__.py index b1ddc2e1cc2..0749df4f0fc 100644 --- a/python/GafferImageUI/__init__.py +++ b/python/GafferImageUI/__init__.py @@ -42,6 +42,7 @@ from ChannelMaskPlugValueWidget import ChannelMaskPlugValueWidget import OpenImageIOReaderUI +import ImageReaderUI import ImageViewToolbar import ImageTransformUI import ConstantUI From 708bfe5820453afe2ac341b09e24b4fce877e832 Mon Sep 17 00:00:00 2001 From: Andrew Kaufman Date: Mon, 16 Nov 2015 09:39:32 -0800 Subject: [PATCH 09/13] Fixed OpenImageIOReader::hashFileName() --- src/GafferImage/OpenImageIOReader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GafferImage/OpenImageIOReader.cpp b/src/GafferImage/OpenImageIOReader.cpp index 793bc63de91..8ba0731f677 100644 --- a/src/GafferImage/OpenImageIOReader.cpp +++ b/src/GafferImage/OpenImageIOReader.cpp @@ -509,9 +509,9 @@ void OpenImageIOReader::hashFileName( const Gaffer::Context *context, IECore::Mu // but we internally vary the result output by // frame, we need to explicitly hash the frame // when the value contains FrameSubstitutions. - const std::string &fileName = fileNamePlug()->getValue(); + const std::string fileName = fileNamePlug()->getValue(); h.append( fileName ); - if( Context::substitutions( fileNamePlug()->getValue() ) & Context::FrameSubstitutions ) + if( Context::substitutions( fileName ) & Context::FrameSubstitutions ) { h.append( context->getFrame() ); } From 9b8d3c1b257993e83806f81e8d342af0ae1778f2 Mon Sep 17 00:00:00 2001 From: Andrew Kaufman Date: Mon, 16 Nov 2015 15:39:06 -0800 Subject: [PATCH 10/13] Fixed FrameSubstitutions for ImageReader fileName --- src/GafferImage/ImageReader.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/GafferImage/ImageReader.cpp b/src/GafferImage/ImageReader.cpp index 766932dbc85..30b5cb36ee1 100644 --- a/src/GafferImage/ImageReader.cpp +++ b/src/GafferImage/ImageReader.cpp @@ -63,7 +63,13 @@ ImageReader::ImageReader( const std::string &name ) : ImageNode( name ) { storeIndexOfNextChild( g_firstChildIndex ); - addChild( new StringPlug( "fileName" ) ); + addChild( + new StringPlug( + "fileName", Plug::In, "", + /* flags */ Plug::Default, + /* substitutions */ Context::AllSubstitutions & ~Context::FrameSubstitutions + ) + ); addChild( new IntPlug( "refreshCount" ) ); addChild( new IntPlug( "missingFrameMode", Plug::In, Error, /* min */ Error, /* max */ Hold ) ); From 823e82e10a2932109e073f128b6c59fde32b3c1d Mon Sep 17 00:00:00 2001 From: Andrew Kaufman Date: Fri, 20 Nov 2015 15:01:00 -0800 Subject: [PATCH 11/13] Addressed note on OpenImageIOReader test --- .../GafferImageTest/OpenImageIOReaderTest.py | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/python/GafferImageTest/OpenImageIOReaderTest.py b/python/GafferImageTest/OpenImageIOReaderTest.py index e1d2570eba9..38607e8d52a 100644 --- a/python/GafferImageTest/OpenImageIOReaderTest.py +++ b/python/GafferImageTest/OpenImageIOReaderTest.py @@ -474,25 +474,18 @@ def testHashesFrame( self ) : reader = GafferImage.OpenImageIOReader() reader["fileName"].setValue( testSequence.fileName ) - # since the reader["out"] is never cached, we need - # to go through an intermediate node which does cache - # in order to see the affects of a frame change. - - metadata = GafferImage.ImageMetadata() - metadata["in"].setInput( reader["out"] ) - context = Gaffer.Context() # get frame 0 data for comparison context.setFrame( 0 ) with context : - sequenceMetadataHash = metadata["out"]["metadata"].hash() - sequenceMetadataValue = metadata["out"]["metadata"].getValue() + sequenceMetadataHash = reader["out"]["metadata"].hash() + sequenceMetadataValue = reader["out"]["metadata"].getValue() context.setFrame( 1 ) with context : - self.assertNotEqual( metadata["out"]["metadata"].hash(), sequenceMetadataHash ) - self.assertNotEqual( metadata["out"]["metadata"].getValue(), sequenceMetadataValue ) + self.assertNotEqual( reader["out"]["metadata"].hash(), sequenceMetadataHash ) + self.assertNotEqual( reader["out"]["metadata"].getValue(), sequenceMetadataValue ) # but when we set an explicit fileName, # we no longer re-compute per frame. @@ -501,15 +494,15 @@ def testHashesFrame( self ) : # get frame 0 data for comparison context.setFrame( 0 ) with context : - explicitMetadataHash = metadata["out"]["metadata"].hash() + explicitMetadataHash = reader["out"]["metadata"].hash() self.assertNotEqual( explicitMetadataHash, sequenceMetadataHash ) - self.assertEqual( metadata["out"]["metadata"].getValue(), sequenceMetadataValue ) + self.assertEqual( reader["out"]["metadata"].getValue(), sequenceMetadataValue ) context.setFrame( 1 ) with context : - self.assertNotEqual( metadata["out"]["metadata"].hash(), sequenceMetadataHash ) - self.assertEqual( metadata["out"]["metadata"].hash(), explicitMetadataHash ) - self.assertEqual( metadata["out"]["metadata"].getValue(), sequenceMetadataValue ) + self.assertNotEqual( reader["out"]["metadata"].hash(), sequenceMetadataHash ) + self.assertEqual( reader["out"]["metadata"].hash(), explicitMetadataHash ) + self.assertEqual( reader["out"]["metadata"].getValue(), sequenceMetadataValue ) if __name__ == "__main__": unittest.main() From d9215da4a4f025fa1b348d3271f9cae697b607c5 Mon Sep 17 00:00:00 2001 From: Andrew Kaufman Date: Fri, 20 Nov 2015 15:07:48 -0800 Subject: [PATCH 12/13] OpenImageIOReader Black mode returns default dataWindow --- python/GafferImageTest/ImageReaderTest.py | 2 +- python/GafferImageTest/OpenImageIOReaderTest.py | 9 ++++----- src/GafferImage/OpenImageIOReader.cpp | 8 ++------ 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/python/GafferImageTest/ImageReaderTest.py b/python/GafferImageTest/ImageReaderTest.py index ba1dc8a9ff4..ad28c63ce24 100644 --- a/python/GafferImageTest/ImageReaderTest.py +++ b/python/GafferImageTest/ImageReaderTest.py @@ -230,7 +230,7 @@ def assertMatch() : with context : self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].image ) self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue ) - self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["dataWindow"].getValue ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), oiio["out"]["dataWindow"].getValue() ) self.assertEqual( reader["out"]["metadata"].getValue(), oiio["out"]["metadata"].getValue() ) self.assertEqual( reader["out"]["channelNames"].getValue(), oiio["out"]["channelNames"].getValue() ) self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), oiio["out"].channelData( "R", IECore.V2i( 0 ) ) ) diff --git a/python/GafferImageTest/OpenImageIOReaderTest.py b/python/GafferImageTest/OpenImageIOReaderTest.py index 38607e8d52a..beb2956f179 100644 --- a/python/GafferImageTest/OpenImageIOReaderTest.py +++ b/python/GafferImageTest/OpenImageIOReaderTest.py @@ -373,7 +373,7 @@ def testMissingFrameMode( self ) : with context : self.assertNotEqual( reader["out"].image(), f1Image ) self.assertEqual( reader["out"]["format"].getValue(), f1Format ) - self.assertEqual( reader["out"]["dataWindow"].getValue(), f1DataWindow ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), reader["out"]["dataWindow"].defaultValue() ) self.assertEqual( reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue() ) self.assertEqual( reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue() ) self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), blackTile ) @@ -412,9 +412,8 @@ def testMissingFrameMode( self ) : reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) with context : self.assertNotEqual( reader["out"]["format"].getValue(), f1Format ) - self.assertNotEqual( reader["out"]["dataWindow"].getValue(), f1DataWindow ) self.assertEqual( reader["out"]["format"].getValue(), f3Format ) - self.assertEqual( reader["out"]["dataWindow"].getValue(), f3DataWindow ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), reader["out"]["dataWindow"].defaultValue() ) self.assertEqual( reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue() ) self.assertEqual( reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue() ) self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), blackTile ) @@ -435,7 +434,7 @@ def testMissingFrameMode( self ) : reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) with context : self.assertEqual( reader["out"]["format"].getValue(), f1Format ) - self.assertEqual( reader["out"]["dataWindow"].getValue(), f1DataWindow ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), reader["out"]["dataWindow"].defaultValue() ) self.assertEqual( reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue() ) self.assertEqual( reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue() ) self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), blackTile ) @@ -455,7 +454,7 @@ def testMissingFrameMode( self ) : with context : self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].image ) self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue ) - self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["dataWindow"].getValue ) + self.assertEqual( reader["out"]["dataWindow"].getValue(), reader["out"]["dataWindow"].defaultValue() ) self.assertEqual( reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue() ) self.assertEqual( reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue() ) self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), blackTile ) diff --git a/src/GafferImage/OpenImageIOReader.cpp b/src/GafferImage/OpenImageIOReader.cpp index 8ba0731f677..868dd097388 100644 --- a/src/GafferImage/OpenImageIOReader.cpp +++ b/src/GafferImage/OpenImageIOReader.cpp @@ -558,14 +558,10 @@ void OpenImageIOReader::hashDataWindow( const GafferImage::ImagePlug *output, co Imath::Box2i OpenImageIOReader::computeDataWindow( const Gaffer::Context *context, const ImagePlug *parent ) const { std::string fileName = fileNamePlug()->getValue(); - // when we're in MissingFrameMode::Black we still want to - // match the data window of the Hold frame. - MissingFrameMode mode = (MissingFrameMode)missingFrameModePlug()->getValue(); - mode = ( mode == Black ) ? Hold : mode; - const ImageSpec *spec = imageSpec( fileName, mode, this, context ); + const ImageSpec *spec = imageSpec( fileName, (MissingFrameMode)missingFrameModePlug()->getValue(), this, context ); if( !spec ) { - return Box2i(); + return parent->dataWindowPlug()->defaultValue(); } Format format( Imath::Box2i( Imath::V2i( spec->full_x, spec->full_y ), Imath::V2i( spec->full_width + spec->full_x, spec->full_height + spec->full_y ) ) ); From cdfbb360b0da4e4b7f9e14473583959a784ef010 Mon Sep 17 00:00:00 2001 From: Andrew Kaufman Date: Mon, 23 Nov 2015 09:26:00 -0800 Subject: [PATCH 13/13] Renamed the mask plugs. Also switched to a borrowed context --- include/GafferImage/ImageReader.h | 25 ++++---- python/GafferImageTest/ImageReaderTest.py | 18 +++--- .../GafferImageTest/OpenImageIOReaderTest.py | 2 +- python/GafferImageUI/ImageReaderUI.py | 16 ++--- src/GafferImage/ImageReader.cpp | 64 +++++++++---------- .../ImageReaderBinding.cpp | 2 +- 6 files changed, 64 insertions(+), 63 deletions(-) diff --git a/include/GafferImage/ImageReader.h b/include/GafferImage/ImageReader.h index 6194cd9b63e..c7186a8da09 100644 --- a/include/GafferImage/ImageReader.h +++ b/include/GafferImage/ImageReader.h @@ -76,12 +76,13 @@ class ImageReader : public ImageNode }; /// The FrameMaskMode controls how to handle images - /// outside of the values provided by frameRangeMask(). + /// outside of the values provided by the start + /// and end frame masks. enum FrameMaskMode { None = 0, BlackOutside, - ClampToRange, + ClampToFrame, }; Gaffer::StringPlug *fileNamePlug(); @@ -94,17 +95,17 @@ class ImageReader : public ImageNode Gaffer::IntPlug *missingFrameModePlug(); const Gaffer::IntPlug *missingFrameModePlug() const; - Gaffer::IntPlug *frameStartMaskModePlug(); - const Gaffer::IntPlug *frameStartMaskModePlug() const; + Gaffer::IntPlug *startModePlug(); + const Gaffer::IntPlug *startModePlug() const; - Gaffer::IntPlug *frameStartMaskPlug(); - const Gaffer::IntPlug *frameStartMaskPlug() const; + Gaffer::IntPlug *startFramePlug(); + const Gaffer::IntPlug *startFramePlug() const; - Gaffer::IntPlug *frameEndMaskModePlug(); - const Gaffer::IntPlug *frameEndMaskModePlug() const; + Gaffer::IntPlug *endModePlug(); + const Gaffer::IntPlug *endModePlug() const; - Gaffer::IntPlug *frameEndMaskPlug(); - const Gaffer::IntPlug *frameEndMaskPlug() const; + Gaffer::IntPlug *endFramePlug(); + const Gaffer::IntPlug *endFramePlug() const; virtual void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const; @@ -136,8 +137,8 @@ class ImageReader : public ImageNode GafferImage::ImagePlug *intermediateImagePlug(); const GafferImage::ImagePlug *intermediateImagePlug() const; - void hashMaskedOutput( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h, bool alwaysClampToRange = false ) const; - void computeMaskedOutput( Gaffer::ValuePlug *output, const Gaffer::Context *context, bool alwaysClampToRange = false ) const; + void hashMaskedOutput( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h, bool alwaysClampToFrame = false ) const; + void computeMaskedOutput( Gaffer::ValuePlug *output, const Gaffer::Context *context, bool alwaysClampToFrame = false ) const; bool computeFrameMask( const Gaffer::Context *context, Gaffer::ContextPtr &maskedContext ) const; diff --git a/python/GafferImageTest/ImageReaderTest.py b/python/GafferImageTest/ImageReaderTest.py index ad28c63ce24..d5ab17b1b41 100644 --- a/python/GafferImageTest/ImageReaderTest.py +++ b/python/GafferImageTest/ImageReaderTest.py @@ -45,7 +45,7 @@ import GafferImage import GafferImageTest -class ImageReaderTest( GafferTest.TestCase ) : +class ImageReaderTest( GafferImageTest.ImageTestCase ) : fileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/circles.exr" ) offsetDataWindowFileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/rgb.100x100.exr" ) @@ -301,12 +301,12 @@ def assertHold( holdFrame ) : self.assertEqual( reader["out"].channelData( "R", IECore.V2i( 0 ) ), holdTile ) self.assertEqual( reader["out"].image(), holdImage ) - reader["frameStartMask"]["frame"].setValue( 4 ) - reader["frameEndMask"]["frame"].setValue( 7 ) + reader["start"]["frame"].setValue( 4 ) + reader["end"]["frame"].setValue( 7 ) # frame 0 errors, match from 1-10 - reader["frameStartMask"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.None ) - reader["frameEndMask"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.None ) + reader["start"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.None ) + reader["end"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.None ) with context : for i in range( 0, 11 ) : @@ -314,7 +314,7 @@ def assertHold( holdFrame ) : assertMatch() # black from 0-3, match from 4-10 - reader["frameStartMask"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.BlackOutside ) + reader["start"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.BlackOutside ) with context : for i in range( 0, 4 ) : @@ -326,7 +326,7 @@ def assertHold( holdFrame ) : assertMatch() # black from 0-3, match from 4-7, black from 8-10 - reader["frameEndMask"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.BlackOutside ) + reader["end"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.BlackOutside ) with context : for i in range( 0, 4 ) : @@ -342,7 +342,7 @@ def assertHold( holdFrame ) : assertBlack() # hold frame 4 from 0-3, match from 4-7, black from 8-10 - reader["frameStartMask"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.ClampToRange ) + reader["start"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.ClampToFrame ) with context : for i in range( 0, 4 ) : @@ -358,7 +358,7 @@ def assertHold( holdFrame ) : assertBlack() # hold frame 4 from 0-3, match from 4-7, hold frame 7 from 8-10 - reader["frameEndMask"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.ClampToRange ) + reader["end"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.ClampToFrame ) with context : for i in range( 0, 4 ) : diff --git a/python/GafferImageTest/OpenImageIOReaderTest.py b/python/GafferImageTest/OpenImageIOReaderTest.py index beb2956f179..93e528bb390 100644 --- a/python/GafferImageTest/OpenImageIOReaderTest.py +++ b/python/GafferImageTest/OpenImageIOReaderTest.py @@ -46,7 +46,7 @@ import GafferImage import GafferImageTest -class OpenImageIOReaderTest( GafferImageTest.TestCase ) : +class OpenImageIOReaderTest( GafferImageTest.ImageTestCase ) : fileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/checker.exr" ) offsetDataWindowFileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/rgb.100x100.exr" ) diff --git a/python/GafferImageUI/ImageReaderUI.py b/python/GafferImageUI/ImageReaderUI.py index 2ddba8973a9..13bccd7f64e 100644 --- a/python/GafferImageUI/ImageReaderUI.py +++ b/python/GafferImageUI/ImageReaderUI.py @@ -103,7 +103,7 @@ ], - "frameStartMask" : [ + "start" : [ "description", """ @@ -118,7 +118,7 @@ ], - "frameStartMask.mode" : [ + "start.mode" : [ "description", """ @@ -127,13 +127,13 @@ "preset:None", GafferImage.ImageReader.FrameMaskMode.None, "preset:Black Outside", GafferImage.ImageReader.FrameMaskMode.BlackOutside, - "preset:Clamp to Range", GafferImage.ImageReader.FrameMaskMode.ClampToRange, + "preset:Clamp to Range", GafferImage.ImageReader.FrameMaskMode.ClampToFrame, "plugValueWidget:type", "GafferUI.PresetsPlugValueWidget", ], - "frameStartMask.frame" : [ + "start.frame" : [ "description", """ @@ -145,7 +145,7 @@ ], - "frameEndMask" : [ + "end" : [ "description", """ @@ -160,7 +160,7 @@ ], - "frameEndMask.mode" : [ + "end.mode" : [ "description", """ @@ -169,13 +169,13 @@ "preset:None", GafferImage.ImageReader.FrameMaskMode.None, "preset:Black Outside", GafferImage.ImageReader.FrameMaskMode.BlackOutside, - "preset:Clamp to Range", GafferImage.ImageReader.FrameMaskMode.ClampToRange, + "preset:Clamp to Range", GafferImage.ImageReader.FrameMaskMode.ClampToFrame, "plugValueWidget:type", "GafferUI.PresetsPlugValueWidget", ], - "frameEndMask.frame" : [ + "end.frame" : [ "description", """ diff --git a/src/GafferImage/ImageReader.cpp b/src/GafferImage/ImageReader.cpp index 30b5cb36ee1..7926036d046 100644 --- a/src/GafferImage/ImageReader.cpp +++ b/src/GafferImage/ImageReader.cpp @@ -73,15 +73,15 @@ ImageReader::ImageReader( const std::string &name ) addChild( new IntPlug( "refreshCount" ) ); addChild( new IntPlug( "missingFrameMode", Plug::In, Error, /* min */ Error, /* max */ Hold ) ); - ValuePlugPtr frameStartMaskPlug = new ValuePlug( "frameStartMask", Plug::In ); - frameStartMaskPlug->addChild( new IntPlug( "mode", Plug::In, None, /* min */ None, /* max */ ClampToRange ) ); - frameStartMaskPlug->addChild( new IntPlug( "frame", Plug::In, 0 ) ); - addChild( frameStartMaskPlug ); + ValuePlugPtr startPlug = new ValuePlug( "start", Plug::In ); + startPlug->addChild( new IntPlug( "mode", Plug::In, None, /* min */ None, /* max */ ClampToFrame ) ); + startPlug->addChild( new IntPlug( "frame", Plug::In, 0 ) ); + addChild( startPlug ); - ValuePlugPtr frameEndMaskPlug = new ValuePlug( "frameEndMask", Plug::In ); - frameEndMaskPlug->addChild( new IntPlug( "mode", Plug::In, None, /* min */ None, /* max */ ClampToRange ) ); - frameEndMaskPlug->addChild( new IntPlug( "frame", Plug::In, 0 ) ); - addChild( frameEndMaskPlug ); + ValuePlugPtr endPlug = new ValuePlug( "end", Plug::In ); + endPlug->addChild( new IntPlug( "mode", Plug::In, None, /* min */ None, /* max */ ClampToFrame ) ); + endPlug->addChild( new IntPlug( "frame", Plug::In, 0 ) ); + addChild( endPlug ); addChild( new CompoundObjectPlug( "__intermediateMetadata", Plug::In, new CompoundObject, Plug::Default & ~Plug::Serialisable ) ); addChild( new StringPlug( "__intermediateColorSpace", Plug::Out, "", Plug::Default & ~Plug::Serialisable ) ); @@ -139,42 +139,42 @@ const IntPlug *ImageReader::missingFrameModePlug() const return getChild( g_firstChildIndex + 2 ); } -IntPlug *ImageReader::frameStartMaskModePlug() +IntPlug *ImageReader::startModePlug() { return getChild( g_firstChildIndex + 3 )->getChild( 0 ); } -const IntPlug *ImageReader::frameStartMaskModePlug() const +const IntPlug *ImageReader::startModePlug() const { return getChild( g_firstChildIndex + 3 )->getChild( 0 ); } -IntPlug *ImageReader::frameStartMaskPlug() +IntPlug *ImageReader::startFramePlug() { return getChild( g_firstChildIndex + 3 )->getChild( 1 ); } -const IntPlug *ImageReader::frameStartMaskPlug() const +const IntPlug *ImageReader::startFramePlug() const { return getChild( g_firstChildIndex + 3 )->getChild( 1 ); } -IntPlug *ImageReader::frameEndMaskModePlug() +IntPlug *ImageReader::endModePlug() { return getChild( g_firstChildIndex + 4 )->getChild( 0 ); } -const IntPlug *ImageReader::frameEndMaskModePlug() const +const IntPlug *ImageReader::endModePlug() const { return getChild( g_firstChildIndex + 4 )->getChild( 0 ); } -IntPlug *ImageReader::frameEndMaskPlug() +IntPlug *ImageReader::endFramePlug() { return getChild( g_firstChildIndex + 4 )->getChild( 1 ); } -const IntPlug *ImageReader::frameEndMaskPlug() const +const IntPlug *ImageReader::endFramePlug() const { return getChild( g_firstChildIndex + 4 )->getChild( 1 ); } @@ -248,10 +248,10 @@ void ImageReader::affects( const Plug *input, AffectedPlugsContainer &outputs ) outputs.push_back( outPlug()->getChild( input->getName() ) ); } else if ( - input == frameStartMaskPlug() || - input == frameStartMaskModePlug() || - input == frameEndMaskPlug() || - input == frameEndMaskModePlug() + input == startFramePlug() || + input == startModePlug() || + input == endFramePlug() || + input == endModePlug() ) { for( ValuePlugIterator it( outPlug() ); it != it.end(); ++it ) @@ -276,7 +276,7 @@ void ImageReader::hash( const ValuePlug *output, const Context *context, IECore: { // we always want to match the windows // we would get inside the frame mask - hashMaskedOutput( output, context, h, /* alwaysClampToRange */ true ); + hashMaskedOutput( output, context, h, /* alwaysClampToFrame */ true ); } else if( output == outPlug()->metadataPlug() || @@ -313,7 +313,7 @@ void ImageReader::compute( ValuePlug *output, const Context *context ) const { // we always want to match the windows // we would get inside the frame mask - computeMaskedOutput( output, context, /* alwaysClampToRange */ true ); + computeMaskedOutput( output, context, /* alwaysClampToFrame */ true ); } else if( output == outPlug()->metadataPlug() || @@ -329,21 +329,21 @@ void ImageReader::compute( ValuePlug *output, const Context *context ) const } } -void ImageReader::hashMaskedOutput( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h, bool alwaysClampToRange ) const +void ImageReader::hashMaskedOutput( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h, bool alwaysClampToFrame ) const { ContextPtr maskedContext = NULL; - if( !computeFrameMask( context, maskedContext ) || alwaysClampToRange ) + if( !computeFrameMask( context, maskedContext ) || alwaysClampToFrame ) { Context::Scope scope( maskedContext.get() ); h = intermediateImagePlug()->getChild( output->getName() )->hash(); } } -void ImageReader::computeMaskedOutput( Gaffer::ValuePlug *output, const Gaffer::Context *context, bool alwaysClampToRange ) const +void ImageReader::computeMaskedOutput( Gaffer::ValuePlug *output, const Gaffer::Context *context, bool alwaysClampToFrame ) const { ContextPtr maskedContext = NULL; bool blackOutside = computeFrameMask( context, maskedContext ); - if( blackOutside && !alwaysClampToRange ) + if( blackOutside && !alwaysClampToFrame ) { output->setToDefault(); return; @@ -355,10 +355,10 @@ void ImageReader::computeMaskedOutput( Gaffer::ValuePlug *output, const Gaffer:: bool ImageReader::computeFrameMask( const Context *context, ContextPtr &maskedContext ) const { - int frameStartMask = frameStartMaskPlug()->getValue(); - int frameEndMask = frameEndMaskPlug()->getValue(); - FrameMaskMode frameStartMaskMode = (FrameMaskMode)frameStartMaskModePlug()->getValue(); - FrameMaskMode frameEndMaskMode = (FrameMaskMode)frameEndMaskModePlug()->getValue(); + int frameStartMask = startFramePlug()->getValue(); + int frameEndMask = endFramePlug()->getValue(); + FrameMaskMode frameStartMaskMode = (FrameMaskMode)startModePlug()->getValue(); + FrameMaskMode frameEndMaskMode = (FrameMaskMode)endModePlug()->getValue(); int origFrame = (int)context->getFrame(); int maskedFrame = std::min( frameEndMask, std::max( frameStartMask, origFrame ) ); @@ -380,11 +380,11 @@ bool ImageReader::computeFrameMask( const Context *context, ContextPtr &maskedCo } // we need to create the masked context - // for both BlackOutSide and ClampToRange, + // for both BlackOutSide and ClampToFrame, // because some plugs require valid data // from the mask range even in either way. - maskedContext = new Gaffer::Context( *context, Context::Shared ); + maskedContext = new Gaffer::Context( *context, Context::Borrowed ); maskedContext->setFrame( maskedFrame ); return ( maskMode == BlackOutside ); diff --git a/src/GafferImageBindings/ImageReaderBinding.cpp b/src/GafferImageBindings/ImageReaderBinding.cpp index e745c36c176..5df959b1a92 100644 --- a/src/GafferImageBindings/ImageReaderBinding.cpp +++ b/src/GafferImageBindings/ImageReaderBinding.cpp @@ -75,7 +75,7 @@ void GafferImageBindings::bindImageReader() boost::python::enum_( "FrameMaskMode" ) .value( "None", ImageReader::None ) .value( "BlackOutside", ImageReader::BlackOutside ) - .value( "ClampToRange", ImageReader::ClampToRange ) + .value( "ClampToFrame", ImageReader::ClampToFrame ) ; }