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