Updated version number to 1.5.6
[deark.git] / src / deark-png.c
blob9537a6667b74ea6e01f32a3edab0b7c70329b626
1 // This file is part of Deark.
2 // Copyright (C) 2016-2019 Jason Summers
3 // See the file COPYING for terms of use.
5 // PNG encoding
7 #define DE_NOT_IN_MODULE
8 #include "deark-config.h"
9 #include "deark-private.h"
10 #include "deark-fmtutil.h"
12 // TODO: Finish removing the "mz" symbols, and other miniz things.
13 #define MY_MZ_MIN(a,b) (((a)<(b))?(a):(b))
14 #define MY_TDEFL_WRITE_ZLIB_HEADER 0x01000
16 #define CODE_IDAT 0x49444154U
17 #define CODE_IEND 0x49454e44U
18 #define CODE_IHDR 0x49484452U
19 #define CODE_htSP 0x68745350U
20 #define CODE_pHYs 0x70485973U
21 #define CODE_tEXt 0x74455874U
22 #define CODE_tIME 0x74494d45U
24 struct deark_png_encode_info {
25 deark *c;
26 dbuf *outf;
27 int width, height;
28 int num_chans;
29 int flip;
30 unsigned int level;
31 int has_phys;
32 u32 xdens;
33 u32 ydens;
34 u8 phys_units;
35 struct de_timestamp internal_mod_time;
36 u8 include_text_chunk_software;
37 u8 has_hotspot;
38 int hotspot_x, hotspot_y;
39 struct de_crcobj *crco;
42 static void write_png_chunk_from_cdbuf(struct deark_png_encode_info *pei,
43 dbuf *cdbuf, u32 chunktype)
45 u32 crc;
46 u8 buf[4];
48 de_crcobj_reset(pei->crco);
50 // length field
51 dbuf_writeu32be(pei->outf, cdbuf->len);
53 // chunk type field
54 de_writeu32be_direct(buf, (i64)chunktype);
55 de_crcobj_addbuf(pei->crco, buf, 4);
56 dbuf_write(pei->outf, buf, 4);
58 // data field
59 de_crcobj_addslice(pei->crco, cdbuf, 0, cdbuf->len);
60 dbuf_copy(cdbuf, 0, cdbuf->len, pei->outf);
62 // CRC field
63 crc = de_crcobj_getval(pei->crco);
64 dbuf_writeu32be(pei->outf, (i64)crc);
67 static void write_png_chunk_IHDR(struct deark_png_encode_info *pei,
68 dbuf *cdbuf)
70 static const u8 color_type_code[] = {0x00, 0x00, 0x04, 0x02, 0x06};
72 dbuf_writeu32be(cdbuf, (i64)pei->width);
73 dbuf_writeu32be(cdbuf, (i64)pei->height);
74 dbuf_writebyte(cdbuf, 8); // bit depth
75 dbuf_writebyte(cdbuf, color_type_code[pei->num_chans]);
76 dbuf_truncate(cdbuf, 13); // rest of chunk is zeroes
77 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_IHDR);
80 static void write_png_chunk_pHYs(struct deark_png_encode_info *pei,
81 dbuf *cdbuf)
83 dbuf_writeu32be(cdbuf, (i64)pei->xdens);
84 dbuf_writeu32be(cdbuf, (i64)pei->ydens);
85 dbuf_writebyte(cdbuf, pei->phys_units);
86 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_pHYs);
89 static void write_png_chunk_tIME(struct deark_png_encode_info *pei,
90 dbuf *cdbuf)
92 struct de_struct_tm tm2;
94 de_gmtime(&pei->internal_mod_time, &tm2);
95 if(!tm2.is_valid) return;
97 dbuf_writeu16be(cdbuf, (i64)tm2.tm_fullyear);
98 dbuf_writebyte(cdbuf, (u8)(1+tm2.tm_mon));
99 dbuf_writebyte(cdbuf, (u8)tm2.tm_mday);
100 dbuf_writebyte(cdbuf, (u8)tm2.tm_hour);
101 dbuf_writebyte(cdbuf, (u8)tm2.tm_min);
102 dbuf_writebyte(cdbuf, (u8)tm2.tm_sec);
103 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_tIME);
106 static void write_png_chunk_htSP(struct deark_png_encode_info *pei,
107 dbuf *cdbuf)
109 // This is the UUID b9fe4f3d-8f32-456f-aa02-dcd79cce0e24
110 static const u8 uuid[16] = {0xb9,0xfe,0x4f,0x3d,0x8f,0x32,0x45,0x6f,
111 0xaa,0x02,0xdc,0xd7,0x9c,0xce,0x0e,0x24};
113 dbuf_write(cdbuf, uuid, 16);
114 dbuf_writei32be(cdbuf, (i64)pei->hotspot_x);
115 dbuf_writei32be(cdbuf, (i64)pei->hotspot_y);
116 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_htSP);
119 static void write_png_chunk_tEXt(struct deark_png_encode_info *pei,
120 dbuf *cdbuf, const char *keyword, const char *value)
122 i64 kwlen, vlen;
123 kwlen = (i64)de_strlen(keyword);
124 vlen = (i64)de_strlen(value);
125 dbuf_write(cdbuf, (const u8*)keyword, kwlen);
126 dbuf_writebyte(cdbuf, 0);
127 dbuf_write(cdbuf, (const u8*)value, vlen);
128 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_tEXt);
131 static int write_png_chunk_IDAT(struct deark_png_encode_info *pei, dbuf *cdbuf,
132 const u8 *src_pixels)
134 int bpl = pei->width * pei->num_chans; // bytes per row in src_pixels
135 int y;
136 static const char nulbyte = '\0';
137 int retval = 0;
138 deark *c = pei->c;
139 struct fmtutil_tdefl_ctx *tdctx = NULL;
140 static const unsigned int my_s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 };
142 // compress image data
143 tdctx = fmtutil_tdefl_create(c, cdbuf,
144 my_s_tdefl_num_probes[MY_MZ_MIN(10, pei->level)] | MY_TDEFL_WRITE_ZLIB_HEADER);
146 for (y = 0; y < pei->height; ++y) {
147 fmtutil_tdefl_compress_buffer(tdctx, &nulbyte, 1, FMTUTIL_TDEFL_NO_FLUSH);
148 fmtutil_tdefl_compress_buffer(tdctx, &src_pixels[(pei->flip ? (pei->height - 1 - y) : y) * bpl],
149 bpl, FMTUTIL_TDEFL_NO_FLUSH);
151 if (fmtutil_tdefl_compress_buffer(tdctx, NULL, 0, FMTUTIL_TDEFL_FINISH) !=
152 FMTUTIL_TDEFL_STATUS_DONE)
154 goto done;
157 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_IDAT);
159 retval = 1;
161 done:
162 fmtutil_tdefl_destroy(tdctx);
163 return retval;
166 static int do_generate_png(struct deark_png_encode_info *pei, const u8 *src_pixels)
168 static const u8 pngsig[8] = { 0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a };
169 dbuf *cdbuf = NULL;
170 int retval = 0;
172 // A membuf that we'll use and reuse for each chunk's data
173 cdbuf = dbuf_create_membuf(pei->c, 64, 0);
175 dbuf_write(pei->outf, pngsig, 8);
177 write_png_chunk_IHDR(pei, cdbuf);
179 if(pei->has_phys) {
180 dbuf_truncate(cdbuf, 0);
181 write_png_chunk_pHYs(pei, cdbuf);
184 if(pei->internal_mod_time.is_valid && pei->c->preserve_file_times_internal) {
185 dbuf_truncate(cdbuf, 0);
186 write_png_chunk_tIME(pei, cdbuf);
189 if(pei->has_hotspot) {
190 dbuf_truncate(cdbuf, 0);
191 write_png_chunk_htSP(pei, cdbuf);
194 if(pei->include_text_chunk_software) {
195 dbuf_truncate(cdbuf, 0);
196 write_png_chunk_tEXt(pei, cdbuf, "Software", "Deark");
199 dbuf_truncate(cdbuf, 0);
200 if(!write_png_chunk_IDAT(pei, cdbuf, src_pixels)) goto done;
202 dbuf_truncate(cdbuf, 0);
203 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_IEND);
204 retval = 1;
206 done:
207 dbuf_close(cdbuf);
208 return retval;
211 int de_write_png(deark *c, de_bitmap *img, dbuf *f)
213 const char *opt_level;
214 int retval = 0;
215 struct deark_png_encode_info *pei = NULL;
217 pei = de_malloc(c, sizeof(struct deark_png_encode_info));
218 pei->c = c;
220 if(img->invalid_image_flag) {
221 goto done;
223 if(!de_good_image_dimensions(c, img->width, img->height)) {
224 goto done;
227 if(f->btype==DBUF_TYPE_NULL) {
228 goto done;
231 if(f->fi_copy && f->fi_copy->density.code>0 && c->write_density) {
232 pei->has_phys = 1;
233 if(f->fi_copy->density.code==1) { // unspecified units
234 pei->phys_units = 0;
235 pei->xdens = (u32)(f->fi_copy->density.xdens+0.5);
236 pei->ydens = (u32)(f->fi_copy->density.ydens+0.5);
238 else if(f->fi_copy->density.code==2) { // dpi
239 pei->phys_units = 1; // pixels/meter
240 pei->xdens = (u32)(0.5+f->fi_copy->density.xdens/0.0254);
241 pei->ydens = (u32)(0.5+f->fi_copy->density.ydens/0.0254);
245 if(pei->has_phys && pei->xdens==pei->ydens && pei->phys_units==0) {
246 // Useless density information. Don't bother to write it.
247 pei->has_phys = 0;
250 // Detect likely-bogus density settings.
251 if(pei->has_phys) {
252 if(pei->xdens<=0 || pei->ydens<=0 ||
253 (pei->xdens > pei->ydens*5) || (pei->ydens > pei->xdens*5))
255 pei->has_phys = 0;
259 pei->outf = f;
260 pei->width = (int)img->width;
261 pei->height = (int)img->height;
262 pei->flip = img->flipped;
263 pei->num_chans = img->bytes_per_pixel;
264 pei->include_text_chunk_software = 0;
266 if(!c->pngcprlevel_valid) {
267 c->pngcmprlevel = 9; // default
268 c->pngcprlevel_valid = 1;
270 opt_level = de_get_ext_option(c, "pngcmprlevel");
271 if(opt_level) {
272 i64 opt_level_n = de_atoi64(opt_level);
273 if(opt_level_n>10) {
274 c->pngcmprlevel = 10;
276 else if(opt_level_n<0) {
277 c->pngcmprlevel = 6;
279 else {
280 c->pngcmprlevel = (unsigned int)opt_level_n;
284 pei->level = c->pngcmprlevel;
286 if(f->fi_copy && f->fi_copy->internal_mod_time.is_valid) {
287 pei->internal_mod_time = f->fi_copy->internal_mod_time;
290 if(f->fi_copy && f->fi_copy->has_hotspot) {
291 pei->has_hotspot = 1;
292 pei->hotspot_x = f->fi_copy->hotspot_x;
293 pei->hotspot_y = f->fi_copy->hotspot_y;
294 // Leave a hint as to where our custom Hotspot chunk came from.
295 pei->include_text_chunk_software = 1;
298 pei->crco = de_crcobj_create(c, DE_CRCOBJ_CRC32_IEEE);
300 if(!do_generate_png(pei, img->bitmap)) {
301 de_err(c, "PNG write failed");
302 goto done;
305 retval = 1;
307 done:
308 if(pei) {
309 de_crcobj_destroy(pei->crco);
310 de_free(c, pei);
312 return retval;