TuttleOFX
1
|
00001 #include "ImageEffectNode.hpp" 00002 #include "HostDescriptor.hpp" 00003 00004 // ofx host 00005 #include <tuttle/host/Core.hpp> // for core().getMemoryCache() 00006 #include <tuttle/host/attribute/ClipImage.hpp> 00007 #include <tuttle/host/attribute/allParams.hpp> 00008 #include <tuttle/host/graph/ProcessEdgeAtTime.hpp> 00009 #include <tuttle/host/graph/ProcessVertexData.hpp> 00010 #include <tuttle/host/graph/ProcessVertexAtTimeData.hpp> 00011 00012 #include <tuttle/host/ofx/OfxhUtilities.hpp> 00013 #include <tuttle/host/ofx/OfxhBinary.hpp> 00014 #include <tuttle/host/ofx/OfxhMemory.hpp> 00015 #include <tuttle/host/ofx/OfxhImageEffectNode.hpp> 00016 #include <tuttle/host/ofx/OfxhPluginAPICache.hpp> 00017 #include <tuttle/host/ofx/OfxhPluginCache.hpp> 00018 #include <tuttle/host/ofx/OfxhHost.hpp> 00019 #include <tuttle/host/ofx/OfxhImageEffectPlugin.hpp> 00020 #include <tuttle/host/ofx/property/OfxhSet.hpp> 00021 #include <tuttle/host/ofx/attribute/OfxhClip.hpp> 00022 #include <tuttle/host/ofx/attribute/OfxhParam.hpp> 00023 00024 // ofx 00025 #include <ofxCore.h> 00026 #include <ofxImageEffect.h> 00027 00028 #include <boost/functional/hash.hpp> 00029 #include <boost/foreach.hpp> 00030 #include <boost/lexical_cast.hpp> 00031 00032 #include <iomanip> 00033 #include <iostream> 00034 #include <fstream> 00035 #include <list> 00036 00037 namespace tuttle { 00038 namespace host { 00039 00040 ImageEffectNode::ImageEffectNode( 00041 tuttle::host::ofx::imageEffect::OfxhImageEffectPlugin& plugin, 00042 tuttle::host::ofx::imageEffect::OfxhImageEffectNodeDescriptor& desc, 00043 const std::string& context ) 00044 : tuttle::host::ofx::imageEffect::OfxhImageEffectNode( plugin, desc, context, false ) 00045 , _defaultOutputFielding( kOfxImageFieldNone ) 00046 { 00047 populate(); 00048 // createInstanceAction(); 00049 } 00050 00051 ImageEffectNode::ImageEffectNode( const ImageEffectNode& other ) 00052 : INode( other ) 00053 , tuttle::host::ofx::imageEffect::OfxhImageEffectNode( other ) 00054 , _defaultOutputFielding( other._defaultOutputFielding ) 00055 { 00056 populate(); 00057 copyAttributesValues( other ); // values need to be setted before the createInstanceAction ! 00058 createInstanceAction(); 00059 } 00060 00061 ImageEffectNode::~ImageEffectNode() 00062 {} 00063 00064 bool ImageEffectNode::operator==( const INode& other ) const 00065 { 00066 const ImageEffectNode* other_ptr = dynamic_cast<const ImageEffectNode*>( &other ); 00067 if( other_ptr == NULL ) 00068 return false; 00069 return operator==( *other_ptr ); 00070 } 00071 00072 /** 00073 * @warning do a deep comparison 00074 */ 00075 bool ImageEffectNode::operator==( const ImageEffectNode& other ) const 00076 { 00077 return ofx::imageEffect::OfxhImageEffectNode::operator==( other ); 00078 } 00079 00080 void ImageEffectNode::connect( const INode& sourceEffect, attribute::Attribute& attr ) 00081 { 00082 const attribute::ClipImage& outputClip = dynamic_cast<const attribute::ClipImage&>( sourceEffect.getClip( kOfxImageEffectOutputClipName ) ); 00083 attribute::ClipImage& inputClip = dynamic_cast<attribute::ClipImage&>( attr ); // throw an exception if not a ClipImage attribute 00084 00085 inputClip.setConnectedClip( outputClip ); 00086 } 00087 00088 attribute::Attribute& ImageEffectNode::getSingleInputAttribute() 00089 { 00090 ofx::attribute::OfxhClipImageSet::ClipImageVector& clips = getClipsByOrder(); 00091 ofx::attribute::OfxhClipImageSet::ClipImageMap& clipsMap = getClipsByName(); 00092 ofx::attribute::OfxhAttribute* inAttr = NULL; 00093 00094 if( clips.size() == 1 ) 00095 { 00096 inAttr = &clips[0]; 00097 } 00098 else if( clips.size() > 1 ) 00099 { 00100 const ofx::attribute::OfxhClipImageSet::ClipImageMap::iterator it( clipsMap.find( kOfxSimpleSourceAttributeName ) ); 00101 if( it != clipsMap.end() ) 00102 { 00103 inAttr = it->second; 00104 } 00105 else 00106 { 00107 inAttr = &clips[0]; 00108 } 00109 } 00110 else // if( inClips.empty() ) 00111 { 00112 BOOST_THROW_EXCEPTION( exception::Logic() 00113 << exception::user( "No source clip." ) ); 00114 } 00115 return dynamic_cast<attribute::ClipImage&>( *inAttr ); 00116 } 00117 00118 // get a new clip instance 00119 tuttle::host::ofx::attribute::OfxhClipImage* ImageEffectNode::newClipImage( const tuttle::host::ofx::attribute::OfxhClipImageDescriptor& descriptor ) 00120 { 00121 return new attribute::ClipImage( *this, descriptor ); 00122 } 00123 00124 std::size_t ImageEffectNode::getLocalHashAtTime( const OfxTime time ) const 00125 { 00126 std::size_t seed = getPlugin().getHash(); 00127 00128 if( isFrameVarying() ) 00129 { 00130 boost::hash_combine( seed, time ); 00131 } 00132 00133 boost::hash_combine( seed, getParamSet().getHashAtTime(time) ); 00134 00135 return seed; 00136 } 00137 00138 /** 00139 * @return 1 to abort processing 00140 */ 00141 int ImageEffectNode::abort() 00142 { 00143 return 0; 00144 } 00145 00146 ofx::OfxhMemory* ImageEffectNode::newMemoryInstance( size_t nBytes ) 00147 { 00148 ofx::OfxhMemory* instance = new ofx::OfxhMemory(); 00149 00150 instance->alloc( nBytes ); 00151 return instance; 00152 } 00153 00154 // vmessage 00155 void ImageEffectNode::vmessage( const char* type, 00156 const char* id, 00157 const char* format, 00158 va_list args ) const OFX_EXCEPTION_SPEC 00159 { 00160 vprintf( format, args ); 00161 } 00162 00163 // get the project size in CANONICAL pixels, so PAL SD return 768, 576 00164 void ImageEffectNode::getProjectSize( double& xSize, double& ySize ) const 00165 { 00166 if (_dataAtTime.size() == 0 ) 00167 { 00168 xSize = 720; 00169 ySize = 576; 00170 } 00171 else 00172 { 00173 OfxRectD rod = getLastData()._apiImageEffect._renderRoD; 00174 xSize = rod.x2 - rod.x1; 00175 ySize = rod.y2 - rod.y1; 00176 if (xSize < 1 || ySize < 1) 00177 { 00178 xSize = 720; 00179 ySize = 576; 00180 } 00181 } 00182 } 00183 00184 // get the project offset in CANONICAL pixels, we are at 0,0 00185 void ImageEffectNode::getProjectOffset( double& xOffset, double& yOffset ) const 00186 { 00187 xOffset = 0; 00188 yOffset = 0; 00189 } 00190 00191 // get the project extent in CANONICAL pixels, so PAL SD return 768, 576 00192 void ImageEffectNode::getProjectExtent( double& xSize, double& ySize ) const 00193 { 00194 if (_dataAtTime.size() == 0 ) 00195 { 00196 xSize = 720; 00197 ySize = 576; 00198 } 00199 else 00200 { 00201 OfxRectD rod = getLastData()._apiImageEffect._renderRoD; 00202 xSize = rod.x2 - rod.x1; 00203 ySize = rod.y2 - rod.y1; 00204 if (xSize < 1 || ySize < 1) 00205 { 00206 xSize = 720; 00207 ySize = 576; 00208 } 00209 } 00210 } 00211 00212 // get the PAR, SD PAL is 768/720=1.0666 00213 double ImageEffectNode::getProjectPixelAspectRatio() const 00214 { 00215 return 1.0; 00216 } 00217 00218 // we are only 25 frames 00219 double ImageEffectNode::getEffectDuration() const 00220 { 00221 return 99999.0; 00222 } 00223 00224 /// This is called whenever a param is changed by the plugin so that 00225 /// the recursive instanceChangedAction will be fed the correct frame 00226 double ImageEffectNode::getFrameRecursive() const 00227 { 00228 return 0.0; 00229 } 00230 00231 /// This is called whenever a param is changed by the plugin so that 00232 /// the recursive instanceChangedAction will be fed the correct 00233 /// renderScale 00234 void ImageEffectNode::getRenderScaleRecursive( double& x, double& y ) const 00235 { 00236 x = y = 1.0; 00237 } 00238 00239 /** 00240 * The pixel components type of the current project 00241 * @todo tuttle: to remove in the future.... size, pixelType, BitDepth, etc... must be locally defined 00242 */ 00243 const std::string ImageEffectNode::getProjectPixelComponentsType() const 00244 { 00245 return kOfxImageComponentRGBA; 00246 } 00247 00248 /** 00249 * The pixel bit depth of the current project (host work in float) 00250 * @todo tuttle: to remove in the future.... size, pixelType, BitDepth, etc... must be locally defined 00251 */ 00252 const std::string ImageEffectNode::getProjectBitDepth() const 00253 { 00254 //return kOfxBitDepthByte; 00255 return kOfxBitDepthFloat; 00256 } 00257 00258 // make a parameter instance 00259 ofx::attribute::OfxhParam* ImageEffectNode::newParam( const ofx::attribute::OfxhParamDescriptor& descriptor ) OFX_EXCEPTION_SPEC 00260 { 00261 const std::string name = descriptor.getName(); 00262 ofx::attribute::OfxhParam* param = NULL; 00263 00264 try 00265 { 00266 if( descriptor.getParamType() == kOfxParamTypeString ) 00267 param = new attribute::ParamString( *this, name, descriptor ); 00268 else if( descriptor.getParamType() == kOfxParamTypeInteger ) 00269 param = new attribute::ParamInteger( *this, name, descriptor ); 00270 else if( descriptor.getParamType() == kOfxParamTypeDouble ) 00271 param = new attribute::ParamDouble( *this, name, descriptor ); 00272 else if( descriptor.getParamType() == kOfxParamTypeBoolean ) 00273 param = new attribute::ParamBoolean( *this, name, descriptor ); 00274 else if( descriptor.getParamType() == kOfxParamTypeChoice ) 00275 param = new attribute::ParamChoice( *this, name, descriptor ); 00276 else if( descriptor.getParamType() == kOfxParamTypeRGBA ) 00277 param = new attribute::ParamRGBA( *this, name, descriptor ); 00278 else if( descriptor.getParamType() == kOfxParamTypeRGB ) 00279 param = new attribute::ParamRGB( *this, name, descriptor ); 00280 else if( descriptor.getParamType() == kOfxParamTypeDouble2D ) 00281 param = new attribute::ParamDouble2D( *this, name, descriptor ); 00282 else if( descriptor.getParamType() == kOfxParamTypeDouble3D ) 00283 param = new attribute::ParamDouble3D( *this, name, descriptor ); 00284 else if( descriptor.getParamType() == kOfxParamTypeInteger2D ) 00285 param = new attribute::ParamInteger2D( *this, name, descriptor ); 00286 else if( descriptor.getParamType() == kOfxParamTypeInteger3D ) 00287 param = new attribute::ParamInteger3D( *this, name, descriptor ); 00288 else if( descriptor.getParamType() == kOfxParamTypePushButton ) 00289 param = new attribute::ParamPushButton( *this, name, descriptor ); 00290 else if( descriptor.getParamType() == kOfxParamTypeGroup ) 00291 param = new attribute::ParamGroup( *this, name, descriptor ); 00292 else if( descriptor.getParamType() == kOfxParamTypePage ) 00293 param = new attribute::ParamPage( *this, name, descriptor ); 00294 else if( descriptor.getParamType() == kOfxParamTypeCustom ) 00295 param = new attribute::ParamCustom( *this, name, descriptor ); 00296 else 00297 { 00298 BOOST_THROW_EXCEPTION( exception::Failed() 00299 << exception::user() + "Can't create param " + quotes( name ) + " instance from param descriptor, type not recognized." ); 00300 } 00301 this->addParam( param ); 00302 } 00303 catch( exception::Common& e ) 00304 { 00305 BOOST_THROW_EXCEPTION( ofx::OfxhException( *boost::get_error_info<exception::ofxStatus>( e ), 00306 boost::diagnostic_information( e ) ) ); 00307 } 00308 catch(... ) 00309 { 00310 BOOST_THROW_EXCEPTION( ofx::OfxhException( kOfxStatErrUnknown, 00311 boost::current_exception_diagnostic_information() ) ); 00312 } 00313 return param; 00314 } 00315 00316 void ImageEffectNode::editBegin( const std::string& name ) OFX_EXCEPTION_SPEC 00317 { 00318 //BOOST_THROW_EXCEPTION( ofx::OfxhException( kOfxStatErrMissingHostFeature ) ); 00319 } 00320 00321 void ImageEffectNode::editEnd() OFX_EXCEPTION_SPEC 00322 { 00323 //BOOST_THROW_EXCEPTION( ofx::OfxhException( kOfxStatErrMissingHostFeature ) ); 00324 } 00325 00326 /// Start doing progress. 00327 void ImageEffectNode::progressStart( const std::string& message ) 00328 { 00329 //TUTTLE_LOG_TRACE( message ); 00330 if( !( getContext() == kOfxImageEffectContextReader ) && !( getContext() == kOfxImageEffectContextWriter ) ) 00331 TUTTLE_LOG_INFO( std::left << " " << common::Color::get()->_green << std::setw( TUTTLE_LOG_PLUGIN_NAME_WIDTH ) << getName() << common::Color::get()->_std ); 00332 } 00333 00334 /// finish yer progress 00335 void ImageEffectNode::progressEnd() 00336 { 00337 //std::cout << std::endl; 00338 } 00339 00340 /// set the progress to some level of completion, 00341 /// returns true if you should abandon processing, false to continue 00342 bool ImageEffectNode::progressUpdate( const double progress ) 00343 { 00344 /* 00345 if( ( getContext() == kOfxImageEffectContextReader ) || ( getContext() == kOfxImageEffectContextWriter ) ) 00346 TUTTLE_LOG_INFO( "\r" << common::Color::get()->_std << "[" << std::right << std::setw(3) << int(progress * 100) << "%] " << " " << std::left << std::flush ); 00347 else 00348 TUTTLE_LOG_INFO( "\r" << common::Color::get()->_std << "[" << std::right << std::setw(3) << int(progress * 100) << "%] " << std::left << common::Color::get()->_green << getName() << common::Color::get()->_std << std::flush ); 00349 */ 00350 return false; 00351 } 00352 00353 /// get the current time on the timeline. This is not necessarily the same 00354 /// time as being passed to an action (eg render) 00355 double ImageEffectNode::timelineGetTime() 00356 { 00357 return 0; 00358 } 00359 00360 /// set the timeline to a specific time 00361 void ImageEffectNode::timelineGotoTime( double t ) 00362 {} 00363 00364 /// get the first and last times available on the effect's timeline 00365 void ImageEffectNode::timelineGetBounds( double& t1, double& t2 ) 00366 { 00367 t1 = 0; 00368 t2 = 99999; 00369 } 00370 00371 /// override to get frame range of the effect 00372 void ImageEffectNode::beginSequenceRenderAction( OfxTime startFrame, 00373 OfxTime endFrame, 00374 double step, 00375 bool interactive, 00376 OfxPointD renderScale ) OFX_EXCEPTION_SPEC 00377 { 00378 OfxhImageEffectNode::beginSequenceRenderAction( startFrame, endFrame, step, interactive, renderScale ); 00379 } 00380 00381 void ImageEffectNode::checkClipsConnected() const 00382 { 00383 for( ClipImageMap::const_iterator it = _clipImages.begin(); 00384 it != _clipImages.end(); 00385 ++it ) 00386 { 00387 const attribute::ClipImage& clip = dynamic_cast<const attribute::ClipImage&>( *( it->second ) ); 00388 if( !clip.isOutput() && !clip.isConnected() && !clip.isOptional() ) // one non optional input clip is unconnected 00389 { 00390 BOOST_THROW_EXCEPTION( exception::Logic() 00391 << exception::user( "A non optional clip is unconnected ! (" + clip.getFullName() + ")" ) ); 00392 } 00393 } 00394 } 00395 00396 void ImageEffectNode::initComponents() 00397 { 00398 attribute::ClipImage& outputClip = dynamic_cast<attribute::ClipImage&>( getOutputClip() ); 00399 //bool inputClipsFound = false; 00400 std::string mostChromaticComponents = kOfxImageComponentNone; 00401 00402 for( ClipImageMap::iterator it = _clipImages.begin(); 00403 it != _clipImages.end(); 00404 ++it ) 00405 { 00406 attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( it->second ) ); 00407 if( !clip.isOutput() && clip.isConnected() ) 00408 { 00409 //inputClipsFound = true; 00410 const attribute::ClipImage& linkClip = clip.getConnectedClip(); 00411 mostChromaticComponents = findMostChromaticComponents( linkClip.getComponentsString(), mostChromaticComponents ); 00412 } 00413 } 00414 // components 00415 for( ClipImageMap::iterator it = _clipImages.begin(); 00416 it != _clipImages.end(); 00417 ++it ) 00418 { 00419 attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( it->second ) ); 00420 if( !clip.isOutput() && clip.isConnected() ) 00421 { 00422 const attribute::ClipImage& linkClip = clip.getConnectedClip(); 00423 if( clip.isSupportedComponent( mostChromaticComponents ) ) 00424 clip.setComponentsStringIfNotModifiedByPlugin( linkClip.getComponentsString() ); 00425 } 00426 } 00427 if( outputClip.isSupportedComponent( mostChromaticComponents ) ) 00428 outputClip.setComponentsStringIfNotModifiedByPlugin( mostChromaticComponents ); 00429 } 00430 00431 /// @todo multiple PAR 00432 void ImageEffectNode::initInputClipsPixelAspectRatio() 00433 { 00434 std::set<double> inputPARs; 00435 // if( supportsMultipleClipPARs() ) 00436 { 00437 for( ClipImageMap::iterator it = _clipImages.begin(); 00438 it != _clipImages.end(); 00439 ++it ) 00440 { 00441 attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( it->second ) ); 00442 TUTTLE_TLOG( TUTTLE_INFO, "[Clip] " << clip.getName() ); 00443 if( !clip.isOutput() && clip.isConnected() ) 00444 { 00445 const attribute::ClipImage& linkClip = clip.getConnectedClip(); 00446 const double par = linkClip.getPixelAspectRatio(); 00447 TUTTLE_TLOG( TUTTLE_INFO, "[Clip] " << linkClip.getName() << ", pixel aspect ratio = " << par ); 00448 clip.setPixelAspectRatio( par, ofx::property::eModifiedByHost ); 00449 inputPARs.insert( par ); 00450 } 00451 } 00452 } 00453 // else 00454 // { 00455 // // @todo The plugin doesn't support PAR, the host should do the conversions! 00456 // // http://openfx.sourceforge.net/Documentation/1.3/ofxProgrammingReference.html#ImageEffectsPixelAspectRatios 00457 // // If a plugin does not accept clips of differing PARs, then the host must resample all images fed to that effect to agree with the output's PAR. 00458 // // If a plugin does accept clips of differing PARs, it will need to specify the output clip's PAR in the kOfxImageEffectActionGetClipPreferences action. 00459 // 00460 // // Convert images here ? Or introduce convert nodes into the ProcessGraph? 00461 // BOOST_ASSERT(false); 00462 // } 00463 00464 // Not supported yet. So fail in debug, 00465 // and process with a wrong pixel aspect ratio in release. 00466 TUTTLE_TLOG( TUTTLE_INFO, "[Clip] support Multiple clip PAR = " << supportsMultipleClipPARs() ); 00467 TUTTLE_TLOG( TUTTLE_INFO, "[Clip] number of clips = " << getNbClips() ); 00468 BOOST_ASSERT( inputPARs.size() <= 1 || supportsMultipleClipPARs() || getNbClips() <= 2 ); 00469 } 00470 00471 void ImageEffectNode::initInputClipsFps() 00472 { 00473 for( ClipImageMap::iterator it = _clipImages.begin(); 00474 it != _clipImages.end(); 00475 ++it ) 00476 { 00477 attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( it->second ) ); 00478 if( !clip.isOutput() && clip.isConnected() ) 00479 { 00480 const attribute::ClipImage& linkClip = clip.getConnectedClip(); 00481 clip.setFrameRate( linkClip.getFrameRate() ); 00482 } 00483 } 00484 } 00485 00486 void ImageEffectNode::initFps() 00487 { 00488 attribute::ClipImage& outputClip = dynamic_cast<attribute::ClipImage&>( getOutputClip() ); 00489 outputClip.setFrameRate( getOutputFrameRate() ); 00490 } 00491 00492 void ImageEffectNode::initPixelAspectRatio() 00493 { 00494 attribute::ClipImage& outputClip = dynamic_cast<attribute::ClipImage&>( getOutputClip() ); 00495 outputClip.setPixelAspectRatio( getOutputPixelAspectRatio(), ofx::property::eModifiedByHost ); 00496 } 00497 00498 void ImageEffectNode::maximizeBitDepthFromReadsToWrites() 00499 { 00500 std::string biggestBitDepth = kOfxBitDepthNone; 00501 attribute::ClipImage& outputClip = dynamic_cast<attribute::ClipImage&>( getOutputClip() ); 00502 bool inputClipsFound = false; 00503 00504 // init variables 00505 for( ClipImageMap::iterator it = _clipImages.begin(); 00506 it != _clipImages.end(); 00507 ++it ) 00508 { 00509 attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( it->second ) ); 00510 if( !clip.isOutput() && clip.isConnected() ) // filter for clip.isMask() and clip.isOptional() ? 00511 { 00512 inputClipsFound = true; 00513 const attribute::ClipImage& linkClip = clip.getConnectedClip(); 00514 biggestBitDepth = ofx::imageEffect::findDeepestBitDepth( linkClip.getBitDepthString(), biggestBitDepth ); 00515 } 00516 } 00517 const std::string validBitDepth = this->bestSupportedBitDepth( biggestBitDepth ); 00518 00519 // bit depth 00520 if( supportsMultipleClipDepths() ) 00521 { 00522 // check if we support the bit depth of each input 00523 // and fill input clip with connected clips bit depth 00524 for( ClipImageMap::iterator it = _clipImages.begin(); 00525 it != _clipImages.end(); 00526 ++it ) 00527 { 00528 attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( it->second ) ); 00529 if( !clip.isOutput() && clip.isConnected() ) 00530 { 00531 const attribute::ClipImage& linkClip = clip.getConnectedClip(); 00532 const std::string& linkClipBitDepth = linkClip.getBitDepthString(); 00533 if( this->isSupportedBitDepth( linkClipBitDepth ) ) 00534 { 00535 clip.setBitDepthStringIfUpperAndNotModifiedByPlugin( linkClipBitDepth ); 00536 } 00537 } 00538 } 00539 } 00540 else // multiple clip depth not supported (standard case) 00541 { 00542 if( inputClipsFound && // if we have an input clip 00543 validBitDepth == kOfxBitDepthNone ) // if we didn't found a valid bit depth value 00544 { 00545 BOOST_THROW_EXCEPTION( exception::Logic() 00546 << exception::user( "Pixel depth " + biggestBitDepth + " not supported on plugin : " + getName() ) ); 00547 } 00548 for( ClipImageMap::iterator it = _clipImages.begin(); 00549 it != _clipImages.end(); 00550 ++it ) 00551 { 00552 attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( it->second ) ); 00553 if( !clip.isOutput() && clip.isConnected() ) 00554 { 00555 const attribute::ClipImage& linkClip = clip.getConnectedClip(); 00556 if( ( linkClip.getNode().getNodeType() == INode::eNodeTypeImageEffect && 00557 linkClip.getNode().asImageEffectNode().isSupportedBitDepth( validBitDepth ) 00558 ) || 00559 linkClip.getNode().getNodeType() == INode::eNodeTypeBuffer 00560 ) 00561 { 00562 clip.setBitDepthStringIfUpperAndNotModifiedByPlugin( validBitDepth ); 00563 } 00564 } 00565 } 00566 } 00567 outputClip.setBitDepthStringIfUpperAndNotModifiedByPlugin( validBitDepth ); 00568 } 00569 00570 void ImageEffectNode::maximizeBitDepthFromWritesToReads() 00571 { 00572 //TUTTLE_TLOG( TUTTLE_INFO, "maximizeBitDepthFromWritesToReads: " << getName() ); 00573 if( !supportsMultipleClipDepths() ) 00574 { 00575 attribute::ClipImage& outputClip = dynamic_cast<attribute::ClipImage&>( getOutputClip() ); 00576 const std::string& outputClipBitDepthStr = outputClip.getBitDepthString(); 00577 for( ClipImageMap::iterator it = _clipImages.begin(); 00578 it != _clipImages.end(); 00579 ++it ) 00580 { 00581 attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( it->second ) ); 00582 if( !clip.isOutput() && clip.isConnected() ) 00583 { 00584 /// @todo tuttle: what is the best way to access another node ? 00585 /// through the graph ? through a graph inside ProcessOptions ? 00586 /*const */ attribute::ClipImage& linkClip = clip.getConnectedClip(); 00587 00588 //TUTTLE_TLOG( TUTTLE_INFO, clip.getFullName() << "(" << clip.getBitDepth() << ")" << "-->" << linkClip.getFullName() << "(" << linkClip.getBitDepth() << ")" ); 00589 if( linkClip.getNode().getNodeType() == INode::eNodeTypeImageEffect && 00590 linkClip.getNode().asImageEffectNode().isSupportedBitDepth( outputClipBitDepthStr ) ) // need to be supported by the other node 00591 { 00592 if( linkClip.getNode().asImageEffectNode().supportsMultipleClipDepths() ) /// @todo tuttle: is this test correct in all cases? 00593 { 00594 linkClip.setBitDepthStringIfUpper( outputClipBitDepthStr ); 00595 } 00596 else 00597 { 00598 linkClip.setBitDepthStringIfUpperAndNotModifiedByPlugin( outputClipBitDepthStr ); 00599 } 00600 } 00601 //TUTTLE_TLOG( TUTTLE_INFO, clip.getFullName() << "(" << clip.getBitDepth() << ")" << "-->" << linkClip.getFullName() << "(" << linkClip.getBitDepth() << ")" ); 00602 } 00603 //else 00604 //{ 00605 // TUTTLE_TLOG( TUTTLE_INFO, clip.getFullName() << "(" << clip.getBitDepth() << ")" << ", unconnected ? " << clip.isConnected() << ", output ? " << clip.isOutput() ); 00606 //} 00607 } 00608 } 00609 } 00610 00611 void ImageEffectNode::coutBitDepthConnections() const 00612 { 00613 #ifdef TUTTLE_DEBUG 00614 // validation 00615 for( ClipImageMap::const_iterator it = _clipImages.begin(); 00616 it != _clipImages.end(); 00617 ++it ) 00618 { 00619 const attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( it->second ) ); 00620 00621 //const ofx::property::String& propPixelDepth = clip.getProperties().fetchStringProperty( kOfxImageEffectPropPixelDepth ); 00622 //const ofx::property::String& propComponent = clip.getProperties().fetchStringProperty( kOfxImageEffectPropComponents ); 00623 //const ofx::property::Double& propPixelAspectRatio = clip.getProperties().fetchDoubleProperty( kOfxImagePropPixelAspectRatio ); 00624 /* 00625 TUTTLE_TLOG( TUTTLE_INFO, "-- " << "clip: " << " = " << clip.getFullName() ); 00626 TUTTLE_TLOG( TUTTLE_INFO, "-- " << kOfxImageEffectPropPixelDepth << " = " << propPixelDepth.getValue() 00627 << " : " << ( propPixelDepth.getModifiedBy() == ofx::property::eModifiedByPlugin ? "(plugin)" : "(host)" ) ); 00628 TUTTLE_TLOG( TUTTLE_INFO, "-- " << kOfxImageEffectPropComponents << " = " << propComponent.getValue() 00629 << " : " << ( propComponent.getModifiedBy() == ofx::property::eModifiedByPlugin ? "(plugin)" : "(host)" ) ); 00630 TUTTLE_TLOG( TUTTLE_INFO, "-- " << kOfxImagePropPixelAspectRatio << " = " << propPixelAspectRatio.getValue() 00631 << " : " << ( propPixelAspectRatio.getModifiedBy() == ofx::property::eModifiedByPlugin ? "(plugin)" : "(host)" ) ); 00632 */ 00633 if( !clip.isOutput() && clip.isConnected() ) 00634 { 00635 const attribute::ClipImage& linkClip = clip.getConnectedClip(); 00636 TUTTLE_TLOG( TUTTLE_INFO, "[Bit Depth Connection] Connection between " << clip.getFullName() << " (" << clip.getBitDepth() << " bytes)" << " => " << linkClip.getFullName() << " (" << linkClip.getBitDepth() << " bytes)." ); 00637 } 00638 } 00639 #endif 00640 } 00641 00642 void ImageEffectNode::validInputClipsConnections() const 00643 { 00644 // validation 00645 for( ClipImageMap::const_iterator it = _clipImages.begin(); 00646 it != _clipImages.end(); 00647 ++it ) 00648 { 00649 const attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( it->second ) ); 00650 if( !clip.isOutput() && clip.isConnected() ) 00651 { 00652 const attribute::ClipImage& linkClip = clip.getConnectedClip(); 00653 if( clip.getComponents() != linkClip.getComponents() ) 00654 { 00655 BOOST_THROW_EXCEPTION( exception::Logic() 00656 << exception::dev() + "Error in graph components propagation.\n" 00657 "Connection \"" + linkClip.getFullName() + "\" (" + linkClip.getComponentsString() + ")" + " => \"" + clip.getFullName() + "\" (" + clip.getComponentsString() + ")." 00658 << exception::pluginName( getName() ) 00659 << exception::pluginIdentifier( getPlugin().getIdentifier() ) ); 00660 } 00661 if( clip.getBitDepth() != linkClip.getBitDepth() ) 00662 { 00663 BOOST_THROW_EXCEPTION( exception::Logic() 00664 << exception::dev() + "Error in graph bit depth propagation.\n" 00665 "Connection \"" + linkClip.getFullName() + "\" (" + linkClip.getBitDepth() + " bytes)" + " => \"" + clip.getFullName() + "\" (" + clip.getBitDepth() + " bytes)." 00666 << exception::pluginName( getName() ) 00667 << exception::pluginIdentifier( getPlugin().getIdentifier() ) ); 00668 } 00669 } 00670 } 00671 } 00672 00673 OfxRangeD ImageEffectNode::getDefaultTimeDomain() const 00674 { 00675 //TUTTLE_TLOG( TUTTLE_INFO, "- ImageEffectNode::getDefaultTimeDomain: " << getName() ); 00676 OfxRangeD range; 00677 range.min = kOfxFlagInfiniteMin; 00678 range.max = kOfxFlagInfiniteMax; 00679 // if no answer, compute it from input clips 00680 bool first = true; 00681 for( ClipImageMap::const_iterator it = _clipImages.begin(); 00682 it != _clipImages.end(); 00683 ++it ) 00684 { 00685 const attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( it->second ) ); 00686 if( !clip.isOutput() && clip.isConnected() ) 00687 { 00688 const attribute::ClipImage& linkClip = clip.getConnectedClip(); 00689 const OfxRangeD clipRange = linkClip.getNode().getTimeDomain(); 00690 if( first ) 00691 { 00692 first = false; 00693 range = clipRange; 00694 } 00695 else 00696 { 00697 // maybe better to use intersection instead of union 00698 range.min = std::min( range.min, clipRange.min ); 00699 range.max = std::max( range.max, clipRange.max ); 00700 } 00701 } 00702 } 00703 return range; 00704 } 00705 00706 OfxRangeD ImageEffectNode::computeTimeDomain() 00707 { 00708 // Copy connected clips frameRange into each input clips 00709 for( ClipImageMap::iterator it = _clipImages.begin(); 00710 it != _clipImages.end(); 00711 ++it ) 00712 { 00713 attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( it->second ) ); 00714 if( !clip.isOutput() && clip.isConnected() ) 00715 { 00716 const attribute::ClipImage& linkClip = clip.getConnectedClip(); 00717 const OfxRangeD clipRange = linkClip.getFrameRange(); 00718 clip.setFrameRange( clipRange.min, clipRange.max ); 00719 } 00720 } 00721 TUTTLE_TLOG( TUTTLE_INFO, "[Time domain] getTimeDomain " << quotes(getName()) << " computed by the host." ); 00722 OfxRangeD defaultRange = getDefaultTimeDomain(); 00723 OfxRangeD range = defaultRange; 00724 00725 // ask to the plugin 00726 if( getTimeDomainAction( range ) ) 00727 { 00728 TUTTLE_TLOG( TUTTLE_INFO, "[Time domain] getTimeDomain " << quotes(getName()) << " computed by the plugin." ); 00729 } 00730 else 00731 { 00732 range = defaultRange; 00733 } 00734 attribute::ClipImage* clip = dynamic_cast<attribute::ClipImage*>(_clipImages[kOfxImageEffectOutputClipName]); 00735 if( clip ) 00736 { 00737 clip->setFrameRange( range.min, range.max ); 00738 clip->setUnmappedFrameRange( range.min, range.max ); 00739 } 00740 return range; 00741 } 00742 00743 00744 void ImageEffectNode::setup1() 00745 { 00746 checkClipsConnected(); 00747 00748 initInputClipsFps(); 00749 initInputClipsPixelAspectRatio(); 00750 00751 getClipPreferencesAction(); 00752 00753 initComponents(); 00754 initPixelAspectRatio(); 00755 initFps(); 00756 00757 maximizeBitDepthFromReadsToWrites(); 00758 } 00759 00760 void ImageEffectNode::setup2_reverse() 00761 { 00762 maximizeBitDepthFromWritesToReads(); 00763 } 00764 00765 void ImageEffectNode::setup3() 00766 { 00767 maximizeBitDepthFromReadsToWrites(); 00768 //coutBitDepthConnections(); 00769 validInputClipsConnections(); 00770 } 00771 00772 void ImageEffectNode::beginSequence( graph::ProcessVertexData& vData ) 00773 { 00774 //TUTTLE_TLOG( TUTTLE_INFO, "begin: " << getName() ); 00775 beginSequenceRenderAction( 00776 vData._renderTimeRange.min, 00777 vData._renderTimeRange.max, 00778 vData._step, 00779 vData._interactive, 00780 vData._renderScale 00781 ); 00782 } 00783 00784 void ImageEffectNode::preProcess1( graph::ProcessVertexAtTimeData& vData ) 00785 { 00786 TUTTLE_TLOG( TUTTLE_INFO, "[Pre Process 1] " << getName() << " at time: " << vData._time ); 00787 //setCurrentTime( vData._time ); 00788 00789 OfxRectD rod; 00790 getRegionOfDefinitionAction( 00791 vData._time, 00792 vData._nodeData->_renderScale, 00793 rod ); 00794 // TUTTLE_TLOG_VAR3( TUTTLE_INFO, this->getName(), vData._time, rod ); 00795 // TUTTLE_TLOG_VAR( TUTTLE_INFO, &getData(vData._time) ); 00796 // TUTTLE_TLOG_VAR( TUTTLE_INFO, &vData ); 00797 vData._apiImageEffect._renderRoD = rod; 00798 vData._apiImageEffect._renderRoI = rod; ///< @todo tuttle: tile supports 00799 00800 TUTTLE_TLOG( TUTTLE_INFO, "[Pre Process 1] rod: x1:" << rod.x1 << " y1:" << rod.y1 << " x2:" << rod.x2 << " y2:" << rod.y2 ); 00801 } 00802 00803 void ImageEffectNode::preProcess2_reverse( graph::ProcessVertexAtTimeData& vData ) 00804 { 00805 // TUTTLE_TLOG( TUTTLE_INFO, "preProcess2_finish: " << getName() << " at time: " << vData._time ); 00806 00807 getRegionOfInterestAction( vData._time, 00808 vData._nodeData->_renderScale, 00809 vData._apiImageEffect._renderRoI, 00810 vData._apiImageEffect._inputsRoI ); 00811 // TUTTLE_TLOG_VAR( TUTTLE_INFO, vData._renderRoD ); 00812 // TUTTLE_TLOG_VAR( TUTTLE_INFO, vData._renderRoI ); 00813 } 00814 00815 00816 bool ImageEffectNode::isIdentity( const graph::ProcessVertexAtTimeData& vData, std::string& clip, OfxTime& time ) const 00817 { 00818 time = vData._time; 00819 double par = this->getOutputClip().getPixelAspectRatio(); 00820 if( par == 0.0 ) 00821 par = 1.0; 00822 OfxRectI renderWindow; 00823 renderWindow.x1 = boost::numeric_cast<int>( std::floor( vData._apiImageEffect._renderRoI.x1 / par ) ); 00824 renderWindow.x2 = boost::numeric_cast<int>( std::ceil( vData._apiImageEffect._renderRoI.x2 / par ) ); 00825 renderWindow.y1 = boost::numeric_cast<int>( std::floor( vData._apiImageEffect._renderRoI.y1 ) ); 00826 renderWindow.y2 = boost::numeric_cast<int>( std::ceil( vData._apiImageEffect._renderRoI.y2 ) ); 00827 return isIdentityAction( time, vData._apiImageEffect._field, renderWindow, vData._nodeData->_renderScale, clip ); 00828 } 00829 00830 00831 void ImageEffectNode::preProcess_infos( const graph::ProcessVertexAtTimeData& vData, const OfxTime time, graph::ProcessVertexAtTimeInfo& nodeInfos ) const 00832 { 00833 // TUTTLE_TLOG( TUTTLE_INFO, "preProcess_infos: " << getName() ); 00834 const OfxRectD rod = vData._apiImageEffect._renderRoD; 00835 const std::size_t bitDepth = this->getOutputClip().getBitDepth(); // value in bytes 00836 const std::size_t nbComponents = getOutputClip().getNbComponents(); 00837 nodeInfos._memory = std::ceil( ( rod.x2 - rod.x1 ) * ( rod.y2 - rod.y1 ) * nbComponents * bitDepth ); 00838 } 00839 00840 00841 void ImageEffectNode::process( graph::ProcessVertexAtTimeData& vData ) 00842 { 00843 try 00844 { 00845 memory::IMemoryCache& memoryCache = vData._nodeData->getInternMemoryCache(); 00846 // keep the hand on all needed datas during the process function 00847 std::list<memory::CACHE_ELEMENT> allNeededDatas; 00848 00849 double par = this->getOutputClip().getPixelAspectRatio(); 00850 if( par == 0.0 ) 00851 par = 1.0; 00852 const OfxRectI renderWindow = { 00853 boost::numeric_cast<int>( std::floor( vData._apiImageEffect._renderRoI.x1 / par ) ), 00854 boost::numeric_cast<int>( std::floor( vData._apiImageEffect._renderRoI.y1 ) ), 00855 boost::numeric_cast<int>( std::ceil( vData._apiImageEffect._renderRoI.x2 / par ) ), 00856 boost::numeric_cast<int>( std::ceil( vData._apiImageEffect._renderRoI.y2 ) ) 00857 }; 00858 // TUTTLE_TLOG_VAR( TUTTLE_INFO, roi ); 00859 00860 // INode::ClipTimesSetMap timesSetMap = this->getFramesNeeded( vData._time ); 00861 00862 // acquire needed clip images 00863 /* 00864 TUTTLE_TLOG( TUTTLE_INFO, "acquire needed input clip images" ); 00865 TUTTLE_TLOG_VAR( TUTTLE_INFO, vData._inEdges.size() ); 00866 TUTTLE_TLOG_VAR( TUTTLE_INFO, vData._outEdges.size() ); 00867 BOOST_FOREACH( const graph::ProcessEdgeAtTime* o, vData._outEdges ) 00868 { 00869 TUTTLE_TLOG_VAR( TUTTLE_INFO, o ); 00870 TUTTLE_TLOG_VAR( TUTTLE_INFO, o->getInTime() ); 00871 TUTTLE_TLOG_VAR( TUTTLE_INFO, o->getInAttrName() ); 00872 } 00873 BOOST_FOREACH( const graph::ProcessEdgeAtTime* i, vData._inEdges ) 00874 { 00875 TUTTLE_TLOG_VAR( TUTTLE_INFO, i ); 00876 TUTTLE_TLOG_VAR( TUTTLE_INFO, i->getInTime() ); 00877 TUTTLE_TLOG_VAR( TUTTLE_INFO, i->getInAttrName() ); 00878 } 00879 */ 00880 TUTTLE_TLOG( TUTTLE_INFO, "[Node Process] Acquire needed input clips images" ); 00881 BOOST_FOREACH( const graph::ProcessVertexAtTimeData::ProcessEdgeAtTimeByClipName::value_type& inEdgePair, vData._inEdges ) 00882 { 00883 const graph::ProcessEdgeAtTime* inEdge = inEdgePair.second; 00884 //TUTTLE_TLOG_VAR( TUTTLE_INFO, i ); 00885 //TUTTLE_TLOG_VAR( TUTTLE_INFO, i->getInTime() ); 00886 //TUTTLE_TLOG_VAR( TUTTLE_INFO, i->getInAttrName() ); 00887 attribute::ClipImage& clip = getClip( inEdge->getInAttrName() ); 00888 const OfxTime outTime = inEdge->getOutTime(); 00889 00890 TUTTLE_TLOG( TUTTLE_INFO, "[Node Process] out: " << inEdge->getOut() << " -> in " << inEdge->getIn() ); 00891 memory::CACHE_ELEMENT imageCache( memoryCache.get( clip.getClipIdentifier(), outTime ) ); 00892 if( imageCache.get() == NULL ) 00893 { 00894 BOOST_THROW_EXCEPTION( exception::Memory() 00895 << exception::dev() + "Input attribute " + quotes( clip.getFullName() ) + " at time " + vData._time + " not in memory cache (identifier:" + quotes( clip.getClipIdentifier() ) + ")." ); 00896 } 00897 allNeededDatas.push_back( imageCache ); 00898 } 00899 00900 TUTTLE_TLOG( TUTTLE_INFO, "[Node Process] Acquire needed output clip images" ); 00901 BOOST_FOREACH( ClipImageMap::value_type& i, _clipImages ) 00902 { 00903 attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( i.second ) ); 00904 if( clip.isOutput() ) 00905 { 00906 TUTTLE_TLOG( TUTTLE_INFO, "[Node Process] " << vData._apiImageEffect._renderRoI ); 00907 memory::CACHE_ELEMENT imageCache( new attribute::Image( 00908 clip, 00909 vData._time, 00910 vData._apiImageEffect._renderRoI, 00911 attribute::Image::eImageOrientationFromBottomToTop, 00912 0 ) 00913 ); 00914 imageCache->setPoolData( core().getMemoryPool().allocate( imageCache->getMemorySize() ) ); 00915 memoryCache.put( clip.getClipIdentifier(), vData._time, imageCache ); 00916 00917 allNeededDatas.push_back( imageCache ); 00918 } 00919 } 00920 00921 TUTTLE_LOG_TRACE( "[Node Process] Plugin Render Action" ); 00922 00923 renderAction( vData._time, 00924 vData._apiImageEffect._field, 00925 renderWindow, 00926 vData._nodeData->_renderScale ); 00927 00928 TUTTLE_LOG_TRACE( "[Node Process] Plugin Render Action - End" ); 00929 00930 debugOutputImage( vData._time ); 00931 00932 // release input images 00933 BOOST_FOREACH( const graph::ProcessVertexAtTimeData::ProcessEdgeAtTimeByClipName::value_type& inEdgePair, vData._inEdges ) 00934 { 00935 const graph::ProcessEdgeAtTime* inEdge = inEdgePair.second; 00936 attribute::ClipImage& clip = getClip( inEdge->getInAttrName() ); 00937 const OfxTime outTime = inEdge->getOutTime(); 00938 00939 // TUTTLE_LOG_VAR2( TUTTLE_INFO, clip.getClipIdentifier(), outTime ); 00940 // TUTTLE_LOG_VAR2( TUTTLE_INFO, inEdge->getOut(), inEdge->getIn() ); 00941 // TUTTLE_LOG_VAR2( TUTTLE_INFO, clip.getClipIdentifier(), clip.getFullName() ); 00942 00943 memory::CACHE_ELEMENT imageCache = memoryCache.get( clip.getClipIdentifier(), outTime ); 00944 if( imageCache.get() == NULL ) 00945 { 00946 BOOST_THROW_EXCEPTION( exception::Memory() 00947 << exception::dev() + "Clip " + quotes( clip.getFullName() ) + " not in memory cache (identifier: " + quotes( clip.getClipIdentifier() ) + ", time: " + outTime + ")." ); 00948 } 00949 TUTTLE_LOG_TRACE( "[ImageEffectNode] releaseReference: " << imageCache->getFullName() ); 00950 // TODO: use RAII technique for add/releaseReference... 00951 imageCache->releaseReference( ofx::imageEffect::OfxhImage::eReferenceOwnerHost ); 00952 } 00953 00954 // declare future usages of the output 00955 BOOST_FOREACH( ClipImageMap::value_type& item, _clipImages ) 00956 { 00957 attribute::ClipImage& clip = dynamic_cast<attribute::ClipImage&>( *( item.second ) ); 00958 if( ! clip.isOutput() && ! clip.isConnected() ) 00959 continue; 00960 00961 if( clip.isOutput() ) 00962 { 00963 memory::CACHE_ELEMENT imageCache = memoryCache.get( clip.getClipIdentifier(), vData._time ); 00964 if( imageCache.get() == NULL ) 00965 { 00966 BOOST_THROW_EXCEPTION( exception::Memory() 00967 << exception::dev() + "Clip " + quotes( clip.getFullName() ) + " not in memory cache (identifier:" + quotes( clip.getClipIdentifier() ) + ")." ); 00968 } 00969 const std::size_t realOutDegree = vData._outDegree - vData._isFinalNode; // final nodes have a connection to the fake output node. 00970 TUTTLE_TLOG( TUTTLE_INFO, "[Node Process] Declare future usages: " << clip.getClipIdentifier() << ", add reference: " << realOutDegree ); 00971 if( realOutDegree > 0 ) 00972 { 00973 TUTTLE_LOG_TRACE( "[ImageEffectNode] addReference: " << imageCache->getFullName() << ", degree=" << realOutDegree ); 00974 // TODO: use RAII technique for add/releaseReference... 00975 // to properly declare image unused when an error occured 00976 // during the computation. 00977 // Add a reference on this node for each future usages 00978 imageCache->addReference( ofx::imageEffect::OfxhImage::eReferenceOwnerHost, realOutDegree ); 00979 } 00980 } 00981 } 00982 } 00983 catch(boost::exception& e) 00984 { 00985 e << exception::time(vData._time) 00986 << exception::pluginIdentifier(this->getPlugin().getIdentifier()) 00987 << exception::nodeName(this->getName()); 00988 throw; 00989 } 00990 00991 } 00992 00993 void ImageEffectNode::postProcess( graph::ProcessVertexAtTimeData& vData ) 00994 { 00995 // TUTTLE_TLOG( TUTTLE_INFO, "postProcess: " << getName() ); 00996 } 00997 00998 00999 void ImageEffectNode::endSequence( graph::ProcessVertexData& vData ) 01000 { 01001 // TUTTLE_TLOG( TUTTLE_INFO, "end: " << getName() ); 01002 endSequenceRenderAction( vData._renderTimeRange.min, 01003 vData._renderTimeRange.max, 01004 vData._step, 01005 vData._interactive, 01006 vData._renderScale ); 01007 } 01008 01009 01010 std::ostream& ImageEffectNode::print( std::ostream& os ) const 01011 { 01012 const ImageEffectNode& v = *this; 01013 os << "________________________________________________________________________________" << std::endl; 01014 os << "Plug-in:" << v.getLabel() << std::endl; 01015 os << "Description:" << v.getLongLabel() << std::endl; 01016 os << "Context:" << v._context << std::endl; 01017 os << "Clips:" << std::endl; 01018 for( ImageEffectNode::ClipImageMap::const_iterator it = v._clipImages.begin(), itEnd = v._clipImages.end(); 01019 it != itEnd; 01020 ++it ) 01021 { 01022 os << " * " << it->second->getName() << std::endl; 01023 } 01024 os << "Params:" << std::endl; 01025 for( ImageEffectNode::ParamVector::const_iterator it = v._paramVector.begin(), itEnd = v._paramVector.end(); 01026 it != itEnd; 01027 ++it ) 01028 { 01029 os << " * " << it->getName() << " (" << it->getLabel() << "): " << it->displayValues(os) << std::endl; 01030 } 01031 os << "________________________________________________________________________________" << std::endl; 01032 return os; 01033 } 01034 01035 std::ostream& operator<<( std::ostream& os, const ImageEffectNode& v ) 01036 { 01037 return v.print(os); 01038 } 01039 01040 void ImageEffectNode::debugOutputImage( const OfxTime time ) const 01041 { 01042 #if(TUTTLE_PNG_EXPORT_BETWEEN_NODES) 01043 for( ClipImageMap::const_iterator it = _clipImages.begin(); 01044 it != _clipImages.end(); 01045 ++it ) 01046 { 01047 const attribute::ClipImage& clip = dynamic_cast<const attribute::ClipImage&>( *( it->second ) ); 01048 if( clip.isOutput() ) 01049 { 01050 boost::shared_ptr<attribute::Image> image = clip.getNode().getData().getInternMemoryCache().get( this->getName() + "." kOfxOutputAttributeName, time ); 01051 image->debugSaveAsPng( boost::lexical_cast<std::string>( time ) + "_" + this->getName() + ".png" ); 01052 } 01053 } 01054 #endif 01055 } 01056 01057 } 01058 }