Split up main loop into png_decode_iterate function - return -1

when we need to goto error, 1 if successful, 0 if we need to
iterate again
This commit is contained in:
twinaphex 2015-02-17 05:57:40 +01:00
parent 23ea613bcf
commit 40e59507dd

View File

@ -162,6 +162,385 @@ static bool mpng_read_plte(struct mpng_ihdr *ihdr,
return true;
}
bool png_decode_iterate(const uint8_t *data, const uint8_t *data_end,
struct mpng_ihdr *ihdr, struct mpng_image *img,
uint32_t *palette, enum video_format format,
unsigned int *bpl, int *palette_len, uint8_t *pixels,
uint8_t *pixelsat, uint8_t *pixelsend)
{
unsigned int chunkchecksum;
unsigned int actualchunkchecksum;
struct mpng_chunk chunk = {0};
if ((data + 4 + 4) > data_end)
return -1;
chunk.size = dword_be(data);
chunk.type = dword_be(data + 4);
if (chunk.size >= 0x80000000)
return -1;
if ((data + 4 + chunk.size + 4) > data_end)
return -1;
chunkchecksum = mz_crc32(mz_crc32(0, NULL, 0), (uint8_t*)data+4, 4 + chunk.size);
chunk.data = (const uint8_t*)(data + 4 + 4);
actualchunkchecksum = dword_be(data + 4 + 4 + chunk.size);
if (actualchunkchecksum != chunkchecksum)
return -1;
data += 4 + 4 + chunk.size + 4;
switch (chunk.type)
{
case MPNG_CHUNK_IHDR:
if (!mpng_parse_ihdr(ihdr, &chunk, format, bpl, pixels,
pixelsat, pixelsend))
return -1;
break;
case MPNG_CHUNK_PLTE:
*palette_len = chunk.size / 3;
if (!mpng_read_plte(ihdr, &chunk, pixels, palette, *palette_len))
return -1;
break;
case MPNG_CHUNK_TRNS:
if (format != FMT_ARGB8888 || !pixels || pixels != pixelsat)
return -1;
if (ihdr->color_type == 2)
{
if (*palette_len == 0)
return -1;
return -1;
}
else if (ihdr->color_type == 3)
return -1;
else
return -1;
break;
case MPNG_CHUNK_IDAT:
{
size_t byteshere;
size_t chunklen_copy;
#ifdef WANT_MINIZ
tinfl_status status;
#endif
if (!pixels || (ihdr->color_type == 3 && (*palette_len) == 0))
return -1;
chunklen_copy = chunk.size;
byteshere = (pixelsend - pixelsat)+1;
#ifdef WANT_MINIZ
status = tinfl_decompress(&inflator,
(const mz_uint8 *)chunk.data,
&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)
return -1;
#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)
return -1;
if (chunk.size)
return -1;
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)
return -1;
if (status > TINFL_STATUS_DONE)
return -1;
#endif
/* Too little data (can't be too much
* because we didn't give it that buffer size) */
if (pixelsat != pixelsend)
return -1;
out = (uint8_t*)malloc(videofmt_byte_per_pixel(format) * ihdr->width * ihdr->height);
if (!out)
return -1;
/* TODO: deinterlace at random point */
/* run filters */
bpp_packed = ((ihdr->color_type == 2) ? 3 : (ihdr->color_type == 6) ? 4 : 1);
prevout = (out + (4 * ihdr->width * 1));
/* This will blow up if a 1px high image
* is filtered with Paeth, but highly unlikely. */
if (ihdr->height==1)
prevout = out;
/* Not using bpp here because we only need a chunk of black anyways */
memset(prevout, 0, 4 * ihdr->width * 1);
filteredline = pixels;
for (y = 0; y < ihdr->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:
return -1;
}
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 (ihdr->color_type == 3)
{
switch (ihdr->depth)
{
case 1:
{
int y = ihdr->height;
uint8_t * outp = out + 3 * ihdr->width * ihdr->height;
do
{
uint8_t * inp = (uint8_t*)(out + y * (*bpl));
int x = (ihdr->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 = ihdr->height;
uint8_t * outp = (uint8_t*)(out + 3 * ihdr->width * ihdr->height);
do
{
unsigned char *inp = out + y * (*bpl);
int x = (ihdr->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 = ihdr->height;
uint8_t *outp = out + 3 * ihdr->width * ihdr->height;
do
{
unsigned char *inp = out + y * (*bpl);
int x = (ihdr->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 = (uint8_t*)(out + ihdr->width * ihdr->height);
uint8_t *outp = (uint8_t*)(out + 3 * ihdr->width * ihdr->height);
int i = ihdr->width * ihdr->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 && ihdr->color_type == 2)
{
uint8_t *inp = (uint8_t*)(out + ihdr->width * ihdr->height * 3);
uint32_t *outp = (uint32_t*)(((uint32_t*)out) + ihdr->width * ihdr->height);
int i = ihdr->width * ihdr->height;
do
{
i--;
inp-=3;
outp--;
*outp = word_be(inp) | 0xFF000000;
} while(i);
}
img->width = ihdr->width;
img->height = ihdr->height;
img->pixels = out;
img->pitch = videofmt_byte_per_pixel(format) * ihdr->width;
img->format = format;
free(pixels);
return 1;
}
break;
default:
if (!(chunk.type & 0x20000000))
return -1; /* unknown critical */
/* otherwise ignore */
}
return 0;
}
bool png_decode(const void *userdata, size_t len,
struct mpng_image *img, enum video_format format)
{
@ -204,374 +583,18 @@ bool png_decode(const void *userdata, size_t len,
while (1)
{
unsigned int chunkchecksum;
unsigned int actualchunkchecksum;
struct mpng_chunk chunk = {0};
int ret = png_decode_iterate(data, data_end,
&ihdr, img, palette, format, &bpl, &palette_len,
pixels, pixelsat, pixelsend);
if ((data + 4 + 4) > data_end)
goto error;
chunk.size = dword_be(data);
chunk.type = dword_be(data + 4);
if (chunk.size >= 0x80000000)
goto error;
if ((data + 4 + chunk.size + 4) > data_end)
goto error;
chunkchecksum = mz_crc32(mz_crc32(0, NULL, 0), (uint8_t*)data+4, 4 + chunk.size);
chunk.data = (const uint8_t*)(data + 4 + 4);
actualchunkchecksum = dword_be(data + 4 + 4 + chunk.size);
if (actualchunkchecksum != chunkchecksum)
goto error;
data += 4 + 4 + chunk.size + 4;
switch (chunk.type)
switch (ret)
{
case MPNG_CHUNK_IHDR:
if (!mpng_parse_ihdr(&ihdr, &chunk, format, &bpl, pixels,
pixelsat, pixelsend))
goto error;
break;
case MPNG_CHUNK_PLTE:
palette_len = chunk.size / 3;
if (!mpng_read_plte(&ihdr, &chunk, pixels, palette, palette_len))
goto error;
break;
case MPNG_CHUNK_TRNS:
if (format != FMT_ARGB8888 || !pixels || pixels != pixelsat)
goto error;
if (ihdr.color_type == 2)
{
if (palette_len == 0)
goto error;
goto error;
}
else if (ihdr.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 || (ihdr.color_type == 3 && palette_len == 0))
goto error;
chunklen_copy = chunk.size;
byteshere = (pixelsend - pixelsat)+1;
#ifdef WANT_MINIZ
status = tinfl_decompress(&inflator,
(const mz_uint8 *)chunk.data,
&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 (chunk.size)
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) * ihdr.width * ihdr.height);
if (!out)
goto error;
/* TODO: deinterlace at random point */
/* run filters */
bpp_packed = ((ihdr.color_type == 2) ? 3 : (ihdr.color_type == 6) ? 4 : 1);
prevout = (out + (4 * ihdr.width * 1));
/* This will blow up if a 1px high image
* is filtered with Paeth, but highly unlikely. */
if (ihdr.height==1)
prevout = out;
/* Not using bpp here because we only need a chunk of black anyways */
memset(prevout, 0, 4 * ihdr.width * 1);
filteredline = pixels;
for (y = 0; y < ihdr.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 (ihdr.color_type == 3)
{
switch (ihdr.depth)
{
case 1:
{
int y = ihdr.height;
uint8_t * outp = out + 3 * ihdr.width * ihdr.height;
do
{
uint8_t * inp = (uint8_t*)(out + y * bpl);
int x = (ihdr.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 = ihdr.height;
uint8_t * outp = (uint8_t*)(out + 3 * ihdr.width * ihdr.height);
do
{
unsigned char *inp = out + y * bpl;
int x = (ihdr.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 = ihdr.height;
uint8_t *outp = out + 3 * ihdr.width * ihdr.height;
do
{
unsigned char *inp = out + y * bpl;
int x = (ihdr.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 = (uint8_t*)(out + ihdr.width * ihdr.height);
uint8_t *outp = (uint8_t*)(out + 3 * ihdr.width * ihdr.height);
int i = ihdr.width * ihdr.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 && ihdr.color_type == 2)
{
uint8_t *inp = (uint8_t*)(out + ihdr.width * ihdr.height * 3);
uint32_t *outp = (uint32_t*)(((uint32_t*)out) + ihdr.width * ihdr.height);
int i = ihdr.width * ihdr.height;
do
{
i--;
inp-=3;
outp--;
*outp = word_be(inp) | 0xFF000000;
} while(i);
}
img->width = ihdr.width;
img->height = ihdr.height;
img->pixels = out;
img->pitch = videofmt_byte_per_pixel(format) * ihdr.width;
img->format = format;
free(pixels);
return true;
}
break;
case -1:
goto error;
case 1:
return true;
default:
if (!(chunk.type & 0x20000000))
goto error; /* unknown critical */
/* otherwise ignore */
break;
}
}