xref: /illumos-gate/usr/src/common/pnglite/pnglite.c (revision f7e4f33f27df122daef98e5b4b537dc159597da5)
1 /*
2  * pnglite.c - pnglite library
3  * For conditions of distribution and use, see copyright notice in pnglite.h
4  */
5 
6 #ifdef _STANDALONE
7 #include <sys/cdefs.h>
8 #include <stand.h>
9 #else
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <fcntl.h>
15 #endif
16 #include <zlib.h>
17 #include "pnglite.h"
18 
19 #ifndef abs
20 #define	abs(x)	((x) < 0? -(x):(x))
21 #endif
22 
23 #define	PNG_32b(b, s) ((uint32_t)(b) << (s))
24 #define	PNG_U32(b1, b2, b3, b4) \
25 	(PNG_32b(b1, 24) | PNG_32b(b2, 16) | PNG_32b(b3, 8) | PNG_32b(b4, 0))
26 
27 #define	png_IDAT PNG_U32(73,  68,  65,  84)
28 #define	png_IEND PNG_U32(73,  69,  78,  68)
29 
30 static ssize_t
file_read(png_t * png,void * out,size_t size,size_t numel)31 file_read(png_t *png, void *out, size_t size, size_t numel)
32 {
33 	ssize_t result;
34 	off_t offset = (off_t)(size * numel);
35 
36 	if (offset < 0)
37 		return (PNG_FILE_ERROR);
38 
39 	if (!out) {
40 		result = lseek(png->fd, offset, SEEK_CUR);
41 	} else {
42 		result = read(png->fd, out, size * numel);
43 	}
44 
45 	return (result);
46 }
47 
48 static int
file_read_ul(png_t * png,unsigned * out)49 file_read_ul(png_t *png, unsigned *out)
50 {
51 	uint8_t buf[4];
52 
53 	if (file_read(png, buf, 1, 4) != 4)
54 		return (PNG_FILE_ERROR);
55 
56 	*out = (buf[0]<<24) | (buf[1]<<16) | (buf[2]<<8) | buf[3];
57 
58 	return (PNG_NO_ERROR);
59 }
60 
61 static unsigned
get_ul(uint8_t * buf)62 get_ul(uint8_t *buf)
63 {
64 	unsigned result;
65 	uint8_t foo[4];
66 
67 	memcpy(foo, buf, 4);
68 
69 	result = (foo[0]<<24) | (foo[1]<<16) | (foo[2]<<8) | foo[3];
70 
71 	return (result);
72 }
73 
74 static int
png_get_bpp(png_t * png)75 png_get_bpp(png_t *png)
76 {
77 	int bpp;
78 
79 	switch (png->color_type) {
80 	case PNG_GREYSCALE:
81 		bpp = 1; break;
82 	case PNG_TRUECOLOR:
83 		bpp = 3; break;
84 	case PNG_INDEXED:
85 		bpp = 1; break;
86 	case PNG_GREYSCALE_ALPHA:
87 		bpp = 2; break;
88 	case PNG_TRUECOLOR_ALPHA:
89 		bpp = 4; break;
90 	default:
91 		return (PNG_FILE_ERROR);
92 	}
93 
94 	bpp *= png->depth / 8;
95 
96 	return (bpp);
97 }
98 
99 static int
png_read_ihdr(png_t * png)100 png_read_ihdr(png_t *png)
101 {
102 	unsigned length = 0;
103 	unsigned orig_crc;
104 	unsigned calc_crc;
105 	uint8_t ihdr[13+4]; /* length should be 13, make room for type (IHDR) */
106 
107 	if (file_read_ul(png, &length) != PNG_NO_ERROR)
108 		return (PNG_FILE_ERROR);
109 
110 	if (length != 13)
111 		return (PNG_CRC_ERROR);
112 
113 	if (file_read(png, ihdr, 1, 13+4) != 13+4)
114 		return (PNG_EOF_ERROR);
115 
116 	if (file_read_ul(png, &orig_crc) != PNG_NO_ERROR)
117 		return (PNG_FILE_ERROR);
118 
119 	calc_crc = crc32(0L, Z_NULL, 0);
120 	calc_crc = crc32(calc_crc, ihdr, 13+4);
121 
122 	if (orig_crc != calc_crc) {
123 		return (PNG_CRC_ERROR);
124 	}
125 
126 	png->width = get_ul(ihdr+4);
127 	png->height = get_ul(ihdr+8);
128 	png->depth = ihdr[12];
129 	png->color_type = ihdr[13];
130 	png->compression_method = ihdr[14];
131 	png->filter_method = ihdr[15];
132 	png->interlace_method = ihdr[16];
133 
134 	if (png->color_type == PNG_INDEXED)
135 		return (PNG_NOT_SUPPORTED);
136 
137 	if (png->depth != 8 && png->depth != 16)
138 		return (PNG_NOT_SUPPORTED);
139 
140 	if (png->interlace_method)
141 		return (PNG_NOT_SUPPORTED);
142 
143 	return (PNG_NO_ERROR);
144 }
145 
146 void
png_print_info(png_t * png)147 png_print_info(png_t *png)
148 {
149 	printf("PNG INFO:\n");
150 	printf("\twidth:\t\t%d\n", png->width);
151 	printf("\theight:\t\t%d\n", png->height);
152 	printf("\tdepth:\t\t%d\n", png->depth);
153 	printf("\tcolor:\t\t");
154 
155 	switch (png->color_type) {
156 	case PNG_GREYSCALE:
157 		printf("greyscale\n"); break;
158 	case PNG_TRUECOLOR:
159 		printf("truecolor\n"); break;
160 	case PNG_INDEXED:
161 		printf("palette\n"); break;
162 	case PNG_GREYSCALE_ALPHA:
163 		printf("greyscale with alpha\n"); break;
164 	case PNG_TRUECOLOR_ALPHA:
165 		printf("truecolor with alpha\n"); break;
166 	default:
167 		printf("unknown, this is not good\n"); break;
168 	}
169 
170 	printf("\tcompression:\t%s\n",
171 	    png->compression_method?
172 	    "unknown, this is not good":"inflate/deflate");
173 	printf("\tfilter:\t\t%s\n",
174 	    png->filter_method? "unknown, this is not good":"adaptive");
175 	printf("\tinterlace:\t%s\n",
176 	    png->interlace_method? "interlace":"no interlace");
177 }
178 
179 int
png_open(png_t * png,const char * filename)180 png_open(png_t *png, const char *filename)
181 {
182 	char header[8];
183 	int result;
184 
185 	png->image = NULL;
186 	png->fd = open(filename, O_RDONLY);
187 	if (png->fd == -1)
188 		return (PNG_FILE_ERROR);
189 
190 	if (file_read(png, header, 1, 8) != 8) {
191 		result = PNG_EOF_ERROR;
192 		goto done;
193 	}
194 
195 	if (memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) != 0) {
196 		result = PNG_HEADER_ERROR;
197 		goto done;
198 	}
199 
200 	result = png_read_ihdr(png);
201 	if (result == PNG_NO_ERROR) {
202 		result = png_get_bpp(png);
203 		if (result > 0) {
204 			png->bpp = (uint8_t)result;
205 			result = PNG_NO_ERROR;
206 		}
207 	}
208 
209 done:
210 	if (result == PNG_NO_ERROR) {
211 		uint64_t size = png->width * png->height * png->bpp;
212 
213 		if (size < UINT_MAX)
214 			png->image = malloc(size);
215 		if (png->image == NULL)
216 			result = PNG_MEMORY_ERROR;
217 	}
218 
219 	if (result == PNG_NO_ERROR)
220 		result = png_get_data(png, png->image);
221 
222 	if (result != PNG_NO_ERROR) {
223 		free(png->image);
224 		(void) close(png->fd);
225 		png->fd = -1;
226 		return (result);
227 	}
228 
229 	return (result);
230 }
231 
232 int
png_close(png_t * png)233 png_close(png_t *png)
234 {
235 	(void) close(png->fd);
236 	png->fd = -1;
237 	free(png->image);
238 	png->image = NULL;
239 
240 	return (PNG_NO_ERROR);
241 }
242 
243 static int
png_init_inflate(png_t * png)244 png_init_inflate(png_t *png)
245 {
246 	z_stream *stream;
247 	png->zs = calloc(1, sizeof (z_stream));
248 
249 	stream = png->zs;
250 
251 	if (!stream)
252 		return (PNG_MEMORY_ERROR);
253 
254 	if (inflateInit(stream) != Z_OK) {
255 		free(png->zs);
256 		png->zs = NULL;
257 		return (PNG_ZLIB_ERROR);
258 	}
259 
260 	stream->next_out = png->png_data;
261 	stream->avail_out = png->png_datalen;
262 
263 	return (PNG_NO_ERROR);
264 }
265 
266 static int
png_end_inflate(png_t * png)267 png_end_inflate(png_t *png)
268 {
269 	z_stream *stream = png->zs;
270 	int rc = PNG_NO_ERROR;
271 
272 	if (!stream)
273 		return (PNG_MEMORY_ERROR);
274 
275 	if (inflateEnd(stream) != Z_OK) {
276 		printf("ZLIB says: %s\n", stream->msg);
277 		rc = PNG_ZLIB_ERROR;
278 	}
279 
280 	free(png->zs);
281 	png->zs = NULL;
282 
283 	return (rc);
284 }
285 
286 static int
png_inflate(png_t * png,uint8_t * data,int len)287 png_inflate(png_t *png, uint8_t *data, int len)
288 {
289 	int result;
290 	z_stream *stream = png->zs;
291 
292 	if (!stream)
293 		return (PNG_MEMORY_ERROR);
294 
295 	stream->next_in = data;
296 	stream->avail_in = len;
297 
298 	result = inflate(stream, Z_SYNC_FLUSH);
299 
300 	if (result != Z_STREAM_END && result != Z_OK) {
301 		printf("%s\n", stream->msg);
302 		return (PNG_ZLIB_ERROR);
303 	}
304 
305 	if (stream->avail_in != 0)
306 		return (PNG_ZLIB_ERROR);
307 
308 	return (PNG_NO_ERROR);
309 }
310 
311 static int
png_read_idat(png_t * png,unsigned length)312 png_read_idat(png_t *png, unsigned length)
313 {
314 	unsigned orig_crc;
315 	unsigned calc_crc;
316 	ssize_t len = length;
317 
318 	if (!png->readbuf || png->readbuflen < length) {
319 		png->readbuf = realloc(png->readbuf, length);
320 		png->readbuflen = length;
321 	}
322 
323 	if (!png->readbuf)
324 		return (PNG_MEMORY_ERROR);
325 
326 	if (file_read(png, png->readbuf, 1, length) != len)
327 		return (PNG_FILE_ERROR);
328 
329 	calc_crc = crc32(0L, Z_NULL, 0);
330 	calc_crc = crc32(calc_crc, (uint8_t *)"IDAT", 4);
331 	calc_crc = crc32(calc_crc, (uint8_t *)png->readbuf, length);
332 
333 	if (file_read_ul(png, &orig_crc) != PNG_NO_ERROR)
334 		return (PNG_FILE_ERROR);
335 
336 	if (orig_crc != calc_crc)
337 		return (PNG_CRC_ERROR);
338 
339 	return (png_inflate(png, png->readbuf, length));
340 }
341 
342 static int
png_process_chunk(png_t * png)343 png_process_chunk(png_t *png)
344 {
345 	int result = PNG_NO_ERROR;
346 	unsigned type;
347 	unsigned length;
348 
349 	if (file_read_ul(png, &length) != PNG_NO_ERROR)
350 		return (PNG_FILE_ERROR);
351 
352 	if (file_read_ul(png, &type) != PNG_NO_ERROR)
353 		return (PNG_FILE_ERROR);
354 
355 	/*
356 	 * if we found an idat, all other idats should be followed with no
357 	 * other chunks in between
358 	 */
359 	if (type == png_IDAT) {
360 		if (!png->png_data) {	/* first IDAT */
361 			png->png_datalen = png->width * png->height *
362 			    png->bpp + png->height;
363 			png->png_data = malloc(png->png_datalen);
364 		}
365 
366 		if (!png->png_data)
367 			return (PNG_MEMORY_ERROR);
368 
369 		if (!png->zs) {
370 			result = png_init_inflate(png);
371 			if (result != PNG_NO_ERROR)
372 				return (result);
373 		}
374 
375 		return (png_read_idat(png, length));
376 	} else if (type == png_IEND)
377 		return (PNG_DONE);
378 	else
379 		(void) file_read(png, 0, 1, length + 4); /* unknown chunk */
380 
381 	return (result);
382 }
383 
384 static void
png_filter_sub(unsigned stride,uint8_t * in,uint8_t * out,unsigned len)385 png_filter_sub(unsigned stride, uint8_t *in, uint8_t *out, unsigned len)
386 {
387 	unsigned i;
388 	uint8_t a = 0;
389 
390 	for (i = 0; i < len; i++) {
391 		if (i >= stride)
392 			a = out[i - stride];
393 
394 		out[i] = in[i] + a;
395 	}
396 }
397 
398 static void
png_filter_up(unsigned stride __unused,uint8_t * in,uint8_t * out,uint8_t * prev_line,unsigned len)399 png_filter_up(unsigned stride __unused, uint8_t *in, uint8_t *out,
400     uint8_t *prev_line, unsigned len)
401 {
402 	unsigned i;
403 
404 	if (prev_line) {
405 		for (i = 0; i < len; i++)
406 			out[i] = in[i] + prev_line[i];
407 	} else
408 		memcpy(out, in, len);
409 }
410 
411 static void
png_filter_average(unsigned stride,uint8_t * in,uint8_t * out,uint8_t * prev_line,unsigned len)412 png_filter_average(unsigned stride, uint8_t *in, uint8_t *out,
413     uint8_t *prev_line, unsigned len)
414 {
415 	unsigned int i;
416 	uint8_t a = 0;
417 	uint8_t b = 0;
418 	unsigned int sum = 0;
419 
420 	for (i = 0; i < len; i++) {
421 		if (prev_line)
422 			b = prev_line[i];
423 
424 		if (i >= stride)
425 			a = out[i - stride];
426 
427 		sum = a;
428 		sum += b;
429 
430 		out[i] = in[i] + sum/2;
431 	}
432 }
433 
434 static uint8_t
png_paeth(uint8_t a,uint8_t b,uint8_t c)435 png_paeth(uint8_t a, uint8_t b, uint8_t c)
436 {
437 	int p = (int)a + b - c;
438 	int pa = abs(p - a);
439 	int pb = abs(p - b);
440 	int pc = abs(p - c);
441 
442 	int pr;
443 
444 	if (pa <= pb && pa <= pc)
445 		pr = a;
446 	else if (pb <= pc)
447 		pr = b;
448 	else
449 		pr = c;
450 
451 	return (pr);
452 }
453 
454 static void
png_filter_paeth(unsigned stride,uint8_t * in,uint8_t * out,uint8_t * prev_line,unsigned len)455 png_filter_paeth(unsigned stride, uint8_t *in, uint8_t *out, uint8_t *prev_line,
456     unsigned len)
457 {
458 	unsigned i;
459 	uint8_t a;
460 	uint8_t b;
461 	uint8_t c;
462 
463 	for (i = 0; i < len; i++) {
464 		if (prev_line && i >= stride) {
465 			a = out[i - stride];
466 			b = prev_line[i];
467 			c = prev_line[i - stride];
468 		} else {
469 			if (prev_line)
470 				b = prev_line[i];
471 			else
472 				b = 0;
473 
474 			if (i >= stride)
475 				a = out[i - stride];
476 			else
477 				a = 0;
478 
479 			c = 0;
480 		}
481 
482 		out[i] = in[i] + png_paeth(a, b, c);
483 	}
484 }
485 
486 static int
png_unfilter(png_t * png,uint8_t * data)487 png_unfilter(png_t *png, uint8_t *data)
488 {
489 	unsigned i;
490 	unsigned pos = 0;
491 	unsigned outpos = 0;
492 	uint8_t *filtered = png->png_data;
493 	unsigned stride = png->bpp;
494 
495 	while (pos < png->png_datalen) {
496 		uint8_t filter = filtered[pos];
497 
498 		pos++;
499 
500 		if (png->depth == 16) {
501 			for (i = 0; i < png->width * stride; i += 2) {
502 				*(short *)(filtered+pos+i) =
503 				    (filtered[pos+i] << 8) | filtered[pos+i+1];
504 			}
505 		}
506 
507 		switch (filter) {
508 		case 0: /* none */
509 			memcpy(data+outpos, filtered+pos, png->width * stride);
510 			break;
511 		case 1: /* sub */
512 			png_filter_sub(stride, filtered+pos, data+outpos,
513 			    png->width * stride);
514 			break;
515 		case 2: /* up */
516 			if (outpos) {
517 				png_filter_up(stride, filtered+pos, data+outpos,
518 				    data + outpos - (png->width*stride),
519 				    png->width*stride);
520 			} else {
521 				png_filter_up(stride, filtered+pos, data+outpos,
522 				    0, png->width*stride);
523 			}
524 			break;
525 		case 3: /* average */
526 			if (outpos) {
527 				png_filter_average(stride, filtered+pos,
528 				    data+outpos,
529 				    data + outpos - (png->width*stride),
530 				    png->width*stride);
531 			} else {
532 				png_filter_average(stride, filtered+pos,
533 				    data+outpos, 0, png->width*stride);
534 			}
535 			break;
536 		case 4: /* paeth */
537 			if (outpos) {
538 				png_filter_paeth(stride, filtered+pos,
539 				    data+outpos,
540 				    data + outpos - (png->width*stride),
541 				    png->width*stride);
542 			} else {
543 				png_filter_paeth(stride, filtered+pos,
544 				    data+outpos, 0, png->width*stride);
545 			}
546 			break;
547 		default:
548 			return (PNG_UNKNOWN_FILTER);
549 		}
550 
551 		outpos += png->width * stride;
552 		pos += png->width * stride;
553 	}
554 
555 	return (PNG_NO_ERROR);
556 }
557 
558 int
png_get_data(png_t * png,uint8_t * data)559 png_get_data(png_t *png, uint8_t *data)
560 {
561 	int result = PNG_NO_ERROR;
562 
563 	png->zs = NULL;
564 	png->png_datalen = 0;
565 	png->png_data = NULL;
566 	png->readbuf = NULL;
567 	png->readbuflen = 0;
568 
569 	while (result == PNG_NO_ERROR)
570 		result = png_process_chunk(png);
571 
572 	if (png->readbuf) {
573 		free(png->readbuf);
574 		png->readbuflen = 0;
575 	}
576 	if (png->zs)
577 		(void) png_end_inflate(png);
578 
579 	if (result != PNG_DONE) {
580 		free(png->png_data);
581 		return (result);
582 	}
583 
584 	result = png_unfilter(png, data);
585 
586 	free(png->png_data);
587 
588 	return (result);
589 }
590 
591 char *
png_error_string(int error)592 png_error_string(int error)
593 {
594 	switch (error) {
595 	case PNG_NO_ERROR:
596 		return ("No error");
597 	case PNG_FILE_ERROR:
598 		return ("Unknown file error.");
599 	case PNG_HEADER_ERROR:
600 		return ("No PNG header found. Are you sure this is a PNG?");
601 	case PNG_IO_ERROR:
602 		return ("Failure while reading file.");
603 	case PNG_EOF_ERROR:
604 		return ("Reached end of file.");
605 	case PNG_CRC_ERROR:
606 		return ("CRC or chunk length error.");
607 	case PNG_MEMORY_ERROR:
608 		return ("Could not allocate memory.");
609 	case PNG_ZLIB_ERROR:
610 		return ("zlib reported an error.");
611 	case PNG_UNKNOWN_FILTER:
612 		return ("Unknown filter method used in scanline.");
613 	case PNG_DONE:
614 		return ("PNG done");
615 	case PNG_NOT_SUPPORTED:
616 		return ("The PNG is unsupported by pnglite, too bad for you!");
617 	case PNG_WRONG_ARGUMENTS:
618 		return ("Wrong combination of arguments passed to png_open. "
619 		    "You must use either a read_function or supply a file "
620 		    "pointer to use.");
621 	default:
622 		return ("Unknown error.");
623 	};
624 }
625