TuttleOFX  1
TuttleOFX/libraries/tuttle/src/tuttle/host/ImageEffectNode.cpp
Go to the documentation of this file.
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 }