aa6434baec66e555c0114c9582c3c3f66b395797
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 <libnr/nr-translate-scale-ops.h>
22 #include <glib/gmessages.h>
23 #include <png.h>
24 #include "png-write.h"
25 #include "io/sys.h"
26 #include <display/nr-arena-item.h>
27 #include <display/nr-arena.h>
28 #include <document.h>
29 #include <sp-item.h>
30 #include <sp-root.h>
31 #include <sp-defs.h>
32 #include "prefs-utils.h"
33 #include "dialogs/rdf.h"
35 /* This is an example of how to use libpng to read and write PNG files.
36 * The file libpng.txt is much more verbose then this. If you have not
37 * read it, do so first. This was designed to be a starting point of an
38 * implementation. This is not officially part of libpng, and therefore
39 * does not require a copyright notice.
40 *
41 * This file does not currently compile, because it is missing certain
42 * parts, like allocating memory to hold an image. You will have to
43 * supply these parts to get it to compile. For an example of a minimal
44 * working PNG reader/writer, see pngtest.c, included in this distribution.
45 */
47 static unsigned int const MAX_STRIPE_SIZE = 1024*1024;
49 struct SPEBP {
50 unsigned long int width, height, sheight;
51 guchar r, g, b, a;
52 NRArenaItem *root; // the root arena item to show; it is assumed that all unneeded items are hidden
53 guchar *px;
54 unsigned (*status)(float, void *);
55 void *data;
56 };
58 /* write a png file */
60 typedef struct SPPNGBD {
61 guchar const *px;
62 int rowstride;
63 } SPPNGBD;
65 /**
66 * A simple wrapper to list png_text.
67 */
68 class PngTextList {
69 public:
70 PngTextList() : count(0), textItems(0) {}
71 ~PngTextList();
73 void add(gchar const* key, gchar const* text);
74 gint getCount() {return count;}
75 png_text* getPtext() {return textItems;}
77 private:
78 gint count;
79 png_text* textItems;
80 };
82 PngTextList::~PngTextList() {
83 for (gint i = 0; i < count; i++) {
84 if (textItems[i].key) {
85 g_free(textItems[i].key);
86 }
87 if (textItems[i].text) {
88 g_free(textItems[i].text);
89 }
90 }
91 }
93 void PngTextList::add(gchar const* key, gchar const* text)
94 {
95 if (count < 0) {
96 count = 0;
97 textItems = 0;
98 }
99 png_text* tmp = (count > 0) ? g_try_renew(png_text, textItems, count + 1): g_try_new(png_text, 1);
100 if (tmp) {
101 textItems = tmp;
102 count++;
104 png_text* item = &(textItems[count - 1]);
105 item->compression = PNG_TEXT_COMPRESSION_NONE;
106 item->key = g_strdup(key);
107 item->text = g_strdup(text);
108 item->text_length = 0;
109 #ifdef PNG_iTXt_SUPPORTED
110 item->itxt_length = 0;
111 item->lang = 0;
112 item->lang_key = 0;
113 #endif // PNG_iTXt_SUPPORTED
114 } else {
115 g_warning("Unable to allocate arrary for %d PNG text data.", count);
116 textItems = 0;
117 count = 0;
118 }
119 }
121 static bool
122 sp_png_write_rgba_striped(SPDocument *doc,
123 gchar const *filename, unsigned long int width, unsigned long int height, double xdpi, double ydpi,
124 int (* get_rows)(guchar const **rows, int row, int num_rows, void *data),
125 void *data)
126 {
127 struct SPEBP *ebp = (struct SPEBP *) data;
128 FILE *fp;
129 png_structp png_ptr;
130 png_infop info_ptr;
131 png_color_8 sig_bit;
132 png_uint_32 r;
134 g_return_val_if_fail(filename != NULL, false);
136 /* open the file */
138 Inkscape::IO::dump_fopen_call(filename, "M");
139 fp = Inkscape::IO::fopen_utf8name(filename, "wb");
140 g_return_val_if_fail(fp != NULL, false);
142 /* Create and initialize the png_struct with the desired error handler
143 * functions. If you want to use the default stderr and longjump method,
144 * you can supply NULL for the last three parameters. We also check that
145 * the library version is compatible with the one used at compile time,
146 * in case we are using dynamically linked libraries. REQUIRED.
147 */
148 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
150 if (png_ptr == NULL) {
151 fclose(fp);
152 return false;
153 }
155 /* Allocate/initialize the image information data. REQUIRED */
156 info_ptr = png_create_info_struct(png_ptr);
157 if (info_ptr == NULL) {
158 fclose(fp);
159 png_destroy_write_struct(&png_ptr, NULL);
160 return false;
161 }
163 /* Set error handling. REQUIRED if you aren't supplying your own
164 * error hadnling functions in the png_create_write_struct() call.
165 */
166 if (setjmp(png_ptr->jmpbuf)) {
167 /* If we get here, we had a problem reading the file */
168 fclose(fp);
169 png_destroy_write_struct(&png_ptr, &info_ptr);
170 return false;
171 }
173 /* set up the output control if you are using standard C streams */
174 png_init_io(png_ptr, fp);
176 /* Set the image information here. Width and height are up to 2^31,
177 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
178 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
179 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
180 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
181 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
182 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
183 */
184 png_set_IHDR(png_ptr, info_ptr,
185 width,
186 height,
187 8, /* bit_depth */
188 PNG_COLOR_TYPE_RGB_ALPHA,
189 PNG_INTERLACE_NONE,
190 PNG_COMPRESSION_TYPE_BASE,
191 PNG_FILTER_TYPE_BASE);
193 /* otherwise, if we are dealing with a color image then */
194 sig_bit.red = 8;
195 sig_bit.green = 8;
196 sig_bit.blue = 8;
197 /* if the image has an alpha channel then */
198 sig_bit.alpha = 8;
199 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
201 PngTextList textList;
203 textList.add("Software", "www.inkscape.org"); // Made by Inkscape comment
204 {
205 const gchar* pngToDc[] = {"Title", "title",
206 "Author", "creator",
207 "Description", "description",
208 //"Copyright", "",
209 "Creation Time", "date",
210 //"Disclaimer", "",
211 //"Warning", "",
212 "Source", "source"
213 //"Comment", ""
214 };
215 for (size_t i = 0; i < G_N_ELEMENTS(pngToDc); i += 2) {
216 struct rdf_work_entity_t * entity = rdf_find_entity ( pngToDc[i + 1] );
217 if (entity) {
218 gchar const* data = rdf_get_work_entity(doc, entity);
219 if (data && *data) {
220 textList.add(pngToDc[i], data);
221 }
222 } else {
223 g_warning("Unable to find entity [%s]", pngToDc[i + 1]);
224 }
225 }
228 struct rdf_license_t *license = rdf_get_license(doc);
229 if (license) {
230 if (license->name && license->uri) {
231 gchar* tmp = g_strdup_printf("%s %s", license->name, license->uri);
232 textList.add("Copyright", tmp);
233 g_free(tmp);
234 } else if (license->name) {
235 textList.add("Copyright", license->name);
236 } else if (license->uri) {
237 textList.add("Copyright", license->uri);
238 }
239 }
240 }
241 if (textList.getCount() > 0) {
242 png_set_text(png_ptr, info_ptr, textList.getPtext(), textList.getCount());
243 }
245 /* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs, */
246 /* note that if sRGB is present the cHRM chunk must be ignored
247 * on read and must be written in accordance with the sRGB profile */
248 png_set_pHYs(png_ptr, info_ptr, unsigned(xdpi / 0.0254 + 0.5), unsigned(ydpi / 0.0254 + 0.5), PNG_RESOLUTION_METER);
250 /* Write the file header information. REQUIRED */
251 png_write_info(png_ptr, info_ptr);
253 /* Once we write out the header, the compression type on the text
254 * chunks gets changed to PNG_TEXT_COMPRESSION_NONE_WR or
255 * PNG_TEXT_COMPRESSION_zTXt_WR, so it doesn't get written out again
256 * at the end.
257 */
259 /* set up the transformations you want. Note that these are
260 * all optional. Only call them if you want them.
261 */
263 /* --- CUT --- */
265 /* The easiest way to write the image (you may have a different memory
266 * layout, however, so choose what fits your needs best). You need to
267 * use the first method if you aren't handling interlacing yourself.
268 */
270 png_bytep* row_pointers = new png_bytep[ebp->sheight];
272 r = 0;
273 while (r < static_cast< png_uint_32 > (height) ) {
274 int n = get_rows((unsigned char const **) row_pointers, r, height-r, data);
275 if (!n) break;
276 png_write_rows(png_ptr, row_pointers, n);
277 r += n;
278 }
280 delete[] row_pointers;
282 /* You can write optional chunks like tEXt, zTXt, and tIME at the end
283 * as well.
284 */
286 /* It is REQUIRED to call this to finish writing the rest of the file */
287 png_write_end(png_ptr, info_ptr);
289 /* if you allocated any text comments, free them here */
291 /* clean up after the write, and free any memory allocated */
292 png_destroy_write_struct(&png_ptr, &info_ptr);
294 /* close the file */
295 fclose(fp);
297 /* that's it */
298 return true;
299 }
302 /**
303 *
304 */
305 static int
306 sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
307 {
308 struct SPEBP *ebp = (struct SPEBP *) data;
310 if (ebp->status) {
311 if (!ebp->status((float) row / ebp->height, ebp->data)) return 0;
312 }
314 num_rows = MIN(num_rows, static_cast<int>(ebp->sheight));
315 num_rows = MIN(num_rows, static_cast<int>(ebp->height - row));
317 /* Set area of interest */
318 // bbox is now set to the entire image to prevent discontinuities
319 // in the image when blur is used (the borders may still be a bit
320 // off, but that's less noticeable).
321 NRRectL bbox;
322 bbox.x0 = 0;
323 bbox.y0 = row;
324 bbox.x1 = ebp->width;
325 bbox.y1 = row + num_rows;
326 /* Update to renderable state */
327 NRGC gc(NULL);
328 gc.transform.set_identity();
330 nr_arena_item_invoke_update(ebp->root, &bbox, &gc,
331 NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE);
333 NRPixBlock pb;
334 nr_pixblock_setup_extern(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
335 bbox.x0, bbox.y0, bbox.x1, bbox.y1,
336 ebp->px, 4 * ebp->width, FALSE, FALSE);
338 for (int r = 0; r < num_rows; r++) {
339 guchar *p = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
340 for (int c = 0; c < static_cast<int>(ebp->width); c++) {
341 *p++ = ebp->r;
342 *p++ = ebp->g;
343 *p++ = ebp->b;
344 *p++ = ebp->a;
345 }
346 }
348 /* Render */
349 nr_arena_item_invoke_render(NULL, ebp->root, &bbox, &pb, 0);
351 for (int r = 0; r < num_rows; r++) {
352 rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
353 }
355 nr_pixblock_release(&pb);
357 return num_rows;
358 }
360 /**
361 * Hide all items that are not listed in list, recursively, skipping groups and defs.
362 */
363 static void
364 hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey)
365 {
366 if ( SP_IS_ITEM(o)
367 && !SP_IS_DEFS(o)
368 && !SP_IS_ROOT(o)
369 && !SP_IS_GROUP(o)
370 && !g_slist_find(list, o) )
371 {
372 sp_item_invoke_hide(SP_ITEM(o), dkey);
373 }
375 // recurse
376 if (!g_slist_find(list, o)) {
377 for (SPObject *child = sp_object_first_child(o) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
378 hide_other_items_recursively(child, list, dkey);
379 }
380 }
381 }
384 /**
385 * Export the given document as a Portable Network Graphics (PNG) file.
386 *
387 * \return true if succeeded (or if no action was taken), false if an error occurred.
388 */
389 bool
390 sp_export_png_file(SPDocument *doc, gchar const *filename,
391 double x0, double y0, double x1, double y1,
392 unsigned long width, unsigned long height, double xdpi, double ydpi,
393 unsigned long bgcolor,
394 unsigned (*status)(float, void *),
395 void *data, bool force_overwrite,
396 GSList *items_only)
397 {
398 g_return_val_if_fail(doc != NULL, false);
399 g_return_val_if_fail(filename != NULL, false);
400 g_return_val_if_fail(width >= 1, false);
401 g_return_val_if_fail(height >= 1, false);
403 if (!force_overwrite && !sp_ui_overwrite_file(filename)) {
404 /* Remark: We return true so as not to invoke an error dialog in case export is cancelled
405 by the user; currently this is safe because the callers only act when false is returned.
406 If this changes in the future we need better distinction of return types (e.g., use int)
407 */
408 return true;
409 }
411 // export with maximum blur rendering quality
412 int saved_quality = prefs_get_int_attribute("options.blurquality", "value", 0);
413 prefs_set_int_attribute("options.blurquality", "value", 2);
415 sp_document_ensure_up_to_date(doc);
417 /* Go to document coordinates */
418 {
419 gdouble const t = y0;
420 y0 = sp_document_height(doc) - y1;
421 y1 = sp_document_height(doc) - t;
422 }
424 /*
425 * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
426 * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
427 * 3) a[0] * x1 + a[2] * y1 + a[4] = width
428 * 4) a[1] * x0 + a[3] * y0 + a[5] = height
429 * 5) a[1] = 0.0;
430 * 6) a[2] = 0.0;
431 *
432 * (1,3) a[0] * x1 - a[0] * x0 = width
433 * a[0] = width / (x1 - x0)
434 * (2,4) a[3] * y0 - a[3] * y1 = height
435 * a[3] = height / (y0 - y1)
436 * (1) a[4] = -a[0] * x0
437 * (2) a[5] = -a[3] * y1
438 */
440 Geom::Matrix const affine(Geom::Translate(-x0, -y0)
441 * Geom::Scale(width / (x1 - x0),
442 height / (y1 - y0)));
444 //SP_PRINT_MATRIX("SVG2PNG", &affine);
446 struct SPEBP ebp;
447 ebp.width = width;
448 ebp.height = height;
449 ebp.r = NR_RGBA32_R(bgcolor);
450 ebp.g = NR_RGBA32_G(bgcolor);
451 ebp.b = NR_RGBA32_B(bgcolor);
452 ebp.a = NR_RGBA32_A(bgcolor);
454 /* Create new arena */
455 NRArena *const arena = NRArena::create();
456 unsigned const dkey = sp_item_display_key_new(1);
458 /* Create ArenaItems and set transform */
459 ebp.root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)), arena, dkey, SP_ITEM_SHOW_DISPLAY);
460 nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), affine);
462 // We show all and then hide all items we don't want, instead of showing only requested items,
463 // because that would not work if the shown item references something in defs
464 if (items_only) {
465 hide_other_items_recursively(sp_document_root(doc), items_only, dkey);
466 }
468 ebp.status = status;
469 ebp.data = data;
471 bool write_status;
472 if ((width < 256) || ((width * height) < 32768)) {
473 ebp.px = nr_pixelstore_64K_new(FALSE, 0);
474 ebp.sheight = 65536 / (4 * width);
475 write_status = sp_png_write_rgba_striped(doc, filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
476 nr_pixelstore_64K_free(ebp.px);
477 } else {
478 ebp.px = g_new(guchar, 4 * 64 * width);
479 ebp.sheight = 64;
480 write_status = sp_png_write_rgba_striped(doc, filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
481 g_free(ebp.px);
482 }
484 // Hide items, this releases arenaitem
485 sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), dkey);
487 /* Free arena */
488 nr_object_unref((NRObject *) arena);
490 // restore saved blur quality
491 prefs_set_int_attribute("options.blurquality", "value", saved_quality);
493 return write_status;
494 }
497 /*
498 Local Variables:
499 mode:c++
500 c-file-style:"stroustrup"
501 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
502 indent-tabs-mode:nil
503 fill-column:99
504 End:
505 */
506 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :