Code

6d9f58ee3dd351ad8076b89b9202d26da926819a
[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 <glib/gmessages.h>
22 #include <png.h>
23 #include "png-write.h"
24 #include "io/sys.h"
25 #include <display/nr-arena-item.h>
26 #include <display/nr-arena.h>
27 #include <document.h>
28 #include <sp-item.h>
29 #include <sp-root.h>
30 #include <sp-defs.h>
31 #include "prefs-utils.h"
33 /* This is an example of how to use libpng to read and write PNG files.
34  * The file libpng.txt is much more verbose then this.  If you have not
35  * read it, do so first.  This was designed to be a starting point of an
36  * implementation.  This is not officially part of libpng, and therefore
37  * does not require a copyright notice.
38  *
39  * This file does not currently compile, because it is missing certain
40  * parts, like allocating memory to hold an image.  You will have to
41  * supply these parts to get it to compile.  For an example of a minimal
42  * working PNG reader/writer, see pngtest.c, included in this distribution.
43  */
45 const unsigned int MAX_STRIPE_SIZE = 1024*1024;
47 struct SPEBP {
48     int width, height, sheight;
49     guchar r, g, b, a;
50     NRArenaItem *root; // the root arena item to show; it is assumed that all unneeded items are hidden
51     guchar *px;
52     unsigned (*status)(float, void *);
53     void *data;
54 };
56 /* write a png file */
58 typedef struct SPPNGBD {
59         const guchar *px;
60         int rowstride;
61 } SPPNGBD;
63 static int
64 sp_png_get_block_stripe (const guchar **rows, int row, int num_rows, void *data)
65 {
66         SPPNGBD *bd = (SPPNGBD *) data;
68         for (int r = 0; r < num_rows; r++) {
69                 rows[r] = bd->px + (row + r) * bd->rowstride;
70         }
72         return num_rows;
73 }
75 int
76 sp_png_write_rgba (const gchar *filename, const guchar *px, int width, int height, double xdpi, double ydpi, int rowstride)
77 {
78         SPPNGBD bd;
80         bd.px = px;
81         bd.rowstride = rowstride;
83         return sp_png_write_rgba_striped (filename, width, height, xdpi, ydpi, sp_png_get_block_stripe, &bd);
84 }
86 int
87 sp_png_write_rgba_striped (const gchar *filename, int width, int height, double xdpi, double ydpi,
88                            int (* get_rows) (const guchar **rows, int row, int num_rows, void *data),
89                            void *data)
90 {
91     struct SPEBP *ebp = (struct SPEBP *) data;
92         FILE *fp;
93         png_structp png_ptr;
94         png_infop info_ptr;
95         png_color_8 sig_bit;
96         png_text text_ptr[3];
97         png_uint_32 r;
99         g_return_val_if_fail (filename != NULL, FALSE);
101         /* open the file */
103         Inkscape::IO::dump_fopen_call(filename, "M");
104         fp = Inkscape::IO::fopen_utf8name(filename, "wb");
105         g_return_val_if_fail (fp != NULL, FALSE);
107         /* Create and initialize the png_struct with the desired error handler
108          * functions.  If you want to use the default stderr and longjump method,
109          * you can supply NULL for the last three parameters.  We also check that
110          * the library version is compatible with the one used at compile time,
111          * in case we are using dynamically linked libraries.  REQUIRED.
112          */
113         png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
115         if (png_ptr == NULL) {
116                 fclose(fp);
117                 return FALSE;
118         }
120         /* Allocate/initialize the image information data.  REQUIRED */
121         info_ptr = png_create_info_struct(png_ptr);
122         if (info_ptr == NULL) {
123                 fclose(fp);
124                 png_destroy_write_struct(&png_ptr, NULL);
125                 return FALSE;
126         }
128         /* Set error handling.  REQUIRED if you aren't supplying your own
129          * error hadnling functions in the png_create_write_struct() call.
130          */
131         if (setjmp(png_ptr->jmpbuf)) {
132                 /* If we get here, we had a problem reading the file */
133                 fclose(fp);
134                 png_destroy_write_struct(&png_ptr, &info_ptr);
135                 return FALSE;
136         }
138         /* set up the output control if you are using standard C streams */
139         png_init_io(png_ptr, fp);
141         /* Set the image information here.  Width and height are up to 2^31,
142          * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
143          * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
144          * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
145          * or PNG_COLOR_TYPE_RGB_ALPHA.  interlace is either PNG_INTERLACE_NONE or
146          * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
147          * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
148          */
149         png_set_IHDR(png_ptr, info_ptr,
150                      width,
151                      height,
152                      8, /* bit_depth */
153                      PNG_COLOR_TYPE_RGB_ALPHA,
154                      PNG_INTERLACE_NONE,
155                      PNG_COMPRESSION_TYPE_BASE,
156                      PNG_FILTER_TYPE_BASE);
158         /* otherwise, if we are dealing with a color image then */
159         sig_bit.red = 8;
160         sig_bit.green = 8;
161         sig_bit.blue = 8;
162         /* if the image has an alpha channel then */
163         sig_bit.alpha = 8;
164         png_set_sBIT(png_ptr, info_ptr, &sig_bit);
166         /* Made by Inkscape comment */
167         text_ptr[0].key = "Software";
168         text_ptr[0].text = "www.inkscape.org";
169         text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
170         png_set_text(png_ptr, info_ptr, text_ptr, 1);
172         /* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs, */
173         /* note that if sRGB is present the cHRM chunk must be ignored
174          * on read and must be written in accordance with the sRGB profile */
175         png_set_pHYs(png_ptr, info_ptr, unsigned(xdpi / 0.0254 + 0.5), unsigned(ydpi / 0.0254 + 0.5), PNG_RESOLUTION_METER); 
177         /* Write the file header information.  REQUIRED */
178         png_write_info(png_ptr, info_ptr);
180         /* Once we write out the header, the compression type on the text
181          * chunks gets changed to PNG_TEXT_COMPRESSION_NONE_WR or
182          * PNG_TEXT_COMPRESSION_zTXt_WR, so it doesn't get written out again
183          * at the end.
184          */
186         /* set up the transformations you want.  Note that these are
187          * all optional.  Only call them if you want them.
188          */
190         /* --- CUT --- */
192         /* The easiest way to write the image (you may have a different memory
193          * layout, however, so choose what fits your needs best).  You need to
194          * use the first method if you aren't handling interlacing yourself.
195          */
197         png_bytep* row_pointers = new png_bytep[ebp->sheight];
199         r = 0;
200         while (r < static_cast< png_uint_32 > (height) ) {
201                 int n = get_rows ((const unsigned char **) row_pointers, r, height-r, data);
202                 if (!n) break;
203                 png_write_rows (png_ptr, row_pointers, n);
204                 r += n;
205         }
207     delete[] row_pointers;
209         /* You can write optional chunks like tEXt, zTXt, and tIME at the end
210          * as well.
211          */
213         /* It is REQUIRED to call this to finish writing the rest of the file */
214         png_write_end(png_ptr, info_ptr);
216         /* if you allocated any text comments, free them here */
218         /* clean up after the write, and free any memory allocated */
219         png_destroy_write_struct(&png_ptr, &info_ptr);
221         /* close the file */
222         fclose(fp);
224         /* that's it */
225         return TRUE;
229 /**
230  *
231  */
232 static int
233 sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
235     struct SPEBP *ebp = (struct SPEBP *) data;
237     if (ebp->status) {
238         if (!ebp->status((float) row / ebp->height, ebp->data)) return 0;
239     }
241     num_rows = MIN(num_rows, ebp->sheight);
242     num_rows = MIN(num_rows, ebp->height - row);
244     /* Set area of interest */
245     // bbox is now set to the entire image to prevent discontinuities
246     // in the image when blur is used (the borders may still be a bit
247     // off, but that's less noticeable).
248     NRRectL bbox;
249     bbox.x0 = 0;
250     bbox.y0 = 0;//row;
251     bbox.x1 = ebp->width;
252     bbox.y1 = ebp->height;//row + num_rows;
253     /* Update to renderable state */
254     NRGC gc(NULL);
255     nr_matrix_set_identity(&gc.transform);
257     nr_arena_item_invoke_update(ebp->root, &bbox, &gc,
258            NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE);
260     NRPixBlock pb;
261     nr_pixblock_setup_extern(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
262                              bbox.x0, row/*bbox.y0*/, bbox.x1, row + num_rows/*bbox.y1*/,
263                              ebp->px, 4 * ebp->width, FALSE, FALSE);
265     for (int r = 0; r < num_rows; r++) {
266         guchar *p = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
267         for (int c = 0; c < ebp->width; c++) {
268             *p++ = ebp->r;
269             *p++ = ebp->g;
270             *p++ = ebp->b;
271             *p++ = ebp->a;
272         }
273     }
275     /* Render */
276     nr_arena_item_invoke_render(ebp->root, &bbox, &pb, 0);
278     for (int r = 0; r < num_rows; r++) {
279         rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
280     }
282     nr_pixblock_release(&pb);
284     return num_rows;
287 /**
288 Hide all items which are not listed in list, recursively, skipping groups and defs
289 */
290 static void
291 hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey)
293     if (SP_IS_ITEM(o)
294         && !SP_IS_DEFS(o)
295         && !SP_IS_ROOT(o)
296         && !SP_IS_GROUP(o)
297         && !g_slist_find(list, o))
298     {
299         sp_item_invoke_hide(SP_ITEM(o), dkey);
300     }
302      // recurse
303     if (!g_slist_find(list, o)) {
304         for (SPObject *child = sp_object_first_child(o) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
305             hide_other_items_recursively(child, list, dkey);
306         }
307     }
311 /**
312  *  Render the SVG drawing onto a PNG raster image, then save to
313  *  a file.  Returns TRUE if succeeded in writing the file,
314  *  FALSE otherwise.
315  */
316 int
317 sp_export_png_file(SPDocument *doc, gchar const *filename,
318                    double x0, double y0, double x1, double y1,
319                    unsigned width, unsigned height, double xdpi, double ydpi,
320                    unsigned long bgcolor,
321                    unsigned (*status)(float, void *),
322                    void *data, bool force_overwrite,
323                    GSList *items_only)
325     int write_status = TRUE;
326     g_return_val_if_fail(doc != NULL, FALSE);
327     g_return_val_if_fail(filename != NULL, FALSE);
328     g_return_val_if_fail(width >= 1, FALSE);
329     g_return_val_if_fail(height >= 1, FALSE);
331     if (!force_overwrite && !sp_ui_overwrite_file(filename)) {
332         return FALSE;
333     }
335     // export with maximum blur rendering quality
336     int saved_quality = prefs_get_int_attribute ("options.blurquality", "value", 0);
337     prefs_set_int_attribute ("options.blurquality", "value", 2);
339     sp_document_ensure_up_to_date(doc);
341     /* Go to document coordinates */
342     gdouble t = y0;
343     y0 = sp_document_height(doc) - y1;
344     y1 = sp_document_height(doc) - t;
346     /*
347      * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
348      * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
349      * 3) a[0] * x1 + a[2] * y1 + a[4] = width
350      * 4) a[1] * x0 + a[3] * y0 + a[5] = height
351      * 5) a[1] = 0.0;
352      * 6) a[2] = 0.0;
353      *
354      * (1,3) a[0] * x1 - a[0] * x0 = width
355      * a[0] = width / (x1 - x0)
356      * (2,4) a[3] * y0 - a[3] * y1 = height
357      * a[3] = height / (y0 - y1)
358      * (1) a[4] = -a[0] * x0
359      * (2) a[5] = -a[3] * y1
360      */
362     NRMatrix affine;
363     affine.c[0] = width / (x1 - x0);
364     affine.c[1] = 0.0;
365     affine.c[2] = 0.0;
366     affine.c[3] = height / (y1 - y0);
367     affine.c[4] = -affine.c[0] * x0;
368     affine.c[5] = -affine.c[3] * y0;
370     //SP_PRINT_MATRIX("SVG2PNG", &affine);
372     struct SPEBP ebp;
373     ebp.width  = width;
374     ebp.height = height;
375     ebp.r      = NR_RGBA32_R(bgcolor);
376     ebp.g      = NR_RGBA32_G(bgcolor);
377     ebp.b      = NR_RGBA32_B(bgcolor);
378     ebp.a      = NR_RGBA32_A(bgcolor);
380     /* Create new arena */
381     NRArena *arena = NRArena::create();
382     unsigned dkey = sp_item_display_key_new(1);
384     /* Create ArenaItems and set transform */
385     ebp.root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)), arena, dkey, SP_ITEM_SHOW_DISPLAY);
386     nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), NR::Matrix(&affine));
388     // We show all and then hide all items we don't want, instead of showing only requested items,
389     // because that would not work if the shown item references something in defs
390     if (items_only) {
391         hide_other_items_recursively(sp_document_root(doc), items_only, dkey);
392     }
394     ebp.status = status;
395     ebp.data   = data;
397     if (4 * width * height < 65536) {
398         ebp.px = nr_pixelstore_64K_new(FALSE, 0);
399         ebp.sheight = height;
400         write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
401         nr_pixelstore_64K_free(ebp.px);
402     } else {
403         ebp.sheight = MAX(1,MIN(MAX_STRIPE_SIZE / (4 * width),height));
404         ebp.px = g_new(guchar, 4 * width * ebp.sheight);
405         write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
406         g_free(ebp.px);
407     }
409     // Hide items
410     sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), dkey);
412     /* Free Arena and ArenaItem */
413     nr_arena_item_unref(ebp.root);
414     nr_object_unref((NRObject *) arena);
416     // restore saved blur quality
417     prefs_set_int_attribute ("options.blurquality", "value", saved_quality);
419     return write_status;