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 int
65 sp_png_get_block_stripe(guchar const **rows, int row, int num_rows, void *data)
66 {
67 SPPNGBD *bd = (SPPNGBD *) data;
69 for (int r = 0; r < num_rows; r++) {
70 rows[r] = bd->px + (row + r) * bd->rowstride;
71 }
73 return num_rows;
74 }
76 int
77 sp_png_write_rgba(gchar const *filename, guchar const *px,
78 int width, int height, double xdpi, double ydpi, int rowstride)
79 {
80 SPPNGBD bd;
82 bd.px = px;
83 bd.rowstride = rowstride;
85 return sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_png_get_block_stripe, &bd);
86 }
88 int
89 sp_png_write_rgba_striped(gchar const *filename, int width, int height, double xdpi, double ydpi,
90 int (* get_rows)(guchar const **rows, int row, int num_rows, void *data),
91 void *data)
92 {
93 struct SPEBP *ebp = (struct SPEBP *) data;
94 FILE *fp;
95 png_structp png_ptr;
96 png_infop info_ptr;
97 png_color_8 sig_bit;
98 png_text text_ptr[3];
99 png_uint_32 r;
101 g_return_val_if_fail(filename != NULL, FALSE);
103 /* open the file */
105 Inkscape::IO::dump_fopen_call(filename, "M");
106 fp = Inkscape::IO::fopen_utf8name(filename, "wb");
107 g_return_val_if_fail(fp != NULL, FALSE);
109 /* Create and initialize the png_struct with the desired error handler
110 * functions. If you want to use the default stderr and longjump method,
111 * you can supply NULL for the last three parameters. We also check that
112 * the library version is compatible with the one used at compile time,
113 * in case we are using dynamically linked libraries. REQUIRED.
114 */
115 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
117 if (png_ptr == NULL) {
118 fclose(fp);
119 return FALSE;
120 }
122 /* Allocate/initialize the image information data. REQUIRED */
123 info_ptr = png_create_info_struct(png_ptr);
124 if (info_ptr == NULL) {
125 fclose(fp);
126 png_destroy_write_struct(&png_ptr, NULL);
127 return FALSE;
128 }
130 /* Set error handling. REQUIRED if you aren't supplying your own
131 * error hadnling functions in the png_create_write_struct() call.
132 */
133 if (setjmp(png_ptr->jmpbuf)) {
134 /* If we get here, we had a problem reading the file */
135 fclose(fp);
136 png_destroy_write_struct(&png_ptr, &info_ptr);
137 return FALSE;
138 }
140 /* set up the output control if you are using standard C streams */
141 png_init_io(png_ptr, fp);
143 /* Set the image information here. Width and height are up to 2^31,
144 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
145 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
146 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
147 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
148 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
149 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
150 */
151 png_set_IHDR(png_ptr, info_ptr,
152 width,
153 height,
154 8, /* bit_depth */
155 PNG_COLOR_TYPE_RGB_ALPHA,
156 PNG_INTERLACE_NONE,
157 PNG_COMPRESSION_TYPE_BASE,
158 PNG_FILTER_TYPE_BASE);
160 /* otherwise, if we are dealing with a color image then */
161 sig_bit.red = 8;
162 sig_bit.green = 8;
163 sig_bit.blue = 8;
164 /* if the image has an alpha channel then */
165 sig_bit.alpha = 8;
166 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
168 /* Made by Inkscape comment */
169 text_ptr[0].key = "Software";
170 text_ptr[0].text = "www.inkscape.org";
171 text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
172 png_set_text(png_ptr, info_ptr, text_ptr, 1);
174 /* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs, */
175 /* note that if sRGB is present the cHRM chunk must be ignored
176 * on read and must be written in accordance with the sRGB profile */
177 png_set_pHYs(png_ptr, info_ptr, unsigned(xdpi / 0.0254 + 0.5), unsigned(ydpi / 0.0254 + 0.5), PNG_RESOLUTION_METER);
179 /* Write the file header information. REQUIRED */
180 png_write_info(png_ptr, info_ptr);
182 /* Once we write out the header, the compression type on the text
183 * chunks gets changed to PNG_TEXT_COMPRESSION_NONE_WR or
184 * PNG_TEXT_COMPRESSION_zTXt_WR, so it doesn't get written out again
185 * at the end.
186 */
188 /* set up the transformations you want. Note that these are
189 * all optional. Only call them if you want them.
190 */
192 /* --- CUT --- */
194 /* The easiest way to write the image (you may have a different memory
195 * layout, however, so choose what fits your needs best). You need to
196 * use the first method if you aren't handling interlacing yourself.
197 */
199 png_bytep* row_pointers = new png_bytep[ebp->sheight];
201 r = 0;
202 while (r < static_cast< png_uint_32 > (height) ) {
203 int n = get_rows((unsigned char const **) row_pointers, r, height-r, data);
204 if (!n) break;
205 png_write_rows(png_ptr, row_pointers, n);
206 r += n;
207 }
209 delete[] row_pointers;
211 /* You can write optional chunks like tEXt, zTXt, and tIME at the end
212 * as well.
213 */
215 /* It is REQUIRED to call this to finish writing the rest of the file */
216 png_write_end(png_ptr, info_ptr);
218 /* if you allocated any text comments, free them here */
220 /* clean up after the write, and free any memory allocated */
221 png_destroy_write_struct(&png_ptr, &info_ptr);
223 /* close the file */
224 fclose(fp);
226 /* that's it */
227 return TRUE;
228 }
231 /**
232 *
233 */
234 static int
235 sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
236 {
237 struct SPEBP *ebp = (struct SPEBP *) data;
239 if (ebp->status) {
240 if (!ebp->status((float) row / ebp->height, ebp->data)) return 0;
241 }
243 num_rows = MIN(num_rows, ebp->sheight);
244 num_rows = MIN(num_rows, ebp->height - row);
246 /* Set area of interest */
247 // bbox is now set to the entire image to prevent discontinuities
248 // in the image when blur is used (the borders may still be a bit
249 // off, but that's less noticeable).
250 NRRectL bbox;
251 bbox.x0 = 0;
252 bbox.y0 = 0;//row;
253 bbox.x1 = ebp->width;
254 bbox.y1 = ebp->height;//row + num_rows;
255 /* Update to renderable state */
256 NRGC gc(NULL);
257 nr_matrix_set_identity(&gc.transform);
259 nr_arena_item_invoke_update(ebp->root, &bbox, &gc,
260 NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE);
262 NRPixBlock pb;
263 nr_pixblock_setup_extern(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
264 bbox.x0, row/*bbox.y0*/, bbox.x1, row + num_rows/*bbox.y1*/,
265 ebp->px, 4 * ebp->width, FALSE, FALSE);
267 for (int r = 0; r < num_rows; r++) {
268 guchar *p = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
269 for (int c = 0; c < ebp->width; c++) {
270 *p++ = ebp->r;
271 *p++ = ebp->g;
272 *p++ = ebp->b;
273 *p++ = ebp->a;
274 }
275 }
277 /* Render */
278 nr_arena_item_invoke_render(NULL, ebp->root, &bbox, &pb, 0);
280 for (int r = 0; r < num_rows; r++) {
281 rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
282 }
284 nr_pixblock_release(&pb);
286 return num_rows;
287 }
289 /**
290 * Hide all items that are not listed in list, recursively, skipping groups and defs.
291 */
292 static void
293 hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey)
294 {
295 if ( SP_IS_ITEM(o)
296 && !SP_IS_DEFS(o)
297 && !SP_IS_ROOT(o)
298 && !SP_IS_GROUP(o)
299 && !g_slist_find(list, o) )
300 {
301 sp_item_invoke_hide(SP_ITEM(o), dkey);
302 }
304 // recurse
305 if (!g_slist_find(list, o)) {
306 for (SPObject *child = sp_object_first_child(o) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
307 hide_other_items_recursively(child, list, dkey);
308 }
309 }
310 }
313 /**
314 * Render the SVG drawing onto a PNG raster image, then save to
315 * a file. Returns TRUE if succeeded in writing the file,
316 * FALSE otherwise.
317 */
318 int
319 sp_export_png_file(SPDocument *doc, gchar const *filename,
320 double x0, double y0, double x1, double y1,
321 unsigned width, unsigned height, double xdpi, double ydpi,
322 unsigned long bgcolor,
323 unsigned (*status)(float, void *),
324 void *data, bool force_overwrite,
325 GSList *items_only)
326 {
327 int write_status = TRUE;
328 g_return_val_if_fail(doc != NULL, FALSE);
329 g_return_val_if_fail(filename != NULL, FALSE);
330 g_return_val_if_fail(width >= 1, FALSE);
331 g_return_val_if_fail(height >= 1, FALSE);
333 if (!force_overwrite && !sp_ui_overwrite_file(filename)) {
334 return FALSE;
335 }
337 // export with maximum blur rendering quality
338 int saved_quality = prefs_get_int_attribute("options.blurquality", "value", 0);
339 prefs_set_int_attribute("options.blurquality", "value", 2);
341 sp_document_ensure_up_to_date(doc);
343 /* Go to document coordinates */
344 {
345 gdouble const t = y0;
346 y0 = sp_document_height(doc) - y1;
347 y1 = sp_document_height(doc) - t;
348 }
350 /*
351 * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
352 * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
353 * 3) a[0] * x1 + a[2] * y1 + a[4] = width
354 * 4) a[1] * x0 + a[3] * y0 + a[5] = height
355 * 5) a[1] = 0.0;
356 * 6) a[2] = 0.0;
357 *
358 * (1,3) a[0] * x1 - a[0] * x0 = width
359 * a[0] = width / (x1 - x0)
360 * (2,4) a[3] * y0 - a[3] * y1 = height
361 * a[3] = height / (y0 - y1)
362 * (1) a[4] = -a[0] * x0
363 * (2) a[5] = -a[3] * y1
364 */
366 NR::Matrix const affine(NR::translate(-x0, -y0)
367 * NR::scale(width / (x1 - x0),
368 height / (y1 - 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 *const arena = NRArena::create();
382 unsigned const 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), 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 (width * height < 65536 / 4) {
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;
420 }
423 /*
424 Local Variables:
425 mode:c++
426 c-file-style:"stroustrup"
427 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
428 indent-tabs-mode:nil
429 fill-column:99
430 End:
431 */
432 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :