TuttleOFX
1
|
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 }