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.
24 #if defined(HAVE_LIBPNG) && defined(HAVE_LIBART)
31 #include "render_libart.h"
32 #include "dialibartrenderer.h"
34 #include "app_procs.h"
37 /* the dots per centimetre to render this diagram at */
38 /* this matches the setting `100%' setting in dia. */
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
{
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)
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
;
73 guint32 width
, height
, band
, row
, i
;
75 guint32 imagewidth
, imageheight
;
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
);
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
;
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");
114 message_error(_("Couldn't open: '%s' for writing.\n"), cbdata
->filename
);
118 png
= png_create_write_struct(PNG_LIBPNG_VER_STRING
,
122 message_error(_("Could not create PNG write structure"));
126 /* allocate/initialise the image information data */
127 info
= png_create_info_struct(png
);
130 png_destroy_write_struct(&png
, (png_infopp
)NULL
);
131 message_error(_("Could not create PNG header info structure"));
135 /* set error handling ... */
136 if (setjmp(png
->jmpbuf
)) {
138 png_destroy_write_struct(&png
, (png_infopp
)NULL
);
139 message_error(_("Error occurred while writing PNG"));
142 /* the compiler said these may be clobbered by setjmp, so we set it again
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
);
149 imageheight
= height
;
151 band
= MIN(imageheight
, BAND_HEIGHT
);
153 png_init_io(png
, fp
);
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
);
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
) {
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
;
187 png_write_end(png
, info
);
188 png_destroy_write_struct(&png
, (png_infopp
)NULL
);
192 g_object_unref(renderer
);
193 if (app_is_interactive()) {
194 gtk_signal_disconnect_by_data(GTK_OBJECT(export_png_okay_button
),
196 gtk_signal_disconnect_by_data(GTK_OBJECT(export_png_cancel_button
),
199 g_free(cbdata
->filename
);
204 /* Stuff to do when cancelling:
205 disconnect signals (since the dialog persists)
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
);
221 /* Adjust the aspect ratio */
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;
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
);
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
);
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
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"),
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:"),
266 export_png_height_entry
=
267 dialog_add_spinbutton(export_png_dialog
, _("Image height:"),
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
)),
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
)),
276 (GtkSignalFunc
)export_png_ratio
, (gpointer
)export_png_width_entry
);
280 /* Store pertinent data in callback data structure */
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
);
306 export_png_ok(NULL
, cbdata
);
310 static const gchar
*extensions
[] = { "png", NULL
};
311 DiaExportFilter png_export_filter
= {
312 N_("Portable Network Graphics"),