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