void ParameterisedHolderModificationCmd::restoreClassParameterStates( const IECore::CompoundData *classes, IECore::Parameter *parameter, const std::string &parentParameterPath ) { std::string parameterPath = parentParameterPath; if( parentParameterPath.size() ) { parameterPath += "."; } parameterPath += parameter->name(); if( parameter->isInstanceOf( "ClassParameter" ) ) { const CompoundData *c = classes->member<const CompoundData>( parameterPath ); if( c ) { ClassParameterHandler::setClass( parameter, c->member<const IECore::StringData>( "className" )->readable().c_str(), c->member<const IECore::IntData>( "classVersion" )->readable(), c->member<const IECore::StringData>( "searchPathEnvVar" )->readable().c_str() ); } } else if( parameter->isInstanceOf( "ClassVectorParameter" ) ) { const CompoundData *c = classes->member<const CompoundData>( parameterPath ); if( c ) { IECore::ConstStringVectorDataPtr parameterNames = c->member<const IECore::StringVectorData>( "parameterNames" ); IECore::ConstStringVectorDataPtr classNames = c->member<const IECore::StringVectorData>( "classNames" ); IECore::ConstIntVectorDataPtr classVersions = c->member<const IECore::IntVectorData>( "classVersions" ); MStringArray mParameterNames; MStringArray mClassNames; MIntArray mClassVersions; int numClasses = parameterNames->readable().size(); for( int i=0; i<numClasses; i++ ) { mParameterNames.append( parameterNames->readable()[i].c_str() ); mClassNames.append( classNames->readable()[i].c_str() ); mClassVersions.append( classVersions->readable()[i] ); } ClassVectorParameterHandler::setClasses( parameter, mParameterNames, mClassNames, mClassVersions ); } } if( parameter->isInstanceOf( IECore::CompoundParameter::staticTypeId() ) ) { CompoundParameter *compoundParameter = static_cast<CompoundParameter *>( parameter ); const CompoundParameter::ParameterVector &childParameters = compoundParameter->orderedParameters(); for( CompoundParameter::ParameterVector::const_iterator it = childParameters.begin(); it!=childParameters.end(); it++ ) { restoreClassParameterStates( classes, it->get(), parameterPath ); } } }
void ImageStats::channelNameFromOutput( const ValuePlug *output, std::string &channelName ) const { IECore::ConstStringVectorDataPtr channelNamesData = inPlug()->channelNamesPlug()->getValue(); std::vector<std::string> maskChannels = channelNamesData->readable(); channelsPlug()->maskChannels( maskChannels ); /// As the channelMaskPlug allows any combination of channels to be input we need to make sure that /// the channels that it masks each have a distinct channelIndex. Otherwise multiple channels would be /// outputting to the same plug. std::vector<std::string> uniqueChannels = maskChannels; GafferImage::ChannelMaskPlug::removeDuplicateIndices( uniqueChannels ); for( int channelIndex = 0; channelIndex < 4; ++channelIndex ) { if ( output == minPlug()->getChild( channelIndex ) || output == maxPlug()->getChild( channelIndex ) || output == averagePlug()->getChild( channelIndex ) ) { for( std::vector<std::string>::iterator it( uniqueChannels.begin() ); it != uniqueChannels.end(); ++it ) { if( ImageAlgo::colorIndex( *it ) == channelIndex ) { channelName = *it; return; } } } } return; }
std::string ImageStats::channelName( int colorIndex ) const { IECore::ConstStringVectorDataPtr channelsData = channelsPlug()->getValue(); const vector<string> &channels = channelsData->readable(); if( channels.size() <= (size_t)colorIndex ) { return ""; } IECore::ConstStringVectorDataPtr channelNamesData = inPlug()->channelNamesPlug()->getValue(); const vector<string> &channelNames = channelNamesData->readable(); if( find( channelNames.begin(), channelNames.end(), channels[colorIndex] ) != channelNames.end() ) { return channels[colorIndex]; } return ""; }
bool ChannelDataProcessor::channelEnabled( const std::string &channel ) const { if( !ImageProcessor::channelEnabled( channel ) ) { return false; } IECore::ConstStringVectorDataPtr channelMaskData = channelMaskPlug()->getValue(); const std::vector<std::string> &channelMask = channelMaskData->readable(); return std::find( channelMask.begin(), channelMask.end(), channel ) != channelMask.end(); }
void Merge::hashChannelData( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ImageProcessor::hashChannelData( output, context, h ); const std::string channelName = context->get<std::string>( ImagePlug::channelNameContextName ); const V2i tileOrigin = context->get<V2i>( ImagePlug::tileOriginContextName ); const Box2i tileBound( tileOrigin, tileOrigin + V2i( ImagePlug::tileSize() ) ); for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) { if( !(*it)->getInput<ValuePlug>() ) { continue; } IECore::ConstStringVectorDataPtr channelNamesData = (*it)->channelNamesPlug()->getValue(); const std::vector<std::string> &channelNames = channelNamesData->readable(); if( channelExists( channelNames, channelName ) ) { (*it)->channelDataPlug()->hash( h ); } if( channelExists( channelNames, "A" ) ) { h.append( (*it)->channelDataHash( "A", tileOrigin ) ); } // The hash of the channel data we include above represents just the data in // the tile itself, and takes no account of the possibility that parts of the // tile may be outside of the data window. This simplifies the implementation of // nodes like Constant (where all tiles are identical, even the edge tiles) and // Crop (which does no processing of tiles at all). For most nodes this doesn't // matter, because they don't change the data window, or they use a Sampler to // deal with invalid pixels. But because our data window is the union of all // input data windows, we may be using/revealing the invalid parts of a tile. We // deal with this in computeChannelData() by treating the invalid parts as black, // and must therefore hash in the valid bound here to take that into account. const Box2i validBound = boxIntersection( tileBound, (*it)->dataWindowPlug()->getValue() ); h.append( validBound ); } operationPlug()->hash( h ); }
void ImageStats::hash( const ValuePlug *output, const Context *context, IECore::MurmurHash &h ) const { ComputeNode::hash( output, context, h); bool earlyOut = true; for( int i = 0; i < 4; ++i ) { if ( output == minPlug()->getChild(i) || output == maxPlug()->getChild(i) || output == averagePlug()->getChild(i) ) { earlyOut = false; break; } } if( earlyOut ) { return; } const Imath::Box2i regionOfInterest( regionOfInterestPlug()->getValue() ); regionOfInterestPlug()->hash( h ); inPlug()->channelNamesPlug()->hash( h ); inPlug()->dataWindowPlug()->hash( h ); IECore::ConstStringVectorDataPtr channelNamesData = inPlug()->channelNamesPlug()->getValue(); std::vector<std::string> maskChannels = channelNamesData->readable(); channelsPlug()->maskChannels( maskChannels ); const int nChannels( maskChannels.size() ); if ( nChannels > 0 ) { std::vector<std::string> uniqueChannels = maskChannels; GafferImage::ChannelMaskPlug::removeDuplicateIndices( uniqueChannels ); std::string channel; channelNameFromOutput( output, channel ); if ( !channel.empty() ) { h.append( channel ); Sampler s( inPlug(), channel, regionOfInterest ); s.hash( h ); return; } } // If our node is not enabled then we just append the default value that we will give the plug. if( output == maxPlug()->getChild(3) || output == minPlug()->getChild(3) || output == averagePlug()->getChild(3) ) { h.append( 0 ); } else { h.append( 1 ); } }
IECore::ConstFloatVectorDataPtr Merge::merge( F f, const std::string &channelName, const Imath::V2i &tileOrigin ) const { FloatVectorDataPtr resultData = NULL; // Temporary buffer for computing the alpha of intermediate composited layers. FloatVectorDataPtr resultAlphaData = NULL; const Box2i tileBound( tileOrigin, tileOrigin + V2i( ImagePlug::tileSize() ) ); for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) { if( !(*it)->getInput<ValuePlug>() ) { continue; } IECore::ConstStringVectorDataPtr channelNamesData = (*it)->channelNamesPlug()->getValue(); const std::vector<std::string> &channelNames = channelNamesData->readable(); ConstFloatVectorDataPtr channelData; ConstFloatVectorDataPtr alphaData; if( channelExists( channelNames, channelName ) ) { channelData = (*it)->channelDataPlug()->getValue(); } else { channelData = ImagePlug::blackTile(); } if( channelExists( channelNames, "A" ) ) { alphaData = (*it)->channelData( "A", tileOrigin ); } else { alphaData = ImagePlug::blackTile(); } const Box2i validBound = boxIntersection( tileBound, (*it)->dataWindowPlug()->getValue() ); if( !resultData ) { // The first connected layer, with which we must initialise our result. // There's no guarantee that this layer actually covers the full data // window though (the data window could have been expanded by the upper // layers) so we must take care to mask out any invalid areas of the input. /// \todo I'm not convinced this is correct - if we have no connection /// to in[0] then should that not be treated as being a black image, so /// we should unconditionally initaliase with in[0] and then always use /// the operation for in[1:], even if in[0] is disconnected. In other /// words, shouldn't multiplying a white constant over an unconnected /// in[0] produce black? resultData = channelData->copy(); resultAlphaData = alphaData->copy(); float *B = &resultData->writable().front(); float *b = &resultAlphaData->writable().front(); for( int y = tileBound.min.y; y < tileBound.max.y; ++y ) { const bool yValid = y >= validBound.min.y && y < validBound.max.y; for( int x = tileBound.min.x; x < tileBound.max.x; ++x ) { if( !yValid || x < validBound.min.x || x >= validBound.max.x ) { *B = *b = 0.0f; } ++B; ++b; } } } else { // A higher layer (A) which must be composited over the result (B). const float *A = &channelData->readable().front(); float *B = &resultData->writable().front(); const float *a = &alphaData->readable().front(); float *b = &resultAlphaData->writable().front(); for( int y = tileBound.min.y; y < tileBound.max.y; ++y ) { const bool yValid = y >= validBound.min.y && y < validBound.max.y; for( int x = tileBound.min.x; x < tileBound.max.x; ++x ) { const bool valid = yValid && x >= validBound.min.x && x < validBound.max.x; *B = f( valid ? *A : 0.0f, *B, valid ? *a : 0.0f, *b ); *b = f( valid ? *a : 0.0f, *b, valid ? *a : 0.0f, *b ); ++A; ++B; ++a; ++b; } } } } return resultData; }
///\todo: It seems that if a JPG is written with RGBA channels the output is wrong but it should be supported. Find out why and fix it. /// There is a test case in ImageWriterTest which checks the output of the jpg writer against an incorrect image and it will fail if it is equal to the writer output. void ImageWriter::execute( const Contexts &contexts ) const { if( !inPlug()->getInput<ImagePlug>() ) { throw IECore::Exception( "No input image." ); } // Loop over the execution contexts... for( Contexts::const_iterator it = contexts.begin(), eIt = contexts.end(); it != eIt; it++ ) { Context::Scope scopedContext( it->get() ); std::string fileName = fileNamePlug()->getValue(); fileName = (*it)->substitute( fileName ); boost::shared_ptr< ImageOutput > out( ImageOutput::create( fileName.c_str() ) ); if ( !out ) { throw IECore::Exception( boost::str( boost::format( "Invalid filename: %s" ) % fileName ) ); } // Grab the intersection of the channels from the "channels" plug and the image input to see which channels we are to write out. IECore::ConstStringVectorDataPtr channelNamesData = inPlug()->channelNamesPlug()->getValue(); std::vector<std::string> maskChannels = channelNamesData->readable(); channelsPlug()->maskChannels( maskChannels ); const int nChannels = maskChannels.size(); // Get the image channel data. IECore::ImagePrimitivePtr imagePtr( inPlug()->image() ); // Get the image's display window. const Imath::Box2i displayWindow( imagePtr->getDisplayWindow() ); const int displayWindowWidth = displayWindow.size().x+1; const int displayWindowHeight = displayWindow.size().y+1; // Get the image's data window and if it then set a flag. bool imageIsBlack = false; Imath::Box2i dataWindow( imagePtr->getDataWindow() ); if ( inPlug()->dataWindowPlug()->getValue().isEmpty() ) { dataWindow = displayWindow; imageIsBlack = true; } int dataWindowWidth = dataWindow.size().x+1; int dataWindowHeight = dataWindow.size().y+1; // Create the image header. ImageSpec spec( dataWindowWidth, dataWindowHeight, nChannels, TypeDesc::FLOAT ); // Add the channel names to the header whilst getting pointers to the channel data. std::vector<const float*> channelPtrs; spec.channelnames.clear(); for ( std::vector<std::string>::iterator channelIt( maskChannels.begin() ); channelIt != maskChannels.end(); channelIt++ ) { spec.channelnames.push_back( *channelIt ); IECore::FloatVectorDataPtr dataPtr = imagePtr->getChannel<float>( *channelIt ); channelPtrs.push_back( &(dataPtr->readable()[0]) ); // OIIO has a special attribute for the Alpha and Z channels. If we find some, we should tag them... if ( *channelIt == "A" ) { spec.alpha_channel = channelIt-maskChannels.begin(); } else if ( *channelIt == "Z" ) { spec.z_channel = channelIt-maskChannels.begin(); } } // Specify the display window. spec.full_x = displayWindow.min.x; spec.full_y = displayWindow.min.y; spec.full_width = displayWindowWidth; spec.full_height = displayWindowHeight; spec.x = dataWindow.min.x; spec.y = dataWindow.min.y; if ( !out->open( fileName, spec ) ) { throw IECore::Exception( boost::str( boost::format( "Could not open \"%s\", error = %s" ) % fileName % out->geterror() ) ); } // Only allow tiled output if our file format supports it. int writeMode = writeModePlug()->getValue() & out->supports( "tile" ); if ( writeMode == Scanline ) { // Create a buffer for the scanline. float scanline[ nChannels*dataWindowWidth ]; if ( imageIsBlack ) { memset( scanline, 0, sizeof(float) * nChannels*dataWindowWidth ); for ( int y = spec.y; y < spec.y + dataWindowHeight; ++y ) { if ( !out->write_scanline( y, 0, TypeDesc::FLOAT, &scanline[0] ) ) { throw IECore::Exception( boost::str( boost::format( "Could not write scanline to \"%s\", error = %s" ) % fileName % out->geterror() ) ); } } } else { // Interleave the channel data and write it by scanline to the file. for ( int y = spec.y; y < spec.y + dataWindowHeight; ++y ) { for ( std::vector<const float *>::iterator channelDataIt( channelPtrs.begin() ); channelDataIt != channelPtrs.end(); channelDataIt++ ) { float *outPtr = &scanline[0] + (channelDataIt - channelPtrs.begin()); // The pointer that we are writing to. // The row that we are reading from is flipped (in the Y) as we use a different image space internally to OpenEXR and OpenImageIO. const float *inRowPtr = (*channelDataIt) + ( y - spec.y ) * dataWindowWidth; const int inc = channelPtrs.size(); for ( int x = 0; x < dataWindowWidth; ++x, outPtr += inc ) { *outPtr = *inRowPtr++; } } if ( !out->write_scanline( y, 0, TypeDesc::FLOAT, &scanline[0] ) ) { throw IECore::Exception( boost::str( boost::format( "Could not write scanline to \"%s\", error = %s" ) % fileName % out->geterror() ) ); } } } } // Tiled output else { // Create a buffer for the tile. const int tileSize = ImagePlug::tileSize(); float tile[ nChannels*tileSize*tileSize ]; if ( imageIsBlack ) { memset( tile, 0, sizeof(float) * nChannels*tileSize*tileSize ); for ( int tileY = 0; tileY < dataWindowHeight; tileY += tileSize ) { for ( int tileX = 0; tileX < dataWindowWidth; tileX += tileSize ) { if ( !out->write_tile( tileX+dataWindow.min.x, tileY+spec.y, 0, TypeDesc::FLOAT, &tile[0] ) ) { throw IECore::Exception( boost::str( boost::format( "Could not write tile to \"%s\", error = %s" ) % fileName % out->geterror() ) ); } } } } else { // Interleave the channel data and write it to the file tile-by-tile. for ( int tileY = 0; tileY < dataWindowHeight; tileY += tileSize ) { for ( int tileX = 0; tileX < dataWindowWidth; tileX += tileSize ) { float *outPtr = &tile[0]; const int r = std::min( tileSize+tileX, dataWindowWidth ); const int t = std::min( tileSize+tileY, dataWindowHeight ); for ( int y = 0; y < t; ++y ) { for ( std::vector<const float *>::iterator channelDataIt( channelPtrs.begin() ); channelDataIt != channelPtrs.end(); channelDataIt++ ) { const int inc = channelPtrs.size(); const float *inRowPtr = (*channelDataIt) + ( tileY + t - y - 1 ) * dataWindowWidth; for ( int x = 0; x < r; ++x, outPtr += inc ) { *outPtr = *inRowPtr+(tileX+x); } } } if ( !out->write_tile( tileX+dataWindow.min.x, tileY+spec.y, 0, TypeDesc::FLOAT, &tile[0] ) ) { throw IECore::Exception( boost::str( boost::format( "Could not write tile to \"%s\", error = %s" ) % fileName % out->geterror() ) ); } } } } } out->close(); } }
IECore::ConstFloatVectorDataPtr Mix::computeChannelData( const std::string &channelName, const Imath::V2i &tileOrigin, const Gaffer::Context *context, const ImagePlug *parent ) const { const float mix = mixPlug()->getValue(); if( mix == 0.0f ) { return inPlugs()->getChild< ImagePlug>( 0 )->channelDataPlug()->getValue(); } else if( mix == 1.0f && !maskPlug()->getInput<ValuePlug>() ) { return inPlugs()->getChild< ImagePlug >( 1 )->channelDataPlug()->getValue(); } const Box2i tileBound( tileOrigin, tileOrigin + V2i( ImagePlug::tileSize() ) ); IECore::ConstStringVectorDataPtr maskChannelNamesData; Box2i maskDataWindow; { ImagePlug::GlobalScope c( Context::current() ); maskChannelNamesData = maskPlug()->channelNamesPlug()->getValue(); maskDataWindow = maskPlug()->dataWindowPlug()->getValue(); } const std::string &maskChannel = maskChannelPlug()->getValue(); ConstFloatVectorDataPtr maskData = NULL; Box2i maskValidBound; if( maskPlug()->getInput<ValuePlug>() && ImageAlgo::channelExists( maskChannelNamesData->readable(), maskChannel ) ) { maskData = maskPlug()->channelData( maskChannel, tileOrigin ); maskValidBound = boxIntersection( tileBound, maskDataWindow ); } ConstFloatVectorDataPtr channelData[2]; Box2i validBound[2]; int i = 0; for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it,++i ) { IECore::ConstStringVectorDataPtr channelNamesData; Box2i dataWindow; { ImagePlug::GlobalScope c( Context::current() ); channelNamesData = (*it)->channelNamesPlug()->getValue(); dataWindow = (*it)->dataWindowPlug()->getValue(); } const std::vector<std::string> &channelNames = channelNamesData->readable(); if( ImageAlgo::channelExists( channelNames, channelName ) ) { channelData[i] = (*it)->channelDataPlug()->getValue(); validBound[i] = boxIntersection( tileBound, dataWindow ); } else { channelData[i] = NULL; validBound[i] = Box2i(); } } FloatVectorDataPtr resultData = ImagePlug::blackTile()->copy(); float *R = &resultData->writable().front(); const float *A = channelData[0] ? &channelData[0]->readable().front() : NULL; const float *B = channelData[1] ? &channelData[1]->readable().front() : NULL; const float *M = maskData ? &maskData->readable().front() : NULL; for( int y = tileBound.min.y; y < tileBound.max.y; ++y ) { const bool yValidIn0 = y >= validBound[0].min.y && y < validBound[0].max.y; const bool yValidIn1 = y >= validBound[1].min.y && y < validBound[1].max.y; const bool yValidMask = y >= maskValidBound.min.y && y < maskValidBound.max.y; for( int x = tileBound.min.x; x < tileBound.max.x; ++x ) { float a = 0; if( yValidIn0 && x >= validBound[0].min.x && x < validBound[0].max.x ) { a = *A; } float b = 0; if( yValidIn1 && x >= validBound[1].min.x && x < validBound[1].max.x ) { b = *B; } float m = mix; if( yValidMask && x >= maskValidBound.min.x && x < maskValidBound.max.x ) { m *= std::max( 0.0f, std::min( 1.0f, *M ) ); } *R = a * ( 1 - m ) + b * m; ++R; ++A; ++B; ++M; } } return resultData; }