Fawkes API  Fawkes Development Version
batch_render.cpp
1 
2 /***************************************************************************
3  * batch_render.cpp - Render a directory of dot graphs
4  *
5  * Created: Sat Mar 21 17:16:01 2009
6  * Copyright 2008-2009 Tim Niemueller [www.niemueller.de]
7  *
8  ****************************************************************************/
9 
10 /* This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Library General Public License for more details.
19  *
20  * Read the full text in the LICENSE.GPL file in the doc directory.
21  */
22 
23 #include "gvplugin_skillgui_cairo.h"
24 
25 #include <utils/system/argparser.h>
26 #include <cstdlib>
27 #include <cstring>
28 #include <cstdio>
29 #include <cmath>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33 #include <dirent.h>
34 #include <fnmatch.h>
35 #include <libgen.h>
36 
37 using namespace fawkes;
38 
39 
40 /** DOT graph batch renderer. */
43 {
44  public:
45  /** Constructor.
46  * @param argc number of arguments
47  * @param argv arguments
48  */
49  SkillGuiBatchRenderer(int argc, char **argv)
50  : argp(argc, argv, "hi:o:f:wps:")
51  {
52  if (! (argp.has_arg("i") && argp.has_arg("o") && argp.has_arg("f"))
53  || argp.has_arg("h")) {
54  usage();
55  exit(-1);
56  }
57 
58  format = argp.arg("f");
59  write_to_png = false;
60  bbw = bbh = 0;
61  white_bg = argp.has_arg("w");
62  postproc_required = false;
63  do_postproc = argp.has_arg("p");
64  maxwidth = maxheight = 0;
65  scale = 1.0;
66 
67  if ( (format != "pdf") && (format != "svg") && (format != "png") ) {
68  printf("Unknown format '%s'\n\n", format.c_str());
69  usage();
70  exit(-2);
71  }
72 
73  if ( do_postproc && (format != "png") ) {
74  printf("Post-processing only available for PNG output format.\n");
75  exit(-7);
76  }
77 
78  if (argp.has_arg("s")) {
79  char *endptr;
80  scale = strtod(argp.arg("s"), &endptr);
81  if ( *endptr != 0 ) {
82  printf("Invalid scale value '%s', could not convert to number (failed at '%s').\n",
83  argp.arg("s"), endptr);
84  exit(-8);
85  }
86  }
87 
88  indir = argp.arg("i");
89  outdir = argp.arg("o");
90 
91  struct stat statbuf_in, statbuf_out;
92  if (stat(indir.c_str(), &statbuf_in) != 0) {
93  perror("Unable to stat input directory");
94  exit(-3);
95  }
96  if (stat(outdir.c_str(), &statbuf_out) != 0) {
97  perror("Unable to stat output directory");
98  exit(-4);
99  }
100  if (! S_ISDIR(statbuf_in.st_mode) || ! S_ISDIR(statbuf_out.st_mode)) {
101  printf("Input or output directory is not a directory.\n\n");
102  exit(-5);
103  }
104 
105  char outdir_real[PATH_MAX];
106  if (realpath(outdir.c_str(), outdir_real)) {
107  outdir = outdir_real;
108  }
109 
110  directory = opendir(indir.c_str());
111  if (! directory) {
112  printf("Could not open input directory\n");
113  exit(-6);
114  }
115 
116  gvc = gvContext();
117  gvplugin_skillgui_cairo_setup(gvc, this);
118  }
119 
120  /** Destructor. */
122  {
123  gvFreeContext(gvc);
124  closedir(directory);
125  }
126 
127  /** Show usage instructions. */
128  void usage()
129  {
130  printf("\nUsage: %s -i <dir> -o <dir> -f <format> [-w] [-s scale]\n"
131  " -i dir Input directory containing dot graphs\n"
132  " -o dir Output directory for generated graphs\n"
133  " -f format Output format, one of pdf, svg, or png\n"
134  " -w White background\n"
135  " -p Postprocess frames to same size (PNG only)\n"
136  " -s scale Scale factor to apply during rendering\n"
137  "\n",
138  argp.program_name());
139  }
140 
141  virtual Cairo::RefPtr<Cairo::Context> get_cairo()
142  {
143  if (! cairo) {
144  if (format == "pdf") {
145  surface = Cairo::PdfSurface::create(outfile, bbw * scale, bbh * scale);
146  printf("Creating PDF context of size %f x %f\n", bbw * scale, bbh * scale);
147  } else if (format == "svg") {
148  surface = Cairo::SvgSurface::create(outfile, bbw * scale, bbh * scale);
149  } else if (format == "png") {
150  surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32,
151  (int)ceilf(bbw * scale),
152  (int)ceilf(bbh * scale));
153  write_to_png = true;
154  }
155  cairo = Cairo::Context::create(surface);
156  if (white_bg) {
157  cairo->set_source_rgb(1, 1, 1);
158  cairo->paint();
159  }
160  }
161  return cairo;
162  }
163 
164  virtual bool scale_override() { return true; }
165 
166  virtual void get_dimensions(double &width, double &height)
167  {
168  width = bbw * scale;
169  height = bbh * scale;
170  }
171 
172  virtual double get_scale() { return scale; }
173  virtual void set_scale(double scale) {};
174  virtual void set_translation(double tx, double ty) {};
175 
176  virtual void get_translation(double &tx, double &ty)
177  {
178  // no padding
179  tx = pad_x * scale;
180  ty = (bbh - pad_y) * scale;
181  }
182 
183  virtual void set_bb(double bbw, double bbh)
184  {
185  this->bbw = bbw;
186  this->bbh = bbh;
187 
188  if ( bbw * scale > maxwidth ) {
189  postproc_required = (maxwidth != 0);
190  maxwidth = bbw * scale;
191  }
192  if ( bbh * scale > maxheight * scale ) {
193  postproc_required = (maxheight != 0);
194  maxheight = bbh * scale;
195  }
196  }
197 
198  virtual void set_pad(double pad_x, double pad_y)
199  {
200  this->pad_x = pad_x;
201  this->pad_y = pad_y;
202  }
203 
204 
205  virtual void get_pad(double &pad_x, double &pad_y)
206  {
207  pad_x = 0;
208  pad_y = 0;
209  }
210 
211  /** Render graph. */
212  void render()
213  {
214 
215  FILE *f = fopen(infile.c_str(), "r");
216 #if defined(GRAPHVIZ_ATLEAST_230) && defined(WITH_CGRAPH)
217  Agraph_t *g = agread(f,0);
218 #else
219  Agraph_t *g = agread(f);
220 #endif
221  if (g) {
222  gvLayout(gvc, g, (char *)"dot");
223  gvRender(gvc, g, (char *)"skillguicairo", NULL);
224  gvFreeLayout(gvc, g);
225  agclose(g);
226  }
227  fclose(f);
228 
229  if (write_to_png) {
230  surface->write_to_png(outfile);
231  }
232 
233  cairo.clear();
234  surface.clear();
235  }
236 
237  /** Run the renderer. */
238  void run()
239  {
240  struct dirent *d;
241 
242  while ((d = readdir(directory)) != NULL) {
243  if (fnmatch("*.dot", d->d_name, FNM_PATHNAME | FNM_PERIOD) == 0) {
244  char infile_real[PATH_MAX];
245  infile = indir + "/" + d->d_name;
246  if (realpath(infile.c_str(), infile_real)) {
247  infile = infile_real;
248  }
249  char *basefile = strdup(infile.c_str());
250  std::string basen = basename(basefile);
251  free(basefile);
252  outfile = outdir + "/" + basen.substr(0, basen.length() - 3) + format;
253  printf("Converting %s to %s\n", infile.c_str(), outfile.c_str());
254  render();
255  } else {
256  printf("%s does not match pattern\n", d->d_name);
257  }
258  }
259 
260  if (do_postproc && postproc_required) {
261  postprocess();
262  }
263  }
264 
265  /** Write function for Cairo.
266  * @param closure contains the file handle
267  * @param data data to write
268  * @param length length of data
269  * @return Cairo status
270  */
271  static cairo_status_t write_func(void *closure,
272  const unsigned char *data, unsigned int length)
273  {
274  FILE *f = (FILE *)closure;
275  if (fwrite(data, length, 1, f)) {
276  return CAIRO_STATUS_SUCCESS;
277  } else {
278  return CAIRO_STATUS_WRITE_ERROR;
279  }
280  }
281 
282  /** Post-process files. Only valid for PNGs. */
283  void postprocess()
284  {
285  printf("Post-processing PNG files, resizing to %fx%f\n", maxwidth, maxheight);
286  struct dirent *d;
287  DIR *output_dir = opendir(outdir.c_str());
288  while ((d = readdir(output_dir)) != NULL) {
289  if (fnmatch("*.png", d->d_name, FNM_PATHNAME | FNM_PERIOD) == 0) {
290  infile = outdir + "/" + d->d_name;
291  Cairo::RefPtr<Cairo::ImageSurface> imgs = Cairo::ImageSurface::create_from_png(infile);
292  if ( (imgs->get_height() != maxheight) || (imgs->get_width() != maxwidth)) {
293  // need to re-create
294  char *tmpout = strdup((outdir + "/tmpXXXXXX").c_str());
295  FILE *f = fdopen(mkstemp(tmpout), "w");
296  outfile = tmpout;
297  free(tmpout);
298 
299  Cairo::RefPtr<Cairo::ImageSurface> outs = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32,
300  (int)ceilf(maxwidth),
301  (int)ceilf(maxheight));
302  double tx = (maxwidth - imgs->get_width()) / 2.0;
303  double ty = (maxheight - imgs->get_height()) / 2.0;
304  printf("Re-creating %s for post-processing, "
305  "resizing from %ix%i, tx=%f, ty=%f\n", infile.c_str(),
306  imgs->get_width(), imgs->get_height(), tx, ty);
307  Cairo::RefPtr<Cairo::Context> cc = Cairo::Context::create(outs);
308  if (white_bg) {
309  cc->set_source_rgb(1, 1, 1);
310  cc->paint();
311  }
312  cc->set_source(imgs, tx, ty);
313  cc->paint();
314  outs->write_to_png(&SkillGuiBatchRenderer::write_func, f);
315  imgs.clear();
316  cc.clear();
317  outs.clear();
318  fclose(f);
319  rename(outfile.c_str(), infile.c_str());
320  }
321  }
322  }
323  closedir(output_dir);
324  }
325 
326  private:
327  GVC_t *gvc;
328  ArgumentParser argp;
329  std::string format;
330  Cairo::RefPtr<Cairo::Surface> surface;
331  Cairo::RefPtr<Cairo::Context> cairo;
332  bool write_to_png;
333  bool white_bg;
334  double bbw, bbh;
335  double pad_x, pad_y;
336  std::string infile;
337  std::string outfile;
338  std::string indir;
339  std::string outdir;
340  DIR *directory;
341  double maxwidth, maxheight;
342  bool postproc_required;
343  bool do_postproc;
344  double scale;
345 };
346 
347 /** This is the main program of the Skill GUI.
348  */
349 int
350 main(int argc, char **argv)
351 {
352  SkillGuiBatchRenderer renderer(argc, argv);
353  renderer.run();
354  return 0;
355 }
virtual void get_pad(double &pad_x, double &pad_y)
Get padding.
Graphviz Cairo render plugin instructor.
Fawkes library namespace.
virtual bool scale_override()
Check if scale override is enabled.
virtual void set_translation(double tx, double ty)
Set translation.
Parse command line arguments.
Definition: argparser.h:66
DOT graph batch renderer.
void usage()
Show usage instructions.
virtual void get_translation(double &tx, double &ty)
Get translation values.
void run()
Run the renderer.
void render()
Render graph.
virtual void get_dimensions(double &width, double &height)
Get available space dimensions.
static cairo_status_t write_func(void *closure, const unsigned char *data, unsigned int length)
Write function for Cairo.
virtual void set_pad(double pad_x, double pad_y)
Set padding.
virtual Cairo::RefPtr< Cairo::Context > get_cairo()
Get Cairo context.
void postprocess()
Post-process files.
virtual double get_scale()
Get scale factor.
virtual void set_scale(double scale)
Set scale.
virtual void set_bb(double bbw, double bbh)
Set the bounding box.
SkillGuiBatchRenderer(int argc, char **argv)
Constructor.
~SkillGuiBatchRenderer()
Destructor.