ddnet/src/engine/client/pnglite/pnglite.c

611 lines
12 KiB
C
Raw Normal View History

/* pnglite.c - pnglite library
For conditions of distribution and use, see copyright notice in pnglite.h
*/
#include <zlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
2007-05-27 10:42:59 +00:00
#include "pnglite.h"
#define DO_CRC_CHECKS 1
static png_alloc_t png_alloc;
static png_free_t png_free;
static size_t file_read(png_t* png, void* out, size_t size, size_t numel)
{
size_t result;
if(png->read_fun)
{
result = png->read_fun(out, size, numel, png->user_pointer);
}
else
{
if(!out)
{
result = fseek(png->user_pointer, (long)(size*numel), SEEK_CUR);
}
else
{
result = fread(out, size, numel, png->user_pointer);
}
}
return result;
}
2007-05-27 10:42:59 +00:00
static int file_read_ul(png_t* png, unsigned *out)
{
unsigned char buf[4];
if(file_read(png, buf, 1, 4) != 4)
return PNG_FILE_ERROR;
2007-05-27 10:42:59 +00:00
*out = (buf[0]<<24) | (buf[1]<<16) | (buf[2]<<8) | buf[3];
2007-05-27 10:42:59 +00:00
return PNG_NO_ERROR;
}
static unsigned get_ul(unsigned char* buf)
{
unsigned result;
unsigned char foo[4];
memcpy(foo, buf, 4);
result = (foo[0]<<24) | (foo[1]<<16) | (foo[2]<<8) | foo[3];
return result;
}
int png_init(png_alloc_t pngalloc, png_free_t pngfree)
{
if(pngalloc)
png_alloc = pngalloc;
else
png_alloc = &malloc;
if(pngfree)
png_free = pngfree;
else
png_free = &free;
return PNG_NO_ERROR;
}
static int png_get_bpp(png_t* png)
{
int bpp;
switch(png->color_type)
{
case PNG_GREYSCALE:
bpp = 1; break;
case PNG_TRUECOLOR:
bpp = 3; break;
case PNG_INDEXED:
bpp = 1; break;
case PNG_GREYSCALE_ALPHA:
bpp = 2; break;
case PNG_TRUECOLOR_ALPHA:
bpp = 4; break;
default:
return PNG_FILE_ERROR;
}
bpp *= png->depth/8;
return bpp;
}
static int png_read_ihdr(png_t* png)
{
unsigned length;
#if DO_CRC_CHECKS
unsigned orig_crc;
unsigned calc_crc;
#endif
2007-05-27 10:42:59 +00:00
unsigned char ihdr[13+4]; /* length should be 13, make room for type (IHDR) */
2007-05-27 10:42:59 +00:00
file_read_ul(png, &length);
if(length != 13)
{
printf("%d\n", length);
return PNG_CRC_ERROR;
}
if(file_read(png, ihdr, 1, 13+4) != 13+4)
return PNG_EOF_ERROR;
#if DO_CRC_CHECKS
2007-05-27 10:42:59 +00:00
file_read_ul(png, &orig_crc);
calc_crc = crc32(0L, Z_NULL, 0);
calc_crc = crc32(calc_crc, ihdr, 13+4);
if(orig_crc != calc_crc)
return PNG_CRC_ERROR;
#else
file_read_ul(png);
#endif
png->width = get_ul(ihdr+4);
png->height = get_ul(ihdr+8);
png->depth = ihdr[12];
png->color_type = ihdr[13];
png->compression_method = ihdr[14];
png->filter_method = ihdr[15];
png->interlace_method = ihdr[16];
if(png->color_type == PNG_INDEXED)
return PNG_NOT_SUPPORTED;
if(png->depth != 8 && png->depth != 16)
return PNG_NOT_SUPPORTED;
if(png->interlace_method)
return PNG_NOT_SUPPORTED;
return PNG_NO_ERROR;
}
void png_print_info(png_t* png)
{
printf("PNG INFO:\n");
printf("\twidth:\t\t%d\n", png->width);
printf("\theight:\t\t%d\n", png->height);
printf("\tdepth:\t\t%d\n", png->depth);
printf("\tcolor:\t\t");
switch(png->color_type)
{
case PNG_GREYSCALE: printf("greyscale\n"); break;
case PNG_TRUECOLOR: printf("truecolor\n"); break;
case PNG_INDEXED: printf("palette\n"); break;
case PNG_GREYSCALE_ALPHA: printf("greyscale with alpha\n"); break;
case PNG_TRUECOLOR_ALPHA: printf("truecolor with alpha\n"); break;
default: printf("unknown, this is not good\n"); break;
}
printf("\tcompression:\t%s\n", png->compression_method?"unknown, this is not good":"inflate/deflate");
printf("\tfilter:\t\t%s\n", png->filter_method?"unknown, this is not good":"adaptive");
printf("\tinterlace:\t%s\n", png->interlace_method?"interlace":"no interlace");
}
int png_open(png_t* png, png_read_callback_t read_fun, void* user_pointer)
{
char header[8];
int result;
png->read_fun = read_fun;
png->user_pointer = user_pointer;
if(!read_fun && !user_pointer)
return PNG_WRONG_ARGUMENTS;
if(file_read(png, header, 1, 8) != 8)
return PNG_EOF_ERROR;
if(memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) != 0)
return PNG_HEADER_ERROR;
result = png_read_ihdr(png);
2007-05-27 10:42:59 +00:00
png->bpp = (unsigned char)png_get_bpp(png);
return result;
}
int png_open_file(png_t *png, const char* filename)
{
FILE* fp = fopen(filename, "rb");
if(!fp)
return PNG_FILE_ERROR;
return png_open(png, 0, fp);
}
int png_close_file(png_t* png)
{
fclose(png->user_pointer);
return PNG_NO_ERROR;
}
static int png_init_inflate(png_t* png)
{
z_stream *stream;
png->zs = png_alloc(sizeof(z_stream));
stream = png->zs;
if(!stream)
return PNG_MEMORY_ERROR;
memset(stream, 0, sizeof(z_stream));
if(inflateInit(stream) != Z_OK)
return PNG_ZLIB_ERROR;
stream->next_out = png->png_data;
stream->avail_out = png->png_datalen;
return PNG_NO_ERROR;
}
static int png_end_inflate(png_t* png)
{
z_stream *stream = png->zs;
if(!stream)
return PNG_MEMORY_ERROR;
if(inflateEnd(stream) != Z_OK)
{
printf("ZLIB says: %s\n", stream->msg);
return PNG_ZLIB_ERROR;
}
png_free(png->zs);
return PNG_NO_ERROR;
}
2007-05-27 10:42:59 +00:00
static int png_inflate(png_t* png, char* data, int len)
{
int result;
z_stream *stream = png->zs;
if(!stream)
return PNG_MEMORY_ERROR;
2007-05-27 10:42:59 +00:00
stream->next_in = (unsigned char*)data;
stream->avail_in = len;
result = inflate(stream, Z_SYNC_FLUSH);
if(result != Z_STREAM_END && result != Z_OK)
{
printf("%s\n", stream->msg);
return PNG_ZLIB_ERROR;
}
if(stream->avail_in != 0)
return PNG_ZLIB_ERROR;
return PNG_NO_ERROR;
}
2007-05-27 10:42:59 +00:00
static int png_read_idat(png_t* png, unsigned firstlen)
{
2007-05-27 10:42:59 +00:00
unsigned type = 0;
char *chunk;
int result;
unsigned length = firstlen;
unsigned old_len = length;
#if DO_CRC_CHECKS
unsigned orig_crc;
unsigned calc_crc;
#endif
chunk = png_alloc(firstlen);
result = png_init_inflate(png);
if(result != PNG_NO_ERROR)
{
png_end_inflate(png);
png_free(chunk);
return result;
}
do
{
if(file_read(png, chunk, 1, length) != length)
{
png_end_inflate(png);
png_free(chunk);
return PNG_FILE_ERROR;
}
#if DO_CRC_CHECKS
calc_crc = crc32(0L, Z_NULL, 0);
2007-05-27 10:42:59 +00:00
calc_crc = crc32(calc_crc, (unsigned char*)"IDAT", 4);
calc_crc = crc32(calc_crc, (unsigned char*)chunk, length);
2007-05-27 10:42:59 +00:00
file_read_ul(png, &orig_crc);
if(orig_crc != calc_crc)
{
result = PNG_CRC_ERROR;
break;
}
#else
file_read_ul(png);
#endif
result = png_inflate(png, chunk, length);
if(result != PNG_NO_ERROR) break;
2007-05-27 10:42:59 +00:00
file_read_ul(png, &length);
if(length > old_len)
{
png_free(chunk);
chunk = png_alloc(length);
old_len = length;
}
if(file_read(png, &type, 1, 4) != 4)
{
result = PNG_FILE_ERROR;
break;
}
2007-05-27 10:42:59 +00:00
}while(type == *(unsigned int*)"IDAT");
2007-05-27 10:42:59 +00:00
if(type == *(unsigned int*)"IEND")
result = PNG_DONE;
png_free(chunk);
png_end_inflate(png);
return result;
}
static int png_process_chunk(png_t* png)
{
2007-05-27 10:42:59 +00:00
int result = PNG_NO_ERROR;
unsigned type;
unsigned length;
2007-05-27 10:42:59 +00:00
file_read_ul(png, &length);
if(file_read(png, &type, 1, 4) != 4)
return PNG_FILE_ERROR;
2007-05-27 10:42:59 +00:00
if(type == *(unsigned int*)"IDAT") /* if we found an idat, all other idats should be followed with no other chunks in between */
{
png->png_datalen = png->width * png->height * png->bpp + png->height;
png->png_data = png_alloc(png->png_datalen);
if(!png->png_data)
return PNG_MEMORY_ERROR;
2007-05-27 10:42:59 +00:00
return png_read_idat(png, length);
}
2007-05-27 10:42:59 +00:00
else if(type == *(unsigned int*)"IEND")
{
return PNG_DONE;
}
else
{
2007-05-27 10:42:59 +00:00
file_read(png, 0, 1, length + 4); /* unknown chunk */
}
return result;
}
2007-05-27 10:42:59 +00:00
static void png_filter_sub(int stride, unsigned char* in, unsigned char* out, int len)
{
int i;
unsigned char a = 0;
for(i = 0; i < len; i++)
{
if(i >= stride)
a = out[i - stride];
out[i] = in[i] + a;
}
}
2007-05-27 10:42:59 +00:00
static void png_filter_up(int stride, unsigned char* in, unsigned char* out, unsigned char* prev_line, int len)
{
int i;
if(prev_line)
{
for(i = 0; i < len; i++)
out[i] = in[i] + prev_line[i];
}
else
memcpy(out, in, len);
}
2007-05-27 10:42:59 +00:00
static void png_filter_average(int stride, unsigned char* in, unsigned char* out, unsigned char* prev_line, int len)
{
int i;
unsigned char a = 0;
unsigned char b = 0;
unsigned int sum = 0;
for(i = 0; i < len; i++)
{
if(prev_line)
b = prev_line[i];
if(i >= stride)
a = out[i - stride];
sum = a;
sum += b;
2007-05-27 10:42:59 +00:00
out[i] = (char)(in[i] + sum/2);
}
}
static unsigned char png_paeth(unsigned char a, unsigned char b, unsigned char c)
{
int p = (int)a + b - c;
int pa = abs(p - a);
int pb = abs(p - b);
int pc = abs(p - c);
int pr;
if(pa <= pb && pa <= pc)
pr = a;
else if(pb <= pc)
pr = b;
else
pr = c;
2007-05-27 10:42:59 +00:00
return (char)pr;
}
2007-05-27 10:42:59 +00:00
static void png_filter_paeth(int stride, unsigned char* in, unsigned char* out, unsigned char* prev_line, int len)
{
int i;
unsigned char a;
unsigned char b;
unsigned char c;
for(i = 0; i < len; i++)
{
if(prev_line && i >= stride)
{
a = out[i - stride];
b = prev_line[i];
c = prev_line[i - stride];
}
else
{
if(prev_line)
b = prev_line[i];
else
b = 0;
if(i >= stride)
a = out[i - stride];
else
a = 0;
c = 0;
}
out[i] = in[i] + png_paeth(a, b, c);
}
}
static int png_unfilter(png_t* png, unsigned char* data)
{
unsigned i;
unsigned pos = 0;
unsigned outpos = 0;
unsigned char *filtered = png->png_data;
int stride = png->bpp;
while(pos < png->png_datalen)
{
unsigned char filter = filtered[pos];
pos++;
if(png->depth == 16)
{
for(i = 0; i < png->width * stride; i+=2)
{
*(short*)(filtered+pos+i) = (filtered[pos+i] << 8) | filtered[pos+i+1];
}
}
switch(filter)
{
2007-05-27 10:42:59 +00:00
case 0: /* none */
memcpy(data+outpos, filtered+pos, png->width * stride);
break;
2007-05-27 10:42:59 +00:00
case 1: /* sub */
png_filter_sub(stride, filtered+pos, data+outpos, png->width * stride);
break;
2007-05-27 10:42:59 +00:00
case 2: /* up */
if(outpos)
2007-05-27 10:42:59 +00:00
png_filter_up(stride, filtered+pos, data+outpos, data + outpos - (png->width*stride), png->width*stride);
else
2007-05-27 10:42:59 +00:00
png_filter_up(stride, filtered+pos, data+outpos, 0, png->width*stride);
break;
2007-05-27 10:42:59 +00:00
case 3: /* average */
if(outpos)
2007-05-27 10:42:59 +00:00
png_filter_average(stride, filtered+pos, data+outpos, data + outpos - (png->width*stride), png->width*stride);
else
2007-05-27 10:42:59 +00:00
png_filter_average(stride, filtered+pos, data+outpos, 0, png->width*stride);
break;
2007-05-27 10:42:59 +00:00
case 4: /* paeth */
if(outpos)
2007-05-27 10:42:59 +00:00
png_filter_paeth(stride, filtered+pos, data+outpos, data + outpos - (png->width*stride), png->width*stride);
else
2007-05-27 10:42:59 +00:00
png_filter_paeth(stride, filtered+pos, data+outpos, 0, png->width*stride);
break;
default:
return PNG_UNKNOWN_FILTER;
}
outpos += png->width * stride;
pos += png->width * stride;
}
return PNG_NO_ERROR;
}
int png_get_data(png_t* png, unsigned char* data)
{
int result = PNG_NO_ERROR;
while(result == PNG_NO_ERROR)
{
result = png_process_chunk(png);
}
if(result != PNG_DONE)
{
png_free(png->png_data);
return result;
}
result = png_unfilter(png, data);
png_free(png->png_data);
return result;
}
char* png_error_string(int error)
{
switch(error)
{
case PNG_NO_ERROR:
return "No error";
case PNG_FILE_ERROR:
return "Unknown file error.";
case PNG_HEADER_ERROR:
return "No PNG header found. Are you sure this is a PNG?";
case PNG_IO_ERROR:
return "Failure while reading file.";
case PNG_EOF_ERROR:
return "Reached end of file.";
case PNG_CRC_ERROR:
return "CRC or chunk length error.";
case PNG_MEMORY_ERROR:
return "Could not allocate memory.";
case PNG_ZLIB_ERROR:
return "zlib reported an error.";
case PNG_UNKNOWN_FILTER:
return "Unknown filter method used in scanline.";
case PNG_DONE:
return "PNG done";
case PNG_NOT_SUPPORTED:
return "The PNG is unsupported by pnglite, too bad for you!";
case PNG_WRONG_ARGUMENTS:
return "Wrong combination of arguments passed to png_open. You must use either a read_function or supply a file pointer to use.";
default:
return "Unknown error.";
};
}