Mercurial > repos > blastem
comparison png.c @ 1569:0ec89dadb36d
Add code for loading PNG images. Added 360 controller image. WIP work on gamepad mapping UI
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Thu, 19 Apr 2018 00:51:10 -0700 |
parents | b505083dcd87 |
children | 31effaadf877 |
comparison
equal
deleted
inserted
replaced
1568:d14490dee01f | 1569:0ec89dadb36d |
---|---|
1 #include <stdint.h> | 1 #include <stdint.h> |
2 #include <stdlib.h> | 2 #include <stdlib.h> |
3 #include <stdio.h> | 3 #include <stdio.h> |
4 #include <string.h> | |
4 #include "zlib/zlib.h" | 5 #include "zlib/zlib.h" |
5 | 6 |
6 static const char png_magic[] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n'}; | 7 static const char png_magic[] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n'}; |
7 static const char ihdr[] = {'I', 'H', 'D', 'R'}; | 8 static const char ihdr[] = {'I', 'H', 'D', 'R'}; |
8 static const char plte[] = {'P', 'L', 'T', 'E'}; | 9 static const char plte[] = {'P', 'L', 'T', 'E'}; |
9 static const char idat[] = {'I', 'D', 'A', 'T'}; | 10 static const char idat[] = {'I', 'D', 'A', 'T'}; |
10 static const char iend[] = {'I', 'E', 'N', 'D'}; | 11 static const char iend[] = {'I', 'E', 'N', 'D'}; |
11 | 12 |
12 enum { | 13 enum { |
14 COLOR_GRAY, | |
13 COLOR_TRUE = 2, | 15 COLOR_TRUE = 2, |
14 COLOR_INDEXED | 16 COLOR_INDEXED, |
17 COLOR_GRAY_ALPHA, | |
18 COLOR_TRUE_ALPHA=6 | |
15 }; | 19 }; |
16 | 20 |
17 static void write_chunk(FILE *f, const char*id, uint8_t *buffer, uint32_t size) | 21 static void write_chunk(FILE *f, const char*id, uint8_t *buffer, uint32_t size) |
18 { | 22 { |
19 uint8_t tmp[4] = {size >> 24, size >> 16, size >> 8, size}; | 23 uint8_t tmp[4] = {size >> 24, size >> 16, size >> 8, size}; |
134 free(index_buffer); | 138 free(index_buffer); |
135 write_chunk(f, idat, compressed, compress_buffer_size); | 139 write_chunk(f, idat, compressed, compress_buffer_size); |
136 write_chunk(f, iend, NULL, 0); | 140 write_chunk(f, iend, NULL, 0); |
137 free(compressed); | 141 free(compressed); |
138 } | 142 } |
143 | |
144 typedef uint8_t (*filter_fun)(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x); | |
145 typedef uint32_t (*pixel_fun)(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun); | |
146 | |
147 static uint8_t filter_none(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x) | |
148 { | |
149 return *cur; | |
150 } | |
151 | |
152 static uint8_t filter_sub(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x) | |
153 { | |
154 if (x) { | |
155 return *cur + *(cur - bpp); | |
156 } else { | |
157 return *cur; | |
158 } | |
159 } | |
160 | |
161 static uint8_t filter_up(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x) | |
162 { | |
163 if (last) { | |
164 return *cur + *last; | |
165 } else { | |
166 return *cur; | |
167 } | |
168 } | |
169 | |
170 static uint8_t filter_avg(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x) | |
171 { | |
172 uint8_t prev = x ? *(cur - bpp) : 0; | |
173 uint8_t prior = last ? *last : 0; | |
174 return *cur + ((prev + prior) >> 1); | |
175 } | |
176 | |
177 static uint8_t paeth(uint8_t a, uint8_t b, uint8_t c) | |
178 { | |
179 int32_t p = a + b - c; | |
180 int32_t pa = abs(p - a); | |
181 int32_t pb = abs(p - b); | |
182 int32_t pc = abs(p - c); | |
183 if (pa <= pb && pa <= pc) { | |
184 return a; | |
185 } | |
186 if (pb <= pc) { | |
187 return b; | |
188 } | |
189 return c; | |
190 } | |
191 | |
192 static uint8_t filter_paeth(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x) | |
193 { | |
194 uint8_t prev, prev_prior; | |
195 if (x) { | |
196 prev = *(cur - bpp); | |
197 prev_prior = *(last - bpp); | |
198 } else { | |
199 prev = prev_prior = 0; | |
200 } | |
201 uint8_t prior = last ? *last : 0; | |
202 return *cur + paeth(prev, prior, prev_prior); | |
203 } | |
204 | |
205 static uint32_t pixel_gray(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter) | |
206 { | |
207 uint8_t value = filter(*cur, *last, bpp, x); | |
208 (*cur)++; | |
209 if (*last) { | |
210 (*last)++; | |
211 } | |
212 return 0xFF000000 | value << 16 | value << 8 | value; | |
213 } | |
214 | |
215 static uint32_t pixel_true(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter) | |
216 { | |
217 uint8_t red = filter(*cur, *last, bpp, x); | |
218 (*cur)++; | |
219 if (*last) { | |
220 (*last)++; | |
221 } | |
222 uint8_t green = filter(*cur, *last, bpp, x); | |
223 (*cur)++; | |
224 if (*last) { | |
225 (*last)++; | |
226 } | |
227 uint8_t blue = filter(*cur, *last, bpp, x); | |
228 (*cur)++; | |
229 if (*last) { | |
230 (*last)++; | |
231 } | |
232 return 0xFF000000 | red << 16 | green << 8 | blue; | |
233 } | |
234 | |
235 static uint32_t pixel_gray_alpha(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter) | |
236 { | |
237 uint8_t value = filter(*cur, *last, bpp, x); | |
238 (*cur)++; | |
239 if (*last) { | |
240 (*last)++; | |
241 } | |
242 uint8_t alpha = filter(*cur, *last, bpp, x); | |
243 (*cur)++; | |
244 if (*last) { | |
245 (*last)++; | |
246 } | |
247 return alpha << 24 | value << 16 | value << 8 | value; | |
248 } | |
249 | |
250 static uint32_t pixel_true_alpha(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter) | |
251 { | |
252 uint8_t red = filter(*cur, *last, bpp, x); | |
253 (*cur)++; | |
254 if (*last) { | |
255 (*last)++; | |
256 } | |
257 uint8_t green = filter(*cur, *last, bpp, x); | |
258 (*cur)++; | |
259 if (*last) { | |
260 (*last)++; | |
261 } | |
262 uint8_t blue = filter(*cur, *last, bpp, x); | |
263 (*cur)++; | |
264 if (*last) { | |
265 (*last)++; | |
266 } | |
267 uint8_t alpha = filter(*cur, *last, bpp, x); | |
268 (*cur)++; | |
269 if (*last) { | |
270 (*last)++; | |
271 } | |
272 return alpha << 24 | red << 16 | green << 8 | blue; | |
273 } | |
274 | |
275 static filter_fun filters[] = {filter_none, filter_sub, filter_up, filter_avg, filter_paeth}; | |
276 | |
277 #define MIN_CHUNK_SIZE 12 | |
278 #define MIN_IHDR_SIZE 0xD | |
279 #define MAX_SUPPORTED_DIM 32767 //chosen to avoid possibility of overflow when calculating uncompressed size | |
280 uint32_t *load_png(uint8_t *buffer, uint32_t buf_size, uint32_t *width, uint32_t *height) | |
281 { | |
282 if (buf_size < sizeof(png_magic) || memcmp(buffer, png_magic, sizeof(png_magic))) { | |
283 return NULL; | |
284 } | |
285 uint32_t cur = sizeof(png_magic); | |
286 uint8_t has_header = 0; | |
287 uint8_t bits, color_type, comp_type, filter_type, interlace; | |
288 uint8_t *idat_buf = NULL; | |
289 uint8_t idat_needs_free = 0; | |
290 uint32_t idat_size; | |
291 uint32_t *out = NULL; | |
292 uint32_t *palette = NULL; | |
293 while(cur + MIN_CHUNK_SIZE <= buf_size) | |
294 { | |
295 uint32_t chunk_size = buffer[cur++] << 24; | |
296 chunk_size |= buffer[cur++] << 16; | |
297 chunk_size |= buffer[cur++] << 8; | |
298 chunk_size |= buffer[cur++]; | |
299 if (!memcmp(ihdr, buffer + cur, sizeof(ihdr))) { | |
300 if (chunk_size < MIN_IHDR_SIZE || cur + MIN_IHDR_SIZE > buf_size) { | |
301 return NULL; | |
302 } | |
303 cur += sizeof(ihdr); | |
304 *width = buffer[cur++] << 24; | |
305 *width |= buffer[cur++] << 16; | |
306 *width |= buffer[cur++] << 8; | |
307 *width |= buffer[cur++]; | |
308 *height = buffer[cur++] << 24; | |
309 *height |= buffer[cur++] << 16; | |
310 *height |= buffer[cur++] << 8; | |
311 *height |= buffer[cur++]; | |
312 if (*width > MAX_SUPPORTED_DIM || *height > MAX_SUPPORTED_DIM) { | |
313 return NULL; | |
314 } | |
315 bits = buffer[cur++]; | |
316 if (bits != 8) { | |
317 //only support 8-bits per element for now | |
318 return NULL; | |
319 } | |
320 color_type = buffer[cur++]; | |
321 if (color_type > COLOR_TRUE_ALPHA || color_type == 1 || color_type == 5) { | |
322 //reject invalid color type | |
323 return NULL; | |
324 } | |
325 comp_type = buffer[cur++]; | |
326 if (comp_type) { | |
327 //only compression type 0 is defined by the spec | |
328 return NULL; | |
329 } | |
330 filter_type = buffer[cur++]; | |
331 interlace = buffer[cur++]; | |
332 if (interlace) { | |
333 //interlacing not supported for now | |
334 return NULL; | |
335 } | |
336 cur += chunk_size - MIN_IHDR_SIZE; | |
337 has_header = 1; | |
338 } else { | |
339 if (!has_header) { | |
340 //IHDR is required to be the first chunk, fail if it isn't | |
341 break; | |
342 } | |
343 if (!memcmp(plte, buffer + cur, sizeof(plte))) { | |
344 //TODO: implement paletted images | |
345 } else if (!memcmp(idat, buffer + cur, sizeof(idat))) { | |
346 cur += sizeof(idat); | |
347 if (idat_buf) { | |
348 if (idat_needs_free) { | |
349 idat_buf = realloc(idat_buf, idat_size + chunk_size); | |
350 } else { | |
351 uint8_t *tmp = idat_buf; | |
352 idat_buf = malloc(idat_size + chunk_size); | |
353 memcpy(idat_buf, tmp, idat_size); | |
354 } | |
355 memcpy(idat_buf + idat_size, buffer + cur, chunk_size); | |
356 idat_size += chunk_size; | |
357 } else { | |
358 idat_buf = buffer + cur; | |
359 idat_size = chunk_size; | |
360 } | |
361 cur += chunk_size; | |
362 } else if (!memcmp(iend, buffer + cur, sizeof(iend))) { | |
363 if (!idat_buf) { | |
364 break; | |
365 } | |
366 if (!palette && color_type == COLOR_INDEXED) { | |
367 //indexed color, but no PLTE chunk found | |
368 return NULL; | |
369 } | |
370 uLongf uncompressed_size = *width * *height; | |
371 uint8_t bpp; | |
372 pixel_fun pixel; | |
373 switch (color_type) | |
374 { | |
375 case COLOR_GRAY: | |
376 uncompressed_size *= bits / 8; | |
377 bpp = bits/8; | |
378 pixel = pixel_gray; | |
379 break; | |
380 case COLOR_TRUE: | |
381 uncompressed_size *= 3 * bits / 8; | |
382 bpp = 3 * bits/8; | |
383 pixel = pixel_true; | |
384 break; | |
385 case COLOR_INDEXED: { | |
386 uint32_t pixels_per_byte = 8 / bits; | |
387 uncompressed_size = (*width / pixels_per_byte) * *height; | |
388 if (*width % pixels_per_byte) { | |
389 uncompressed_size += *height; | |
390 } | |
391 bpp = 1; | |
392 break; | |
393 } | |
394 case COLOR_GRAY_ALPHA: | |
395 uncompressed_size *= bits / 4; | |
396 bpp = bits / 4; | |
397 pixel = pixel_gray_alpha; | |
398 break; | |
399 case COLOR_TRUE_ALPHA: | |
400 uncompressed_size *= bits / 2; | |
401 bpp = bits / 2; | |
402 pixel = pixel_true_alpha; | |
403 break; | |
404 } | |
405 //add filter type byte | |
406 uncompressed_size += *height; | |
407 uint8_t *decomp_buffer = malloc(uncompressed_size); | |
408 if (Z_OK != uncompress(decomp_buffer, &uncompressed_size, idat_buf, idat_size)) { | |
409 free(decomp_buffer); | |
410 break; | |
411 } | |
412 out = calloc(*width * *height, sizeof(uint32_t)); | |
413 uint32_t *cur_pixel = out; | |
414 uint8_t *cur_byte = decomp_buffer; | |
415 uint8_t *last_line = NULL; | |
416 for (uint32_t y = 0; y < *height; y++) | |
417 { | |
418 uint8_t filter_type = *(cur_byte++); | |
419 if (filter_type >= sizeof(filters)/sizeof(*filters)) { | |
420 free(out); | |
421 out = NULL; | |
422 free(decomp_buffer); | |
423 break; | |
424 } | |
425 filter_fun filter = filters[filter_type]; | |
426 uint8_t *line_start = cur_byte; | |
427 for (uint32_t x = 0; x < *width; x++) | |
428 { | |
429 *(cur_pixel++) = pixel(&cur_byte, &last_line, bpp, x, filter); | |
430 } | |
431 last_line = line_start; | |
432 } | |
433 } else { | |
434 //skip uncrecognized chunks | |
435 cur += 4 + chunk_size; | |
436 } | |
437 } | |
438 //skip CRC for now | |
439 cur += sizeof(uint32_t); | |
440 } | |
441 if (idat_needs_free) { | |
442 free(idat_buf); | |
443 } | |
444 free(palette); | |
445 return out; | |
446 } |