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 }