Fawkes API
Fawkes Development Version
|
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 }