libwebsockets/lib/misc/upng.c

727 lines
15 KiB
C

/*
* LWS PNG -- derived from uPNG -- derived from LodePNG version 20100808
* Stateful, linewise PNG decode requiring ~36KB fixed heap
*
* Copyright (c) 2005-2010 Lode Vandevenne (LodePNG)
* Copyright (c) 2010 Sean Middleditch (uPNG)
* Copyright (c) 2021 Andy Green <andy@warmcat.com> (Stateful, incremental)
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
*
* AG: The above notice is the ZLIB license, libpng also uses it.
*
* This version was rewritten from the upng project's fork of lodepng and
* adapted to be a stateful stream parser. This rewrite retains the ZLIB
* license of the source material for simplicity.
*
* That allows it to use a fixed 32KB ringbuffer to hold decodes, and
* incrementally decode chunks into it as we want output lines that are not yet
* present there. The input png nor the output bitmap need to be all in one
* place at one time.
*/
#include <private-lib-core.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
typedef enum upng_color {
LWS_UPNG_LUM = 0,
LWS_UPNG_RGB = 2,
LWS_UPNG_LUMA = 4,
LWS_UPNG_RGBA = 6
} upng_color;
struct upng_unfline {
uint8_t *recon;
const uint8_t *scanline;
const uint8_t *precon;
uint8_t filterType;
unsigned int bypp;
unsigned int bypl;
const uint8_t *in;
uint8_t *lines;
unsigned int bpp;
unsigned int y;
unsigned long diff;
unsigned long ibp;
unsigned long sp;
char padded;
char alt;
};
typedef enum {
UOF_MAGIC,
UOF_SKIP,
UOF_TYPE4,
UOF_WIDTH4,
UOF_HEIGHT4,
UOF_CDEPTH,
UOF_CTYPE,
UOF_ONLY_ZERO3,
UOF_SKIP4,
UOF_CHUNK_LEN,
UOF_CHUNK_TYPE,
UOF_INSIDE,
UOF_SKIP_CHUNK_LEN,
} upng_outer_framing_t;
struct lws_upng_t {
struct upng_unfline u;
inflator_ctx_t inf;
unsigned int width;
unsigned int height;
upng_color color_type;
unsigned int color_depth;
lws_upng_format_t format;
const uint8_t *chunk;
int sctr;
uint32_t acc;
uint32_t chunklen;
uint32_t ctype;
upng_outer_framing_t of;
uint8_t no_more_input;
char hold_at_metadata;
};
static lws_stateful_ret_t
lws_upng_decode(lws_upng_t *upng, const uint8_t **buf, size_t *size);
static int
paeth(int a, int b, int c)
{
int p = a + b - c;
int pa = p > a ? p - a : a - p;
int pb = p > b ? p - b : b - p;
int pc = p > c ? p - c : c - p;
if (pa <= pb && pa <= pc)
return a;
if (pb <= pc)
return b;
return c;
}
static lws_stateful_ret_t
unfilter_scanline(lws_upng_t *u)
{
struct upng_unfline *uf = &u->u;
unsigned long i;
switch (uf->filterType) {
case 0: /* None */
for (i = 0; i < uf->bypl; i++)
uf->recon[i] = u->inf.out[(uf->sp + i) %
u->inf.info_size];
break;
case 1: /* Sub */
for (i = 0; i < uf->bypp; i++)
uf->recon[i] = u->inf.out[(uf->sp + i) %
u->inf.info_size];
for (i = uf->bypp; i < uf->bypl; i++)
uf->recon[i] = (uint8_t)(u->inf.out[(uf->sp + i) %
u->inf.info_size] +
uf->recon[i - uf->bypp]);
break;
case 2: /* Up */
if (uf->y)
for (i = 0; i < uf->bypl; i++)
uf->recon[i] = (uint8_t)(u->inf.out[(uf->sp + i) %
u->inf.info_size] + uf->precon[i]);
else
for (i = 0; i < uf->bypl; i++)
uf->recon[i] = (uint8_t)(u->inf.out[(uf->sp + i) %
u->inf.info_size]);
break;
case 3: /* Average */
if (uf->y) {
for (i = 0; i < uf->bypp; i++)
uf->recon[i] = (uint8_t)(u->inf.out[(uf->sp + i) %
u->inf.info_size] + uf->precon[i] / 2);
for (i = uf->bypp; i < uf->bypl; i++)
uf->recon[i] = (uint8_t)
(u->inf.out[(uf->sp + i) %
u->inf.info_size] +
((uf->recon[i - uf->bypp] +
uf->precon[i]) / 2));
} else {
for (i = 0; i < uf->bypp; i++)
uf->recon[i] = (uint8_t)(u->inf.out[(uf->sp + i) %
u->inf.info_size]);
for (i = uf->bypp; i < uf->bypl; i++)
uf->recon[i] = (uint8_t)(u->inf.out[(uf->sp + i) %
u->inf.info_size] +
uf->recon[i - uf->bypp] / 2);
}
break;
case 4: /* Paeth */
if (uf->y) {
for (i = 0; i < uf->bypp; i++)
uf->recon[i] = (uint8_t)(u->inf.out[(uf->sp + i) %
u->inf.info_size] +
paeth(0, uf->precon[i], 0));
for (i = uf->bypp; i < uf->bypl; i++)
uf->recon[i] = (uint8_t)(u->inf.out[(uf->sp + i) %
u->inf.info_size] +
paeth(uf->recon[i - uf->bypp],
uf->precon[i],
uf->precon[i - uf->bypp]));
break;
}
for (i = 0; i < uf->bypp; i++)
uf->recon[i] = (uint8_t)(u->inf.out[(uf->sp + i) %
u->inf.info_size]);
for (i = uf->bypp; i < uf->bypl; i++)
uf->recon[i] = (uint8_t)(u->inf.out[(uf->sp + i) %
u->inf.info_size] +
paeth(uf->recon[i - uf->bypp], 0, 0));
break;
default:
lwsl_err("%s: line start is broken %d\n", __func__,
uf->filterType);
return LWS_SRET_FATAL + 12;
}
u->inf.consumed_linear += uf->bypl;
return LWS_SRET_OK;
}
lws_stateful_ret_t
lws_upng_emit_next_line(lws_upng_t *u, const uint8_t **ppix,
const uint8_t **pos, size_t *size, char hold_at_metadata)
{
struct upng_unfline *uf = &u->u;
unsigned long obp;
lws_stateful_ret_t ret = LWS_SRET_OK;
*ppix = NULL;
u->hold_at_metadata = hold_at_metadata;
if (u->height && uf->y >= u->height)
goto out;
/*
* The decoder emits into the 32KB window ringbuffer, if we don't
* already have at least one line's worth of output in there, we'll
* have to do more inflation
*/
if (u->inf.outpos_linear - u->inf.consumed_linear < uf->bypl + 1) {
ret = lws_upng_decode(u, pos, size);
if ((!*size && ret == LWS_SRET_WANT_INPUT) ||
(ret & (LWS_SRET_FATAL | LWS_SRET_YIELD)) ||
!u->inf.outpos_linear)
return ret;
assert(u->inf.info_size);
assert(uf->bypl + 1);
}
if (u->inf.outpos_linear - u->inf.consumed_linear < uf->bypl + 1)
return ret;
obp = uf->alt ? uf->bypl : 0;
uf->precon = uf->alt ? uf->lines : uf->lines + uf->bypl;
uf->recon = &uf->lines[obp];
*ppix = uf->recon;
uf->filterType = uf->in[(u->inf.consumed_linear++) % u->inf.info_size];
uf->sp = u->inf.consumed_linear % u->inf.info_size;
if (unfilter_scanline(u) != LWS_SRET_OK) {
ret = LWS_SRET_FATAL + 13;
goto out;
}
if (uf->padded) {
unsigned long x;
for (x = 0; x < (unsigned long)u->width * (unsigned long)uf->bpp; x++) {
uint8_t bit = (uint8_t)((uf->in[(uf->ibp) >> 3] >>
(7 - ((uf->ibp) & 7))) & 1);
uf->ibp++;
if (!bit)
uf->lines[obp >> 3] &=
(uint8_t)(~(1 << (7 - (obp & 7))));
else
uf->lines[obp >> 3] = (uint8_t)(uf->lines[obp >> 3] |
(uint8_t)(1 << (7 - (obp & 7))));
obp++;
}
uf->ibp += uf->diff;
}
out:
uf->alt ^= 1;
uf->y++;
return ret;
}
static lws_upng_format_t
determine_format(lws_upng_t* upng) {
switch (upng->color_type) {
case LWS_UPNG_LUM:
switch (upng->color_depth) {
case 1:
return LWS_UPNG_LUMINANCE1;
case 2:
return LWS_UPNG_LUMINANCE2;
case 4:
return LWS_UPNG_LUMINANCE4;
case 8:
return LWS_UPNG_LUMINANCE8;
default:
return LWS_UPNG_BADFORMAT;
}
case LWS_UPNG_RGB:
switch (upng->color_depth) {
case 8:
return LWS_UPNG_RGB8;
case 16:
return LWS_UPNG_RGB16;
default:
return LWS_UPNG_BADFORMAT;
}
case LWS_UPNG_LUMA:
switch (upng->color_depth) {
case 1:
return LWS_UPNG_LUMINANCE_ALPHA1;
case 2:
return LWS_UPNG_LUMINANCE_ALPHA2;
case 4:
return LWS_UPNG_LUMINANCE_ALPHA4;
case 8:
return LWS_UPNG_LUMINANCE_ALPHA8;
default:
return LWS_UPNG_BADFORMAT;
}
case LWS_UPNG_RGBA:
switch (upng->color_depth) {
case 8:
return LWS_UPNG_RGBA8;
case 16:
return LWS_UPNG_RGBA16;
default:
return LWS_UPNG_BADFORMAT;
}
default:
return LWS_UPNG_BADFORMAT;
}
}
static const uint8_t magic[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
static lws_stateful_ret_t
lws_upng_decode(lws_upng_t* u, const uint8_t **_pos, size_t *_size)
{
const uint8_t *pos = _pos ? *_pos : NULL, *end = pos + *_size;
lws_stateful_ret_t r = LWS_SRET_FATAL + 60;
size_t m;
if (u->of == UOF_INSIDE && !u->inf.in) {
u->inf.inpos = 0;
u->inf.in = pos;
u->inf.bp = 0;
m = lws_ptr_diff_size_t(end, pos);
if (m > u->chunklen)
m = u->chunklen;
u->inf.inlen = m;
}
while (!u->no_more_input &&
((u->of == UOF_INSIDE && _pos == NULL) || pos < end)) {
switch (u->of) {
case UOF_MAGIC:
if (*pos++ != magic[u->sctr++])
return LWS_SRET_FATAL + 17;
if (u->sctr == sizeof(magic)) {
u->of++;
u->sctr = 0;
}
break;
case UOF_SKIP:
pos++;
if (++u->sctr == 4) {
u->of++;
u->sctr = 0;
}
break;
case UOF_TYPE4:
u->acc = (u->acc << 8) | *pos++;
if (++u->sctr == 4) {
if (u->acc != LWS_FOURCC('I','H','D','R'))
return LWS_SRET_FATAL + 18;
u->of++;
u->sctr = 0;
}
break;
case UOF_WIDTH4:
u->acc = (u->acc << 8) | *pos++;
if (++u->sctr == 4) {
u->width = u->acc;
if (!u->acc)
return LWS_SRET_FATAL + 18;
u->of++;
u->sctr = 0;
}
break;
case UOF_HEIGHT4:
u->acc = (u->acc << 8) | *pos++;
if (++u->sctr == 4) {
u->height = u->acc;
u->of++;
u->sctr = 0;
}
break;
case UOF_CDEPTH:
u->color_depth =*pos++;
u->of++;
break;
case UOF_CTYPE:
u->color_type = *pos++;
//lwsl_notice("w %d, h %d, depth %d, type %d\n",
// u->width, u->height,
// u->color_depth, u->color_type);
u->format = determine_format(u);
if (u->format == LWS_UPNG_BADFORMAT)
return LWS_SRET_FATAL + 19;
u->of++;
break;
case UOF_ONLY_ZERO3:
if (*pos++)
return LWS_SRET_FATAL + 20;
if (++u->sctr == 3) {
u->of++;
u->sctr = 0;
}
break;
case UOF_SKIP4:
pos++;
if (++u->sctr != 4)
break;
/* takes us to +33 */
memset(&u->inf, 0, sizeof(u->inf));
/* 32KB gz sliding window */
u->inf.info_size = 32768 + 512;
u->u.bpp = lws_upng_get_bpp(u);
if (!u->u.bpp)
return LWS_SRET_FATAL + 14;
u->u.y = 0;
u->u.ibp = 0;
u->u.bypp = (u->u.bpp + 7) / 8;
u->inf.bypl = u->u.bypl = u->width * u->u.bypp;
u->inf.outlen = u->inf.info_size;
u->inf.outpos = 0;
u->inf.state = UPNS_ID_BL_GB_DONE;
u->inf.upng = u;
u->u.alt = 0; /* which of the two lines to write to */
u->u.padded = u->u.bpp < 8 &&
u->width * u->u.bpp !=
((u->width * u->u.bpp + 7) / 8) * 8;
u->u.diff = (((u->width * u->u.bpp + 7) / 8) * 8) -
(u->width * u->u.bpp);
u->of++;
u->sctr = 0;
break;
case UOF_CHUNK_LEN:
if (!u->inf.out) {
size_t ims = (u->u.bypl * 2) + u->inf.info_size;
if (u->u.bypl > UINT_MAX / 2 || u->inf.info_size > UINT_MAX - (u->u.bypl * 2)) {
lwsl_err("%s: integer overflow occur in ims %llu",
__func__, (unsigned long long)ims);
return LWS_SRET_FATAL + 27;
}
if (u->hold_at_metadata)
return LWS_SRET_AWAIT_RETRY;
u->inf.out = (uint8_t *)lws_malloc(ims, __func__);
if (!u->inf.out) {
lwsl_notice("%s: inf malloc %u OOM\n",
__func__, (unsigned int)ims);
return LWS_SRET_YIELD;
}
u->u.lines = u->inf.out + u->inf.info_size;
u->u.in = u->inf.out;
}
u->chunklen = (u->chunklen << 8) | *pos++;
if (++u->sctr == 4) {
u->of++;
u->sctr = 0;
}
break;
case UOF_CHUNK_TYPE:
u->ctype = (u->ctype << 8) | *pos++;
if (++u->sctr != 4)
break;
u->sctr = 0;
if (u->ctype == LWS_FOURCC('I','E','N','D')) {
u->no_more_input = 1;
break;
}
if (u->ctype != LWS_FOURCC('I','D','A','T')) {
if (!(u->ctype & (32 << 24)))
/* says it is critical... */
return LWS_SRET_FATAL + 27;
u->chunklen += 4; /* chunk-end CRC */
/* noncritical, skip */
u->of = UOF_SKIP_CHUNK_LEN;
break;
}
if (u->chunklen < 2)
return LWS_SRET_FATAL + 31;
/* it's a usable IDAT */
if (!u->inf.subsequent)
u->inf.inpos = 2;
else
u->inf.inpos = 0;
m = lws_ptr_diff_size_t(end, pos);
if (m > u->chunklen)
m = u->chunklen;
u->inf.in = pos;
u->inf.inlen = m;
u->inf.bp = 0;
u->of++;
break;
case UOF_INSIDE:
if (!u->inf.subsequent) {
switch (u->sctr) {
case 0:
u->acc = (uint32_t)((*pos++) << 8);
u->sctr++;
continue;
case 1:
u->acc |= *pos++;
if (u->acc % 31)
return LWS_SRET_FATAL + 31;
if (((u->acc >> 8) & 15) != 8 ||
((u->acc >> 12) & 15) > 7)
return LWS_SRET_FATAL + 31;
if ((u->acc >> 5) & 1)
return LWS_SRET_FATAL + 31;
u->inf.subsequent = 1;
break;
}
}
r = _lws_upng_inflate_data(&u->inf);
switch (r) {
case LWS_SRET_WANT_INPUT:
/* indicate no existing to drain */
u->inf.in = NULL;
pos += u->inf.inlen - u->inf.inpos;
u->chunklen = u->chunklen -
(unsigned int)(u->inf.inlen);
if (!u->chunklen) {
u->chunklen = 4; /* skip the 32-bit CRC */
u->of = UOF_SKIP_CHUNK_LEN;
break;
}
if (pos != end) {
u->inf.inpos = 0;
u->inf.in = pos;
m = lws_ptr_diff_size_t(end, pos);
if (m > u->chunklen)
m = u->chunklen;
u->inf.inlen = m;
continue;
}
goto bail;
default:
goto bail;
}
break;
case UOF_SKIP_CHUNK_LEN:
pos++;
if (!--u->chunklen) {
u->of = UOF_CHUNK_LEN;
u->sctr = 0;
break;
}
break;
}
}
r = LWS_SRET_OK;
if (!u->no_more_input)
r = LWS_SRET_WANT_INPUT;
bail:
*_pos = pos;
*_size = lws_ptr_diff_size_t(end, pos);
return r;
}
lws_upng_t *
lws_upng_new(void)
{
lws_upng_t* upng;
upng = (lws_upng_t*)lws_zalloc(sizeof(lws_upng_t), __func__);
if (upng == NULL)
return NULL;
upng->color_type = LWS_UPNG_RGBA;
upng->color_depth = 8;
upng->format = LWS_UPNG_RGBA8;
upng->of = UOF_MAGIC;
upng->sctr = 0;
upng->inf.upng = upng;
return upng;
}
void
lws_upng_free(lws_upng_t** upng)
{
if ((*upng)->inf.out)
lws_free_set_NULL((*upng)->inf.out);
lws_free_set_NULL(*upng);
}
unsigned int
lws_upng_get_width(const lws_upng_t* upng)
{
return upng->width;
}
unsigned int
lws_upng_get_height(const lws_upng_t* upng)
{
return upng->height;
}
unsigned int
lws_upng_get_bpp(const lws_upng_t* upng)
{
return lws_upng_get_bitdepth(upng) *
lws_upng_get_components(upng);
}
unsigned int
lws_upng_get_components(const lws_upng_t* upng)
{
switch (upng->color_type) {
case LWS_UPNG_LUM:
return 1;
case LWS_UPNG_RGB:
return 3;
case LWS_UPNG_LUMA:
return 2;
case LWS_UPNG_RGBA:
return 4;
default:
return 0;
}
}
unsigned int
lws_upng_get_bitdepth(const lws_upng_t* upng)
{
return upng->color_depth;
}
unsigned int
lws_upng_get_pixelsize(const lws_upng_t* upng)
{
unsigned bits = lws_upng_get_bitdepth(upng) *
lws_upng_get_components(upng);
bits += bits % 8;
return bits;
}
lws_upng_format_t
lws_upng_get_format(const lws_upng_t *upng)
{
return upng->format;
}