Fawkes API  Fawkes Development Version
batch_render.cpp
00001 
00002 /***************************************************************************
00003  *  batch_render.cpp - Render a directory of dot graphs
00004  *
00005  *  Created: Sat Mar 21 17:16:01 2009
00006  *  Copyright  2008-2009  Tim Niemueller [www.niemueller.de]
00007  *
00008  ****************************************************************************/
00009 
00010 /*  This program is free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU General Public License as published by
00012  *  the Free Software Foundation; either version 2 of the License, or
00013  *  (at your option) any later version.
00014  *
00015  *  This program is distributed in the hope that it will be useful,
00016  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00017  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018  *  GNU Library General Public License for more details.
00019  *
00020  *  Read the full text in the LICENSE.GPL file in the doc directory.
00021  */
00022 
00023 #include "gvplugin_skillgui_cairo.h"
00024 
00025 #include <utils/system/argparser.h>
00026 #include <cstdlib>
00027 #include <cstring>
00028 #include <cstdio>
00029 #include <cmath>
00030 #include <sys/types.h>
00031 #include <sys/stat.h>
00032 #include <unistd.h>
00033 #include <dirent.h>
00034 #include <fnmatch.h>
00035 #include <libgen.h>
00036 
00037 using namespace fawkes;
00038 
00039 
00040 /** DOT graph batch renderer. */
00041 class SkillGuiBatchRenderer
00042   : public SkillGuiCairoRenderInstructor
00043 {
00044  public:
00045   /** Constructor.
00046    * @param argc number of arguments
00047    * @param argv arguments
00048    */
00049   SkillGuiBatchRenderer(int argc, char **argv)
00050     : argp(argc, argv, "hi:o:f:wps:")
00051   {
00052     if (! (argp.has_arg("i") && argp.has_arg("o") && argp.has_arg("f"))
00053         || argp.has_arg("h")) {
00054       usage();
00055       exit(-1);
00056     }
00057 
00058     format = argp.arg("f");
00059     write_to_png = false;
00060     bbw = bbh = 0;
00061     white_bg = argp.has_arg("w");
00062     postproc_required = false;
00063     do_postproc = argp.has_arg("p");
00064     maxwidth = maxheight = 0;
00065     scale = 1.0;
00066 
00067     if ( (format != "pdf") && (format != "svg") && (format != "png") ) {
00068       printf("Unknown format '%s'\n\n", format.c_str());
00069       usage();
00070       exit(-2);
00071     }
00072 
00073     if ( do_postproc && (format != "png") ) {
00074       printf("Post-processing only available for PNG output format.\n");
00075       exit(-7);
00076     }
00077 
00078     if (argp.has_arg("s")) {
00079       char *endptr;
00080       scale = strtod(argp.arg("s"), &endptr);
00081       if ( *endptr != 0 ) {
00082         printf("Invalid scale value '%s', could not convert to number (failed at '%s').\n",
00083                argp.arg("s"), endptr);
00084         exit(-8);
00085       }
00086     }
00087 
00088     indir  = argp.arg("i");
00089     outdir = argp.arg("o");
00090 
00091     struct stat statbuf_in, statbuf_out;
00092     if (stat(indir.c_str(), &statbuf_in) != 0) {
00093       perror("Unable to stat input directory");
00094       exit(-3);
00095     }
00096     if (stat(outdir.c_str(), &statbuf_out) != 0) {
00097       perror("Unable to stat output directory");
00098       exit(-4);
00099     }
00100     if (! S_ISDIR(statbuf_in.st_mode) || ! S_ISDIR(statbuf_out.st_mode)) {
00101       printf("Input or output directory is not a directory.\n\n");
00102       exit(-5);
00103     }
00104 
00105     char outdir_real[PATH_MAX];
00106     if (realpath(outdir.c_str(), outdir_real)) {
00107       outdir = outdir_real;
00108     }
00109 
00110     directory = opendir(indir.c_str());
00111     if (! directory) {
00112       printf("Could not open input directory\n");
00113       exit(-6);
00114     }
00115 
00116     gvc = gvContext();
00117     gvplugin_skillgui_cairo_setup(gvc, this);
00118   }
00119 
00120   /** Destructor. */
00121   ~SkillGuiBatchRenderer()
00122   {
00123     gvFreeContext(gvc);
00124     closedir(directory);
00125   }
00126 
00127   /** Show usage instructions. */
00128   void usage()
00129   {
00130     printf("\nUsage: %s -i <dir> -o <dir> -f <format> [-w] [-s scale]\n"
00131            " -i dir     Input directory containing dot graphs\n"
00132            " -o dir     Output directory for generated graphs\n"
00133            " -f format  Output format, one of pdf, svg, or png\n"
00134            " -w         White background\n"
00135            " -p         Postprocess frames to same size (PNG only)\n"
00136            " -s scale   Scale factor to apply during rendering\n"
00137            "\n",
00138            argp.program_name());
00139   }
00140 
00141   virtual Cairo::RefPtr<Cairo::Context> get_cairo()
00142   {
00143     if (! cairo) {
00144       this->bbw = bbw;
00145       this->bbh = bbh;
00146       if (format == "pdf") {
00147         surface = Cairo::PdfSurface::create(outfile, bbw * scale, bbh * scale);
00148         printf("Creating PDF context of size %f x %f\n", bbw * scale, bbh * scale);
00149       } else if (format == "svg") {
00150         surface = Cairo::SvgSurface::create(outfile, bbw * scale, bbh * scale);
00151       } else if (format == "png") {
00152         surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32,
00153                                               (int)ceilf(bbw * scale),
00154                                               (int)ceilf(bbh * scale));
00155         write_to_png = true;
00156       }
00157       cairo = Cairo::Context::create(surface);
00158       if (white_bg) {
00159         cairo->set_source_rgb(1, 1, 1);
00160         cairo->paint();
00161       }
00162     }
00163     return cairo;
00164   }
00165 
00166   virtual bool scale_override() { return true; }
00167 
00168   virtual void get_dimensions(double &width, double &height)
00169   {
00170     width  = bbw * scale;
00171     height = bbh * scale;
00172   }
00173 
00174   virtual double get_scale() { return scale; }
00175   virtual void   set_scale(double scale) {};
00176   virtual void   set_translation(double tx, double ty) {};
00177 
00178   virtual void   get_translation(double &tx, double &ty)
00179   {
00180     // no padding
00181     tx = pad_x * scale;
00182     ty = (bbh - pad_y) * scale;
00183   }
00184 
00185   virtual void   set_bb(double bbw, double bbh)
00186   {
00187     this->bbw = bbw;
00188     this->bbh = bbh;
00189 
00190     if ( bbw * scale > maxwidth  ) {
00191       postproc_required = (maxwidth  != 0);
00192       maxwidth  = bbw * scale;
00193     }
00194     if ( bbh * scale > maxheight * scale ) {
00195       postproc_required = (maxheight != 0);
00196       maxheight = bbh * scale;
00197     }
00198   }
00199 
00200   virtual void   set_pad(double pad_x, double pad_y)
00201   {
00202     this->pad_x = pad_x;
00203     this->pad_y = pad_y;
00204   }
00205 
00206 
00207   virtual void   get_pad(double &pad_x, double &pad_y)
00208   {
00209     pad_x = 0;
00210     pad_y = 0;
00211   }
00212 
00213   /** Render graph. */
00214   void render()
00215   {
00216 
00217     FILE *f = fopen(infile.c_str(), "r");
00218     Agraph_t *g = agread(f);
00219     if (g) {
00220       gvLayout(gvc, g, (char *)"dot");
00221       gvRender(gvc, g, (char *)"skillguicairo", NULL);
00222       gvFreeLayout(gvc, g);
00223       agclose(g);
00224     }
00225     fclose(f);
00226 
00227     if (write_to_png) {
00228       surface->write_to_png(outfile);
00229     }
00230 
00231     cairo.clear();
00232     surface.clear();
00233   }
00234 
00235   /** Run the renderer. */
00236   void run()
00237   {
00238     struct dirent *d;
00239     
00240     while ((d = readdir(directory)) != NULL) {
00241       if (fnmatch("*.dot", d->d_name, FNM_PATHNAME | FNM_PERIOD) == 0) {
00242         char infile_real[PATH_MAX];
00243         infile  = indir  + "/" + d->d_name;
00244         if (realpath(infile.c_str(), infile_real)) {
00245           infile = infile_real;
00246         }
00247         char *basefile = strdup(infile.c_str());
00248         std::string basen = basename(basefile);
00249         free(basefile);
00250         outfile = outdir + "/" + basen.substr(0, basen.length() - 3) + format;
00251         printf("Converting %s to %s\n", infile.c_str(), outfile.c_str());
00252         render();
00253       } else {
00254         printf("%s does not match pattern\n", d->d_name);
00255       }
00256     }
00257 
00258     if (do_postproc && postproc_required) {
00259       postprocess();
00260     }
00261   }
00262 
00263   /** Write function for Cairo.
00264    * @param closure contains the file handle
00265    * @param data data to write
00266    * @param length length of data
00267    * @return Cairo status
00268    */
00269   static cairo_status_t write_func(void *closure,
00270                                    const unsigned char *data, unsigned int length)
00271   {
00272     FILE *f = (FILE *)closure;
00273     if (fwrite(data, length, 1, f)) {
00274       return CAIRO_STATUS_SUCCESS;
00275     } else {
00276       return CAIRO_STATUS_WRITE_ERROR;
00277     }
00278   }
00279   
00280   /** Post-process files. Only valid for PNGs. */
00281   void postprocess()
00282   {
00283     printf("Post-processing PNG files, resizing to %fx%f\n", maxwidth, maxheight);
00284     struct dirent *d;
00285     DIR *output_dir = opendir(outdir.c_str());
00286     while ((d = readdir(output_dir)) != NULL) {
00287       if (fnmatch("*.png", d->d_name, FNM_PATHNAME | FNM_PERIOD) == 0) {
00288         infile = outdir + "/" + d->d_name;
00289         Cairo::RefPtr<Cairo::ImageSurface> imgs = Cairo::ImageSurface::create_from_png(infile);
00290         if ( (imgs->get_height() != maxheight) || (imgs->get_width() != maxwidth)) {
00291           // need to re-create
00292           char *tmpout = strdup((outdir + "/tmpXXXXXX").c_str());
00293           FILE *f = fdopen(mkstemp(tmpout), "w");
00294           outfile = tmpout;
00295           free(tmpout);
00296 
00297           Cairo::RefPtr<Cairo::ImageSurface> outs = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32,
00298                                                                                 (int)ceilf(maxwidth),
00299                                                                                 (int)ceilf(maxheight));
00300           double tx = (maxwidth  - imgs->get_width()) / 2.0;
00301           double ty = (maxheight - imgs->get_height()) / 2.0;
00302           printf("Re-creating %s for post-processing, "
00303                  "resizing from %ix%i, tx=%f, ty=%f\n", infile.c_str(),
00304                  imgs->get_width(), imgs->get_height(), tx, ty);
00305           Cairo::RefPtr<Cairo::Context> cc = Cairo::Context::create(outs);
00306           if (white_bg) {
00307             cc->set_source_rgb(1, 1, 1);
00308             cc->paint();
00309           }
00310           cc->set_source(imgs, tx, ty);
00311           cc->paint();
00312           outs->write_to_png(&SkillGuiBatchRenderer::write_func, f);
00313           imgs.clear();
00314           cc.clear();
00315           outs.clear();
00316           fclose(f);
00317           rename(outfile.c_str(), infile.c_str());
00318         }
00319       }
00320     }
00321     closedir(output_dir);
00322   }
00323 
00324  private:
00325   GVC_t *gvc;
00326   ArgumentParser argp;
00327   std::string format;
00328   Cairo::RefPtr<Cairo::Surface> surface;
00329   Cairo::RefPtr<Cairo::Context> cairo;
00330   bool write_to_png;
00331   bool white_bg;
00332   double bbw, bbh;
00333   double pad_x, pad_y;
00334   std::string infile;
00335   std::string outfile;
00336   std::string indir;
00337   std::string outdir;
00338   DIR *directory;
00339   double maxwidth, maxheight;
00340   bool postproc_required;
00341   bool do_postproc;
00342   double scale;
00343 };
00344 
00345 /** This is the main program of the Skill GUI.
00346  */
00347 int
00348 main(int argc, char **argv)
00349 {
00350   SkillGuiBatchRenderer renderer(argc, argv);
00351   renderer.run();
00352   return 0;
00353 }