Code

From trunk
[inkscape.git] / src / helper / png-write.cpp
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 "preferences.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     }
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)
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;
302 /**
303  *
304  */
305 static int
306 sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
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.setIdentity();
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;
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)
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     }
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)
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     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
413     int saved_quality = prefs->getInt("/options/blurquality/value", 0);
414     prefs->setInt("/options/blurquality/value", 2);
415     int saved_filter_quality = prefs->getInt("/options/filterquality/value", 0);
416     prefs->setInt("/options/filterquality/value", 2);
418     sp_document_ensure_up_to_date(doc);
420     /* Go to document coordinates */
421     {
422         gdouble const t = y0;
423         y0 = sp_document_height(doc) - y1;
424         y1 = sp_document_height(doc) - t;
425     }
427     /*
428      * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
429      * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
430      * 3) a[0] * x1 + a[2] * y1 + a[4] = width
431      * 4) a[1] * x0 + a[3] * y0 + a[5] = height
432      * 5) a[1] = 0.0;
433      * 6) a[2] = 0.0;
434      *
435      * (1,3) a[0] * x1 - a[0] * x0 = width
436      * a[0] = width / (x1 - x0)
437      * (2,4) a[3] * y0 - a[3] * y1 = height
438      * a[3] = height / (y0 - y1)
439      * (1) a[4] = -a[0] * x0
440      * (2) a[5] = -a[3] * y1
441      */
443     Geom::Matrix const affine(Geom::Translate(-x0, -y0)
444                             * Geom::Scale(width / (x1 - x0),
445                                         height / (y1 - y0)));
447     //SP_PRINT_MATRIX("SVG2PNG", &affine);
449     struct SPEBP ebp;
450     ebp.width  = width;
451     ebp.height = height;
452     ebp.r      = NR_RGBA32_R(bgcolor);
453     ebp.g      = NR_RGBA32_G(bgcolor);
454     ebp.b      = NR_RGBA32_B(bgcolor);
455     ebp.a      = NR_RGBA32_A(bgcolor);
457     /* Create new arena */
458     NRArena *const arena = NRArena::create();
459     unsigned const dkey = sp_item_display_key_new(1);
461     /* Create ArenaItems and set transform */
462     ebp.root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)), arena, dkey, SP_ITEM_SHOW_DISPLAY);
463     nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), affine);
465     // We show all and then hide all items we don't want, instead of showing only requested items,
466     // because that would not work if the shown item references something in defs
467     if (items_only) {
468         hide_other_items_recursively(sp_document_root(doc), items_only, dkey);
469     }
471     ebp.status = status;
472     ebp.data   = data;
474     bool write_status;
475     if ((width < 256) || ((width * height) < 32768)) {
476         ebp.px = nr_pixelstore_64K_new(FALSE, 0);
477         ebp.sheight = 65536 / (4 * width);
478         write_status = sp_png_write_rgba_striped(doc, filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
479         nr_pixelstore_64K_free(ebp.px);
480     } else {
481         ebp.px = g_new(guchar, 4 * 64 * width);
482         ebp.sheight = 64;
483         write_status = sp_png_write_rgba_striped(doc, filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
484         g_free(ebp.px);
485     }
487     // Hide items, this releases arenaitem
488     sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), dkey);
490     /* Free arena */
491     nr_object_unref((NRObject *) arena);
493     // restore saved blur and filter quality
494     prefs->setInt("/options/blurquality/value", saved_quality);
495     prefs->setInt("/options/filterquality/value", saved_filter_quality);
497     return write_status;
501 /*
502   Local Variables:
503   mode:c++
504   c-file-style:"stroustrup"
505   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
506   indent-tabs-mode:nil
507   fill-column:99
508   End:
509 */
510 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :