main png.c
  1#include <stdint.h>
  2#include <stdlib.h>
  3#include <stdio.h>
  4#include <string.h>
  5#include <zlib.h>
  6
  7static const char png_magic[] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n'};
  8static const char ihdr[] = {'I', 'H', 'D', 'R'};
  9static const char plte[] = {'P', 'L', 'T', 'E'};
 10static const char idat[] = {'I', 'D', 'A', 'T'};
 11static const char iend[] = {'I', 'E', 'N', 'D'};
 12
 13enum {
 14	COLOR_GRAY,
 15	COLOR_TRUE = 2,
 16	COLOR_INDEXED,
 17	COLOR_GRAY_ALPHA,
 18	COLOR_TRUE_ALPHA=6
 19};
 20
 21static void write_chunk(FILE *f, const char*id, uint8_t *buffer, uint32_t size)
 22{
 23	uint8_t tmp[4] = {size >> 24, size >> 16, size >> 8, size};
 24	uint8_t warn = 0;
 25	warn = warn || (sizeof(tmp) != fwrite(tmp, 1, sizeof(tmp), f));
 26	warn = warn || (4 != fwrite(id, 1, 4, f));
 27	if (size) {
 28		warn = warn || (size != fwrite(buffer, 1, size, f));
 29	}
 30	
 31	uint32_t crc = crc32(0, NULL, 0);
 32	crc = crc32(crc, id, 4);
 33	if (size) {
 34		crc = crc32(crc, buffer, size);
 35	}
 36	tmp[0] = crc >> 24;
 37	tmp[1] = crc >> 16;
 38	tmp[2] = crc >> 8;
 39	tmp[3] = crc;
 40	warn = warn || (sizeof(tmp) != fwrite(tmp, 1, sizeof(tmp), f));
 41	if (warn) {
 42		fprintf(stderr, "Failure during write of %c%c%c%c chunk\n", id[0], id[1], id[2], id[3]);
 43	}
 44}
 45
 46static void write_header(FILE *f, uint32_t width, uint32_t height, uint8_t color_type)
 47{
 48	uint8_t chunk[13] = {
 49		width >> 24, width >> 16, width >> 8, width,
 50		height >> 24, height >> 16, height >> 8, height,
 51		8, color_type, 0, 0, 0
 52	};
 53	if (sizeof(png_magic) != fwrite(png_magic, 1, sizeof(png_magic), f)) {
 54		fputs("Error writing PNG magic\n", stderr);
 55	}
 56	write_chunk(f, ihdr, chunk, sizeof(chunk));
 57}
 58
 59void save_png24(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch)
 60{
 61	uint32_t idat_size = (1 + width*3) * height;
 62	uint8_t *idat_buffer = malloc(idat_size);
 63	uint32_t *pixel = buffer;
 64	uint8_t *cur = idat_buffer;
 65	for (uint32_t y = 0; y < height; y++)
 66	{
 67		//save filter type
 68		*(cur++) = 0;
 69		uint32_t *start = pixel;
 70		for (uint32_t x = 0; x < width; x++, pixel++)
 71		{
 72			uint32_t value = *pixel;
 73			*(cur++) = value >> 16;
 74			*(cur++) = value >> 8;
 75			*(cur++) = value;
 76		}
 77		pixel = start + pitch / sizeof(uint32_t);
 78	}
 79	write_header(f, width, height, COLOR_TRUE);
 80	uLongf compress_buffer_size = idat_size + 5 * (idat_size/16383 + 1) + 3;
 81	uint8_t *compressed = malloc(compress_buffer_size);
 82	compress(compressed, &compress_buffer_size, idat_buffer, idat_size);
 83	free(idat_buffer);
 84	write_chunk(f, idat, compressed, compress_buffer_size);
 85	write_chunk(f, iend, NULL, 0);
 86	free(compressed);
 87}
 88
 89void save_png(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch)
 90{
 91	uint32_t palette[256];
 92	uint8_t pal_buffer[256*3];
 93	uint32_t num_pal = 0;
 94	uint32_t index_size = (1 + width) * height;
 95	uint8_t *index_buffer = malloc(index_size);
 96	uint8_t *cur = index_buffer;
 97	uint32_t *pixel = buffer;
 98	for (uint32_t y = 0; y < height; y++)
 99	{
100		//save filter type
101		*(cur++) = 0;
102		uint32_t *start = pixel;
103		for (uint32_t x = 0; x < width; x++, pixel++, cur++)
104		{
105			uint32_t value = (*pixel) & 0xFFFFFF;
106			uint32_t i;
107			for (i = 0; i < num_pal; i++)
108			{
109				if (palette[i] == value) {
110					break;
111				}
112			}
113			if (i == num_pal) {
114				if (num_pal == 256) {
115					free(index_buffer);
116					save_png24(f, buffer, width, height, pitch);
117					return;
118				}
119				palette[i] = value;
120				num_pal++;
121			}
122			*cur = i;
123		}
124		pixel = start + pitch / sizeof(uint32_t);
125	}
126	write_header(f, width, height, COLOR_INDEXED);
127	cur = pal_buffer;
128	for (uint32_t i = 0; i < num_pal; i++)
129	{
130		*(cur++) = palette[i] >> 16;
131		*(cur++) = palette[i] >> 8;
132		*(cur++) = palette[i];
133	}
134	write_chunk(f, plte, pal_buffer, num_pal * 3);
135	uLongf compress_buffer_size = index_size + 5 * (index_size/16383 + 1) + 3;
136	uint8_t *compressed = malloc(compress_buffer_size);
137	compress(compressed, &compress_buffer_size, index_buffer, index_size);
138	free(index_buffer);
139	write_chunk(f, idat, compressed, compress_buffer_size);
140	write_chunk(f, iend, NULL, 0);
141	free(compressed);
142}
143
144typedef uint8_t (*filter_fun)(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x);
145typedef uint32_t (*pixel_fun)(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun);
146
147static uint8_t filter_none(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x)
148{
149	return *cur;
150}
151
152static 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
161static 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
170static 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
177static 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
192static 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
205static 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
215static 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
235static 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
250static 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
275static 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
280uint32_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				free(decomp_buffer);
434			} else {
435				//skip uncrecognized chunks
436				cur += 4 + chunk_size;
437			}
438		}
439		//skip CRC for now
440		cur += sizeof(uint32_t);
441	}
442	if (idat_needs_free) {
443		free(idat_buf);
444	}
445	free(palette);
446	return out;
447}