Code

753b0df45ee1a684ad07e283663ee00cf9e93016
[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 <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     }
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)
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;
303 /**
304  *
305  */
306 static int
307 sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
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;
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)
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     }
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)
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);
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)
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;
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 :