TuttleOFX  1
TextProcess.tcc
Go to the documentation of this file.
00001 #include <boost/python.hpp>
00002 // From Boost.Python:
00003 // The rule is that <Python.h> must be included before any system
00004 // headers (so it can get control over some awful macros).
00005 #include <Python.h> // Need to be included, because it is not always included by "boost/python.hpp".
00006 
00007 #include "TextPlugin.hpp"
00008 #include "TextProcess.hpp"
00009 #include "TextDefinitions.hpp"
00010 
00011 #include <terry/globals.hpp>
00012 #include <terry/merge/MergeFunctors.hpp>
00013 #include <terry/merge/ViewsMerging.hpp>
00014 
00015 #include <tuttle/plugin/exceptions.hpp>
00016 #include <tuttle/plugin/exceptions.hpp>
00017 #include <tuttle/common/ofx/core.hpp>
00018 
00019 #include <boost/gil/extension/color/hsl.hpp>
00020 #include <boost/gil/gil_all.hpp>
00021 
00022 #include <boost/filesystem.hpp>
00023 #include <boost/ptr_container/ptr_inserter.hpp>
00024 
00025 #include <sstream>
00026 #include <string>
00027 #include <iostream>
00028 
00029 #ifndef __WINDOWS__
00030 #include <fontconfig/fontconfig.h>
00031 #endif
00032 
00033 namespace tuttle {
00034 namespace plugin {
00035 namespace text {
00036 
00037 template<class View, class Functor>
00038 TextProcess<View, Functor>::TextProcess( TextPlugin& instance )
00039         : ImageGilProcessor<View>( instance, eImageOrientationFromTopToBottom )
00040         , _plugin( instance )
00041 {
00042 //      Py_Initialize();
00043         _clipSrc = instance.fetchClip( kOfxImageEffectSimpleSourceClipName );
00044         this->setNoMultiThreading();
00045 }
00046 
00047 template<class View, class Functor>
00048 void TextProcess<View, Functor>::setup( const OFX::RenderArguments& args )
00049 {
00050         using namespace terry;
00051         ImageGilProcessor<View>::setup( args );
00052         
00053         if( _clipSrc->isConnected() )
00054         {
00055                 _src.reset( _clipSrc->fetchImage( args.time ) );
00056                 if( ! _src.get() )
00057                         BOOST_THROW_EXCEPTION( exception::ImageNotReady()
00058                                         << exception::dev() + "Error on clip " + quotes(_clipSrc->name())
00059                                         << exception::time( args.time ) );
00060                 if( _src->getRowDistanceBytes() == 0 )
00061                         BOOST_THROW_EXCEPTION( exception::WrongRowBytes()
00062                                         << exception::dev() + "Error on clip " + quotes(_clipSrc->name())
00063                                         << exception::time( args.time ) );
00064                 
00065                 if( OFX::getImageEffectHostDescription()->hostName == "uk.co.thefoundry.nuke" )
00066                 {
00067                         // bug in nuke, getRegionOfDefinition() on OFX::Image returns bounds
00068                         _srcPixelRod   = _clipSrc->getPixelRod( args.time, args.renderScale );
00069                 }
00070                 else
00071                 {
00072                         _srcPixelRod = _src->getRegionOfDefinition();
00073                 }
00074                 _srcView = ImageGilProcessor<View>::template getCustomView<View>( _src.get(), _srcPixelRod );
00075         }
00076         
00077         _params = _plugin.getProcessParams( args.renderScale );
00078         
00079         if( ! _params._isExpression )
00080         {
00081                 _text = _params._text;
00082         }
00083         else
00084         {
00085                 try
00086                 {
00087                         Py_Initialize();
00088 
00089                         boost::python::object main_module = boost::python::import( "__main__" );
00090                         boost::python::object main_namespace = main_module.attr( "__dict__" );
00091                         
00092                         std::ostringstream context;
00093                         context << "class tuttleArgs :" << std::endl;
00094                         context << "    time = " << args.time << std::endl;
00095                         context << "    renderScale = [" << args.renderScale.x << "," << args.renderScale.y << "]" << std::endl;
00096                         context << "    renderWindow = [" << args.renderWindow.x1 << "," << args.renderWindow.y1 << ","
00097                                                               << args.renderWindow.x2 << "," << args.renderWindow.y2 << "]" << std::endl;
00098 
00099                         OfxRectD dstCanonicalRod = this->_clipDst->getCanonicalRod( args.time );
00100                         context << "    dstCanonicalRod = [" << dstCanonicalRod.x1 << "," << dstCanonicalRod.y1 << ","
00101                                                                  << dstCanonicalRod.x2 << "," << dstCanonicalRod.y2 << "]" << std::endl;
00102                         OfxRectI dstPixelRod = this->_clipDst->getPixelRod( args.time );
00103                         context << "    dstPixelRod = [" << dstPixelRod.x1 << "," << dstPixelRod.y1 << ","
00104                                                                                          << dstPixelRod.x2 << "," << dstPixelRod.y2 << "]" << std::endl;
00105                         
00106                         context << "    fps = " << _clipSrc->getFrameRate() << std::endl;
00107                         
00108                         context << "    def timecode( self ):" << std::endl;
00109                         context << "        return '{0:02d}:{1:02d}:{2:02d}:{3:02d}'.format(  self.time / (3600 * self.fps ), "
00110                                                                                                                                                                 " self.time / (60 * self.fps ) % 60, "
00111                                                                                                                                                                 " self.time / self.fps % 60, "
00112                                                                                                                                                                 " self.time % self.fps )" << std::endl;
00113                         
00114                         //TUTTLE_LOG_INFO( context.str().c_str() );
00115 
00116                         /*object ignored = */
00117                         
00118                         boost::python::exec( context.str().c_str(), main_namespace );
00119                         boost::python::object returnText = boost::python::eval( _params._text.c_str(), main_namespace );
00120 
00121                         _text = boost::python::extract<std::string>( returnText );
00122                 }
00123                 catch( boost::python::error_already_set const & )
00124                 {
00125                         // if we can't evaluate the expression
00126                         // use the text without interpretation
00127 
00128                         //Get error message from python
00129                         PyObject *ptype, *pvalue, *ptraceback;
00130                         PyErr_Fetch(&ptype, &pvalue, &ptraceback);
00131 #if PY_MAJOR_VERSION < 3
00132                         // Python version is < 3.0
00133                         char *pStrErrorMessage = PyString_AsString(pvalue);
00134 #elif PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 3
00135                         // PYTHON version is < 3.3
00136                         PyObject* stringObj = PyUnicode_AsUTF8String(pvalue);
00137                         char *pStrErrorMessage = PyBytes_AsString(stringObj);;
00138 #else
00139                         // PYTHON version is >= 3.3
00140                         char *pStrErrorMessage = PyUnicode_AsUTF8(pvalue);
00141 #endif
00142                         TUTTLE_LOG_ERROR("Python error : " << pStrErrorMessage);
00143 #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 3
00144                         Py_DECREF(stringObj);
00145 #endif
00146 
00147                         _text = _params._text;
00148                 }
00149 //              Py_Finalize();
00150         }
00151         
00152         
00153         //Step 1. Create terry image
00154         //Step 2. Initialize freetype
00155         //Step 3. Make Glyphs Array
00156         //Step 4. Make Metrics Array
00157         //Step 5. Make Kerning Array
00158         //Step 6. Get Coordinates (x,y)
00159         //Step 7. Render Glyphs on GIL View
00160         //Step 8. Save GIL Image
00161 
00162         //Step 1. Create terry image -----------
00163 
00164         //Step 2. Initialize freetype ---------------
00165         FT_Library library;
00166         FT_Init_FreeType( &library );
00167 
00168         FT_Face face;
00169 
00170         std::string selectedFont = "";
00171 
00172         if( !boost::filesystem::exists( _params._fontPath ) || boost::filesystem::is_directory( _params._fontPath ) )
00173         {
00174 #ifdef __WINDOWS__
00175                 BOOST_THROW_EXCEPTION( exception::FileNotExist( _params._fontPath )
00176                                                         << exception::user( "Text: Error in Font Path." )
00177                                                         << exception::filename( _params._fontPath ) );
00178 #else
00179                 FcInit();
00180 
00181                 FcChar8 *file;
00182                 FcResult result;
00183                 FcConfig *config = FcInitLoadConfigAndFonts();
00184                 FcPattern *p = FcPatternBuild(
00185                                                    NULL,
00186                                                    FC_WEIGHT, FcTypeInteger, FC_WEIGHT_BOLD,
00187                                                    FC_SLANT, FcTypeInteger, FC_SLANT_ITALIC,
00188                                                    NULL );
00189 
00190                 FcObjectSet *os = FcObjectSetBuild( FC_FAMILY, NULL );
00191                 FcFontSet   *fs = FcFontList( config, p, os );
00192 
00193                 selectedFont = (char*) FcNameUnparse( fs->fonts[_params._font] );
00194 
00195                 int weight = ( _params._bold   == 1) ? FC_WEIGHT_BOLD  : FC_WEIGHT_MEDIUM;
00196                 int slant  = ( _params._italic == 1) ? FC_SLANT_ITALIC : FC_SLANT_ROMAN;
00197 
00198                 p  = FcPatternBuild( NULL,
00199                                                          FC_FAMILY, FcTypeString, selectedFont.c_str(),
00200                                                          FC_WEIGHT, FcTypeInteger, weight,
00201                                                          FC_SLANT, FcTypeInteger, slant,
00202                                                          NULL );
00203 
00204                 FcPatternGetString( FcFontMatch( 0, p, &result ), FC_FAMILY, 0, &file );
00205                 FcPatternGetString( FcFontMatch( 0, p, &result ), FC_FILE, 0, &file );
00206                 selectedFont = (char*) file;
00207 #endif
00208         }
00209         else
00210         {
00211                 selectedFont = _params._fontPath;
00212         }
00213         FT_New_Face( library, selectedFont.c_str(), 0, &face );
00214         
00215         FT_Set_Pixel_Sizes( face, _params._fontX, _params._fontY );     
00216 
00217         //Step 3. Make Glyphs Array ------------------
00218         rgba32f_pixel_t rgba32f_foregroundColor( _params._fontColor.r,
00219                                                                                          _params._fontColor.g,
00220                                                                                          _params._fontColor.b,
00221                                                                                          _params._fontColor.a );
00222         color_convert( rgba32f_foregroundColor, _foregroundColor );
00223         std::transform( _text.begin(), _text.end(), boost::ptr_container::ptr_back_inserter( _glyphs ), make_glyph( face ) );
00224 
00225         //Step 4. Make Metrics Array --------------------
00226         std::transform( _glyphs.begin(), _glyphs.end(), std::back_inserter( _metrics ), terry::make_metric() );
00227 
00228         //Step 5. Make Kerning Array ----------------
00229         std::transform( _glyphs.begin(), _glyphs.end(), std::back_inserter( _kerning ), terry::make_kerning() );
00230 
00231         //Step 6. Get Coordinates (x,y) ----------------
00232         _textSize.x   = std::for_each( _metrics.begin(), _metrics.end(), _kerning.begin(), terry::make_width() );
00233         _textSize.y   = std::for_each( _metrics.begin(), _metrics.end(), terry::make_height() );
00234 
00235         if( _metrics.size() > 1 )
00236                 _textSize.x   += _params._letterSpacing * (_metrics.size() - 1);
00237 
00238         switch( _params._vAlign )
00239         {
00240                 case eParamVAlignTop:
00241                 {
00242                         _textCorner.y = 0;
00243                         break;
00244                 }
00245                 case eParamVAlignCenter:
00246                 {
00247                         _textCorner.y = ( this->_dstView.height() - _textSize.y ) * 0.5;
00248                         break;
00249                 }
00250                 case eParamVAlignBottom:
00251                 {
00252                         _textCorner.y = this->_dstView.height() - (_textSize.y + _textSize.y / 3);
00253                         break;
00254                 }
00255         }
00256         switch( _params._hAlign )
00257         {
00258                 case eParamHAlignLeft:
00259                 {
00260                         _textCorner.x = 0;
00261                         break;
00262                 }
00263                 case eParamHAlignCenter:
00264                 {
00265                         _textCorner.x = ( this->_dstView.width() - _textSize.x ) * 0.5;
00266                         break;
00267                 }
00268                 case eParamHAlignRight:
00269                 {
00270                         _textCorner.x = this->_dstView.width() - _textSize.x;
00271                         break;
00272                 }
00273         }
00274 
00275         if( _params._verticalFlip )
00276         {
00277                 _dstViewForGlyphs = flipped_up_down_view( this->_dstView );
00278                 _textCorner.y    -= _params._position.y;
00279         }
00280         else
00281         {
00282                 _dstViewForGlyphs = this->_dstView;
00283                 _textCorner.y    += _params._position.y;
00284         }
00285         
00286         _textCorner.x += _params._position.x;
00287 }
00288 
00289 /**
00290  * @brief Function called by rendering thread each time a process must be done.
00291  * @param[in] procWindowRoW  Processing window in RoW
00292  */
00293 template<class View, class Functor>
00294 void TextProcess<View, Functor>::multiThreadProcessImages( const OfxRectI& procWindowRoW )
00295 {
00296         using namespace terry;
00297         
00298         rgba32f_pixel_t backgroundColor( _params._backgroundColor.r,
00299                                                                          _params._backgroundColor.g,
00300                                                                          _params._backgroundColor.b,
00301                                                                          _params._backgroundColor.a );
00302         fill_pixels( this->_dstView, backgroundColor );
00303         
00304         if( _clipSrc->isConnected() )
00305         {
00306                 //merge_views( this->_dstView, _srcView, this->_dstView, FunctorMatte<Pixel>() );
00307                 merge_views( this->_dstView, _srcView, this->_dstView, Functor() );
00308         }
00309         
00310         //Step 7. Render Glyphs ------------------------
00311         // if outside dstRod
00312         // ...
00313         // else
00314         const OfxRectI textRod = { _textCorner.x, _textCorner.y, _textCorner.x + _textSize.x, _textCorner.y + _textSize.y + _textSize.y / 3};
00315         const OfxRectI textRoi = rectanglesIntersection( textRod, procWindowRoW );
00316         const OfxRectI textLocalRoi = translateRegion( textRoi, - _textCorner );
00317         
00318         //TUTTLE_LOG_VAR( TUTTLE_INFO, _textSize );
00319         //TUTTLE_LOG_VAR( TUTTLE_INFO, _textCorner );
00320         
00321         //TUTTLE_LOG_VAR( TUTTLE_INFO, textRod );
00322         //TUTTLE_LOG_VAR( TUTTLE_INFO, procWindowRoW );
00323         //TUTTLE_LOG_VAR( TUTTLE_INFO, textRoi );
00324         //TUTTLE_LOG_VAR( TUTTLE_INFO, textLocalRoi );
00325         //TUTTLE_LOG_VAR2( TUTTLE_INFO, _dstViewForGlyphs.width(), _dstViewForGlyphs.height() );
00326         
00327         View tmpDstViewForGlyphs = subimage_view( _dstViewForGlyphs, _textCorner.x, _textCorner.y, _textSize.x, _textSize.y);
00328         
00329         std::for_each( _glyphs.begin(), _glyphs.end(), _kerning.begin(),
00330                        render_glyph<View>( tmpDstViewForGlyphs, _foregroundColor, _params._letterSpacing, Rect<std::ptrdiff_t>(textLocalRoi) )
00331                        );
00332 }
00333 
00334 }
00335 }
00336 }