Minor refactoring, related to lzah
[deark.git] / src / deark-png.c
blob869d946a10f3c3e8d32c03e2637e917d1075fcf9
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 #define DE_MAX_IDAT_CHUNKSIZE 1048576
14 // TODO: Finish removing the "mz" symbols, and other miniz things.
15 #define MY_MZ_MIN(a,b) (((a)<(b))?(a):(b))
16 #define MY_TDEFL_WRITE_ZLIB_HEADER 0x01000
18 #define CODE_IDAT 0x49444154U
19 #define CODE_IEND 0x49454e44U
20 #define CODE_IHDR 0x49484452U
21 #define CODE_htSP 0x68745350U
22 #define CODE_pHYs 0x70485973U
23 #define CODE_tEXt 0x74455874U
24 #define CODE_tIME 0x74494d45U
26 struct deark_png_encode_info {
27 deark *c;
28 dbuf *outf;
29 int width, height;
30 int rowspan;
31 int num_chans;
32 int flip;
33 unsigned int level;
34 int has_phys;
35 u32 xdens;
36 u32 ydens;
37 u8 phys_units;
38 struct de_timestamp internal_mod_time;
39 u8 include_text_chunk_software;
40 u8 has_hotspot;
41 int hotspot_x, hotspot_y;
42 struct de_crcobj *crco;
45 static void write_png_chunk_from_mem(struct deark_png_encode_info *pei,
46 const u8 *mem, i64 memlen, u32 chunktype)
48 u32 crc;
49 u8 tmpbuf[4];
51 de_crcobj_reset(pei->crco);
53 // length field
54 dbuf_writeu32be(pei->outf, memlen);
56 // chunk type field
57 de_writeu32be_direct(tmpbuf, (i64)chunktype);
58 de_crcobj_addbuf(pei->crco, tmpbuf, 4);
59 dbuf_write(pei->outf, tmpbuf, 4);
61 // data field
62 de_crcobj_addbuf(pei->crco, mem, memlen);
63 dbuf_write(pei->outf, mem, memlen);
65 // CRC field
66 crc = de_crcobj_getval(pei->crco);
67 dbuf_writeu32be(pei->outf, (i64)crc);
70 static void write_png_chunk_from_cdbuf(struct deark_png_encode_info *pei,
71 dbuf *cdbuf, u32 chunktype)
73 const u8 *mem;
75 mem = dbuf_get_membuf_direct_ptr(cdbuf);
76 if(mem==NULL && cdbuf->len!=0) goto done;
77 write_png_chunk_from_mem(pei, mem, cdbuf->len, chunktype);
79 done:
83 static void write_png_chunk_IHDR(struct deark_png_encode_info *pei,
84 dbuf *cdbuf)
86 static const u8 color_type_code[] = {0x00, 0x00, 0x04, 0x02, 0x06};
88 dbuf_writeu32be(cdbuf, (i64)pei->width);
89 dbuf_writeu32be(cdbuf, (i64)pei->height);
90 dbuf_writebyte(cdbuf, 8); // bit depth
91 dbuf_writebyte(cdbuf, color_type_code[pei->num_chans]);
92 dbuf_truncate(cdbuf, 13); // rest of chunk is zeroes
93 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_IHDR);
96 static void write_png_chunk_pHYs(struct deark_png_encode_info *pei,
97 dbuf *cdbuf)
99 dbuf_writeu32be(cdbuf, (i64)pei->xdens);
100 dbuf_writeu32be(cdbuf, (i64)pei->ydens);
101 dbuf_writebyte(cdbuf, pei->phys_units);
102 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_pHYs);
105 static void write_png_chunk_tIME(struct deark_png_encode_info *pei,
106 dbuf *cdbuf)
108 struct de_struct_tm tm2;
110 de_gmtime(&pei->internal_mod_time, &tm2);
111 if(!tm2.is_valid) return;
113 dbuf_writeu16be(cdbuf, (i64)tm2.tm_fullyear);
114 dbuf_writebyte(cdbuf, (u8)(1+tm2.tm_mon));
115 dbuf_writebyte(cdbuf, (u8)tm2.tm_mday);
116 dbuf_writebyte(cdbuf, (u8)tm2.tm_hour);
117 dbuf_writebyte(cdbuf, (u8)tm2.tm_min);
118 dbuf_writebyte(cdbuf, (u8)tm2.tm_sec);
119 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_tIME);
122 static void write_png_chunk_htSP(struct deark_png_encode_info *pei,
123 dbuf *cdbuf)
125 // This is the UUID b9fe4f3d-8f32-456f-aa02-dcd79cce0e24
126 static const u8 uuid[16] = {0xb9,0xfe,0x4f,0x3d,0x8f,0x32,0x45,0x6f,
127 0xaa,0x02,0xdc,0xd7,0x9c,0xce,0x0e,0x24};
129 dbuf_write(cdbuf, uuid, 16);
130 dbuf_writei32be(cdbuf, (i64)pei->hotspot_x);
131 dbuf_writei32be(cdbuf, (i64)pei->hotspot_y);
132 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_htSP);
135 static void write_png_chunk_tEXt(struct deark_png_encode_info *pei,
136 dbuf *cdbuf, const char *keyword, const char *value)
138 i64 kwlen, vlen;
139 kwlen = (i64)de_strlen(keyword);
140 vlen = (i64)de_strlen(value);
141 dbuf_write(cdbuf, (const u8*)keyword, kwlen);
142 dbuf_writebyte(cdbuf, 0);
143 dbuf_write(cdbuf, (const u8*)value, vlen);
144 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_tEXt);
147 struct IDAT_write_userdata_struct {
148 struct deark_png_encode_info *pei;
149 dbuf *cdbuf;
150 int IDAT_count;
153 static void my_IDAT_write_cb(dbuf *f, void *userdata,
154 const u8 *mem_orig, i64 memsize_orig)
156 struct IDAT_write_userdata_struct *iwu = (struct IDAT_write_userdata_struct*)userdata;
157 const u8 *mem = mem_orig;
158 i64 memsize = memsize_orig;
160 // We *could* just write a single IDAT chunk for each call to this function.
161 // We would get a PNG with IDATs having various random-ish sizes.
162 // I want the PNG output to be more predictable and stable than that, though,
163 // so we'll do some buffering to make each chunk (except the last) have
164 // DE_MAX_IDAT_CHUNKSIZE bytes.
166 while(iwu->cdbuf->len + memsize >= DE_MAX_IDAT_CHUNKSIZE) {
167 i64 amt_to_use_from_membuf;
169 if(iwu->cdbuf->len >= DE_MAX_IDAT_CHUNKSIZE) {
170 amt_to_use_from_membuf = 0;
172 else {
173 amt_to_use_from_membuf = DE_MAX_IDAT_CHUNKSIZE - iwu->cdbuf->len;
176 dbuf_write(iwu->cdbuf, mem, amt_to_use_from_membuf);
177 mem += amt_to_use_from_membuf;
178 memsize -= amt_to_use_from_membuf;
179 // cdbuf should now have exactly DE_MAX_IDAT_CHUNKSIZE bytes in it.
181 write_png_chunk_from_cdbuf(iwu->pei, iwu->cdbuf, CODE_IDAT);
182 iwu->IDAT_count++;
183 dbuf_empty(iwu->cdbuf);
186 // Save any remaining compressed pixel data for next time.
187 if(memsize > 0) {
188 dbuf_write(iwu->cdbuf, mem, memsize);
192 static int write_png_chunk_IDATs(struct deark_png_encode_info *pei, dbuf *cdbuf,
193 const u8 *src_pixels)
195 int y;
196 static const char nulbyte = '\0';
197 int retval = 0;
198 deark *c = pei->c;
199 struct fmtutil_tdefl_ctx *tdctx = NULL;
200 static const unsigned int my_s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 };
201 dbuf *outf_IDAT = NULL;
202 struct IDAT_write_userdata_struct iwu;
204 de_zeromem(&iwu, sizeof(struct IDAT_write_userdata_struct));
205 iwu.pei = pei;
206 iwu.cdbuf = cdbuf;
208 outf_IDAT = dbuf_create_custom_dbuf(c, 0, 0);
209 outf_IDAT->userdata_for_customwrite = (void*)&iwu;
210 outf_IDAT->customwrite_fn = my_IDAT_write_cb;
212 // compress image data
213 tdctx = fmtutil_tdefl_create(c, outf_IDAT,
214 my_s_tdefl_num_probes[MY_MZ_MIN(10, pei->level)] | MY_TDEFL_WRITE_ZLIB_HEADER);
216 for (y = 0; y < pei->height; ++y) {
217 fmtutil_tdefl_compress_buffer(tdctx, &nulbyte, 1, FMTUTIL_TDEFL_NO_FLUSH);
218 fmtutil_tdefl_compress_buffer(tdctx, &src_pixels[(pei->flip ? (pei->height - 1 - y) : y) * pei->rowspan],
219 (size_t)pei->width * (size_t)pei->num_chans, FMTUTIL_TDEFL_NO_FLUSH);
221 if (fmtutil_tdefl_compress_buffer(tdctx, NULL, 0, FMTUTIL_TDEFL_FINISH) !=
222 FMTUTIL_TDEFL_STATUS_DONE)
224 goto done;
227 if(cdbuf->len>0 || iwu.IDAT_count==0) {
228 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_IDAT);
231 retval = 1;
233 done:
234 fmtutil_tdefl_destroy(tdctx);
235 dbuf_close(outf_IDAT);
236 return retval;
239 static int do_generate_png(struct deark_png_encode_info *pei, const u8 *src_pixels)
241 static const u8 pngsig[8] = { 0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a };
242 dbuf *cdbuf = NULL;
243 int retval = 0;
245 // A membuf that we'll use and reuse for each chunk's data
246 cdbuf = dbuf_create_membuf(pei->c, 64, 0);
248 dbuf_write(pei->outf, pngsig, 8);
250 write_png_chunk_IHDR(pei, cdbuf);
252 if(pei->has_phys) {
253 dbuf_truncate(cdbuf, 0);
254 write_png_chunk_pHYs(pei, cdbuf);
257 if(pei->internal_mod_time.is_valid && pei->c->preserve_file_times_internal) {
258 dbuf_truncate(cdbuf, 0);
259 write_png_chunk_tIME(pei, cdbuf);
262 if(pei->has_hotspot) {
263 dbuf_truncate(cdbuf, 0);
264 write_png_chunk_htSP(pei, cdbuf);
267 if(pei->include_text_chunk_software) {
268 dbuf_truncate(cdbuf, 0);
269 write_png_chunk_tEXt(pei, cdbuf, "Software", "Deark");
272 dbuf_truncate(cdbuf, 0);
273 if(!write_png_chunk_IDATs(pei, cdbuf, src_pixels)) goto done;
275 dbuf_truncate(cdbuf, 0);
276 write_png_chunk_from_cdbuf(pei, cdbuf, CODE_IEND);
277 retval = 1;
279 done:
280 dbuf_close(cdbuf);
281 return retval;
284 int de_write_png(deark *c, de_bitmap *img, dbuf *f, UI createflags)
286 const char *opt_level;
287 int retval = 0;
288 struct deark_png_encode_info *pei = NULL;
290 pei = de_malloc(c, sizeof(struct deark_png_encode_info));
291 pei->c = c;
293 if(img->invalid_image_flag) {
294 goto done;
296 if(!de_good_image_dimensions(c, img->width, img->height)) {
297 goto done;
300 if(f->btype==DBUF_TYPE_NULL) {
301 goto done;
304 if(f->fi_copy && f->fi_copy->density.code>0 && c->write_density) {
305 pei->has_phys = 1;
306 if(f->fi_copy->density.code==1) { // unspecified units
307 pei->phys_units = 0;
308 pei->xdens = (u32)(f->fi_copy->density.xdens+0.5);
309 pei->ydens = (u32)(f->fi_copy->density.ydens+0.5);
311 else if(f->fi_copy->density.code==2) { // dpi
312 pei->phys_units = 1; // pixels/meter
313 pei->xdens = (u32)(0.5+f->fi_copy->density.xdens/0.0254);
314 pei->ydens = (u32)(0.5+f->fi_copy->density.ydens/0.0254);
318 if(pei->has_phys && pei->xdens==pei->ydens && pei->phys_units==0) {
319 // Useless density information. Don't bother to write it.
320 pei->has_phys = 0;
323 // Detect likely-bogus density settings.
324 if(pei->has_phys) {
325 if(pei->xdens<=0 || pei->ydens<=0 ||
326 (pei->xdens > pei->ydens*5) || (pei->ydens > pei->xdens*5))
328 pei->has_phys = 0;
332 pei->outf = f;
333 if(!c->padpix && img->unpadded_width>0 && img->unpadded_width<img->width) {
334 pei->width = (int)img->unpadded_width;
336 else {
337 pei->width = (int)img->width;
339 pei->rowspan = (int)(img->width * img->bytes_per_pixel);
340 pei->height = (int)img->height;
341 pei->flip = (createflags & DE_CREATEFLAG_FLIP_IMAGE)?1:0;
342 pei->num_chans = img->bytes_per_pixel;
343 pei->include_text_chunk_software = 0;
345 if(!c->pngcprlevel_valid) {
346 c->pngcmprlevel = 9; // default
347 c->pngcprlevel_valid = 1;
349 opt_level = de_get_ext_option(c, "pngcmprlevel");
350 if(opt_level) {
351 i64 opt_level_n = de_atoi64(opt_level);
352 if(opt_level_n>10) {
353 c->pngcmprlevel = 10;
355 else if(opt_level_n<0) {
356 c->pngcmprlevel = 6;
358 else {
359 c->pngcmprlevel = (unsigned int)opt_level_n;
363 pei->level = c->pngcmprlevel;
365 if(f->fi_copy && f->fi_copy->internal_mod_time.is_valid) {
366 pei->internal_mod_time = f->fi_copy->internal_mod_time;
369 if(f->fi_copy && f->fi_copy->has_hotspot) {
370 pei->has_hotspot = 1;
371 pei->hotspot_x = f->fi_copy->hotspot_x;
372 pei->hotspot_y = f->fi_copy->hotspot_y;
373 // Leave a hint as to where our custom Hotspot chunk came from.
374 pei->include_text_chunk_software = 1;
377 pei->crco = de_crcobj_create(c, DE_CRCOBJ_CRC32_IEEE);
379 if(!do_generate_png(pei, img->bitmap)) {
380 de_err(c, "PNG write failed");
381 goto done;
384 retval = 1;
386 done:
387 if(pei) {
388 de_crcobj_destroy(pei->crco);
389 de_free(c, pei);
391 return retval;