e14d94ddcb4a4c8df65e74711586c434245b99b8
1 #define __SP_PNG_WRITE_C__
3 /*
4 * PNG file format utilities
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * Whoever wrote this example in libpng documentation
9 *
10 * Copyright (C) 1999-2002 authors
11 *
12 * Released under GNU GPL, read the file 'COPYING' for more information
13 */
15 #ifdef HAVE_CONFIG_H
16 # include "config.h"
17 #endif
19 #include <interface.h>
20 #include <libnr/nr-pixops.h>
21 #include <glib/gmessages.h>
22 #include <png.h>
23 #include "png-write.h"
24 #include "io/sys.h"
25 #include <display/nr-arena-item.h>
26 #include <display/nr-arena.h>
27 #include <document.h>
28 #include <sp-item.h>
29 #include <sp-root.h>
30 #include <sp-defs.h>
31 #include "prefs-utils.h"
33 /* This is an example of how to use libpng to read and write PNG files.
34 * The file libpng.txt is much more verbose then this. If you have not
35 * read it, do so first. This was designed to be a starting point of an
36 * implementation. This is not officially part of libpng, and therefore
37 * does not require a copyright notice.
38 *
39 * This file does not currently compile, because it is missing certain
40 * parts, like allocating memory to hold an image. You will have to
41 * supply these parts to get it to compile. For an example of a minimal
42 * working PNG reader/writer, see pngtest.c, included in this distribution.
43 */
45 /* write a png file */
47 typedef struct SPPNGBD {
48 const guchar *px;
49 int rowstride;
50 } SPPNGBD;
52 static int
53 sp_png_get_block_stripe (const guchar **rows, int row, int num_rows, void *data)
54 {
55 SPPNGBD *bd = (SPPNGBD *) data;
57 for (int r = 0; r < num_rows; r++) {
58 rows[r] = bd->px + (row + r) * bd->rowstride;
59 }
61 return num_rows;
62 }
64 int
65 sp_png_write_rgba (const gchar *filename, const guchar *px, int width, int height, double xdpi, double ydpi, int rowstride)
66 {
67 SPPNGBD bd;
69 bd.px = px;
70 bd.rowstride = rowstride;
72 return sp_png_write_rgba_striped (filename, width, height, xdpi, ydpi, sp_png_get_block_stripe, &bd);
73 }
75 int
76 sp_png_write_rgba_striped (const gchar *filename, int width, int height, double xdpi, double ydpi,
77 int (* get_rows) (const guchar **rows, int row, int num_rows, void *data),
78 void *data)
79 {
80 FILE *fp;
81 png_structp png_ptr;
82 png_infop info_ptr;
83 png_color_8 sig_bit;
84 png_text text_ptr[3];
85 png_uint_32 r;
87 g_return_val_if_fail (filename != NULL, FALSE);
89 /* open the file */
91 Inkscape::IO::dump_fopen_call(filename, "M");
92 fp = Inkscape::IO::fopen_utf8name(filename, "wb");
93 g_return_val_if_fail (fp != NULL, FALSE);
95 /* Create and initialize the png_struct with the desired error handler
96 * functions. If you want to use the default stderr and longjump method,
97 * you can supply NULL for the last three parameters. We also check that
98 * the library version is compatible with the one used at compile time,
99 * in case we are using dynamically linked libraries. REQUIRED.
100 */
101 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
103 if (png_ptr == NULL) {
104 fclose(fp);
105 return FALSE;
106 }
108 /* Allocate/initialize the image information data. REQUIRED */
109 info_ptr = png_create_info_struct(png_ptr);
110 if (info_ptr == NULL) {
111 fclose(fp);
112 png_destroy_write_struct(&png_ptr, NULL);
113 return FALSE;
114 }
116 /* Set error handling. REQUIRED if you aren't supplying your own
117 * error hadnling functions in the png_create_write_struct() call.
118 */
119 if (setjmp(png_ptr->jmpbuf)) {
120 /* If we get here, we had a problem reading the file */
121 fclose(fp);
122 png_destroy_write_struct(&png_ptr, &info_ptr);
123 return FALSE;
124 }
126 /* set up the output control if you are using standard C streams */
127 png_init_io(png_ptr, fp);
129 /* Set the image information here. Width and height are up to 2^31,
130 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
131 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
132 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
133 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
134 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
135 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
136 */
137 png_set_IHDR(png_ptr, info_ptr,
138 width,
139 height,
140 8, /* bit_depth */
141 PNG_COLOR_TYPE_RGB_ALPHA,
142 PNG_INTERLACE_NONE,
143 PNG_COMPRESSION_TYPE_BASE,
144 PNG_FILTER_TYPE_BASE);
146 /* otherwise, if we are dealing with a color image then */
147 sig_bit.red = 8;
148 sig_bit.green = 8;
149 sig_bit.blue = 8;
150 /* if the image has an alpha channel then */
151 sig_bit.alpha = 8;
152 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
154 /* Made by Inkscape comment */
155 text_ptr[0].key = "Software";
156 text_ptr[0].text = "www.inkscape.org";
157 text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
158 png_set_text(png_ptr, info_ptr, text_ptr, 1);
160 /* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs, */
161 /* note that if sRGB is present the cHRM chunk must be ignored
162 * on read and must be written in accordance with the sRGB profile */
163 png_set_pHYs(png_ptr, info_ptr, unsigned(xdpi / 0.0254 + 0.5), unsigned(ydpi / 0.0254 + 0.5), PNG_RESOLUTION_METER);
165 /* Write the file header information. REQUIRED */
166 png_write_info(png_ptr, info_ptr);
168 /* Once we write out the header, the compression type on the text
169 * chunks gets changed to PNG_TEXT_COMPRESSION_NONE_WR or
170 * PNG_TEXT_COMPRESSION_zTXt_WR, so it doesn't get written out again
171 * at the end.
172 */
174 /* set up the transformations you want. Note that these are
175 * all optional. Only call them if you want them.
176 */
178 /* --- CUT --- */
180 /* The easiest way to write the image (you may have a different memory
181 * layout, however, so choose what fits your needs best). You need to
182 * use the first method if you aren't handling interlacing yourself.
183 */
185 r = 0;
186 while (r < static_cast< png_uint_32 > (height) ) {
187 png_bytep row_pointers[64];
188 int h, n;
190 h = MIN (height - r, 64);
191 n = get_rows ((const unsigned char **) row_pointers, r, h, data);
192 if (!n) break;
193 png_write_rows (png_ptr, row_pointers, n);
194 r += n;
195 }
197 /* You can write optional chunks like tEXt, zTXt, and tIME at the end
198 * as well.
199 */
201 /* It is REQUIRED to call this to finish writing the rest of the file */
202 png_write_end(png_ptr, info_ptr);
204 /* if you allocated any text comments, free them here */
206 /* clean up after the write, and free any memory allocated */
207 png_destroy_write_struct(&png_ptr, &info_ptr);
209 /* close the file */
210 fclose(fp);
212 /* that's it */
213 return TRUE;
214 }
218 struct SPEBP {
219 int width, height, sheight;
220 guchar r, g, b, a;
221 NRArenaItem *root; // the root arena item to show; it is assumed that all unneeded items are hidden
222 guchar *px;
223 unsigned (*status)(float, void *);
224 void *data;
225 };
228 /**
229 *
230 */
231 static int
232 sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
233 {
234 struct SPEBP *ebp = (struct SPEBP *) data;
236 if (ebp->status) {
237 if (!ebp->status((float) row / ebp->height, ebp->data)) return 0;
238 }
240 num_rows = MIN(num_rows, ebp->sheight);
241 num_rows = MIN(num_rows, ebp->height - row);
243 /* Set area of interest */
244 NRRectL bbox;
245 bbox.x0 = 0;
246 bbox.y0 = row;
247 bbox.x1 = ebp->width;
248 bbox.y1 = row + num_rows;
249 /* Update to renderable state */
250 NRGC gc(NULL);
251 nr_matrix_set_identity(&gc.transform);
253 nr_arena_item_invoke_update(ebp->root, &bbox, &gc,
254 NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE);
256 NRPixBlock pb;
257 nr_pixblock_setup_extern(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
258 bbox.x0, bbox.y0, bbox.x1, bbox.y1,
259 ebp->px, 4 * ebp->width, FALSE, FALSE);
261 for (int r = 0; r < num_rows; r++) {
262 guchar *p = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
263 for (int c = 0; c < ebp->width; c++) {
264 *p++ = ebp->r;
265 *p++ = ebp->g;
266 *p++ = ebp->b;
267 *p++ = ebp->a;
268 }
269 }
271 /* Render */
272 nr_arena_item_invoke_render(ebp->root, &bbox, &pb, 0);
274 for (int r = 0; r < num_rows; r++) {
275 rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
276 }
278 nr_pixblock_release(&pb);
280 return num_rows;
281 }
283 /**
284 Hide all items which are not listed in list, recursively, skipping groups and defs
285 */
286 static void
287 hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey)
288 {
289 if (SP_IS_ITEM(o)
290 && !SP_IS_DEFS(o)
291 && !SP_IS_ROOT(o)
292 && !SP_IS_GROUP(o)
293 && !g_slist_find(list, o))
294 {
295 sp_item_invoke_hide(SP_ITEM(o), dkey);
296 }
298 // recurse
299 if (!g_slist_find(list, o)) {
300 for (SPObject *child = sp_object_first_child(o) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
301 hide_other_items_recursively(child, list, dkey);
302 }
303 }
304 }
307 /**
308 * Render the SVG drawing onto a PNG raster image, then save to
309 * a file. Returns TRUE if succeeded in writing the file,
310 * FALSE otherwise.
311 */
312 int
313 sp_export_png_file(SPDocument *doc, gchar const *filename,
314 double x0, double y0, double x1, double y1,
315 unsigned width, unsigned height, double xdpi, double ydpi,
316 unsigned long bgcolor,
317 unsigned (*status)(float, void *),
318 void *data, bool force_overwrite,
319 GSList *items_only)
320 {
321 int write_status = TRUE;
322 g_return_val_if_fail(doc != NULL, FALSE);
323 g_return_val_if_fail(filename != NULL, FALSE);
324 g_return_val_if_fail(width >= 1, FALSE);
325 g_return_val_if_fail(height >= 1, FALSE);
327 if (!force_overwrite && !sp_ui_overwrite_file(filename)) {
328 return FALSE;
329 }
331 // export with maximum blur rendering quality
332 int saved_quality = prefs_get_int_attribute ("options.blurquality", "value", 0);
333 prefs_set_int_attribute ("options.blurquality", "value", 2);
335 sp_document_ensure_up_to_date(doc);
337 /* Go to document coordinates */
338 gdouble t = y0;
339 y0 = sp_document_height(doc) - y1;
340 y1 = sp_document_height(doc) - t;
342 /*
343 * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
344 * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
345 * 3) a[0] * x1 + a[2] * y1 + a[4] = width
346 * 4) a[1] * x0 + a[3] * y0 + a[5] = height
347 * 5) a[1] = 0.0;
348 * 6) a[2] = 0.0;
349 *
350 * (1,3) a[0] * x1 - a[0] * x0 = width
351 * a[0] = width / (x1 - x0)
352 * (2,4) a[3] * y0 - a[3] * y1 = height
353 * a[3] = height / (y0 - y1)
354 * (1) a[4] = -a[0] * x0
355 * (2) a[5] = -a[3] * y1
356 */
358 NRMatrix affine;
359 affine.c[0] = width / (x1 - x0);
360 affine.c[1] = 0.0;
361 affine.c[2] = 0.0;
362 affine.c[3] = height / (y1 - y0);
363 affine.c[4] = -affine.c[0] * x0;
364 affine.c[5] = -affine.c[3] * y0;
366 //SP_PRINT_MATRIX("SVG2PNG", &affine);
368 struct SPEBP ebp;
369 ebp.width = width;
370 ebp.height = height;
371 ebp.r = NR_RGBA32_R(bgcolor);
372 ebp.g = NR_RGBA32_G(bgcolor);
373 ebp.b = NR_RGBA32_B(bgcolor);
374 ebp.a = NR_RGBA32_A(bgcolor);
376 /* Create new arena */
377 NRArena *arena = NRArena::create();
378 unsigned dkey = sp_item_display_key_new(1);
380 /* Create ArenaItems and set transform */
381 ebp.root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)), arena, dkey, SP_ITEM_SHOW_DISPLAY);
382 nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), NR::Matrix(&affine));
384 // We show all and then hide all items we don't want, instead of showing only requested items,
385 // because that would not work if the shown item references something in defs
386 if (items_only) {
387 hide_other_items_recursively(sp_document_root(doc), items_only, dkey);
388 }
390 ebp.status = status;
391 ebp.data = data;
393 if ((width < 256) || ((width * height) < 32768)) {
394 ebp.px = nr_pixelstore_64K_new(FALSE, 0);
395 ebp.sheight = 65536 / (4 * width);
396 write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
397 nr_pixelstore_64K_free(ebp.px);
398 } else {
399 ebp.px = g_new(guchar, 4 * 64 * width);
400 ebp.sheight = 64;
401 write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
402 g_free(ebp.px);
403 }
405 // Hide items
406 sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), dkey);
408 /* Free Arena and ArenaItem */
409 nr_arena_item_unref(ebp.root);
410 nr_object_unref((NRObject *) arena);
412 // restore saved blur quality
413 prefs_set_int_attribute ("options.blurquality", "value", saved_quality);
415 return write_status;
416 }