offset fixes, admit to textline, proper updating of height/font at attr set,
[dia.git] / app / export_png.c
blob54810c9bba377c3be6c6681151e991a0855b918e
1 /* Dia -- a diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
4 * export_png.c: export a diagram to a PNG file.
5 * Copyright (C) 2000 James Henstridge
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 #include <config.h>
24 #if defined(HAVE_LIBPNG) && defined(HAVE_LIBART)
26 #include <stdio.h>
27 #include <png.h>
28 #include <string.h>
29 #include <errno.h>
31 #include <gtk/gtk.h>
33 #include "intl.h"
34 #include "filter.h"
35 #include "render_libart.h"
36 #include "dialibartrenderer.h"
37 #include "message.h"
38 #include "app_procs.h"
39 #include "dialogs.h"
41 /* the dots per centimetre to render this diagram at */
42 /* this matches the setting `100%' setting in dia. */
43 #define DPCM 20
45 /* the height of the band to use when rendering. Smaller bands mean
46 * rendering is slower, but less memory is used. Setting this to G_MAXINT
47 * should get the renderer to use one pass. */
48 #define BAND_HEIGHT 50
50 struct png_callback_data {
51 DiagramData *data;
52 gchar *filename;
53 gchar *size; /* for command line option --size */
56 /* Static data. When the dialog is not reentrant, you could have all data
57 be static. I don't like it that way, though:) I only hold static that
58 which pertains to the dialog itself (including the aspect ratio, as that
59 is used to connect the two entries). */
60 static GtkWidget *export_png_dialog;
61 static GtkSpinButton *export_png_width_entry, *export_png_height_entry;
62 static GtkWidget *export_png_okay_button, *export_png_cancel_button;
63 static real export_png_aspect_ratio;
65 /* The heart of the png exporter.
66 Deals with a bit of dialog handling and all the rendering and writing.
67 The dialog is not used when dia is non-interactive (export mode)
69 static void
70 export_png_ok(GtkButton *button, gpointer userdata)
72 struct png_callback_data *cbdata = (struct png_callback_data *)userdata;
73 DiagramData *data = cbdata->data;
74 DiaRenderer *renderer;
75 DiaLibartRenderer *la_renderer;
76 Rectangle *ext = &data->extents;
77 Rectangle visible;
78 guint32 width, height, band, row, i;
79 real band_height;
80 guint32 imagewidth = 0, imageheight = 0;
81 long req_width, req_height;
82 real imagezoom;
84 FILE *fp;
85 png_structp png;
86 png_infop info;
87 png_color_8 sig_bit;
88 png_bytep *row_ptr;
90 width = (guint32) ((ext->right - ext->left) * DPCM * data->paper.scaling);
91 height = (guint32) ((ext->bottom - ext->top) * DPCM * data->paper.scaling);
93 if (app_is_interactive()) {
94 /* We don't want multiple clicks:) */
95 gtk_widget_hide(export_png_dialog);
97 imagewidth = gtk_spin_button_get_value_as_int(export_png_width_entry);
98 imageheight = gtk_spin_button_get_value_as_int(export_png_height_entry);
99 } else {
100 if (cbdata && cbdata->size) {
101 float ratio = (float) width/(float) height;
103 parse_size(cbdata->size, &req_width, &req_height);
104 if (req_width && !req_height) {
105 imagewidth = req_width;
106 imageheight = req_width / ratio;
107 } else if (req_height && !req_width) {
108 imagewidth = req_height * ratio;
109 imageheight = req_height;
110 } else if (req_width && req_height) {
111 imagewidth = req_width;
112 imageheight = req_height;
114 } else {
115 imagewidth = width;
116 imageheight = height;
120 imagezoom = ((real)imageheight/height) * DPCM * data->paper.scaling;
122 /* we render in bands to try to keep memory consumption down ... */
123 band = MIN(imageheight, BAND_HEIGHT);
124 band_height = (real)band / imagezoom;
126 visible = *ext;
127 visible.bottom = MIN(visible.bottom,
128 visible.top + band_height);
130 renderer = new_libart_renderer(dia_transform_new (&visible, &imagezoom), 0);
131 la_renderer = DIA_LIBART_RENDERER (renderer);
132 dia_renderer_set_size(renderer, NULL, imagewidth, band);
134 fp = fopen(cbdata->filename, "wb");
135 if (fp == NULL) {
136 message_error(_("Can't open output file %s: %s\n"), cbdata->filename, strerror(errno));
137 goto error;
140 png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
141 NULL, NULL, NULL);
142 if (!png) {
143 fclose(fp);
144 message_error(_("Could not create PNG write structure"));
145 goto error;
148 /* allocate/initialise the image information data */
149 info = png_create_info_struct(png);
150 if (!info) {
151 fclose(fp);
152 png_destroy_write_struct(&png, (png_infopp)NULL);
153 message_error(_("Could not create PNG header info structure"));
154 goto error;
157 /* set error handling ... */
158 if (setjmp(png->jmpbuf)) {
159 fclose(fp);
160 png_destroy_write_struct(&png, &info);
161 message_error(_("Error occurred while writing PNG"));
162 goto error;
164 /* the compiler said these may be clobbered by setjmp, so we set it again
165 * here. */
166 if (app_is_interactive()) {
167 imagewidth = gtk_spin_button_get_value_as_int(export_png_width_entry);
168 imageheight = gtk_spin_button_get_value_as_int(export_png_height_entry);
169 } else {
170 if (cbdata && cbdata->size) {
171 float ratio = (float) width/(float) height;
173 parse_size(cbdata->size, &req_width, &req_height);
174 if (req_width && !req_height) {
175 imagewidth = req_width;
176 imageheight = req_width / ratio;
177 } else if (req_height && !req_width) {
178 imagewidth = req_height * ratio;
179 imageheight = req_height;
180 } else if (req_width && req_height) {
181 imagewidth = req_width;
182 imageheight = req_height;
184 } else {
185 imagewidth = width;
186 imageheight = height;
189 band = MIN(imageheight, BAND_HEIGHT);
191 png_init_io(png, fp);
193 /* header fields */
194 png_set_IHDR(png, info, imagewidth, imageheight, 8,
195 PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
196 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
197 sig_bit.red = 8;
198 sig_bit.green = 8;
199 sig_bit.blue = 8;
200 png_set_sBIT(png, info, &sig_bit);
202 png_set_pHYs(png, info,
203 imagewidth/width*DPCM*100,
204 imageheight/height*DPCM*100,
205 PNG_RESOLUTION_METER);
207 png_write_info(png, info);
208 png_set_shift(png, &sig_bit);
209 png_set_packing(png);
211 row_ptr = g_new(png_bytep, band);
213 for (row = 0; row < imageheight; row += band) {
214 /* render band */
215 for (i = 0; i < imagewidth*band; i++) {
216 la_renderer->rgb_buffer[3*i] = 0xff * data->bg_color.red;
217 la_renderer->rgb_buffer[3*i+1] = 0xff * data->bg_color.green;
218 la_renderer->rgb_buffer[3*i+2] = 0xff * data->bg_color.blue;
220 data_render(data, renderer, &visible, NULL,NULL);
221 /* write rows to png file */
222 for (i = 0; i < band; i++)
223 row_ptr[i] = la_renderer->rgb_buffer + 3 * i * imagewidth;
224 png_write_rows(png, row_ptr, MIN(band, imageheight - row));
226 visible.top += band_height;
227 visible.bottom += band_height;
229 g_free(row_ptr);
230 png_write_end(png, info);
231 png_destroy_write_struct(&png, &info);
232 fclose(fp);
234 error:
235 g_object_unref(renderer);
236 if (app_is_interactive()) {
237 gtk_signal_disconnect_by_data(GTK_OBJECT(export_png_okay_button),
238 userdata);
239 gtk_signal_disconnect_by_data(GTK_OBJECT(export_png_cancel_button),
240 userdata);
242 g_free(cbdata->filename);
243 g_free(cbdata);
244 return;
247 /* Stuff to do when cancelling:
248 disconnect signals (since the dialog persists)
249 hide dialog
250 free callback data
252 static void
253 export_png_cancel(GtkButton *button, gpointer userdata) {
254 struct png_callback_data *cbdata = (struct png_callback_data *)userdata;
256 gtk_signal_disconnect_by_data(GTK_OBJECT(export_png_okay_button), userdata);
257 gtk_signal_disconnect_by_data(GTK_OBJECT(export_png_cancel_button), userdata);
259 gtk_widget_hide(export_png_dialog);
260 g_free(cbdata->filename);
261 g_free(cbdata);
264 /* Adjust the aspect ratio */
265 static void
266 export_png_ratio(GtkAdjustment *limits, gpointer userdata)
268 /* This variable makes sure that we don't have a loopback effect. */
269 static gboolean in_progress;
270 if (in_progress) return;
271 in_progress = TRUE;
272 if (userdata == export_png_height_entry) {
273 gtk_spin_button_set_value(GTK_SPIN_BUTTON(userdata),
274 (int)((real)gtk_spin_button_get_value_as_int(export_png_width_entry))/export_png_aspect_ratio);
275 } else {
276 gtk_spin_button_set_value(GTK_SPIN_BUTTON(userdata),
277 (int)((real)gtk_spin_button_get_value_as_int(export_png_height_entry))*export_png_aspect_ratio);
279 in_progress = FALSE;
282 static void
283 export_png(DiagramData *data, const gchar *filename,
284 const gchar *diafilename, void* user_data)
286 /* Create the callback data. Can't be stack allocated, as the function
287 returns before the callback is called. Must be freed by the
288 final callbacks. */
289 struct png_callback_data *cbdata =
290 (struct png_callback_data *) g_new0(struct png_callback_data, 1);
291 Rectangle *ext = &data->extents;
292 guint32 width, height;
294 /* Note that this dialog, while not modal, is no reentrant, as it creates
295 a single dialog and uses that every time. Trying to do two exports at
296 the same time will lead to confusion.
299 if (export_png_dialog == NULL && app_is_interactive()) {
300 /* Create a dialog */
301 export_png_dialog = dialog_make(_("PNG Export Options"),
302 _("Export"), NULL,
303 &export_png_okay_button,
304 &export_png_cancel_button);
305 /* Add two integer entries */
306 export_png_width_entry =
307 dialog_add_spinbutton(export_png_dialog, _("Image width:"),
308 0.0, 10000.0, 0);
309 export_png_height_entry =
310 dialog_add_spinbutton(export_png_dialog, _("Image height:"),
311 0.0, 10000.0, 0);
313 /* Make sure that the aspect ratio stays the same */
314 g_signal_connect(GTK_OBJECT(gtk_spin_button_get_adjustment(export_png_width_entry)),
315 "value_changed",
316 G_CALLBACK(export_png_ratio), (gpointer)export_png_height_entry);
317 g_signal_connect(GTK_OBJECT(gtk_spin_button_get_adjustment(export_png_height_entry)),
318 "value_changed",
319 G_CALLBACK(export_png_ratio), (gpointer)export_png_width_entry);
323 /* Store pertinent data in callback data structure */
324 cbdata->data = data;
325 cbdata->filename = g_strdup(filename);
327 if (app_is_interactive()) {
328 /* Find the default size */
329 width = (guint32) ((ext->right - ext->left) * DPCM * data->paper.scaling);
330 height = (guint32) ((ext->bottom - ext->top) * DPCM * data->paper.scaling);
332 /* Store aspect ratio */
333 export_png_aspect_ratio = ((real)width)/height;
335 /* Set the default size */
336 gtk_spin_button_set_value(export_png_width_entry, (float)width);
337 /* This is set from the aspect ratio */
338 /* gtk_spin_button_set_value(export_png_height_entry, (float)height);*/
340 /* Set OK and Cancel buttons to call the relevant callbacks with cbdata */
341 g_signal_connect(GTK_OBJECT(export_png_okay_button), "clicked",
342 G_CALLBACK(export_png_ok), (gpointer)cbdata);
343 g_signal_connect(GTK_OBJECT(export_png_cancel_button), "clicked",
344 G_CALLBACK(export_png_cancel), (gpointer)cbdata);
346 /* Show the whole thing */
347 gtk_widget_show_all(export_png_dialog);
348 } else {
349 cbdata->size = (gchar *) user_data;
350 export_png_ok(NULL, cbdata);
354 static const gchar *extensions[] = { "png", NULL };
355 DiaExportFilter png_export_filter = {
356 N_("Portable Network Graphics"),
357 extensions,
358 export_png,
359 NULL,
360 "png-libart"
363 #endif