diff --git a/python/GafferImageTest/ShuffleTest.py b/python/GafferImageTest/ShuffleTest.py index 65dfa63893b..d68fd205de7 100644 --- a/python/GafferImageTest/ShuffleTest.py +++ b/python/GafferImageTest/ShuffleTest.py @@ -259,6 +259,31 @@ def testWildCards( self ) : constant["out"].channelData( channel, imath.V2i( 0 ) ), ) + def testWildCardsDontMatchSpecialChannels( self ) : + + constant = GafferImage.Constant() + constant["color"].setValue( imath.Color4f( 0, 1, 2, 3 ) ) + + shuffle = GafferImage.Shuffle() + shuffle["in"].setInput( constant["out"] ) + + self.assertImagesEqual( shuffle["out"], constant["out"] ) + + shuffle["shuffles"].addChild( + Gaffer.ShufflePlug( "*", "newLayer.${source}" ) + ) + + self.assertEqual( + shuffle["out"].channelNames(), + IECore.StringVectorData( [ "R", "G", "B", "A", "newLayer.R", "newLayer.G", "newLayer.B", "newLayer.A" ] ) + ) + + for channel in "RGBA" : + self.assertEqual( + shuffle["out"].channelData( f"newLayer.{channel}", imath.V2i( 0 ) ), + constant["out"].channelData( channel, imath.V2i( 0 ) ), + ) + def testDeleteSource( self ) : constant = GafferImage.Constant() diff --git a/src/GafferImage/Shuffle.cpp b/src/GafferImage/Shuffle.cpp index 650e816a7e7..8dfb9d38a0d 100644 --- a/src/GafferImage/Shuffle.cpp +++ b/src/GafferImage/Shuffle.cpp @@ -50,35 +50,69 @@ using namespace GafferImage; namespace { -struct MappingData : public IECore::Data +class SourceMap : public unordered_map { + public : - MappingData( const StringVectorData *inChannelNames, const ShufflesPlug *shuffles, Shuffle::MissingSourceMode mode ) - { - for( const auto &channelName : inChannelNames->readable() ) + SourceMap( const vector &inChannelNames, Shuffle::MissingSourceMode mode ) + : m_missingSourceMode( mode ) { - m_mapping[channelName] = channelName; + for( const auto &channelName : inChannelNames ) + { + (*this)[channelName] = channelName; + } + m_virtualChannels["__white"] = "__white"; + m_virtualChannels["__black"] = "__black"; } - m_mapping["__white"] = "__white"; - m_mapping["__black"] = "__black"; - if( mode == Shuffle::MissingSourceMode::Black ) - { - const std::string black( "__black" ); - m_mapping = shuffles->shuffleWithDefaultSource( m_mapping, black ); - } - else + const_iterator find( const std::string &source ) const { - m_mapping = shuffles->shuffle( m_mapping, mode == Shuffle::MissingSourceMode::Ignore ); + auto it = unordered_map::find( source ); + if( it != end() ) + { + return it; + } + + // Source channel doesn't exist, see if it's a "virtual" channel. We + // can't just add these to the main map, because then they are + // available for matching by wildcards, which is not what we want. + it = m_virtualChannels.find( source ); + if( it != m_virtualChannels.end() ) + { + // Note : This is technically undefined behaviour, + // because ShufflePlug will compare it against `end()` + // which is from a different container. + return it; + } + + if( m_missingSourceMode == Shuffle::MissingSourceMode::Black ) + { + return m_virtualChannels.find( "__black" ); + } + + return end(); } + private : + + const Shuffle::MissingSourceMode m_missingSourceMode; + unordered_map m_virtualChannels; + +}; + + +struct MappingData : public IECore::Data +{ + + MappingData( const StringVectorData *inChannelNames, const ShufflesPlug *shuffles, Shuffle::MissingSourceMode mode ) + { + const SourceMap sourceMap( inChannelNames->readable(), mode ); + m_mapping = shuffles->shuffle( sourceMap, mode == Shuffle::MissingSourceMode::Ignore ); + m_outChannelNames = new StringVectorData(); for( const auto &m : m_mapping ) { - if( m.first != "__white" && m.first != "__black" ) - { - m_outChannelNames->writable().push_back( m.first ); - } + m_outChannelNames->writable().push_back( m.first ); } m_outChannelNames->writable() = ImageAlgo::sortedChannelNames( m_outChannelNames->readable() ); }