1
#define __SP_PNG_WRITE_C__
4
* PNG file format utilities
7
* Lauris Kaplinski <lauris@kaplinski.com>
8
* Whoever wrote this example in libpng documentation
10
* Copyright (C) 1999-2002 authors
12
* Released under GNU GPL, read the file 'COPYING' for more information
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>
25
#include "png-write.h"
27
#include <display/nr-arena-item.h>
28
#include <display/nr-arena.h>
33
#include "preferences.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.
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.
48
static unsigned int const MAX_STRIPE_SIZE = 1024*1024;
51
unsigned long int width, height, sheight;
53
NRArenaItem *root; // the root arena item to show; it is assumed that all unneeded items are hidden
55
unsigned (*status)(float, void *);
59
/* write a png file */
61
typedef struct SPPNGBD {
67
* A simple wrapper to list png_text.
71
PngTextList() : count(0), textItems(0) {}
74
void add(gchar const* key, gchar const* text);
75
gint getCount() {return count;}
76
png_text* getPtext() {return textItems;}
83
PngTextList::~PngTextList() {
84
for (gint i = 0; i < count; i++) {
85
if (textItems[i].key) {
86
g_free(textItems[i].key);
88
if (textItems[i].text) {
89
g_free(textItems[i].text);
94
void PngTextList::add(gchar const* key, gchar const* text)
100
png_text* tmp = (count > 0) ? g_try_renew(png_text, textItems, count + 1): g_try_new(png_text, 1);
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;
114
#endif // PNG_iTXt_SUPPORTED
116
g_warning("Unable to allocate arrary for %d PNG text data.", count);
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),
128
struct SPEBP *ebp = (struct SPEBP *) data;
135
g_return_val_if_fail(filename != NULL, false);
136
g_return_val_if_fail(data != NULL, false);
140
Inkscape::IO::dump_fopen_call(filename, "M");
141
fp = Inkscape::IO::fopen_utf8name(filename, "wb");
142
g_return_val_if_fail(fp != NULL, false);
144
/* Create and initialize the png_struct with the desired error handler
145
* functions. If you want to use the default stderr and longjump method,
146
* you can supply NULL for the last three parameters. We also check that
147
* the library version is compatible with the one used at compile time,
148
* in case we are using dynamically linked libraries. REQUIRED.
150
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
152
if (png_ptr == NULL) {
157
/* Allocate/initialize the image information data. REQUIRED */
158
info_ptr = png_create_info_struct(png_ptr);
159
if (info_ptr == NULL) {
161
png_destroy_write_struct(&png_ptr, NULL);
165
/* Set error handling. REQUIRED if you aren't supplying your own
166
* error hadnling functions in the png_create_write_struct() call.
168
if (setjmp(png_ptr->jmpbuf)) {
169
/* If we get here, we had a problem reading the file */
171
png_destroy_write_struct(&png_ptr, &info_ptr);
175
/* set up the output control if you are using standard C streams */
176
png_init_io(png_ptr, fp);
178
/* Set the image information here. Width and height are up to 2^31,
179
* bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
180
* the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
181
* PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
182
* or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
183
* PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
184
* currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
186
png_set_IHDR(png_ptr, info_ptr,
190
PNG_COLOR_TYPE_RGB_ALPHA,
192
PNG_COMPRESSION_TYPE_BASE,
193
PNG_FILTER_TYPE_BASE);
195
/* otherwise, if we are dealing with a color image then */
199
/* if the image has an alpha channel then */
201
png_set_sBIT(png_ptr, info_ptr, &sig_bit);
203
PngTextList textList;
205
textList.add("Software", "www.inkscape.org"); // Made by Inkscape comment
207
const gchar* pngToDc[] = {"Title", "title",
209
"Description", "description",
211
"Creation Time", "date",
217
for (size_t i = 0; i < G_N_ELEMENTS(pngToDc); i += 2) {
218
struct rdf_work_entity_t * entity = rdf_find_entity ( pngToDc[i + 1] );
220
gchar const* data = rdf_get_work_entity(doc, entity);
222
textList.add(pngToDc[i], data);
225
g_warning("Unable to find entity [%s]", pngToDc[i + 1]);
230
struct rdf_license_t *license = rdf_get_license(doc);
232
if (license->name && license->uri) {
233
gchar* tmp = g_strdup_printf("%s %s", license->name, license->uri);
234
textList.add("Copyright", tmp);
236
} else if (license->name) {
237
textList.add("Copyright", license->name);
238
} else if (license->uri) {
239
textList.add("Copyright", license->uri);
243
if (textList.getCount() > 0) {
244
png_set_text(png_ptr, info_ptr, textList.getPtext(), textList.getCount());
247
/* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs, */
248
/* note that if sRGB is present the cHRM chunk must be ignored
249
* on read and must be written in accordance with the sRGB profile */
250
png_set_pHYs(png_ptr, info_ptr, unsigned(xdpi / 0.0254 + 0.5), unsigned(ydpi / 0.0254 + 0.5), PNG_RESOLUTION_METER);
252
/* Write the file header information. REQUIRED */
253
png_write_info(png_ptr, info_ptr);
255
/* Once we write out the header, the compression type on the text
256
* chunks gets changed to PNG_TEXT_COMPRESSION_NONE_WR or
257
* PNG_TEXT_COMPRESSION_zTXt_WR, so it doesn't get written out again
261
/* set up the transformations you want. Note that these are
262
* all optional. Only call them if you want them.
267
/* The easiest way to write the image (you may have a different memory
268
* layout, however, so choose what fits your needs best). You need to
269
* use the first method if you aren't handling interlacing yourself.
272
png_bytep* row_pointers = new png_bytep[ebp->sheight];
275
while (r < static_cast< png_uint_32 > (height) ) {
276
int n = get_rows((unsigned char const **) row_pointers, r, height-r, data);
278
png_write_rows(png_ptr, row_pointers, n);
282
delete[] row_pointers;
284
/* You can write optional chunks like tEXt, zTXt, and tIME at the end
288
/* It is REQUIRED to call this to finish writing the rest of the file */
289
png_write_end(png_ptr, info_ptr);
291
/* if you allocated any text comments, free them here */
293
/* clean up after the write, and free any memory allocated */
294
png_destroy_write_struct(&png_ptr, &info_ptr);
308
sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
310
struct SPEBP *ebp = (struct SPEBP *) data;
313
if (!ebp->status((float) row / ebp->height, ebp->data)) return 0;
316
num_rows = MIN(num_rows, static_cast<int>(ebp->sheight));
317
num_rows = MIN(num_rows, static_cast<int>(ebp->height - row));
319
/* Set area of interest */
320
// bbox is now set to the entire image to prevent discontinuities
321
// in the image when blur is used (the borders may still be a bit
322
// off, but that's less noticeable).
326
bbox.x1 = ebp->width;
327
bbox.y1 = row + num_rows;
328
/* Update to renderable state */
330
gc.transform.setIdentity();
332
nr_arena_item_invoke_update(ebp->root, &bbox, &gc,
333
NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE);
336
nr_pixblock_setup_extern(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
337
bbox.x0, bbox.y0, bbox.x1, bbox.y1,
338
ebp->px, 4 * ebp->width, FALSE, FALSE);
340
for (int r = 0; r < num_rows; r++) {
341
guchar *p = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
342
for (int c = 0; c < static_cast<int>(ebp->width); c++) {
351
nr_arena_item_invoke_render(NULL, ebp->root, &bbox, &pb, 0);
353
for (int r = 0; r < num_rows; r++) {
354
rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
357
nr_pixblock_release(&pb);
363
* Hide all items that are not listed in list, recursively, skipping groups and defs.
366
hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey)
372
&& !g_slist_find(list, o) )
374
sp_item_invoke_hide(SP_ITEM(o), dkey);
378
if (!g_slist_find(list, o)) {
379
for (SPObject *child = sp_object_first_child(o) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
380
hide_other_items_recursively(child, list, dkey);
387
* Export the given document as a Portable Network Graphics (PNG) file.
389
* \return true if succeeded (or if no action was taken), false if an error occurred.
391
bool sp_export_png_file (SPDocument *doc, gchar const *filename,
392
double x0, double y0, double x1, double y1,
393
unsigned long int width, unsigned long int height, double xdpi, double ydpi,
394
unsigned long bgcolor,
395
unsigned int (*status) (float, void *),
396
void *data, bool force_overwrite,
399
return sp_export_png_file(doc, filename, Geom::Rect(Geom::Point(x0,y0),Geom::Point(x1,y1)),
400
width, height, xdpi, ydpi, bgcolor, status, data, force_overwrite, items_only);
403
sp_export_png_file(SPDocument *doc, gchar const *filename,
404
Geom::Rect const &area,
405
unsigned long width, unsigned long height, double xdpi, double ydpi,
406
unsigned long bgcolor,
407
unsigned (*status)(float, void *),
408
void *data, bool force_overwrite,
411
g_return_val_if_fail(doc != NULL, false);
412
g_return_val_if_fail(filename != NULL, false);
413
g_return_val_if_fail(width >= 1, false);
414
g_return_val_if_fail(height >= 1, false);
415
g_return_val_if_fail(!area.hasZeroArea(), false);
417
//Make relative paths absolute, if possible:
419
if (!g_path_is_absolute(filename) && doc->uri) {
420
gchar *dirname = g_path_get_dirname(doc->uri);
422
path = g_build_filename(dirname, filename, NULL);
427
path = g_strdup(filename);
430
if (!force_overwrite && !sp_ui_overwrite_file(path)) {
431
/* Remark: We return true so as not to invoke an error dialog in case export is cancelled
432
by the user; currently this is safe because the callers only act when false is returned.
433
If this changes in the future we need better distinction of return types (e.g., use int)
438
sp_document_ensure_up_to_date(doc);
440
/* Calculate translation by transforming to document coordinates (flipping Y)*/
441
Geom::Point translation = Geom::Point(-area[Geom::X][0], area[Geom::Y][1] - sp_document_height(doc));
443
/* This calculation is only valid when assumed that (x0,y0)= area.corner(0) and (x1,y1) = area.corner(2)
444
* 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
445
* 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
446
* 3) a[0] * x1 + a[2] * y1 + a[4] = width
447
* 4) a[1] * x0 + a[3] * y0 + a[5] = height
451
* (1,3) a[0] * x1 - a[0] * x0 = width
452
* a[0] = width / (x1 - x0)
453
* (2,4) a[3] * y0 - a[3] * y1 = height
454
* a[3] = height / (y0 - y1)
455
* (1) a[4] = -a[0] * x0
456
* (2) a[5] = -a[3] * y1
459
Geom::Matrix const affine(Geom::Translate(translation)
460
* Geom::Scale(width / area.width(),
461
height / area.height()));
463
//SP_PRINT_MATRIX("SVG2PNG", &affine);
468
ebp.r = NR_RGBA32_R(bgcolor);
469
ebp.g = NR_RGBA32_G(bgcolor);
470
ebp.b = NR_RGBA32_B(bgcolor);
471
ebp.a = NR_RGBA32_A(bgcolor);
473
/* Create new arena */
474
NRArena *const arena = NRArena::create();
475
// export with maximum blur rendering quality
476
nr_arena_set_renderoffscreen(arena);
477
unsigned const dkey = sp_item_display_key_new(1);
479
/* Create ArenaItems and set transform */
480
ebp.root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)), arena, dkey, SP_ITEM_SHOW_DISPLAY);
481
nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), affine);
483
// We show all and then hide all items we don't want, instead of showing only requested items,
484
// because that would not work if the shown item references something in defs
486
hide_other_items_recursively(sp_document_root(doc), items_only, dkey);
493
if ((width < 256) || ((width * height) < 32768)) {
494
ebp.px = nr_pixelstore_64K_new(FALSE, 0);
495
ebp.sheight = 65536 / (4 * width);
496
write_status = sp_png_write_rgba_striped(doc, path, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
497
nr_pixelstore_64K_free(ebp.px);
500
ebp.px = g_try_new(guchar, 4 * ebp.sheight * width);
501
write_status = sp_png_write_rgba_striped(doc, path, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
505
// Hide items, this releases arenaitem
506
sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), dkey);
509
nr_object_unref((NRObject *) arena);
520
c-file-style:"stroustrup"
521
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
526
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :