utils.cpp

Go to the documentation of this file.
00001 // -*- c-basic-offset: 4 -*-
00002 
00025 #include "utils.h"
00026 #include "hugin_version.h"
00027 
00028 #ifdef WIN32
00029     #define NOMINMAX
00030     #include <sys/utime.h>
00031     #include <ShlObj.h>
00032 #else
00033     #include <sys/time.h>
00034     #include <cstdlib>
00035     #include <unistd.h>
00036     #include <sys/types.h>
00037     #include <pwd.h>
00038 #endif
00039 #include <time.h>
00040 #include <fstream>
00041 #include <stdio.h>
00042 #include <cstdio>
00043 #ifdef _WINDOWS
00044 #define NOMINMAX
00045 #include <windows.h>
00046 #else
00047 #include <hugin_config.h>
00048 #endif
00049 #include <algorithm>
00050 #define BOOST_FILESYSTEM_VERSION 3
00051 #include <boost/filesystem.hpp>
00052 #include <lcms2.h>
00053 
00054 #ifdef __APPLE__
00055 #include <mach-o/dyld.h>  /* _NSGetExecutablePath */
00056 #include <limits.h>       /* PATH_MAX */
00057 #include <libgen.h>       /* dirname */
00058 #endif
00059 
00060 #if !defined Hugin_shared || !defined _WINDOWS
00061 #define GLEW_STATIC
00062 #endif
00063 #include <GL/glew.h>
00064 #ifdef _WIN32
00065 #include <GL/wglew.h>
00066 #elif defined __APPLE__
00067   #include <GLUT/glut.h>
00068 #endif
00069 
00070 namespace hugin_utils {
00071     
00072 #ifdef UNIX_LIKE
00073 std::string GetCurrentTimeString()
00074 {
00075   char tmp[100];
00076   struct tm t;
00077   struct timeval tv;
00078   gettimeofday(&tv,NULL);
00079   localtime_r((time_t*)&tv.tv_sec, &t); // is the casting safe?
00080   strftime(tmp,99,"%H:%M:%S",&t);
00081   sprintf(tmp+8,".%06ld", (long)tv.tv_usec);
00082   return tmp;
00083 }
00084 #else
00085 std::string GetCurrentTimeString()
00086 {
00087     // FIXME implement for Win
00088     return "";
00089 }
00090 #endif
00091 
00092 
00093 std::string getExtension(const std::string & basename2)
00094 {
00095         std::string::size_type idx = basename2.rfind('.');
00096     // check if the dot is not followed by a \ or a /
00097     // to avoid cutting pathes.
00098     if (idx == std::string::npos) {
00099         // no dot found
00100                 return std::string("");
00101     }
00102 #ifdef UNIX_LIKE
00103     // check for slashes after dot
00104     std::string::size_type slashidx = basename2.find('/', idx);
00105     if ( slashidx == std::string::npos)
00106     {
00107         return basename2.substr(idx+1);
00108     } else {
00109         return std::string("");
00110     }
00111 #else
00112     // check for slashes after dot
00113     std::string::size_type slashidx = basename2.find('/', idx);
00114     std::string::size_type backslashidx = basename2.find('\\', idx);
00115     if ( slashidx == std::string::npos &&  backslashidx == std::string::npos)
00116     {
00117         return basename2.substr(idx+1);
00118     } else {
00119                 return std::string("");
00120     }
00121 #endif
00122 }
00123 
00124 std::string stripExtension(const std::string & basename2)
00125 {
00126     std::string::size_type idx = basename2.rfind('.');
00127     // check if the dot is not followed by a \ or a /
00128     // to avoid cutting pathes.
00129     if (idx == std::string::npos) {
00130         // no dot found
00131         return basename2;
00132     }
00133 #ifdef UNIX_LIKE
00134     std::string::size_type slashidx = basename2.find('/', idx);
00135     if ( slashidx == std::string::npos)
00136     {
00137         return basename2.substr(0, idx);
00138     } else {
00139         return basename2;
00140     }
00141 #else
00142     // check for slashes after dot
00143     std::string::size_type slashidx = basename2.find('/', idx);
00144     std::string::size_type backslashidx = basename2.find('\\', idx);
00145     if ( slashidx == std::string::npos &&  backslashidx == std::string::npos)
00146     {
00147         return basename2.substr(0, idx);
00148     } else {
00149         return basename2;
00150     }
00151 #endif
00152 }
00153 
00154 std::string stripPath(const std::string & filename)
00155 {
00156 #ifdef UNIX_LIKE
00157     std::string::size_type idx = filename.rfind('/');
00158 #else
00159     std::string::size_type idx1 = filename.rfind('\\');
00160     std::string::size_type idx2 = filename.rfind('/');
00161     std::string::size_type idx;
00162     if (idx1 == std::string::npos) {
00163         idx = idx2;
00164     } else if (idx2 == std::string::npos) {
00165         idx = idx1;
00166     } else {
00167         idx = std::max(idx1, idx2);
00168     }
00169 #endif
00170     if (idx != std::string::npos) {
00171 //        DEBUG_DEBUG("returning substring: " << filename.substr(idx + 1));
00172         return filename.substr(idx + 1);
00173     } else {
00174         return filename;
00175     }
00176 }
00177 
00178 std::string getPathPrefix(const std::string & filename)
00179 {
00180 #ifdef UNIX_LIKE
00181     std::string::size_type idx = filename.rfind('/');
00182 #else
00183     std::string::size_type idx1 = filename.rfind('\\');
00184     std::string::size_type idx2 = filename.rfind('/');
00185     std::string::size_type idx;
00186     if (idx1 == std::string::npos) {
00187         idx = idx2;
00188     } else if (idx2 == std::string::npos) {
00189         idx = idx1;
00190     } else {
00191         idx = std::max(idx1, idx2);
00192     }
00193 #endif
00194     if (idx != std::string::npos) {
00195 //        DEBUG_DEBUG("returning substring: " << filename.substr(idx + 1));
00196         return filename.substr(0, idx+1);
00197     } else {
00198         return "";
00199     }
00200 }
00201 
00202 std::string StrTrim(const std::string& str)
00203 {
00204     std::string s(str);
00205     std::string::size_type pos = s.find_last_not_of(" \t");
00206     if (pos != std::string::npos)
00207     {
00208         s.erase(pos + 1);
00209         pos = s.find_first_not_of(" \t");
00210         if (pos != std::string::npos)
00211         {
00212             s.erase(0, pos);
00213         };
00214     }
00215     else
00216     {
00217         s.erase(s.begin(), s.end());
00218     };
00219     return s;
00220 }
00221 
00222 std::string doubleToString(double d, int digits)
00223 {
00224     char fmt[10];
00225     if (digits < 0) {
00226         strcpy(fmt,"%f");
00227     } else {
00228         std::sprintf(fmt,"%%.%df",digits);
00229     }
00230     char c[1024];
00231     c[1023] = 0;
00232 #ifdef _MSC_VER
00233     _snprintf (c, 1023, fmt, d);
00234 #else
00235     snprintf (c, 1023, fmt, d);
00236 #endif
00237 
00238     std::string number (c);
00239 
00240     int l = (int)number.length()-1;
00241 
00242     while ( l != 0 && number[l] == '0' ) {
00243       number.erase (l);
00244       l--;
00245     }
00246     if ( number[l] == ',' ) {
00247       number.erase (l);
00248       l--;
00249     }
00250     if ( number[l] == '.' ) {
00251       number.erase (l);
00252       l--;
00253     }
00254     return number;
00255 }
00256 
00257 bool stringToInt(const std::string& s, int& val)
00258 {
00259     if (StrTrim(s) == "0")
00260     {
00261         val = 0;
00262         return true;
00263     };
00264     int x = atoi(s.c_str());
00265     if (x != 0)
00266     {
00267         val = x;
00268         return true;
00269     };
00270     return false;
00271 };
00272 
00273 bool stringToUInt(const std::string&s, unsigned int& val)
00274 {
00275     int x;
00276     if (stringToInt(s, x))
00277     {
00278         if (x >= 0)
00279         {
00280             val = static_cast<unsigned int>(x);
00281             return true;
00282         };
00283     };
00284     return false;
00285 };
00286 
00287 std::vector<std::string> SplitString(const std::string& s, const std::string& sep)
00288 {
00289     std::vector<std::string> result;
00290     std::size_t pos = s.find_first_of(sep, 0);
00291     std::size_t pos2 = 0;
00292     while (pos != std::string::npos)
00293     {
00294         if (pos - pos2 > 0)
00295         {
00296             std::string t(s.substr(pos2, pos - pos2));
00297             t=StrTrim(t);
00298             if (!t.empty())
00299             {
00300                 result.push_back(t);
00301             };
00302         };
00303         pos2 = pos + 1;
00304         pos = s.find_first_of(sep, pos2);
00305     }
00306     if (pos2 < s.length())
00307     {
00308         std::string t(s.substr(pos2));
00309         t = StrTrim(t);
00310         if (!t.empty())
00311         {
00312             result.push_back(t);
00313         };
00314     };
00315     return result;
00316 };
00317 
00318 void ReplaceAll(std::string& s, const std::string& oldChar, char newChar)
00319 {
00320     std::size_t found = s.find_first_of(oldChar);
00321     while (found != std::string::npos)
00322     {
00323         s[found] = newChar;
00324         found = s.find_first_of(oldChar, found + 1);
00325     };
00326 };
00327 
00328     void ControlPointErrorColour(const double cperr, 
00329         double &r,double &g, double &b)
00330     {
00331         //Colour change points
00332 #define XP1 5.0f
00333 #define XP2 10.0f
00334 
00335         if ( cperr<= XP1) 
00336         {
00337             //low error
00338             r = cperr / XP1;
00339             g = 0.75;
00340         }
00341         else
00342         {
00343             r = 1.0;
00344             g = 0.75 * ((1.0 - std::min<double>(cperr - XP1, XP2 - XP1) / (XP2 - XP1)));
00345         } 
00346         b = 0.0;
00347     }
00348 
00349 bool FileExists(const std::string& filename)
00350 {
00351     std::ifstream ifile(filename.c_str());
00352     return !ifile.fail();
00353 }
00354 
00355 std::string GetAbsoluteFilename(const std::string& filename)
00356 {
00357 #ifdef _WINDOWS
00358     char fullpath[_MAX_PATH];
00359     _fullpath(fullpath,filename.c_str(),_MAX_PATH);
00360     return std::string(fullpath);
00361 #else
00362     //realpath works only with existing files
00363     //so as work around we create the file first, call then realpath 
00364     //and delete the temp file
00366     bool tempFileCreated=false;
00367     if(!FileExists(filename))
00368     {
00369         tempFileCreated=true;
00370         std::ofstream os(filename.c_str());
00371         os.close();
00372     };
00373     char *real_path = realpath(filename.c_str(), NULL);
00374     std::string absPath;
00375     if(real_path!=NULL)
00376     {
00377         absPath=std::string(real_path);
00378         free(real_path);
00379     }
00380     else
00381     {
00382         absPath=filename;
00383     };
00384     if(tempFileCreated)
00385     {
00386         remove(filename.c_str());
00387     };
00388     return absPath;
00389 #endif
00390 };
00391 
00392 std::string GetDataDir()
00393 {
00394 #if _WINDOWS
00395     char buffer[MAX_PATH];//always use MAX_PATH for filepaths
00396     GetModuleFileName(NULL,buffer,sizeof(buffer));
00397     std::string working_path(buffer);
00398     std::string data_path("");
00399     //remove filename
00400     std::string::size_type pos=working_path.rfind("\\");
00401     if(pos!=std::string::npos)
00402     {
00403         working_path.erase(pos);
00404         //remove last dir: should be bin
00405         pos=working_path.rfind("\\");
00406         if(pos!=std::string::npos)
00407         {
00408             working_path.erase(pos);
00409             //append path delimiter and path
00410             working_path.append("\\share\\hugin\\data\\");
00411             data_path=working_path;
00412         }
00413     }
00414 #elif defined MAC_SELF_CONTAINED_BUNDLE
00415     char path[PATH_MAX + 1];
00416     uint32_t size = sizeof(path);
00417     std::string data_path("");
00418     if (_NSGetExecutablePath(path, &size) == 0)
00419     {
00420         data_path=dirname(path);
00421         data_path.append("/../Resources/xrc/");
00422     }
00423 #else
00424     std::string data_path = (INSTALL_DATA_DIR);
00425 #endif
00426     return data_path;
00427 };
00428 
00429 std::string GetUserAppDataDir()
00430 {
00431     boost::filesystem::path path;
00432 #ifdef _WIN32
00433     char fullpath[_MAX_PATH];
00434     if(SHGetFolderPath(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, fullpath)!=S_OK)
00435     {
00436         return std::string();
00437     };
00438     path = boost::filesystem::path(fullpath);
00439     path /= "hugin";
00440 #else
00441     char *homedir = getenv("HOME");
00442     struct passwd *pw;
00443     if (homedir == NULL)
00444     {
00445         pw = getpwuid(getuid());
00446         if(pw != NULL)
00447         {
00448             homedir = pw->pw_dir;
00449         };
00450     };
00451     if(homedir == NULL)
00452     {
00453         return std::string();
00454     };
00455     path = boost::filesystem::path(homedir);
00456     // we have already a file with name ".hugin" for our wxWidgets settings
00457     // therefore we use directory ".hugindata" in homedir
00458     path /= ".hugindata";
00459 #endif
00460     if (!boost::filesystem::exists(path))
00461     {
00462         if (!boost::filesystem::create_directories(path))
00463         {
00464             std::cerr << "ERROR: Could not create destination directory: " << path.string() << std::endl
00465                 << "Maybe you have not sufficent rights to create this directory." << std::endl;
00466             return std::string();
00467         };
00468     };
00469     return path.string();
00470 };
00471 
00472 // initialization and wrapup of GPU for GPU remapping
00473 #ifdef _WIN32
00474 struct ContextSettings
00475 {
00476     HWND window;
00477     HDC dc;
00478     HGLRC renderingContext;
00479     
00480     ContextSettings()
00481     {
00482         window = NULL;
00483         dc = NULL;
00484         renderingContext = NULL;
00485     }
00486 };
00487 static ContextSettings context;
00488 
00489 // create context, return false if failed
00490 bool CreateContext(int *argcp, char **argv)
00491 {
00492     WNDCLASS windowClass;
00493     /* register window class */
00494     ZeroMemory(&windowClass, sizeof(WNDCLASS));
00495     windowClass.hInstance = GetModuleHandle(NULL);
00496     windowClass.lpfnWndProc = DefWindowProc;
00497     windowClass.lpszClassName = "Hugin";
00498     if (RegisterClass(&windowClass) == 0)
00499     {
00500         return false;
00501     };
00502     /* create window */
00503     context.window = CreateWindow("Hugin", "Hugin", 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
00504         CW_USEDEFAULT, NULL, NULL, GetModuleHandle(NULL), NULL);
00505     if (context.window == NULL)
00506     {
00507         return false;
00508     };
00509     /* get the device context */
00510     context.dc = GetDC(context.window);
00511     if (context.dc == NULL)
00512     {
00513         return false;
00514     };
00515     /* find pixel format */
00516     PIXELFORMATDESCRIPTOR pixelFormatDesc;
00517     ZeroMemory(&pixelFormatDesc, sizeof(PIXELFORMATDESCRIPTOR));
00518     pixelFormatDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
00519     pixelFormatDesc.nVersion = 1;
00520     pixelFormatDesc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
00521     int pixelFormat = ChoosePixelFormat(context.dc, &pixelFormatDesc);
00522     if (pixelFormat == 0)
00523     {
00524         return false;
00525     }
00526     /* set the pixel format for the dc */
00527     if (SetPixelFormat(context.dc, pixelFormat, &pixelFormatDesc) == FALSE)
00528     {
00529         return false;
00530     };
00531     /* create rendering context */
00532     context.renderingContext = wglCreateContext(context.dc);
00533     if (context.renderingContext == NULL)
00534     {
00535         return false;
00536     };
00537     if (wglMakeCurrent(context.dc, context.renderingContext) == FALSE)
00538     {
00539         return false;
00540     };
00541     return true;
00542 }
00543 
00544 void DestroyContext()
00545 {
00546     if (context.renderingContext != NULL)
00547     {
00548         wglMakeCurrent(NULL, NULL);
00549         wglDeleteContext(context.renderingContext);
00550     }
00551     if (context.window != NULL && context.dc != NULL)
00552     {
00553         ReleaseDC(context.window, context.dc);
00554     };
00555     if (context.window != NULL)
00556     {
00557         DestroyWindow(context.window);
00558     };
00559     UnregisterClass("Hugin", GetModuleHandle(NULL));
00560 }
00561 
00562 #elif defined __APPLE__
00563 static GLuint GlutWindowHandle;
00564 bool CreateContext(int *argcp, char **argv)
00565 {
00566     glutInit(argcp, argv);
00567     glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA | GLUT_ALPHA);
00568     GlutWindowHandle = glutCreateWindow("Hugin");
00569     return true;
00570 }
00571 
00572 void DestroyContext()
00573 {
00574     glutDestroyWindow(GlutWindowHandle);
00575 }
00576 
00577 #else
00578 #include <X11/Xlib.h>
00579 #include <X11/Xutil.h>
00580 #include <GL/glx.h>
00581 
00582 struct ContextSettings
00583 {
00584     Display* display;
00585     XVisualInfo* visualInfo;
00586     GLXContext context;
00587     Window window;
00588     Colormap colormap;
00589     
00590     ContextSettings()
00591     {
00592         display=NULL;
00593         visualInfo=NULL;
00594         context=NULL;
00595         window=0;
00596         colormap=0;
00597     };
00598 };
00599 
00600 static ContextSettings context;
00601 
00602 bool CreateContext(int *argcp, char **argv)
00603 {
00604     /* open display */
00605     context.display = XOpenDisplay(NULL);
00606     if (context.display == NULL)
00607     {
00608         return false;
00609     };
00610     /* query for glx */
00611     int erb, evb;
00612     if (!glXQueryExtension(context.display, &erb, &evb))
00613     {
00614         return false;
00615     };
00616     /* choose visual */
00617     int attrib[] = { GLX_RGBA, None };
00618     context.visualInfo = glXChooseVisual(context.display, DefaultScreen(context.display), attrib);
00619     if (context.visualInfo == NULL)
00620     {
00621         return false;
00622     };
00623     /* create context */
00624     context.context = glXCreateContext(context.display, context.visualInfo, None, True);
00625     if (context.context == NULL)
00626     {
00627         return false;
00628     };
00629     /* create window */
00630     context.colormap = XCreateColormap(context.display, RootWindow(context.display, context.visualInfo->screen), context.visualInfo->visual, AllocNone);
00631     XSetWindowAttributes swa;
00632     swa.border_pixel = 0;
00633     swa.colormap = context.colormap;
00634     context.window = XCreateWindow(context.display, RootWindow(context.display, context.visualInfo->screen),
00635         0, 0, 1, 1, 0, context.visualInfo->depth, InputOutput, context.visualInfo->visual,
00636         CWBorderPixel | CWColormap, &swa);
00637     /* make context current */
00638     if (!glXMakeCurrent(context.display, context.window, context.context))
00639     {
00640         return false;
00641     };
00642     return true;
00643 };
00644 
00645 void DestroyContext()
00646 {
00647     if (context.display != NULL && context.context != NULL)
00648     {
00649         glXDestroyContext(context.display, context.context);
00650     }
00651     if (context.display != NULL && context.window != 0)
00652     {
00653         XDestroyWindow(context.display, context.window);
00654     };
00655     if (context.display != NULL && context.colormap != 0)
00656     {
00657         XFreeColormap(context.display, context.colormap);
00658     };
00659     if (context.visualInfo != NULL)
00660     {
00661         XFree(context.visualInfo);
00662     };
00663     if (context.display != NULL)
00664     {
00665         XCloseDisplay(context.display);
00666     };
00667 };
00668 #endif
00669 
00670 bool initGPU(int *argcp, char **argv)
00671 {
00672     if (!CreateContext(argcp, argv))
00673     {
00674         return false;
00675     };
00676     int err = glewInit();
00677     if (err != GLEW_OK)
00678     {
00679         std::cerr << argv[0] << ": an error occured while setting up the GPU:" << std::endl;
00680         std::cerr << glewGetErrorString(err) << std::endl;
00681         std::cerr << argv[0] << ": Switching to CPU calculation." << std::endl;
00682         DestroyContext();
00683         return false;
00684     }
00685 
00686     std::cout << hugin_utils::stripPath(argv[0]) << ": using graphics card: " << glGetString(GL_VENDOR) << " " << glGetString(GL_RENDERER) << std::endl;
00687 
00688     const GLboolean has_arb_fragment_shader = glewGetExtension("GL_ARB_fragment_shader");
00689     const GLboolean has_arb_vertex_shader = glewGetExtension("GL_ARB_vertex_shader");
00690     const GLboolean has_arb_shader_objects = glewGetExtension("GL_ARB_shader_objects");
00691     const GLboolean has_arb_shading_language = glewGetExtension("GL_ARB_shading_language_100");
00692     const GLboolean has_ext_framebuffer = glewGetExtension("GL_EXT_framebuffer_object");
00693     const GLboolean has_arb_texture_rectangle = glewGetExtension("GL_ARB_texture_rectangle");
00694     const GLboolean has_arb_texture_border_clamp = glewGetExtension("GL_ARB_texture_border_clamp");
00695     const GLboolean has_arb_texture_float = glewGetExtension("GL_ARB_texture_float");
00696 
00697     if (!(has_arb_fragment_shader && has_arb_vertex_shader && has_arb_shader_objects && has_arb_shading_language && has_ext_framebuffer && has_arb_texture_rectangle && has_arb_texture_border_clamp && has_arb_texture_float)) {
00698         const char * msg[] = {"false", "true"};
00699         std::cerr << argv[0] << ": extension GL_ARB_fragment_shader = " << msg[has_arb_fragment_shader] << std::endl;
00700         std::cerr << argv[0] << ": extension GL_ARB_vertex_shader = " << msg[has_arb_vertex_shader] << std::endl;
00701         std::cerr << argv[0] << ": extension GL_ARB_shader_objects = " << msg[has_arb_shader_objects] << std::endl;
00702         std::cerr << argv[0] << ": extension GL_ARB_shading_language_100 = " << msg[has_arb_shading_language] << std::endl;
00703         std::cerr << argv[0] << ": extension GL_EXT_framebuffer_object = " << msg[has_ext_framebuffer] << std::endl;
00704         std::cerr << argv[0] << ": extension GL_ARB_texture_rectangle = " << msg[has_arb_texture_rectangle] << std::endl;
00705         std::cerr << argv[0] << ": extension GL_ARB_texture_border_clamp = " << msg[has_arb_texture_border_clamp] << std::endl;
00706         std::cerr << argv[0] << ": extension GL_ARB_texture_float = " << msg[has_arb_texture_float] << std::endl;
00707         std::cerr << argv[0] << ": This graphics system lacks the necessary extensions for -g." << std::endl;
00708         std::cerr << argv[0] << ": Switching to CPU calculation." << std::endl;
00709         DestroyContext();
00710         return false;
00711     }
00712 
00713     return true;
00714 }
00715 
00716 bool wrapupGPU()
00717 {
00718     DestroyContext();
00719     return true;
00720 }
00721 
00722 std::string GetHuginVersion()
00723 {
00724     return std::string(DISPLAY_VERSION);
00725 };
00726 
00727 std::string GetICCDesc(const vigra::ImageImportInfo::ICCProfile& iccProfile)
00728 {
00729     if (iccProfile.empty())
00730     {
00731         // no profile
00732         return std::string();
00733     };
00734     cmsHPROFILE profile = cmsOpenProfileFromMem(iccProfile.data(), iccProfile.size());
00735     if (profile == NULL)
00736     {
00737         // invalid profile
00738         return std::string();
00739     };
00740     const std::string name=GetICCDesc(profile);
00741     cmsCloseProfile(profile);
00742     return name;
00743 };
00744 
00745 std::string GetICCDesc(const cmsHPROFILE& profile)
00746 {
00747     const size_t size = cmsGetProfileInfoASCII(profile, cmsInfoDescription, cmsNoLanguage, cmsNoCountry, nullptr, 0);
00748     std::string information(size, '\000');
00749     cmsGetProfileInfoASCII(profile, cmsInfoDescription, cmsNoLanguage, cmsNoCountry, &information[0], size);
00750     StrTrim(information);
00751     return information;
00752 }
00753 } //namespace

Generated on 31 Jul 2015 for Hugintrunk by  doxygen 1.4.7