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
38using namespace fawkes;
39
40/** DOT graph batch renderer. */
42{
43public:
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
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
345private:
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 */
368int
369main(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 void set_translation(double tx, double ty)
Set translation.
~SkillGuiBatchRenderer()
Destructor.
virtual Cairo::RefPtr< Cairo::Context > get_cairo()
Get Cairo context.
void run()
Run the renderer.
Graphviz Cairo render plugin instructor.
Parse command line arguments.
Definition: argparser.h:64
const char * program_name() const
Get name of program.
Definition: argparser.cpp:483
const char * arg(const char *argn)
Get argument value.
Definition: argparser.cpp:177
bool has_arg(const char *argn)
Check if argument has been supplied.
Definition: argparser.cpp:165
Fawkes library namespace.