fixed dia_image_rgb_data() for non-alpha images
[dia.git] / app / export_png.c
blob1814dd42335e7db5035f413fcc2b89a965c9099f
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>
29 #include "intl.h"
30 #include "filter.h"
31 #include "render_libart.h"
32 #include "dialibartrenderer.h"
33 #include "message.h"
34 #include "app_procs.h"
35 #include "dialogs.h"
37 /* the dots per centimetre to render this diagram at */
38 /* this matches the setting `100%' setting in dia. */
39 #define DPCM 20
41 /* the height of the band to use when rendering. Smaller bands mean
42 * rendering is slower, but less memory is used. Setting this to G_MAXINT
43 * should get the renderer to use one pass. */
44 #define BAND_HEIGHT 50
46 struct png_callback_data {
47 DiagramData *data;
48 gchar *filename;
51 /* Static data. When the dialog is not reentrant, you could have all data
52 be static. I don't like it that way, though:) I only hold static that
53 which pertains to the dialog itself (including the aspect ratio, as that
54 is used to connect the two entries). */
55 static GtkWidget *export_png_dialog;
56 static GtkSpinButton *export_png_width_entry, *export_png_height_entry;
57 static GtkWidget *export_png_okay_button, *export_png_cancel_button;
58 static real export_png_aspect_ratio;
60 /* The heart of the png exporter.
61 Deals with a bit of dialog handling and all the rendering and writing.
62 The dialog is not used when dia is non-interactive (export mode)
64 static void
65 export_png_ok(GtkButton *button, gpointer userdata)
67 struct png_callback_data *cbdata = (struct png_callback_data *)userdata;
68 DiagramData *data = cbdata->data;
69 DiaRenderer *renderer;
70 DiaLibartRenderer *la_renderer;
71 Rectangle *ext = &data->extents;
72 Rectangle visible;
73 guint32 width, height, band, row, i;
74 real band_height;
75 guint32 imagewidth, imageheight;
76 real imagezoom;
78 FILE *fp;
79 png_structp png;
80 png_infop info;
81 png_color_8 sig_bit;
82 png_bytep *row_ptr;
84 width = (guint32) ((ext->right - ext->left) * DPCM * data->paper.scaling);
85 height = (guint32) ((ext->bottom - ext->top) * DPCM * data->paper.scaling);
87 if (app_is_interactive()) {
88 /* We don't want multiple clicks:) */
89 gtk_widget_hide(export_png_dialog);
91 imagewidth = gtk_spin_button_get_value_as_int(export_png_width_entry);
92 imageheight = gtk_spin_button_get_value_as_int(export_png_height_entry);
93 } else {
94 imagewidth = width;
95 imageheight = height;
98 imagezoom = ((real)imageheight/height) * DPCM * data->paper.scaling;
100 /* we render in bands to try to keep memory consumption down ... */
101 band = MIN(imageheight, BAND_HEIGHT);
102 band_height = (real)band / imagezoom;
104 visible = *ext;
105 visible.bottom = MIN(visible.bottom,
106 visible.top + band_height);
108 renderer = new_libart_renderer(dia_transform_new (&visible, &imagezoom), 0);
109 la_renderer = DIA_LIBART_RENDERER (renderer);
110 dia_renderer_set_size(renderer, NULL, imagewidth, band);
112 fp = fopen(cbdata->filename, "wb");
113 if (fp == NULL) {
114 message_error(_("Couldn't open: '%s' for writing.\n"), cbdata->filename);
115 goto error;
118 png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
119 NULL, NULL, NULL);
120 if (!png) {
121 fclose(fp);
122 message_error(_("Could not create PNG write structure"));
123 goto error;
126 /* allocate/initialise the image information data */
127 info = png_create_info_struct(png);
128 if (!info) {
129 fclose(fp);
130 png_destroy_write_struct(&png, (png_infopp)NULL);
131 message_error(_("Could not create PNG header info structure"));
132 goto error;
135 /* set error handling ... */
136 if (setjmp(png->jmpbuf)) {
137 fclose(fp);
138 png_destroy_write_struct(&png, (png_infopp)NULL);
139 message_error(_("Error occurred while writing PNG"));
140 goto error;
142 /* the compiler said these may be clobbered by setjmp, so we set it again
143 * here. */
144 if (app_is_interactive()) {
145 imagewidth = gtk_spin_button_get_value_as_int(export_png_width_entry);
146 imageheight = gtk_spin_button_get_value_as_int(export_png_height_entry);
147 } else {
148 imagewidth = width;
149 imageheight = height;
151 band = MIN(imageheight, BAND_HEIGHT);
153 png_init_io(png, fp);
155 /* header fields */
156 png_set_IHDR(png, info, imagewidth, imageheight, 8,
157 PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
158 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
159 sig_bit.red = 8;
160 sig_bit.green = 8;
161 sig_bit.blue = 8;
162 png_set_sBIT(png, info, &sig_bit);
164 png_write_info(png, info);
165 png_set_shift(png, &sig_bit);
166 png_set_packing(png);
168 row_ptr = g_new(png_bytep, band);
170 for (row = 0; row < imageheight; row += band) {
171 /* render band */
172 for (i = 0; i < imagewidth*band; i++) {
173 la_renderer->rgb_buffer[3*i] = 0xff * data->bg_color.red;
174 la_renderer->rgb_buffer[3*i+1] = 0xff * data->bg_color.green;
175 la_renderer->rgb_buffer[3*i+2] = 0xff * data->bg_color.blue;
177 data_render(data, renderer, &visible, NULL,NULL);
178 /* write rows to png file */
179 for (i = 0; i < band; i++)
180 row_ptr[i] = la_renderer->rgb_buffer + 3 * i * imagewidth;
181 png_write_rows(png, row_ptr, MIN(band, imageheight - row));
183 visible.top += band_height;
184 visible.bottom += band_height;
186 g_free(row_ptr);
187 png_write_end(png, info);
188 png_destroy_write_struct(&png, (png_infopp)NULL);
189 fclose(fp);
191 error:
192 g_object_unref(renderer);
193 if (app_is_interactive()) {
194 gtk_signal_disconnect_by_data(GTK_OBJECT(export_png_okay_button),
195 userdata);
196 gtk_signal_disconnect_by_data(GTK_OBJECT(export_png_cancel_button),
197 userdata);
199 g_free(cbdata->filename);
200 g_free(cbdata);
201 return;
204 /* Stuff to do when cancelling:
205 disconnect signals (since the dialog persists)
206 hide dialog
207 free callback data
209 static void
210 export_png_cancel(GtkButton *button, gpointer userdata) {
211 struct png_callback_data *cbdata = (struct png_callback_data *)userdata;
213 gtk_signal_disconnect_by_data(GTK_OBJECT(export_png_okay_button), userdata);
214 gtk_signal_disconnect_by_data(GTK_OBJECT(export_png_cancel_button), userdata);
216 gtk_widget_hide(export_png_dialog);
217 g_free(cbdata->filename);
218 g_free(cbdata);
221 /* Adjust the aspect ratio */
222 static void
223 export_png_ratio(GtkAdjustment *limits, gpointer userdata)
225 /* This variable makes sure that we don't have a loopback effect. */
226 static gboolean in_progress;
227 if (in_progress) return;
228 in_progress = TRUE;
229 if (userdata == export_png_height_entry) {
230 gtk_spin_button_set_value(GTK_SPIN_BUTTON(userdata),
231 (int)((real)gtk_spin_button_get_value_as_int(export_png_width_entry))/export_png_aspect_ratio);
232 } else {
233 gtk_spin_button_set_value(GTK_SPIN_BUTTON(userdata),
234 (int)((real)gtk_spin_button_get_value_as_int(export_png_height_entry))*export_png_aspect_ratio);
236 in_progress = FALSE;
239 static void
240 export_png(DiagramData *data, const gchar *filename,
241 const gchar *diafilename, void* user_data)
243 /* Create the callback data. Can't be stack allocated, as the function
244 returns before the callback is called. Must be freed by the
245 final callbacks. */
246 struct png_callback_data *cbdata =
247 (struct png_callback_data *)g_malloc(sizeof(struct png_callback_data));
248 Rectangle *ext = &data->extents;
249 guint32 width, height;
251 /* Note that this dialog, while not modal, is no reentrant, as it creates
252 a single dialog and uses that every time. Trying to do two exports at
253 the same time will lead to confusion.
256 if (export_png_dialog == NULL && app_is_interactive()) {
257 /* Create a dialog */
258 export_png_dialog = dialog_make(_("PNG Export Options"),
259 _("Export"), NULL,
260 &export_png_okay_button,
261 &export_png_cancel_button);
262 /* Add two integer entries */
263 export_png_width_entry =
264 dialog_add_spinbutton(export_png_dialog, _("Image width:"),
265 0.0, 10000.0, 0);
266 export_png_height_entry =
267 dialog_add_spinbutton(export_png_dialog, _("Image height:"),
268 0.0, 10000.0, 0);
270 /* Make sure that the aspect ratio stays the same */
271 gtk_signal_connect(GTK_OBJECT(gtk_spin_button_get_adjustment(export_png_width_entry)),
272 "value_changed",
273 (GtkSignalFunc)export_png_ratio, (gpointer)export_png_height_entry);
274 gtk_signal_connect(GTK_OBJECT(gtk_spin_button_get_adjustment(export_png_height_entry)),
275 "value_changed",
276 (GtkSignalFunc)export_png_ratio, (gpointer)export_png_width_entry);
280 /* Store pertinent data in callback data structure */
281 cbdata->data = data;
282 cbdata->filename = g_strdup(filename);
284 if (app_is_interactive()) {
285 /* Find the default size */
286 width = (guint32) ((ext->right - ext->left) * DPCM * data->paper.scaling);
287 height = (guint32) ((ext->bottom - ext->top) * DPCM * data->paper.scaling);
289 /* Store aspect ratio */
290 export_png_aspect_ratio = ((real)width)/height;
292 /* Set the default size */
293 gtk_spin_button_set_value(export_png_width_entry, (float)width);
294 /* This is set from the aspect ratio */
295 /* gtk_spin_button_set_value(export_png_height_entry, (float)height);*/
297 /* Set OK and Cancel buttons to call the relevant callbacks with cbdata */
298 gtk_signal_connect(GTK_OBJECT(export_png_okay_button), "clicked",
299 (GtkSignalFunc)export_png_ok, (gpointer)cbdata);
300 gtk_signal_connect(GTK_OBJECT(export_png_cancel_button), "clicked",
301 (GtkSignalFunc)export_png_cancel, (gpointer)cbdata);
303 /* Show the whole thing */
304 gtk_widget_show_all(export_png_dialog);
305 } else {
306 export_png_ok(NULL, cbdata);
310 static const gchar *extensions[] = { "png", NULL };
311 DiaExportFilter png_export_filter = {
312 N_("Portable Network Graphics"),
313 extensions,
314 export_png
317 #endif