2015-02-17 03:49:39 +01:00

546 lines
17 KiB
C

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <boolean.h>
#ifdef WANT_MINIZ
#define MINIZ_HEADER_FILE_ONLY
#include "miniz.c"
#endif
#include <formats/mpng.h>
static uint32_t dword_be(const uint8_t *buf)
{
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | (buf[3] << 0);
}
static uint16_t word_be(const uint8_t *buf)
{
return (buf[0] << 16) | (buf[1] << 8) | (buf[2] << 0);
}
#define read8r(source) (*(source))
#define read8(target) do { target = read8r(chunkdata); chunkdata += 1; } while(0)
#define read24(target) do { target = word_be(chunkdata); chunkdata += 3; } while(0)
#define read32(target) do { target = dword_be(chunkdata); chunkdata += 4; } while(0)
enum mpng_chunk_type
{
MPNG_CHUNK_TRNS = 0x74524E53,
MPNG_CHUNK_IHDR = 0x49484452,
MPNG_CHUNK_IDAT = 0x49444154,
MPNG_CHUNK_PLTE = 0x504c5445,
MPNG_CHUNK_IEND = 0x49454e44,
};
bool png_decode(const void *userdata, size_t len,
struct mpng_image *img, enum video_format format)
{
/* chop off some warnings... these are all initialized in IHDR */
unsigned int depth;
unsigned int color_type;
unsigned int compression;
unsigned int filter;
unsigned int interlace;
unsigned int bpl;
unsigned int palette[256];
unsigned int width;
unsigned int height;
int palette_len = 0;
uint8_t *pixelsat = NULL;
uint8_t *pixelsend = NULL;
uint8_t *pixels = NULL;
const uint8_t *data_end = NULL;
const uint8_t *data = (const uint8_t*)userdata;
if (!data)
return false;
memset(img, 0, sizeof(struct mpng_image));
/* Only works for RGB888, XRGB8888, and ARGB8888 */
if (format != FMT_RGB888
&& format != FMT_XRGB8888
&& format != FMT_ARGB8888)
return false;
if (len < 8)
return false;
if (memcmp(data, "\x89PNG\r\n\x1A\n", 8))
return false;
data_end = data + len;
data += 8;
memset(palette, 0, sizeof(palette));
#ifdef WANT_MINIZ
tinfl_decompressor inflator;
tinfl_init(&inflator);
#endif
while (1)
{
unsigned int chunklen, chunktype;
unsigned int chunkchecksum;
unsigned int actualchunkchecksum;
const uint8_t * chunkdata = NULL;
if ((data + 4 + 4) > data_end)
goto error;
chunklen = dword_be(data);
chunktype = dword_be(data+4);
if (chunklen>=0x80000000)
goto error;
if ((data + 4 + chunklen + 4) > data_end)
goto error;
chunkchecksum = mz_crc32(mz_crc32(0, NULL, 0), (uint8_t*)data+4, 4 + chunklen);
chunkdata = data + 4 + 4;
actualchunkchecksum = dword_be(data + 4 + 4 + chunklen);
if (actualchunkchecksum != chunkchecksum)
goto error;
data += 4 + 4 + chunklen + 4;
switch (chunktype)
{
case MPNG_CHUNK_IHDR:
{
read32(width);
read32(height);
read8(depth);
read8(color_type);
read8(compression);
read8(filter);
read8(interlace);
if (width >= 0x80000000)
goto error;
if (width == 0 || height == 0)
goto error;
if (height >= 0x80000000)
goto error;
if (color_type != 2 && color_type != 3 && color_type != 6)
goto error;
/*
* Greyscale 0
* Truecolour 2
* Indexed-colour 3
* Greyscale with alpha 4
* Truecolour with alpha 6
**/
/* Truecolor; can be 16bpp but I don't want that. */
if (color_type == 2 && depth != 8)
goto error;
/* Paletted. */
if (color_type == 3 && depth != 1 && depth != 2 && depth != 4 && depth != 8)
goto error;
/* Truecolor with alpha. */
if (color_type == 6 && depth != 8)
goto error;
/* Can only decode alpha on ARGB formats. */
if (color_type == 6 && format != FMT_ARGB8888)
goto error;
if (compression != 0)
goto error;
if (filter != 0)
goto error;
if (interlace != 0 && interlace != 1)
goto error;
if (color_type == 2)
bpl = 3 * width;
if (color_type == 3)
bpl = (width * depth + depth - 1) / 8;
if (color_type == 6)
bpl = 4 * width;
pixels = (uint8_t*)malloc((bpl + 1) * height);
if (!pixels)
goto error;
pixelsat = pixels;
pixelsend = pixels + (bpl + 1) * height;
}
break;
case MPNG_CHUNK_PLTE:
{
unsigned i;
if (!pixels || palette_len != 0)
goto error;
if (chunklen == 0 || chunklen % 3 || chunklen > 3 * 256)
goto error;
/* Palette on RGB is allowed but rare,
* and it's just a recommendation anyways. */
if (color_type != 3)
break;
palette_len = chunklen / 3;
for (i = 0; i < palette_len; i++)
{
read24(palette[i]);
palette[i] |= 0xFF000000;
}
}
break;
case MPNG_CHUNK_TRNS:
{
if (format != FMT_ARGB8888 || !pixels || pixels != pixelsat)
goto error;
if (color_type == 2)
{
if (palette_len == 0)
goto error;
goto error;
}
else if (color_type == 3)
goto error;
else
goto error;
}
break;
case MPNG_CHUNK_IDAT:
{
size_t byteshere;
size_t chunklen_copy;
#ifdef WANT_MINIZ
tinfl_status status;
#endif
if (!pixels || (color_type == 3 && palette_len == 0))
goto error;
chunklen_copy = chunklen;
byteshere = (pixelsend - pixelsat)+1;
#ifdef WANT_MINIZ
status = tinfl_decompress(&inflator,
(const mz_uint8 *)chunkdata,
&chunklen_copy, pixels,
pixelsat,
&byteshere,
TINFL_FLAG_HAS_MORE_INPUT |
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF |
TINFL_FLAG_PARSE_ZLIB_HEADER);
#endif
pixelsat += byteshere;
#ifdef WANT_MINIZ
if (status < TINFL_STATUS_DONE)
goto error;
#endif
}
break;
case MPNG_CHUNK_IEND:
{
unsigned b, x, y;
#ifdef WANT_MINIZ
tinfl_status status;
#endif
size_t finalbytes;
unsigned int bpp_packed;
uint8_t *prevout;
size_t zero = 0;
uint8_t * out = NULL;
uint8_t * filteredline = NULL;
if (data != data_end)
goto error;
if (chunklen)
goto error;
finalbytes = (pixelsend - pixelsat);
#ifdef WANT_MINIZ
status = tinfl_decompress(&inflator, (const mz_uint8 *)NULL, &zero, pixels, pixelsat, &finalbytes,
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | TINFL_FLAG_PARSE_ZLIB_HEADER);
#endif
pixelsat += finalbytes;
#ifdef WANT_MINIZ
if (status < TINFL_STATUS_DONE)
goto error;
if (status > TINFL_STATUS_DONE)
goto error;
#endif
/* Too little data (can't be too much
* because we didn't give it that buffer size) */
if (pixelsat != pixelsend)
goto error;
out = (uint8_t*)malloc(videofmt_byte_per_pixel(format) * width * height);
/* TODO: deinterlace at random point */
/* run filters */
bpp_packed = ((color_type == 2) ? 3 : (color_type == 6) ? 4 : 1);
prevout = (out + (4 * width * 1));
/* This will blow up if a 1px high image
* is filtered with Paeth, but highly unlikely. */
if (height==1)
prevout=out;
/* Not using bpp here because we only need a chunk of black anyways */
memset(prevout, 0, 4 * width * 1);
filteredline = pixels;
for (y = 0; y < height; y++)
{
uint8_t *thisout = (uint8_t*)(out + (bpl*y));
switch (*(filteredline++))
{
case 0:
memcpy(thisout, filteredline, bpl);
break;
case 1:
memcpy(thisout, filteredline, bpp_packed);
for (x = bpp_packed; x < bpl; x++)
thisout[x] = thisout[x - bpp_packed] + filteredline[x];
break;
case 2:
for (x = 0; x < bpl; x++)
thisout[x] = prevout[x] + filteredline[x];
break;
case 3:
for (x = 0; x < bpp_packed; x++)
{
int a = 0;
int b = prevout[x];
thisout[x] = (a+b)/2 + filteredline[x];
}
for (x = bpp_packed; x < bpl; x++)
{
int a = thisout[x - bpp_packed];
int b = prevout[x];
thisout[x] = (a + b) / 2 + filteredline[x];
}
break;
case 4:
for (x = 0; x < bpp_packed; x++)
{
int prediction;
int a = 0;
int b = prevout[x];
int c = 0;
int p = a+b-c;
int pa = abs(p-a);
int pb = abs(p-b);
int pc = abs(p-c);
if (pa <= pb && pa <= pc)
prediction=a;
else if (pb <= pc)
prediction=b;
else
prediction=c;
thisout[x] = filteredline[x]+prediction;
}
for (x = bpp_packed; x < bpl; x++)
{
int prediction;
int a = thisout[x - bpp_packed];
int b = prevout[x];
int c = prevout[x - bpp_packed];
int p = a+b-c;
int pa = abs(p-a);
int pb = abs(p-b);
int pc = abs(p-c);
if (pa <= pb && pa <= pc)
prediction = a;
else if (pb <= pc)
prediction = b;
else
prediction = c;
thisout[x] = filteredline[x] + prediction;
}
break;
default:
goto error;
}
prevout = thisout;
filteredline += bpl;
}
/* Unpack paletted data
* not sure if these aliasing tricks are valid,
* but the prerequisites for that bugging up
* are pretty much impossible to hit.
**/
if (color_type == 3)
{
switch (depth)
{
case 1:
{
int y=height;
uint8_t * outp=out+3*width*height;
do
{
uint8_t * inp=out+y*bpl;
int x=(width+7)/8;
do
{
x--;
inp--;
for (b = 0; b < 8; b++)
{
int rgb32 = palette[((*inp)>>b)&1];
*(--outp) = rgb32 >> 0;
*(--outp) = rgb32 >> 8;
*(--outp) = rgb32 >> 16;
}
} while(x);
y--;
} while(y);
}
break;
case 2:
{
int y=height;
uint8_t * outp=out+3*width*height;
do
{
unsigned char *inp = out + y * bpl;
int x = (width + 3) / 4;
do
{
int b;
x--;
inp--;
for (b = 0;b < 8; b += 2)
{
int rgb32 = palette[((*inp)>>b)&3];
*(--outp) = rgb32 >> 0;
*(--outp) = rgb32 >> 8;
*(--outp) = rgb32 >> 16;
}
} while(x);
y--;
} while(y);
}
break;
case 4:
{
int y = height;
uint8_t *outp = out + 3 * width * height;
do
{
unsigned char *inp = out + y * bpl;
int x = (width+1) / 2;
do
{
int rgb32;
x--;
inp--;
rgb32 = palette[*inp&15];
*(--outp) = rgb32 >> 0;
*(--outp) = rgb32 >> 8;
*(--outp) = rgb32 >> 16;
rgb32 = palette[*inp>>4];
*(--outp) = rgb32 >> 0;
*(--outp) = rgb32 >> 8;
*(--outp) = rgb32 >> 16;
} while(x);
y--;
} while(y);
}
break;
case 8:
{
uint8_t *inp = out + width * height;
uint8_t *outp = out + 3 * width * height;
int i = width * height;
do
{
int rgb32;
i--;
inp -= 1;
rgb32 = palette[*inp];
*(--outp) = rgb32 >> 0;
*(--outp) = rgb32 >> 8;
*(--outp) = rgb32 >> 16;
} while(i);
}
break;
}
}
/* unpack to 32bpp if requested */
if (format != FMT_RGB888 && color_type == 2)
{
uint8_t *inp = out + width * height * 3;
uint32_t *outp = ((uint32_t*)out) + width * height;
int i = width*height;
do
{
i--;
inp-=3;
outp--;
*outp = word_be(inp) | 0xFF000000;
} while(i);
}
img->width = width;
img->height = height;
img->pixels = out;
img->pitch = videofmt_byte_per_pixel(format)*width;
img->format = format;
free(pixels);
return true;
}
break;
default:
if (!(chunktype & 0x20000000))
goto error;//unknown critical
//otherwise ignore
}
}
error:
free(pixels);
memset(img, 0, sizeof(struct mpng_image));
return false;
}