1 // This file is part of Deark.
2 // Copyright (C) 2016-2019 Jason Summers
3 // See the file COPYING for terms of use.
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
{
38 struct de_timestamp internal_mod_time
;
39 u8 include_text_chunk_software
;
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
)
51 de_crcobj_reset(pei
->crco
);
54 dbuf_writeu32be(pei
->outf
, memlen
);
57 de_writeu32be_direct(tmpbuf
, (i64
)chunktype
);
58 de_crcobj_addbuf(pei
->crco
, tmpbuf
, 4);
59 dbuf_write(pei
->outf
, tmpbuf
, 4);
62 de_crcobj_addbuf(pei
->crco
, mem
, memlen
);
63 dbuf_write(pei
->outf
, mem
, memlen
);
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
)
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
);
83 static void write_png_chunk_IHDR(struct deark_png_encode_info
*pei
,
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
,
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
,
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
,
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
)
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
;
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;
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
);
183 dbuf_empty(iwu
->cdbuf
);
186 // Save any remaining compressed pixel data for next time.
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
)
196 static const char nulbyte
= '\0';
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
));
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
)
227 if(cdbuf
->len
>0 || iwu
.IDAT_count
==0) {
228 write_png_chunk_from_cdbuf(pei
, cdbuf
, CODE_IDAT
);
234 fmtutil_tdefl_destroy(tdctx
);
235 dbuf_close(outf_IDAT
);
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 };
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
);
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
);
284 int de_write_png(deark
*c
, de_bitmap
*img
, dbuf
*f
, UI createflags
)
286 const char *opt_level
;
288 struct deark_png_encode_info
*pei
= NULL
;
290 pei
= de_malloc(c
, sizeof(struct deark_png_encode_info
));
293 if(img
->invalid_image_flag
) {
296 if(!de_good_image_dimensions(c
, img
->width
, img
->height
)) {
300 if(f
->btype
==DBUF_TYPE_NULL
) {
304 if(f
->fi_copy
&& f
->fi_copy
->density
.code
>0 && c
->write_density
) {
306 if(f
->fi_copy
->density
.code
==1) { // unspecified units
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.
323 // Detect likely-bogus density settings.
325 if(pei
->xdens
<=0 || pei
->ydens
<=0 ||
326 (pei
->xdens
> pei
->ydens
*5) || (pei
->ydens
> pei
->xdens
*5))
333 if(!c
->padpix
&& img
->unpadded_width
>0 && img
->unpadded_width
<img
->width
) {
334 pei
->width
= (int)img
->unpadded_width
;
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");
351 i64 opt_level_n
= de_atoi64(opt_level
);
353 c
->pngcmprlevel
= 10;
355 else if(opt_level_n
<0) {
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");
388 de_crcobj_destroy(pei
->crco
);