1 | // MIT License |
2 | |
3 | // Copyright (c) 2021 Vadim Grigoruk @nesbox // grigoruk@gmail.com |
4 | |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
6 | // of this software and associated documentation files (the "Software"), to deal |
7 | // in the Software without restriction, including without limitation the rights |
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
9 | // copies of the Software, and to permit persons to whom the Software is |
10 | // furnished to do so, subject to the following conditions: |
11 | |
12 | // The above copyright notice and this permission notice shall be included in all |
13 | // copies or substantial portions of the Software. |
14 | |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
21 | // SOFTWARE. |
22 | |
23 | #include "png.h" |
24 | #include "defines.h" |
25 | |
26 | #include <string.h> |
27 | #include <stdlib.h> |
28 | #include <png.h> |
29 | #include "tic_assert.h" |
30 | |
31 | #define RGBA_SIZE sizeof(u32) |
32 | |
33 | png_buffer png_create(s32 size) |
34 | { |
35 | return (png_buffer){malloc(size), size}; |
36 | } |
37 | |
38 | typedef struct |
39 | { |
40 | png_buffer buffer; |
41 | size_t pos; |
42 | } PngStream; |
43 | |
44 | static void pngReadCallback(png_structp png, png_bytep out, png_size_t size) |
45 | { |
46 | PngStream* stream = png_get_io_ptr(png); |
47 | memcpy(out, stream->buffer.data + stream->pos, size); |
48 | stream->pos += size; |
49 | } |
50 | |
51 | png_img png_read(png_buffer buf) |
52 | { |
53 | png_img res = { 0 }; |
54 | |
55 | if (png_sig_cmp(buf.data, 0, 8) == 0) |
56 | { |
57 | png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); |
58 | png_infop info = png_create_info_struct(png); |
59 | |
60 | PngStream stream = { .buffer = buf}; |
61 | |
62 | png_set_read_fn(png, &stream, pngReadCallback); |
63 | png_read_info(png, info); |
64 | |
65 | res.width = png_get_image_width(png, info); |
66 | res.height = png_get_image_height(png, info); |
67 | s32 colorType = png_get_color_type(png, info); |
68 | s32 bitDepth = png_get_bit_depth(png, info); |
69 | |
70 | if (bitDepth == 16) |
71 | png_set_strip_16(png); |
72 | |
73 | if (colorType == PNG_COLOR_TYPE_PALETTE) |
74 | png_set_palette_to_rgb(png); |
75 | |
76 | // PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth. |
77 | if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) |
78 | png_set_expand_gray_1_2_4_to_8(png); |
79 | |
80 | if (png_get_valid(png, info, PNG_INFO_tRNS)) |
81 | png_set_tRNS_to_alpha(png); |
82 | |
83 | // These colorType don't have an alpha channel then fill it with 0xff. |
84 | if (colorType == PNG_COLOR_TYPE_RGB || |
85 | colorType == PNG_COLOR_TYPE_GRAY || |
86 | colorType == PNG_COLOR_TYPE_PALETTE) |
87 | png_set_filler(png, 0xFF, PNG_FILLER_AFTER); |
88 | |
89 | if (colorType == PNG_COLOR_TYPE_GRAY || |
90 | colorType == PNG_COLOR_TYPE_GRAY_ALPHA) |
91 | png_set_gray_to_rgb(png); |
92 | |
93 | png_read_update_info(png, info); |
94 | |
95 | res.data = malloc(RGBA_SIZE * res.width * res.height); |
96 | png_bytep* rows = (png_bytep*)malloc(sizeof(png_bytep) * res.height); |
97 | |
98 | for (s32 i = 0; i < res.height; i++) |
99 | rows[i] = res.data + res.width * i * RGBA_SIZE; |
100 | |
101 | png_read_image(png, rows); |
102 | |
103 | free(rows); |
104 | |
105 | png_destroy_read_struct(&png, &info, NULL); |
106 | } |
107 | |
108 | return res; |
109 | } |
110 | |
111 | static void pngWriteCallback(png_structp png, png_bytep data, png_size_t size) |
112 | { |
113 | PngStream* stream = png_get_io_ptr(png); |
114 | |
115 | stream->buffer.size += (u32)size; |
116 | |
117 | stream->buffer.data = realloc(stream->buffer.data, stream->buffer.size); |
118 | memcpy(stream->buffer.data + stream->pos, data, size); |
119 | stream->pos += size; |
120 | } |
121 | |
122 | static void pngFlushCallback(png_structp png) {} |
123 | |
124 | png_buffer png_write(png_img src) |
125 | { |
126 | png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); |
127 | png_infop info = png_create_info_struct(png); |
128 | |
129 | PngStream stream = {0}; |
130 | png_set_write_fn(png, &stream, pngWriteCallback, pngFlushCallback); |
131 | |
132 | // Output is 8bit depth, RGBA format. |
133 | png_set_IHDR( |
134 | png, |
135 | info, |
136 | src.width, src.height, |
137 | 8, |
138 | PNG_COLOR_TYPE_RGBA, |
139 | PNG_INTERLACE_NONE, |
140 | PNG_COMPRESSION_TYPE_DEFAULT, |
141 | PNG_FILTER_TYPE_DEFAULT |
142 | ); |
143 | |
144 | png_write_info(png, info); |
145 | |
146 | png_bytep* rows = malloc(sizeof(png_bytep) * src.height); |
147 | for (s32 i = 0; i < src.height; i++) |
148 | rows[i] = src.data + src.width * i * RGBA_SIZE; |
149 | |
150 | png_write_image(png, rows); |
151 | png_write_end(png, NULL); |
152 | |
153 | png_destroy_write_struct(&png, &info); |
154 | |
155 | free(rows); |
156 | |
157 | return stream.buffer; |
158 | } |
159 | |
160 | typedef union |
161 | { |
162 | struct |
163 | { |
164 | u32 bits:8; |
165 | u32 size:24; |
166 | }; |
167 | |
168 | u8 data[RGBA_SIZE]; |
169 | } ; |
170 | |
171 | static_assert(sizeof(Header) == RGBA_SIZE, "header_size" ); |
172 | |
173 | #define BITS_IN_BYTE 8 |
174 | #define 4 |
175 | #define (sizeof(Header) * BITS_IN_BYTE / HEADER_BITS) |
176 | |
177 | static inline void bitcpy(u8* dst, u32 to, const u8* src, u32 from, u32 size) |
178 | { |
179 | for(s32 i = 0; i < size; i++, to++, from++) |
180 | BITCHECK(src[from >> 3], from & 7) |
181 | ? _BITSET(dst[to >> 3], to & 7) |
182 | : _BITCLEAR(dst[to >> 3], to & 7); |
183 | } |
184 | |
185 | static inline s32 ceildiv(s32 a, s32 b) |
186 | { |
187 | return (a + b - 1) / b; |
188 | } |
189 | |
190 | png_buffer png_encode(png_buffer cover, png_buffer cart) |
191 | { |
192 | png_img png = png_read(cover); |
193 | |
194 | const s32 cartBits = cart.size * BITS_IN_BYTE; |
195 | const s32 coverSize = png.width * png.height * RGBA_SIZE - HEADER_SIZE; |
196 | Header = {CLAMP(ceildiv(cartBits, coverSize), 1, BITS_IN_BYTE), cart.size}; |
197 | |
198 | for (s32 i = 0; i < HEADER_SIZE; i++) |
199 | bitcpy(png.data, i << 3, header.data, i * HEADER_BITS, HEADER_BITS); |
200 | |
201 | u8* dst = png.data + HEADER_SIZE; |
202 | s32 end = ceildiv(cartBits, header.bits); |
203 | for (s32 i = 0; i < end; i++) |
204 | bitcpy(dst, i << 3, cart.data, i * header.bits, header.bits); |
205 | |
206 | for (s32 i = end; i < coverSize; i++) |
207 | bitcpy(dst, i << 3, (const u8[]){rand()}, 0, header.bits); |
208 | |
209 | png_buffer out = png_write(png); |
210 | |
211 | free(png.data); |
212 | |
213 | return out; |
214 | } |
215 | |
216 | png_buffer png_decode(png_buffer cover) |
217 | { |
218 | png_img png = png_read(cover); |
219 | |
220 | if (png.data) |
221 | { |
222 | Header ; |
223 | |
224 | for (s32 i = 0; i < HEADER_SIZE; i++) |
225 | bitcpy(header.data, i * HEADER_BITS, png.data, i << 3, HEADER_BITS); |
226 | |
227 | if (header.bits > 0 |
228 | && header.bits <= BITS_IN_BYTE |
229 | && header.size > 0 |
230 | && header.size <= png.width * png.height * RGBA_SIZE * header.bits / BITS_IN_BYTE - HEADER_SIZE) |
231 | { |
232 | s32 aligned = header.size + ceildiv(header.size * BITS_IN_BYTE % header.bits, BITS_IN_BYTE); |
233 | png_buffer out = { malloc(aligned), header.size }; |
234 | |
235 | const u8* from = png.data + HEADER_SIZE; |
236 | for (s32 i = 0, end = ceildiv(header.size * BITS_IN_BYTE, header.bits); i < end; i++) |
237 | bitcpy(out.data, i * header.bits, from, i << 3, header.bits); |
238 | |
239 | free(png.data); |
240 | |
241 | return out; |
242 | } |
243 | } |
244 | |
245 | return (png_buffer) { 0 }; |
246 | } |
247 | |