Commit | Line | Data |
---|---|---|
3719602c PC |
1 | /* Copyright (C) 2010-2020 The RetroArch team |
2 | * | |
3 | * --------------------------------------------------------------------------------------- | |
4 | * The following license statement only applies to this file (rpng_encode.c). | |
5 | * --------------------------------------------------------------------------------------- | |
6 | * | |
7 | * Permission is hereby granted, free of charge, | |
8 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), | |
9 | * to deal in the Software without restriction, including without limitation the rights to | |
10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, | |
11 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
14 | * | |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |
16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
19 | * 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 SOFTWARE. | |
21 | */ | |
22 | ||
23 | #include <stdio.h> | |
24 | #include <stdlib.h> | |
25 | #include <string.h> | |
26 | ||
27 | #include <libretro.h> | |
28 | #include <encodings/crc32.h> | |
29 | #include <streams/interface_stream.h> | |
30 | #include <streams/trans_stream.h> | |
31 | ||
32 | #include "rpng_internal.h" | |
33 | ||
34 | #undef GOTO_END_ERROR | |
35 | #define GOTO_END_ERROR() do { \ | |
36 | fprintf(stderr, "[RPNG]: Error in line %d.\n", __LINE__); \ | |
37 | ret = false; \ | |
38 | goto end; \ | |
39 | } while (0) | |
40 | ||
41 | double DEFLATE_PADDING = 1.1; | |
42 | int PNG_ROUGH_HEADER = 100; | |
43 | ||
44 | static void dword_write_be(uint8_t *buf, uint32_t val) | |
45 | { | |
46 | *buf++ = (uint8_t)(val >> 24); | |
47 | *buf++ = (uint8_t)(val >> 16); | |
48 | *buf++ = (uint8_t)(val >> 8); | |
49 | *buf++ = (uint8_t)(val >> 0); | |
50 | } | |
51 | ||
52 | static bool png_write_crc_string(intfstream_t *intf_s, const uint8_t *data, size_t size) | |
53 | { | |
54 | uint8_t crc_raw[4] = {0}; | |
55 | uint32_t crc = encoding_crc32(0, data, size); | |
56 | ||
57 | dword_write_be(crc_raw, crc); | |
58 | return intfstream_write(intf_s, crc_raw, sizeof(crc_raw)) == sizeof(crc_raw); | |
59 | } | |
60 | ||
61 | static bool png_write_ihdr_string(intfstream_t *intf_s, const struct png_ihdr *ihdr) | |
62 | { | |
63 | uint8_t ihdr_raw[21]; | |
64 | ||
65 | ihdr_raw[0] = '0'; /* Size */ | |
66 | ihdr_raw[1] = '0'; | |
67 | ihdr_raw[2] = '0'; | |
68 | ihdr_raw[3] = '0'; | |
69 | ihdr_raw[4] = 'I'; | |
70 | ihdr_raw[5] = 'H'; | |
71 | ihdr_raw[6] = 'D'; | |
72 | ihdr_raw[7] = 'R'; | |
73 | ihdr_raw[8] = 0; /* Width */ | |
74 | ihdr_raw[9] = 0; | |
75 | ihdr_raw[10] = 0; | |
76 | ihdr_raw[11] = 0; | |
77 | ihdr_raw[12] = 0; /* Height */ | |
78 | ihdr_raw[13] = 0; | |
79 | ihdr_raw[14] = 0; | |
80 | ihdr_raw[15] = 0; | |
81 | ihdr_raw[16] = ihdr->depth; /* Depth */ | |
82 | ihdr_raw[17] = ihdr->color_type; | |
83 | ihdr_raw[18] = ihdr->compression; | |
84 | ihdr_raw[19] = ihdr->filter; | |
85 | ihdr_raw[20] = ihdr->interlace; | |
86 | ||
87 | dword_write_be(ihdr_raw + 0, sizeof(ihdr_raw) - 8); | |
88 | dword_write_be(ihdr_raw + 8, ihdr->width); | |
89 | dword_write_be(ihdr_raw + 12, ihdr->height); | |
90 | if (intfstream_write(intf_s, ihdr_raw, sizeof(ihdr_raw)) != sizeof(ihdr_raw)) | |
91 | return false; | |
92 | ||
93 | return png_write_crc_string(intf_s, ihdr_raw + sizeof(uint32_t), | |
94 | sizeof(ihdr_raw) - sizeof(uint32_t)); | |
95 | } | |
96 | ||
97 | static bool png_write_idat_string(intfstream_t* intf_s, const uint8_t *data, size_t size) | |
98 | { | |
99 | if (intfstream_write(intf_s, data, size) != (ssize_t)size) | |
100 | return false; | |
101 | ||
102 | return png_write_crc_string(intf_s, data + sizeof(uint32_t), size - sizeof(uint32_t)); | |
103 | } | |
104 | ||
105 | static bool png_write_iend_string(intfstream_t* intf_s) | |
106 | { | |
107 | const uint8_t data[] = { | |
108 | 0, 0, 0, 0, | |
109 | 'I', 'E', 'N', 'D', | |
110 | }; | |
111 | ||
112 | if (intfstream_write(intf_s, data, sizeof(data)) != sizeof(data)) | |
113 | return false; | |
114 | ||
115 | return png_write_crc_string(intf_s, data + sizeof(uint32_t), | |
116 | sizeof(data) - sizeof(uint32_t)); | |
117 | } | |
118 | ||
119 | static void copy_argb_line(uint8_t *dst, const uint32_t *src, unsigned width) | |
120 | { | |
121 | unsigned i; | |
122 | for (i = 0; i < width; i++) | |
123 | { | |
124 | uint32_t col = src[i]; | |
125 | *dst++ = (uint8_t)(col >> 16); | |
126 | *dst++ = (uint8_t)(col >> 8); | |
127 | *dst++ = (uint8_t)(col >> 0); | |
128 | *dst++ = (uint8_t)(col >> 24); | |
129 | } | |
130 | } | |
131 | ||
132 | static void copy_bgr24_line(uint8_t *dst, const uint8_t *src, unsigned width) | |
133 | { | |
134 | unsigned i; | |
135 | for (i = 0; i < width; i++, dst += 3, src += 3) | |
136 | { | |
137 | dst[2] = src[0]; | |
138 | dst[1] = src[1]; | |
139 | dst[0] = src[2]; | |
140 | } | |
141 | } | |
142 | ||
143 | static unsigned count_sad(const uint8_t *data, size_t size) | |
144 | { | |
145 | size_t i; | |
146 | unsigned cnt = 0; | |
147 | for (i = 0; i < size; i++) | |
148 | { | |
149 | if (data[i]) | |
150 | cnt += abs((int8_t)data[i]); | |
151 | } | |
152 | return cnt; | |
153 | } | |
154 | ||
155 | static unsigned filter_up(uint8_t *target, const uint8_t *line, | |
156 | const uint8_t *prev, unsigned width, unsigned bpp) | |
157 | { | |
158 | unsigned i; | |
159 | width *= bpp; | |
160 | for (i = 0; i < width; i++) | |
161 | target[i] = line[i] - prev[i]; | |
162 | ||
163 | return count_sad(target, width); | |
164 | } | |
165 | ||
166 | static unsigned filter_sub(uint8_t *target, const uint8_t *line, | |
167 | unsigned width, unsigned bpp) | |
168 | { | |
169 | unsigned i; | |
170 | width *= bpp; | |
171 | for (i = 0; i < bpp; i++) | |
172 | target[i] = line[i]; | |
173 | for (i = bpp; i < width; i++) | |
174 | target[i] = line[i] - line[i - bpp]; | |
175 | ||
176 | return count_sad(target, width); | |
177 | } | |
178 | ||
179 | static unsigned filter_avg(uint8_t *target, const uint8_t *line, | |
180 | const uint8_t *prev, unsigned width, unsigned bpp) | |
181 | { | |
182 | unsigned i; | |
183 | width *= bpp; | |
184 | for (i = 0; i < bpp; i++) | |
185 | target[i] = line[i] - (prev[i] >> 1); | |
186 | for (i = bpp; i < width; i++) | |
187 | target[i] = line[i] - ((line[i - bpp] + prev[i]) >> 1); | |
188 | ||
189 | return count_sad(target, width); | |
190 | } | |
191 | ||
192 | static unsigned filter_paeth(uint8_t *target, | |
193 | const uint8_t *line, const uint8_t *prev, | |
194 | unsigned width, unsigned bpp) | |
195 | { | |
196 | unsigned i; | |
197 | width *= bpp; | |
198 | for (i = 0; i < bpp; i++) | |
199 | target[i] = line[i] - paeth(0, prev[i], 0); | |
200 | for (i = bpp; i < width; i++) | |
201 | target[i] = line[i] - paeth(line[i - bpp], prev[i], prev[i - bpp]); | |
202 | ||
203 | return count_sad(target, width); | |
204 | } | |
205 | ||
206 | bool rpng_save_image_stream(const uint8_t *data, intfstream_t* intf_s, | |
207 | unsigned width, unsigned height, signed pitch, unsigned bpp) | |
208 | { | |
209 | unsigned h; | |
210 | struct png_ihdr ihdr = {0}; | |
211 | bool ret = true; | |
212 | const struct trans_stream_backend *stream_backend = NULL; | |
213 | size_t encode_buf_size = 0; | |
214 | uint8_t *encode_buf = NULL; | |
215 | uint8_t *deflate_buf = NULL; | |
216 | uint8_t *rgba_line = NULL; | |
217 | uint8_t *up_filtered = NULL; | |
218 | uint8_t *sub_filtered = NULL; | |
219 | uint8_t *avg_filtered = NULL; | |
220 | uint8_t *paeth_filtered = NULL; | |
221 | uint8_t *prev_encoded = NULL; | |
222 | uint8_t *encode_target = NULL; | |
223 | void *stream = NULL; | |
224 | uint32_t total_in = 0; | |
225 | uint32_t total_out = 0; | |
226 | ||
227 | if (!intf_s) | |
228 | GOTO_END_ERROR(); | |
229 | ||
230 | stream_backend = trans_stream_get_zlib_deflate_backend(); | |
231 | ||
232 | if (intfstream_write(intf_s, png_magic, sizeof(png_magic)) != sizeof(png_magic)) | |
233 | GOTO_END_ERROR(); | |
234 | ||
235 | ihdr.width = width; | |
236 | ihdr.height = height; | |
237 | ihdr.depth = 8; | |
238 | ihdr.color_type = bpp == sizeof(uint32_t) ? 6 : 2; /* RGBA or RGB */ | |
239 | if (!png_write_ihdr_string(intf_s, &ihdr)) | |
240 | GOTO_END_ERROR(); | |
241 | ||
242 | encode_buf_size = (width * bpp + 1) * height; | |
243 | encode_buf = (uint8_t*)malloc(encode_buf_size); | |
244 | if (!encode_buf) | |
245 | GOTO_END_ERROR(); | |
246 | ||
247 | prev_encoded = (uint8_t*)calloc(1, width * bpp); | |
248 | if (!prev_encoded) | |
249 | GOTO_END_ERROR(); | |
250 | ||
251 | rgba_line = (uint8_t*)malloc(width * bpp); | |
252 | up_filtered = (uint8_t*)malloc(width * bpp); | |
253 | sub_filtered = (uint8_t*)malloc(width * bpp); | |
254 | avg_filtered = (uint8_t*)malloc(width * bpp); | |
255 | paeth_filtered = (uint8_t*)malloc(width * bpp); | |
256 | if (!rgba_line || !up_filtered || !sub_filtered || !avg_filtered || !paeth_filtered) | |
257 | GOTO_END_ERROR(); | |
258 | ||
259 | encode_target = encode_buf; | |
260 | for (h = 0; h < height; | |
261 | h++, encode_target += width * bpp, data += pitch) | |
262 | { | |
263 | if (bpp == sizeof(uint32_t)) | |
264 | copy_argb_line(rgba_line, (const uint32_t*)data, width); | |
265 | else | |
266 | copy_bgr24_line(rgba_line, data, width); | |
267 | ||
268 | /* Try every filtering method, and choose the method | |
269 | * which has most entries as zero. | |
270 | * | |
271 | * This is probably not very optimal, but it's very | |
272 | * simple to implement. | |
273 | */ | |
274 | { | |
275 | unsigned none_score = count_sad(rgba_line, width * bpp); | |
276 | unsigned up_score = filter_up(up_filtered, rgba_line, prev_encoded, width, bpp); | |
277 | unsigned sub_score = filter_sub(sub_filtered, rgba_line, width, bpp); | |
278 | unsigned avg_score = filter_avg(avg_filtered, rgba_line, prev_encoded, width, bpp); | |
279 | unsigned paeth_score = filter_paeth(paeth_filtered, rgba_line, prev_encoded, width, bpp); | |
280 | ||
281 | uint8_t filter = 0; | |
282 | unsigned min_sad = none_score; | |
283 | const uint8_t *chosen_filtered = rgba_line; | |
284 | ||
285 | if (sub_score < min_sad) | |
286 | { | |
287 | filter = 1; | |
288 | chosen_filtered = sub_filtered; | |
289 | min_sad = sub_score; | |
290 | } | |
291 | ||
292 | if (up_score < min_sad) | |
293 | { | |
294 | filter = 2; | |
295 | chosen_filtered = up_filtered; | |
296 | min_sad = up_score; | |
297 | } | |
298 | ||
299 | if (avg_score < min_sad) | |
300 | { | |
301 | filter = 3; | |
302 | chosen_filtered = avg_filtered; | |
303 | min_sad = avg_score; | |
304 | } | |
305 | ||
306 | if (paeth_score < min_sad) | |
307 | { | |
308 | filter = 4; | |
309 | chosen_filtered = paeth_filtered; | |
310 | } | |
311 | ||
312 | *encode_target++ = filter; | |
313 | memcpy(encode_target, chosen_filtered, width * bpp); | |
314 | ||
315 | memcpy(prev_encoded, rgba_line, width * bpp); | |
316 | } | |
317 | } | |
318 | ||
319 | deflate_buf = (uint8_t*)malloc(encode_buf_size * 2); /* Just to be sure. */ | |
320 | if (!deflate_buf) | |
321 | GOTO_END_ERROR(); | |
322 | ||
323 | stream = stream_backend->stream_new(); | |
324 | ||
325 | if (!stream) | |
326 | GOTO_END_ERROR(); | |
327 | ||
328 | stream_backend->set_in( | |
329 | stream, | |
330 | encode_buf, | |
331 | (unsigned)encode_buf_size); | |
332 | stream_backend->set_out( | |
333 | stream, | |
334 | deflate_buf + 8, | |
335 | (unsigned)(encode_buf_size * 2)); | |
336 | ||
337 | if (!stream_backend->trans(stream, true, &total_in, &total_out, NULL)) | |
338 | GOTO_END_ERROR(); | |
339 | ||
340 | memcpy(deflate_buf + 4, "IDAT", 4); | |
341 | dword_write_be(deflate_buf + 0, ((uint32_t)total_out)); | |
342 | if (!png_write_idat_string(intf_s, deflate_buf, ((size_t)total_out + 8))) | |
343 | GOTO_END_ERROR(); | |
344 | ||
345 | if (!png_write_iend_string(intf_s)) | |
346 | GOTO_END_ERROR(); | |
347 | end: | |
348 | free(encode_buf); | |
349 | free(deflate_buf); | |
350 | free(rgba_line); | |
351 | free(prev_encoded); | |
352 | free(up_filtered); | |
353 | free(sub_filtered); | |
354 | free(avg_filtered); | |
355 | free(paeth_filtered); | |
356 | ||
357 | if (stream_backend) | |
358 | { | |
359 | if (stream) | |
360 | { | |
361 | if (stream_backend->stream_free) | |
362 | stream_backend->stream_free(stream); | |
363 | } | |
364 | } | |
365 | return ret; | |
366 | } | |
367 | ||
368 | bool rpng_save_image_argb(const char *path, const uint32_t *data, | |
369 | unsigned width, unsigned height, unsigned pitch) | |
370 | { | |
371 | bool ret = false; | |
372 | intfstream_t* intf_s = NULL; | |
373 | ||
374 | intf_s = intfstream_open_file(path, | |
375 | RETRO_VFS_FILE_ACCESS_WRITE, | |
376 | RETRO_VFS_FILE_ACCESS_HINT_NONE); | |
377 | ||
378 | ret = rpng_save_image_stream((const uint8_t*) data, intf_s, | |
379 | width, height, | |
380 | (signed) pitch, sizeof(uint32_t)); | |
381 | intfstream_close(intf_s); | |
382 | free(intf_s); | |
383 | return ret; | |
384 | } | |
385 | ||
386 | bool rpng_save_image_bgr24(const char *path, const uint8_t *data, | |
387 | unsigned width, unsigned height, unsigned pitch) | |
388 | { | |
389 | bool ret = false; | |
390 | intfstream_t* intf_s = NULL; | |
391 | ||
392 | intf_s = intfstream_open_file(path, | |
393 | RETRO_VFS_FILE_ACCESS_WRITE, | |
394 | RETRO_VFS_FILE_ACCESS_HINT_NONE); | |
395 | ret = rpng_save_image_stream(data, intf_s, width, height, | |
396 | (signed) pitch, 3); | |
397 | intfstream_close(intf_s); | |
398 | free(intf_s); | |
399 | return ret; | |
400 | } | |
401 | ||
402 | ||
403 | uint8_t* rpng_save_image_bgr24_string(const uint8_t *data, | |
404 | unsigned width, unsigned height, signed pitch, uint64_t* bytes) | |
405 | { | |
406 | bool ret = false; | |
407 | uint8_t* buf = NULL; | |
408 | uint8_t* output = NULL; | |
409 | int buf_length = 0; | |
410 | intfstream_t* intf_s = NULL; | |
411 | ||
412 | buf_length = (int)(width*height*3*DEFLATE_PADDING)+PNG_ROUGH_HEADER; | |
413 | buf = (uint8_t*)malloc(buf_length*sizeof(uint8_t)); | |
414 | if (!buf) | |
415 | GOTO_END_ERROR(); | |
416 | ||
417 | intf_s = intfstream_open_writable_memory(buf, | |
418 | RETRO_VFS_FILE_ACCESS_WRITE, | |
419 | RETRO_VFS_FILE_ACCESS_HINT_NONE, | |
420 | buf_length); | |
421 | ||
422 | ret = rpng_save_image_stream((const uint8_t*)data, | |
423 | intf_s, width, height, pitch, 3); | |
424 | ||
425 | *bytes = intfstream_get_ptr(intf_s); | |
426 | intfstream_rewind(intf_s); | |
427 | output = (uint8_t*)malloc((size_t)((*bytes)*sizeof(uint8_t))); | |
428 | if (!output) | |
429 | GOTO_END_ERROR(); | |
430 | intfstream_read(intf_s, output, *bytes); | |
431 | ||
432 | end: | |
433 | if (buf) | |
434 | free(buf); | |
435 | if (intf_s) | |
436 | { | |
437 | intfstream_close(intf_s); | |
438 | free(intf_s); | |
439 | } | |
440 | if (ret == false) | |
441 | { | |
442 | if (output) | |
443 | free(output); | |
444 | return NULL; | |
445 | } | |
446 | return output; | |
447 | } | |
448 |