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