Code

revert jasper's way overzealous fix in png-write.cpp rev 13700; new fix in item_rende...
[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 "prefs-utils.h"
34 /* This is an example of how to use libpng to read and write PNG files.
35  * The file libpng.txt is much more verbose then this.  If you have not
36  * read it, do so first.  This was designed to be a starting point of an
37  * implementation.  This is not officially part of libpng, and therefore
38  * does not require a copyright notice.
39  *
40  * This file does not currently compile, because it is missing certain
41  * parts, like allocating memory to hold an image.  You will have to
42  * supply these parts to get it to compile.  For an example of a minimal
43  * working PNG reader/writer, see pngtest.c, included in this distribution.
44  */
46 static unsigned int const MAX_STRIPE_SIZE = 1024*1024;
48 struct SPEBP {
49     int width, height, sheight;
50     guchar r, g, b, a;
51     NRArenaItem *root; // the root arena item to show; it is assumed that all unneeded items are hidden
52     guchar *px;
53     unsigned (*status)(float, void *);
54     void *data;
55 };
57 /* write a png file */
59 typedef struct SPPNGBD {
60     guchar const *px;
61     int rowstride;
62 } SPPNGBD;
64 static bool
65 sp_png_write_rgba_striped(gchar const *filename, int width, int height, double xdpi, double ydpi,
66                           int (* get_rows)(guchar const **rows, int row, int num_rows, void *data),
67                           void *data)
68 {
69     struct SPEBP *ebp = (struct SPEBP *) data;
70     FILE *fp;
71     png_structp png_ptr;
72     png_infop info_ptr;
73     png_color_8 sig_bit;
74     png_text text_ptr[3];
75     png_uint_32 r;
77     g_return_val_if_fail(filename != NULL, false);
79     /* open the file */
81     Inkscape::IO::dump_fopen_call(filename, "M");
82     fp = Inkscape::IO::fopen_utf8name(filename, "wb");
83     g_return_val_if_fail(fp != NULL, false);
85     /* Create and initialize the png_struct with the desired error handler
86      * functions.  If you want to use the default stderr and longjump method,
87      * you can supply NULL for the last three parameters.  We also check that
88      * the library version is compatible with the one used at compile time,
89      * in case we are using dynamically linked libraries.  REQUIRED.
90      */
91     png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
93     if (png_ptr == NULL) {
94         fclose(fp);
95         return false;
96     }
98     /* Allocate/initialize the image information data.  REQUIRED */
99     info_ptr = png_create_info_struct(png_ptr);
100     if (info_ptr == NULL) {
101         fclose(fp);
102         png_destroy_write_struct(&png_ptr, NULL);
103         return false;
104     }
106     /* Set error handling.  REQUIRED if you aren't supplying your own
107      * error hadnling functions in the png_create_write_struct() call.
108      */
109     if (setjmp(png_ptr->jmpbuf)) {
110         /* If we get here, we had a problem reading the file */
111         fclose(fp);
112         png_destroy_write_struct(&png_ptr, &info_ptr);
113         return false;
114     }
116     /* set up the output control if you are using standard C streams */
117     png_init_io(png_ptr, fp);
119     /* Set the image information here.  Width and height are up to 2^31,
120      * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
121      * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
122      * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
123      * or PNG_COLOR_TYPE_RGB_ALPHA.  interlace is either PNG_INTERLACE_NONE or
124      * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
125      * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
126      */
127     png_set_IHDR(png_ptr, info_ptr,
128                  width,
129                  height,
130                  8, /* bit_depth */
131                  PNG_COLOR_TYPE_RGB_ALPHA,
132                  PNG_INTERLACE_NONE,
133                  PNG_COMPRESSION_TYPE_BASE,
134                  PNG_FILTER_TYPE_BASE);
136     /* otherwise, if we are dealing with a color image then */
137     sig_bit.red = 8;
138     sig_bit.green = 8;
139     sig_bit.blue = 8;
140     /* if the image has an alpha channel then */
141     sig_bit.alpha = 8;
142     png_set_sBIT(png_ptr, info_ptr, &sig_bit);
144     /* Made by Inkscape comment */
145     text_ptr[0].key = "Software";
146     text_ptr[0].text = "www.inkscape.org";
147     text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
148     png_set_text(png_ptr, info_ptr, text_ptr, 1);
150     /* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs, */
151     /* note that if sRGB is present the cHRM chunk must be ignored
152      * on read and must be written in accordance with the sRGB profile */
153     png_set_pHYs(png_ptr, info_ptr, unsigned(xdpi / 0.0254 + 0.5), unsigned(ydpi / 0.0254 + 0.5), PNG_RESOLUTION_METER);
155     /* Write the file header information.  REQUIRED */
156     png_write_info(png_ptr, info_ptr);
158     /* Once we write out the header, the compression type on the text
159      * chunks gets changed to PNG_TEXT_COMPRESSION_NONE_WR or
160      * PNG_TEXT_COMPRESSION_zTXt_WR, so it doesn't get written out again
161      * at the end.
162      */
164     /* set up the transformations you want.  Note that these are
165      * all optional.  Only call them if you want them.
166      */
168     /* --- CUT --- */
170     /* The easiest way to write the image (you may have a different memory
171      * layout, however, so choose what fits your needs best).  You need to
172      * use the first method if you aren't handling interlacing yourself.
173      */
175     png_bytep* row_pointers = new png_bytep[ebp->sheight];
177     r = 0;
178     while (r < static_cast< png_uint_32 > (height) ) {
179         int n = get_rows((unsigned char const **) row_pointers, r, height-r, data);
180         if (!n) break;
181         png_write_rows(png_ptr, row_pointers, n);
182         r += n;
183     }
185     delete[] row_pointers;
187     /* You can write optional chunks like tEXt, zTXt, and tIME at the end
188      * as well.
189      */
191     /* It is REQUIRED to call this to finish writing the rest of the file */
192     png_write_end(png_ptr, info_ptr);
194     /* if you allocated any text comments, free them here */
196     /* clean up after the write, and free any memory allocated */
197     png_destroy_write_struct(&png_ptr, &info_ptr);
199     /* close the file */
200     fclose(fp);
202     /* that's it */
203     return true;
207 /**
208  *
209  */
210 static int
211 sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
213     struct SPEBP *ebp = (struct SPEBP *) data;
215     if (ebp->status) {
216         if (!ebp->status((float) row / ebp->height, ebp->data)) return 0;
217     }
219     num_rows = MIN(num_rows, ebp->sheight);
220     num_rows = MIN(num_rows, ebp->height - row);
222     /* Set area of interest */
223     // bbox is now set to the entire image to prevent discontinuities
224     // in the image when blur is used (the borders may still be a bit
225     // off, but that's less noticeable).
226     NRRectL bbox;
227     bbox.x0 = 0;
228     bbox.y0 = row;
229     bbox.x1 = ebp->width;
230     bbox.y1 = row + num_rows;
231     /* Update to renderable state */
232     NRGC gc(NULL);
233     nr_matrix_set_identity(&gc.transform);
235     nr_arena_item_invoke_update(ebp->root, &bbox, &gc,
236            NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE);
238     NRPixBlock pb;
239     nr_pixblock_setup_extern(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
240                              bbox.x0, bbox.y0, bbox.x1, bbox.y1,
241                              ebp->px, 4 * ebp->width, FALSE, FALSE);
243     for (int r = 0; r < num_rows; r++) {
244         guchar *p = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
245         for (int c = 0; c < ebp->width; c++) {
246             *p++ = ebp->r;
247             *p++ = ebp->g;
248             *p++ = ebp->b;
249             *p++ = ebp->a;
250         }
251     }
253     /* Render */
254     nr_arena_item_invoke_render(NULL, ebp->root, &bbox, &pb, 0);
256     for (int r = 0; r < num_rows; r++) {
257         rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
258     }
260     nr_pixblock_release(&pb);
262     return num_rows;
265 /**
266  * Hide all items that are not listed in list, recursively, skipping groups and defs.
267  */
268 static void
269 hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey)
271     if ( SP_IS_ITEM(o)
272          && !SP_IS_DEFS(o)
273          && !SP_IS_ROOT(o)
274          && !SP_IS_GROUP(o)
275          && !g_slist_find(list, o) )
276     {
277         sp_item_invoke_hide(SP_ITEM(o), dkey);
278     }
280     // recurse
281     if (!g_slist_find(list, o)) {
282         for (SPObject *child = sp_object_first_child(o) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
283             hide_other_items_recursively(child, list, dkey);
284         }
285     }
289 /**
290  * Export the given document as a Portable Network Graphics (PNG) file.
291  *
292  * \return true if succeeded, false if an error occurred.
293  */
294 bool
295 sp_export_png_file(SPDocument *doc, gchar const *filename,
296                    double x0, double y0, double x1, double y1,
297                    unsigned width, unsigned height, double xdpi, double ydpi,
298                    unsigned long bgcolor,
299                    unsigned (*status)(float, void *),
300                    void *data, bool force_overwrite,
301                    GSList *items_only)
303     g_return_val_if_fail(doc != NULL, false);
304     g_return_val_if_fail(filename != NULL, false);
305     g_return_val_if_fail(width >= 1, false);
306     g_return_val_if_fail(height >= 1, false);
308     if (!force_overwrite && !sp_ui_overwrite_file(filename)) {
309         return false;
310     }
312     // export with maximum blur rendering quality
313     int saved_quality = prefs_get_int_attribute("options.blurquality", "value", 0);
314     prefs_set_int_attribute("options.blurquality", "value", 2);
316     sp_document_ensure_up_to_date(doc);
318     /* Go to document coordinates */
319     {
320         gdouble const t = y0;
321         y0 = sp_document_height(doc) - y1;
322         y1 = sp_document_height(doc) - t;
323     }
325     /*
326      * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
327      * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
328      * 3) a[0] * x1 + a[2] * y1 + a[4] = width
329      * 4) a[1] * x0 + a[3] * y0 + a[5] = height
330      * 5) a[1] = 0.0;
331      * 6) a[2] = 0.0;
332      *
333      * (1,3) a[0] * x1 - a[0] * x0 = width
334      * a[0] = width / (x1 - x0)
335      * (2,4) a[3] * y0 - a[3] * y1 = height
336      * a[3] = height / (y0 - y1)
337      * (1) a[4] = -a[0] * x0
338      * (2) a[5] = -a[3] * y1
339      */
341     NR::Matrix const affine(NR::translate(-x0, -y0)
342                             * NR::scale(width / (x1 - x0),
343                                         height / (y1 - y0)));
345     //SP_PRINT_MATRIX("SVG2PNG", &affine);
347     struct SPEBP ebp;
348     ebp.width  = width;
349     ebp.height = height;
350     ebp.r      = NR_RGBA32_R(bgcolor);
351     ebp.g      = NR_RGBA32_G(bgcolor);
352     ebp.b      = NR_RGBA32_B(bgcolor);
353     ebp.a      = NR_RGBA32_A(bgcolor);
355     /* Create new arena */
356     NRArena *const arena = NRArena::create();
357     unsigned const dkey = sp_item_display_key_new(1);
359     /* Create ArenaItems and set transform */
360     ebp.root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)), arena, dkey, SP_ITEM_SHOW_DISPLAY);
361     nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), affine);
363     // We show all and then hide all items we don't want, instead of showing only requested items,
364     // because that would not work if the shown item references something in defs
365     if (items_only) {
366         hide_other_items_recursively(sp_document_root(doc), items_only, dkey);
367     }
369     ebp.status = status;
370     ebp.data   = data;
372     bool write_status;
373     if ((width < 256) || ((width * height) < 32768)) {
374         ebp.px = nr_pixelstore_64K_new(FALSE, 0);
375         ebp.sheight = 65536 / (4 * width);
376         write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
377         nr_pixelstore_64K_free(ebp.px);
378     } else {
379         ebp.px = g_new(guchar, 4 * 64 * width);
380         ebp.sheight = 64;
381         write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
382         g_free(ebp.px);
383     }
385     // Hide items
386     sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), dkey);
388     /* Free Arena and ArenaItem */
389     nr_arena_item_unref(ebp.root);
390     nr_object_unref((NRObject *) arena);
392     // restore saved blur quality
393     prefs_set_int_attribute("options.blurquality", "value", saved_quality);
395     return write_status;
399 /*
400   Local Variables:
401   mode:c++
402   c-file-style:"stroustrup"
403   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
404   indent-tabs-mode:nil
405   fill-column:99
406   End:
407 */
408 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :