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 // 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
{
35 struct de_timestamp internal_mod_time
;
36 u8 include_text_chunk_software
;
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
)
48 de_crcobj_reset(pei
->crco
);
51 dbuf_writeu32be(pei
->outf
, cdbuf
->len
);
54 de_writeu32be_direct(buf
, (i64
)chunktype
);
55 de_crcobj_addbuf(pei
->crco
, buf
, 4);
56 dbuf_write(pei
->outf
, buf
, 4);
59 de_crcobj_addslice(pei
->crco
, cdbuf
, 0, cdbuf
->len
);
60 dbuf_copy(cdbuf
, 0, cdbuf
->len
, pei
->outf
);
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
,
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
,
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
,
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
,
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
)
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
136 static const char nulbyte
= '\0';
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
)
157 write_png_chunk_from_cdbuf(pei
, cdbuf
, CODE_IDAT
);
162 fmtutil_tdefl_destroy(tdctx
);
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 };
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
);
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
);
211 int de_write_png(deark
*c
, de_bitmap
*img
, dbuf
*f
)
213 const char *opt_level
;
215 struct deark_png_encode_info
*pei
= NULL
;
217 pei
= de_malloc(c
, sizeof(struct deark_png_encode_info
));
220 if(img
->invalid_image_flag
) {
223 if(!de_good_image_dimensions(c
, img
->width
, img
->height
)) {
227 if(f
->btype
==DBUF_TYPE_NULL
) {
231 if(f
->fi_copy
&& f
->fi_copy
->density
.code
>0 && c
->write_density
) {
233 if(f
->fi_copy
->density
.code
==1) { // unspecified units
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.
250 // Detect likely-bogus density settings.
252 if(pei
->xdens
<=0 || pei
->ydens
<=0 ||
253 (pei
->xdens
> pei
->ydens
*5) || (pei
->ydens
> pei
->xdens
*5))
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");
272 i64 opt_level_n
= de_atoi64(opt_level
);
274 c
->pngcmprlevel
= 10;
276 else if(opt_level_n
<0) {
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");
309 de_crcobj_destroy(pei
->crco
);