Code

No more NRMatrix or NRPoint.
[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     unsigned long 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, unsigned long int width, unsigned long 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, static_cast<int>(ebp->sheight));
220     num_rows = MIN(num_rows, static_cast<int>(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     gc.transform.set_identity();
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 < static_cast<int>(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 (or if no action was taken), 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 long width, unsigned long 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         /* Remark: We return true so as not to invoke an error dialog in case export is cancelled
310            by the user; currently this is safe because the callers only act when false is returned.
311            If this changes in the future we need better distinction of return types (e.g., use int)
312         */
313         return true;
314     }
316     // export with maximum blur rendering quality
317     int saved_quality = prefs_get_int_attribute("options.blurquality", "value", 0);
318     prefs_set_int_attribute("options.blurquality", "value", 2);
320     sp_document_ensure_up_to_date(doc);
322     /* Go to document coordinates */
323     {
324         gdouble const t = y0;
325         y0 = sp_document_height(doc) - y1;
326         y1 = sp_document_height(doc) - t;
327     }
329     /*
330      * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
331      * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
332      * 3) a[0] * x1 + a[2] * y1 + a[4] = width
333      * 4) a[1] * x0 + a[3] * y0 + a[5] = height
334      * 5) a[1] = 0.0;
335      * 6) a[2] = 0.0;
336      *
337      * (1,3) a[0] * x1 - a[0] * x0 = width
338      * a[0] = width / (x1 - x0)
339      * (2,4) a[3] * y0 - a[3] * y1 = height
340      * a[3] = height / (y0 - y1)
341      * (1) a[4] = -a[0] * x0
342      * (2) a[5] = -a[3] * y1
343      */
345     NR::Matrix const affine(NR::translate(-x0, -y0)
346                             * NR::scale(width / (x1 - x0),
347                                         height / (y1 - y0)));
349     //SP_PRINT_MATRIX("SVG2PNG", &affine);
351     struct SPEBP ebp;
352     ebp.width  = width;
353     ebp.height = height;
354     ebp.r      = NR_RGBA32_R(bgcolor);
355     ebp.g      = NR_RGBA32_G(bgcolor);
356     ebp.b      = NR_RGBA32_B(bgcolor);
357     ebp.a      = NR_RGBA32_A(bgcolor);
359     /* Create new arena */
360     NRArena *const arena = NRArena::create();
361     unsigned const dkey = sp_item_display_key_new(1);
363     /* Create ArenaItems and set transform */
364     ebp.root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)), arena, dkey, SP_ITEM_SHOW_DISPLAY);
365     nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), affine);
367     // We show all and then hide all items we don't want, instead of showing only requested items,
368     // because that would not work if the shown item references something in defs
369     if (items_only) {
370         hide_other_items_recursively(sp_document_root(doc), items_only, dkey);
371     }
373     ebp.status = status;
374     ebp.data   = data;
376     bool write_status;
377     if ((width < 256) || ((width * height) < 32768)) {
378         ebp.px = nr_pixelstore_64K_new(FALSE, 0);
379         ebp.sheight = 65536 / (4 * width);
380         write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
381         nr_pixelstore_64K_free(ebp.px);
382     } else {
383         ebp.px = g_new(guchar, 4 * 64 * width);
384         ebp.sheight = 64;
385         write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
386         g_free(ebp.px);
387     }
389     // Hide items
390     sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), dkey);
392     /* Free Arena and ArenaItem */
393     nr_arena_item_unref(ebp.root);
394     nr_object_unref((NRObject *) arena);
396     // restore saved blur quality
397     prefs_set_int_attribute("options.blurquality", "value", saved_quality);
399     return write_status;
403 /*
404   Local Variables:
405   mode:c++
406   c-file-style:"stroustrup"
407   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
408   indent-tabs-mode:nil
409   fill-column:99
410   End:
411 */
412 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :