Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
png-write.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * PNG file format utilities
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * Whoever wrote this example in libpng documentation
8 * Peter Bostrom
9 * Jon A. Cruz <jon@joncruz.org>
10 * Abhishek Sharma
11 *
12 * Copyright (C) 1999-2002 authors
13 *
14 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
15 */
16
17
18#include <2geom/rect.h>
19#include <2geom/transforms.h>
20
21#include <png.h>
22
23#include "colors/color.h"
24#include "document.h"
25#include "png-write.h"
26#include "rdf.h"
27
28#include "display/cairo-utils.h"
30#include "display/drawing.h"
31
32#include "io/sys.h"
33
34#include "object/sp-defs.h"
35#include "object/sp-item.h"
36#include "object/sp-root.h"
37
38#include "ui/interface.h"
39#include <glibmm/convert.h>
40
41/* This is an example of how to use libpng to read and write PNG files.
42 * The file libpng.txt is much more verbose then this. If you have not
43 * read it, do so first. This was designed to be a starting point of an
44 * implementation. This is not officially part of libpng, and therefore
45 * does not require a copyright notice.
46 *
47 * This file does not currently compile, because it is missing certain
48 * parts, like allocating memory to hold an image. You will have to
49 * supply these parts to get it to compile. For an example of a minimal
50 * working PNG reader/writer, see pngtest.c, included in this distribution.
51 */
52
53struct SPEBP {
54 unsigned long int width, height, sheight;
55 std::optional<Colors::Color> background;
56 Inkscape::Drawing *drawing; // it is assumed that all unneeded items are hidden
57 guchar *px;
58 unsigned (*status)(float, void *);
59 void *data;
60};
61
62/* write a png file */
63
64struct SPPNGBD {
65 guchar const *px;
66 int rowstride;
67};
68
72class PngTextList {
73public:
74 PngTextList() : count(0), textItems(nullptr) {}
75 ~PngTextList();
76
77 void add(gchar const* key, gchar const* text);
78 gint getCount() {return count;}
79 png_text* getPtext() {return textItems;}
80
81private:
82 gint count;
83 png_text* textItems;
84};
85
86PngTextList::~PngTextList() {
87 for (gint i = 0; i < count; i++) {
88 if (textItems[i].key) {
89 g_free(textItems[i].key);
90 }
91 if (textItems[i].text) {
92 g_free(textItems[i].text);
93 }
94 }
95}
96
97void PngTextList::add(gchar const* key, gchar const* text)
98{
99 if (count < 0) {
100 count = 0;
101 textItems = nullptr;
102 }
103 png_text* tmp = (count > 0) ? g_try_renew(png_text, textItems, count + 1): g_try_new(png_text, 1);
104 if (tmp) {
105 textItems = tmp;
106 count++;
107
108 png_text* item = &(textItems[count - 1]);
109 item->compression = PNG_TEXT_COMPRESSION_NONE;
110 item->key = g_strdup(key);
111 item->text = g_strdup(text);
112 item->text_length = 0;
113#ifdef PNG_iTXt_SUPPORTED
114 item->itxt_length = 0;
115 item->lang = nullptr;
116 item->lang_key = nullptr;
117#endif // PNG_iTXt_SUPPORTED
118 } else {
119 g_warning("Unable to allocate array for %d PNG text data.", count);
120 textItems = nullptr;
121 count = 0;
122 }
123}
124
130static bool
132 gchar const *filename, unsigned long int width, unsigned long int height, double xdpi, double ydpi,
133 int (* get_rows)(guchar const **rows, void **to_free, int row, int num_rows, void *data, int color_type, int bit_depth),
134 void *data, bool interlace, int color_type, int bit_depth, int zlib)
135{
136 g_return_val_if_fail(filename != nullptr, false);
137 g_return_val_if_fail(data != nullptr, false);
138
139 struct SPEBP *ebp = (struct SPEBP *) data;
140 FILE *fp;
141 png_structp png_ptr;
142 png_infop info_ptr;
143 png_color_8 sig_bit;
144 png_uint_32 r;
145
146 /* open the file */
147
148 Inkscape::IO::dump_fopen_call(filename, "M");
149 fp = Inkscape::IO::fopen_utf8name(filename, "wb");
150 if(fp == nullptr) return false;
151
152 /* Create and initialize the png_struct with the desired error handler
153 * functions. If you want to use the default stderr and longjump method,
154 * you can supply NULL for the last three parameters. We also check that
155 * the library version is compatible with the one used at compile time,
156 * in case we are using dynamically linked libraries. REQUIRED.
157 */
158 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
159
160 if (png_ptr == nullptr) {
161 fclose(fp);
162 return false;
163 }
164
165 /* Allocate/initialize the image information data. REQUIRED */
166 info_ptr = png_create_info_struct(png_ptr);
167 if (info_ptr == nullptr) {
168 fclose(fp);
169 png_destroy_write_struct(&png_ptr, nullptr);
170 return false;
171 }
172
173 /* Set error handling. REQUIRED if you aren't supplying your own
174 * error handling functions in the png_create_write_struct() call.
175 */
176 if (setjmp(png_jmpbuf(png_ptr))) {
177 // If we get here, we had a problem reading the file
178 fclose(fp);
179 png_destroy_write_struct(&png_ptr, &info_ptr);
180 return false;
181 }
182
183 /* set up the output control if you are using standard C streams */
184 png_init_io(png_ptr, fp);
185
186 /* Set the image information here. Width and height are up to 2^31,
187 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
188 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
189 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
190 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
191 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
192 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
193 */
194
195 png_set_compression_level(png_ptr, zlib);
196
197 png_set_IHDR(png_ptr, info_ptr,
198 width,
199 height,
200 bit_depth,
201 color_type,
202 interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE,
203 PNG_COMPRESSION_TYPE_BASE,
204 PNG_FILTER_TYPE_BASE);
205
206 if ((color_type&2) && bit_depth == 16) {
207 // otherwise, if we are dealing with a color image then
208 sig_bit.red = 8;
209 sig_bit.green = 8;
210 sig_bit.blue = 8;
211 // if the image has an alpha channel then
212 if (color_type&4)
213 sig_bit.alpha = 8;
214 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
215 }
216
217 PngTextList textList;
218
219 textList.add("Software", "www.inkscape.org"); // Made by Inkscape comment
220 {
221 const gchar* pngToDc[] = {"Title", "title",
222 "Author", "creator",
223 "Description", "description",
224 //"Copyright", "",
225 "Creation Time", "date",
226 //"Disclaimer", "",
227 //"Warning", "",
228 "Source", "source"
229 //"Comment", ""
230 };
231 for (size_t i = 0; i < G_N_ELEMENTS(pngToDc); i += 2) {
232 struct rdf_work_entity_t * entity = rdf_find_entity ( pngToDc[i + 1] );
233 if (entity) {
234 gchar const* data = rdf_get_work_entity(doc, entity);
235 if (data && *data) {
236 textList.add(pngToDc[i], data);
237 }
238 } else {
239 g_warning("Unable to find entity [%s]", pngToDc[i + 1]);
240 }
241 }
242
243
244 struct rdf_license_t *license = rdf_get_license(doc, true);
245 if (license) {
246 if (license->name && license->uri) {
247 gchar* tmp = g_strdup_printf("%s %s", license->name, license->uri);
248 textList.add("Copyright", tmp);
249 g_free(tmp);
250 } else if (license->name) {
251 textList.add("Copyright", license->name);
252 } else if (license->uri) {
253 textList.add("Copyright", license->uri);
254 }
255 }
256 }
257 if (textList.getCount() > 0) {
258 png_set_text(png_ptr, info_ptr, textList.getPtext(), textList.getCount());
259 }
260
261 /* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs, */
262 /* note that if sRGB is present the cHRM chunk must be ignored
263 * on read and must be written in accordance with the sRGB profile */
264 if(xdpi < 0.0254 ) xdpi = 0.0255;
265 if(ydpi < 0.0254 ) ydpi = 0.0255;
266
267 png_set_pHYs(png_ptr, info_ptr, unsigned(xdpi / 0.0254 ), unsigned(ydpi / 0.0254 ), PNG_RESOLUTION_METER);
268
269 /* Write the file header information. REQUIRED */
270 png_write_info(png_ptr, info_ptr);
271
272 /* Once we write out the header, the compression type on the text
273 * chunks gets changed to PNG_TEXT_COMPRESSION_NONE_WR or
274 * PNG_TEXT_COMPRESSION_zTXt_WR, so it doesn't get written out again
275 * at the end.
276 */
277
278 /* set up the transformations you want. Note that these are
279 * all optional. Only call them if you want them.
280 */
281
282 /* --- CUT --- */
283
284 /* The easiest way to write the image (you may have a different memory
285 * layout, however, so choose what fits your needs best). You need to
286 * use the first method if you aren't handling interlacing yourself.
287 */
288
289 png_bytep* row_pointers = new png_bytep[ebp->sheight];
290 int number_of_passes = interlace ? png_set_interlace_handling(png_ptr) : 1;
291
292 for(int i=0;i<number_of_passes; ++i){
293 r = 0;
294 while (r < static_cast<png_uint_32>(height)) {
295 void *to_free;
296 int n = get_rows((unsigned char const **) row_pointers, &to_free, r, height-r, data, color_type, bit_depth);
297 if (!n) break;
298 png_write_rows(png_ptr, row_pointers, n);
299 g_free(to_free);
300 r += n;
301 }
302 }
303
304 delete[] row_pointers;
305
306 /* You can write optional chunks like tEXt, zTXt, and tIME at the end
307 * as well.
308 */
309
310 /* It is REQUIRED to call this to finish writing the rest of the file */
311 png_write_end(png_ptr, info_ptr);
312
313 /* if you allocated any text comments, free them here */
314
315 /* clean up after the write, and free any memory allocated */
316 png_destroy_write_struct(&png_ptr, &info_ptr);
317
318 /* close the file */
319 fclose(fp);
320
321 /* that's it */
322 return true;
323}
324
325
329static int
330sp_export_get_rows(guchar const **rows, void **to_free, int row, int num_rows, void *data, int color_type, int bit_depth)
331{
332 struct SPEBP *ebp = (struct SPEBP *) data;
333
334 if (ebp->status) {
335 if (!ebp->status((float) row / ebp->height, ebp->data)) return 0;
336 }
337 if (!ebp->background) {
338 return 0;
339 }
340
341 num_rows = MIN(num_rows, static_cast<int>(ebp->sheight));
342 num_rows = MIN(num_rows, static_cast<int>(ebp->height - row));
343
344 /* Set area of interest */
345 // bbox is now set to the entire image to prevent discontinuities
346 // in the image when blur is used (the borders may still be a bit
347 // off, but that's less noticeable).
348 Geom::IntRect bbox = Geom::IntRect::from_xywh(0, row, ebp->width, num_rows);
349
350 /* Update to renderable state */
351 ebp->drawing->update(bbox);
352
353 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, ebp->width);
354 unsigned char *px = g_new(guchar, num_rows * stride);
355
356 cairo_surface_t *s = cairo_image_surface_create_for_data(
357 px, CAIRO_FORMAT_ARGB32, ebp->width, num_rows, stride);
358 Inkscape::DrawingContext dc(s, bbox.min());
359 dc.setSource(*ebp->background);
360 dc.setOperator(CAIRO_OPERATOR_SOURCE);
361 dc.paint();
362 dc.setOperator(CAIRO_OPERATOR_OVER);
363
364 /* Render */
365 ebp->drawing->render(dc, bbox, 0);
366 cairo_surface_destroy(s);
367
368 // PNG stores data as unpremultiplied big-endian RGBA, which means
369 // it's identical to the GdkPixbuf format.
370 convert_pixels_argb32_to_pixbuf(px, ebp->width, num_rows, stride, ebp->background->toARGB());
371
372 // If a custom bit depth or color type is asked, then convert rgb to grayscale, etc.
373 const guchar* new_data = pixbuf_to_png(rows, px, num_rows, ebp->width, stride, color_type, bit_depth);
374 *to_free = (void*) new_data;
375 free(px);
376
377 return num_rows;
378}
379
380ExportResult sp_export_png_file(SPDocument *doc, gchar const *filename,
381 double x0, double y0, double x1, double y1,
382 unsigned long int width, unsigned long int height, double xdpi, double ydpi,
383 Colors::Color const &bgcolor,
384 unsigned int (*status) (float, void *),
385 void *data, bool force_overwrite,
386 const std::vector<SPItem const *> &items_only, bool interlace, int color_type, int bit_depth, int zlib, int antialiasing)
387{
388 return sp_export_png_file(doc, filename, Geom::Rect(Geom::Point(x0,y0),Geom::Point(x1,y1)),
389 width, height, xdpi, ydpi, bgcolor, status, data, force_overwrite, items_only, interlace, color_type, bit_depth, zlib, antialiasing);
390}
391
398ExportResult sp_export_png_file(SPDocument *doc, gchar const *filename,
399 Geom::Rect const &area,
400 unsigned long width, unsigned long height, double xdpi, double ydpi,
401 Colors::Color const &bgcolor,
402 unsigned (*status)(float, void *),
403 void *data, bool force_overwrite,
404 const std::vector<SPItem const *> &items_only, bool interlace, int color_type, int bit_depth, int zlib, int antialiasing)
405{
406 g_return_val_if_fail(doc != nullptr, EXPORT_ERROR);
407 g_return_val_if_fail(filename != nullptr, EXPORT_ERROR);
408 g_return_val_if_fail(width >= 1, EXPORT_ERROR);
409 g_return_val_if_fail(height >= 1, EXPORT_ERROR);
410 g_return_val_if_fail(!area.hasZeroArea(), EXPORT_ERROR);
411
412 if (!force_overwrite && !sp_ui_overwrite_file(Glib::filename_from_utf8(filename))) {
413 // aborted overwrite
414 return EXPORT_ABORTED;
415 }
416
417 doc->ensureUpToDate();
418
419 /* Calculate translation by transforming to document coordinates (flipping Y)*/
420 Geom::Point translation = -area.min();
421
422 /* This calculation is only valid when assumed that (x0,y0)= area.corner(0) and (x1,y1) = area.corner(2)
423 * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
424 * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
425 * 3) a[0] * x1 + a[2] * y1 + a[4] = width
426 * 4) a[1] * x0 + a[3] * y0 + a[5] = height
427 * 5) a[1] = 0.0;
428 * 6) a[2] = 0.0;
429 *
430 * (1,3) a[0] * x1 - a[0] * x0 = width
431 * a[0] = width / (x1 - x0)
432 * (2,4) a[3] * y0 - a[3] * y1 = height
433 * a[3] = height / (y0 - y1)
434 * (1) a[4] = -a[0] * x0
435 * (2) a[5] = -a[3] * y1
436 */
437
438 Geom::Affine const affine(Geom::Translate(translation)
439 * Geom::Scale(width / area.width(),
440 height / area.height()));
441
442 struct SPEBP ebp;
443 ebp.width = width;
444 ebp.height = height;
445 ebp.background = bgcolor;
446
447 /* Create new drawing */
448 Inkscape::Drawing drawing;
449 unsigned const dkey = SPItem::display_key_new(1);
450 drawing.setRoot(doc->getRoot()->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY));
451 drawing.root()->setTransform(affine);
452 drawing.setExact(); // export with maximum blur rendering quality
453 drawing.setAntialiasingOverride(static_cast<Inkscape::Antialiasing>(antialiasing));
454
455 ebp.drawing = &drawing;
456
457 // We show all and then hide all items we don't want, instead of showing only requested items,
458 // because that would not work if the shown item references something in defs
459 if (!items_only.empty()) {
460 doc->getRoot()->invoke_hide_except(dkey, items_only);
461 }
462
463 ebp.status = status;
464 ebp.data = data;
465
466 bool write_status = false;;
467
468 ebp.sheight = 64;
469 ebp.px = g_try_new(guchar, 4 * ebp.sheight * width);
470
471 if (ebp.px) {
472 write_status = sp_png_write_rgba_striped(doc, filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp, interlace, color_type, bit_depth, zlib);
473 g_free(ebp.px);
474 }
475
476 // Hide items, this releases arenaitem
477 doc->getRoot()->invoke_hide(dkey);
478
479 return write_status ? EXPORT_OK : EXPORT_ERROR;
480}
481
482
483/*
484 Local Variables:
485 mode:c++
486 c-file-style:"stroustrup"
487 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
488 indent-tabs-mode:nil
489 fill-column:99
490 End:
491*/
492// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
const guchar * pixbuf_to_png(guchar const **rows, guchar *px, int num_rows, int num_cols, int stride, int color_type, int bit_depth)
Converts a pixbuf to a PNG data structure.
void convert_pixels_argb32_to_pixbuf(guchar *data, int w, int h, int stride, guint32 bgcolor)
Convert pixel data from ARGB to GdkPixbuf format.
Cairo integration helpers.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Axis aligned, non-empty, generic rectangle.
static CRect from_xywh(C x, C y, C w, C h)
Create rectangle from origin and dimensions.
C height() const
Get the vertical extent of the rectangle.
C width() const
Get the horizontal extent of the rectangle.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
bool hasZeroArea(Coord eps=EPSILON) const
Check whether the rectangle has zero area up to specified tolerance.
Definition rect.h:113
Scaling from the origin.
Definition transforms.h:150
Translation by a vector.
Definition transforms.h:115
Minimal wrapper over Cairo.
void setSource(cairo_pattern_t *source)
void paint(double alpha=1.0)
void setOperator(cairo_operator_t op)
void setTransform(Geom::Affine const &trans)
void setRoot(DrawingItem *root)
Definition drawing.cpp:67
DrawingItem * root()
Definition drawing.h:47
void setAntialiasingOverride(std::optional< Antialiasing > antialiasing_override)
Definition drawing.cpp:222
Typed SVG document implementation.
Definition document.h:101
SPRoot * getRoot()
Returns our SPRoot.
Definition document.h:200
int ensureUpToDate(unsigned int object_modified_tag=0)
Repeatedly works on getting the document updated, since sometimes it takes more than one pass to get ...
Inkscape::DrawingItem * invoke_show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags)
Definition sp-item.cpp:1277
void invoke_hide(unsigned int key)
Definition sp-item.cpp:1328
static unsigned int display_key_new(unsigned numkeys)
Allocates unique integer keys.
Definition sp-item.cpp:1254
void invoke_hide_except(unsigned key, const std::vector< SPItem const * > &to_keep)
Invoke hide on all non-group items, except for the list of items to keep.
Definition sp-item.cpp:1366
Glib::ustring lang
Definition sp-object.h:185
Cairo drawing context with Inkscape extensions.
struct _cairo_surface cairo_surface_t
SVG drawing for display.
SPItem * item
bool sp_ui_overwrite_file(std::string const &filename)
If necessary, ask the user if a file may be overwritten.
Definition interface.cpp:74
void dump_fopen_call(char const *utf8name, char const *id)
Definition sys.cpp:37
FILE * fopen_utf8name(char const *utf8name, char const *mode)
Open a file with g_fopen().
Definition sys.cpp:72
static cairo_user_data_key_t key
int stride
unsigned char * data
static int sp_export_get_rows(guchar const **rows, void **to_free, int row, int num_rows, void *data, int color_type, int bit_depth)
static bool sp_png_write_rgba_striped(SPDocument *doc, gchar const *filename, unsigned long int width, unsigned long int height, double xdpi, double ydpi, int(*get_rows)(guchar const **rows, void **to_free, int row, int num_rows, void *data, int color_type, int bit_depth), void *data, bool interlace, int color_type, int bit_depth, int zlib)
Write to PNG.
ExportResult sp_export_png_file(SPDocument *doc, gchar const *filename, double x0, double y0, double x1, double y1, unsigned long int width, unsigned long int height, double xdpi, double ydpi, Colors::Color const &bgcolor, unsigned int(*status)(float, void *), void *data, bool force_overwrite, const std::vector< SPItem const * > &items_only, bool interlace, int color_type, int bit_depth, int zlib, int antialiasing)
ExportResult
Definition png-write.h:30
@ EXPORT_ABORTED
Definition png-write.h:33
@ EXPORT_ERROR
Definition png-write.h:31
@ EXPORT_OK
Definition png-write.h:32
struct rdf_work_entity_t * rdf_find_entity(gchar const *name)
Retrieves a known RDF/Work entity by name.
Definition rdf.cpp:364
struct rdf_license_t * rdf_get_license(SPDocument *document, bool read_only)
Attempts to match and retrieve a known RDF/License from the document XML.
Definition rdf.cpp:1047
const gchar * rdf_get_work_entity(SPDocument const *doc, struct rdf_work_entity_t *entity)
Retrieves a known RDF/Work entity's contents from the document XML by name.
Definition rdf.cpp:885
headers for RDF types
Axis-aligned rectangle.
Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx.
SPRoot: SVG <svg> implementation.
static const Point data[]
Holds license name and RDF information.
Definition rdf.h:33
char const * uri
Definition rdf.h:35
char const * name
Definition rdf.h:34
Holds known RDF/Work tags.
Definition rdf.h:71
double height
double width
Affine transformation classes.