From 48907fca0a25c39ce35692c527cb8aa82ee60d85 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Mon, 27 Dec 2021 14:41:26 +0000 Subject: [PATCH] upng: rewrite for stateful stream decode Add a rewritten version of upng that decodes statefully line by line, and so does not require a bitmap buffer for the output. This compares to original upng approach that needs heap allocations for the input, the whole output and intermediate allocations. Instead of buffers for input, decompression and output, it only allocates 2 x lines of RGBA pixels (ie, a few KB), and 32KB of decompressed data for backward references in the decoder, and decodes as needed into the 2-line buffer to produce line rasterized results. For a 600px width PNG, this is just 40KB heap for the duration. --- CMakeLists.txt | 1 + LICENSE | 3 +- READMEs/README.png-decoder.md | 56 + cmake/lws_config.h.in | 1 + include/libwebsockets.h | 15 + include/libwebsockets/lws-upng.h | 118 ++ lib/misc/CMakeLists.txt | 5 + lib/misc/upng.c | 1535 +++++++++++++++++ .../api-tests/api-test-upng/CMakeLists.txt | 25 + .../api-tests/api-test-upng/main.c | 138 ++ 10 files changed, 1896 insertions(+), 1 deletion(-) create mode 100644 READMEs/README.png-decoder.md create mode 100644 include/libwebsockets/lws-upng.h create mode 100644 lib/misc/upng.c create mode 100644 minimal-examples-lowlevel/api-tests/api-test-upng/CMakeLists.txt create mode 100644 minimal-examples-lowlevel/api-tests/api-test-upng/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 53eb5b44e..1742cd119 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,6 +159,7 @@ option(LWS_WITH_SYS_STATE "lws_system state support" ON) option(LWS_WITH_SYS_SMD "Lws System Message Distribution" ON) option(LWS_WITH_SYS_FAULT_INJECTION "Enable fault injection support" OFF) option(LWS_WITH_SYS_METRICS "Lws Metrics API" OFF) +option(LWS_WITH_UPNG "Enable stateful PNG stream decoder" ON) # # Secure Streams diff --git a/LICENSE b/LICENSE index 917f8dcf2..767d2d368 100644 --- a/LICENSE +++ b/LICENSE @@ -7,7 +7,8 @@ them. Original liberal license retained: - lib/misc/sha-1.c - 3-clause BSD license retained, link to original [BSD3] - - win32port/zlib - ZLIB license (see zlib.h) [ZLIB] + - win32port/zlib + lib/drivers/display/upng.* - ZLIB license (see zlib.h) [ZLIB] - lib/tls/mbedtls/wrapper - Apache 2.0 (only built if linked against mbedtls) [APACHE2] lib/tls/mbedtls/mbedtls-extensions.c - lib/misc/base64-decode.c - already MIT diff --git a/READMEs/README.png-decoder.md b/READMEs/README.png-decoder.md new file mode 100644 index 000000000..d01b5bd9d --- /dev/null +++ b/READMEs/README.png-decoder.md @@ -0,0 +1,56 @@ +# lws_upng stateful PNG decoder + +Lws includes a rewrite of UPNG that performs stateful, line-at-a-time decoding. + +The memory requirement is fixed at 40KB plus enough buffer for two output +lines of pixels. In particular the input PNG data is stream parsed, so there +is no requirement for it all to be in memory at the same time, and there is +no framebuffer required either, so there is no requirement for all the output +to be in memory at the same time, either. + +The results in an extremely tight decoder suitable for microcontroller type +platforms that lack enough memory to hold a framebuffer, but can stream the +rendered data out over SPI or i2c to a display device that does have its own +(usually write-only) framebuffer memory. + +## Creating and destroying the decoding context + +The apis to create and destroy a decoding context are very simple... + +``` +LWS_VISIBLE LWS_EXTERN lws_upng_t * +lws_upng_new(void); + +LWS_VISIBLE LWS_EXTERN void +lws_upng_free(lws_upng_t **upng); +``` + +## Performing the decoding + +The only decoding API provides input PNG data which may or may not be partly or +wholly consumed, to produce a line of output pixels that can be found at `*ppix`. + +``` +LWS_VISIBLE LWS_EXTERN lws_stateful_ret_t +lws_upng_emit_next_line(lws_upng_t *upng, const uint8_t **ppix, + const uint8_t **buf, size_t *size); +``` + +If input data is consumed, `*buf` and `*size` are adjusted accordingly. +This api returns a bitfield consisting of: + +|Return value bit|Meaning| +|---|---| +|`LWS_SRET_OK` (0, no bits set)|Completed| +|`LWS_SRET_WANT_INPUT`|Decoder needs to be called again with more PNG input before it can produce a line of pixels| +|`LWS_SRET_WANT_OUTPUT`|Decoder has paused to emit a line of pixels, and can resume| +|`LWS_SRET_FATAL`|Decoder has encountered a fatal error, any return greater than `LWS_SRET_FATAL` indicates the type of error| +|`LWS_SRET_NO_FURTHER_IN`|Indicate no further new input will be used| +|`LWS_SRET_NO_FURTHER_OUT`|Indicate no further output is forthcoming| + +To get early information about the dimensions and colourspace of the PNG, you +can call this api initially with the first 33 bytes (`*size` restricted to 33) +and adjust the true size -33 for further calls. This will make it return with +`WANT_INPUT` after having processed the PNG header information but not produced +any pixel line information. + diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index e87b18725..50ee8d780 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -241,6 +241,7 @@ #cmakedefine LWS_WITH_UDP #cmakedefine LWS_WITH_ULOOP #cmakedefine LWS_WITH_UNIX_SOCK +#cmakedefine LWS_WITH_UPNG #cmakedefine LWS_WITH_ZIP_FOPS #cmakedefine USE_OLD_CYASSL #cmakedefine USE_WOLFSSL diff --git a/include/libwebsockets.h b/include/libwebsockets.h index 1f867207d..e1bb15829 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -612,6 +612,19 @@ struct lws_tokens; struct lws_vhost; struct lws; +/* Generic stateful operation return codes */ + +typedef enum { + LWS_SRET_OK = 0, + LWS_SRET_WANT_INPUT = (1 << 16), + LWS_SRET_WANT_OUTPUT = (1 << 17), + LWS_SRET_FATAL = (1 << 18), + LWS_SRET_NO_FURTHER_IN = (1 << 19), + LWS_SRET_NO_FURTHER_OUT = (1 << 20), + LWS_SRET_AWAIT_RETRY = (1 << 21), + LWS_SRET_YIELD = (1 << 22), /* return to the event loop and continue */ +} lws_stateful_ret_t; + typedef struct lws_fixed3232 { int32_t whole; /* signed 32-bit int */ int32_t frac; /* signed frac proportion from 0 to (100M - 1) */ @@ -767,6 +780,7 @@ lws_fx_string(const lws_fx_t *a, char *buf, size_t size); #include #include #include +#include #include #include #include @@ -774,6 +788,7 @@ lws_fx_string(const lws_fx_t *a, char *buf, size_t size); #if defined(LWS_WITH_NETWORK) #include #endif + #ifdef __cplusplus } #endif diff --git a/include/libwebsockets/lws-upng.h b/include/libwebsockets/lws-upng.h new file mode 100644 index 000000000..e7fef9dc8 --- /dev/null +++ b/include/libwebsockets/lws-upng.h @@ -0,0 +1,118 @@ +/* + * uPNG -- derived from LodePNG version 20100808 + * + * Copyright (c) 2005-2010 Lode Vandevenne + * Copyright (c) 2010 Sean Middleditch + * Copyright (c) 2021-2022 Andy Green + * + * 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. + * + * The above notice is the ZLIB license, libpng also uses it. + * + * This version is based on upng project's fork of lodepng and rewritten for + * lws, changing the whole approach to decode on demand to issue a line of + * output at a time, statefully. + */ + +typedef enum lws_upng_format_t { + LWS_UPNG_BADFORMAT, + LWS_UPNG_RGB8, + LWS_UPNG_RGB16, + LWS_UPNG_RGBA8, + LWS_UPNG_RGBA16, + LWS_UPNG_LUMINANCE1, + LWS_UPNG_LUMINANCE2, + LWS_UPNG_LUMINANCE4, + LWS_UPNG_LUMINANCE8, + LWS_UPNG_LUMINANCE_ALPHA1, + LWS_UPNG_LUMINANCE_ALPHA2, + LWS_UPNG_LUMINANCE_ALPHA4, + LWS_UPNG_LUMINANCE_ALPHA8 +} lws_upng_format_t; + +typedef struct lws_upng_t lws_upng_t; + +/** + * lws_upng_new() - Create new UPNG decode object + * + * Returns a new PNG decoding object, which should be destroyed with + * lws_upng_free() when done with, or NULL if OOM. + */ +LWS_VISIBLE LWS_EXTERN lws_upng_t * +lws_upng_new(void); + +/** + * lws_upng_free() - Destroy a PNG decode object + * + * \param upng: Pointer to the decode object to destroy and set to NULL + * + * This also frees any sub-allocations in the object. + */ +LWS_VISIBLE LWS_EXTERN void +lws_upng_free(lws_upng_t **upng); + +/** + * lws_upng_emit_next_line() - deocde the next line + * + * \param upng: the decode object + * \param ppix: pointer to a pointer set to the line's decoded pixel data + * \param buf: pointer to a const uint8_t array of PNG input + * \param size: pointer to the count of bytes available at *buf + * \param hold_at_metadata: true if we should not advance to decode + * + * Make PNG input available to the decoder so it can issue the next line's + * worth of pixels. If the call consumed any input, *buf and *size are + * adjusted accordingly. + * + * The decoder is stateful so it is not sensitive to the chunk size for the + * input. + * + * If \p hold_at_metadata is set, then the decoder will only go as far as + * picking out the metadata like image dimensions, but not start the decode, + * which requires the >30KB heap allocation. This lets you put off for as long + * as possible committing to the decode allocation... this only helps overall + * if you have flow controlled the incoming PNG data. + * + * Return will be one of LWS_SRET_WANT_INPUT is the decoder is stalled waiting + * for more input to be provided, LWS_SRET_WANT_OUTPUT is the decoder stopped + * because it had produced a whole line of output pixels (which can be found + * starting at *ppix), LWS_SRET_OK is it completed and LWS_SRET_FATAL or larger + * if the decode failed. + */ +LWS_VISIBLE LWS_EXTERN lws_stateful_ret_t +lws_upng_emit_next_line(lws_upng_t *upng, const uint8_t **ppix, + const uint8_t **buf, size_t *size, + char hold_at_metadata); + +LWS_VISIBLE LWS_EXTERN unsigned int +lws_upng_get_width(const lws_upng_t *upng); +LWS_VISIBLE LWS_EXTERN unsigned int +lws_upng_get_height(const lws_upng_t *upng); +LWS_VISIBLE LWS_EXTERN unsigned int +lws_upng_get_bpp(const lws_upng_t *upng); +LWS_VISIBLE LWS_EXTERN unsigned int +lws_upng_get_bitdepth(const lws_upng_t *upng); +LWS_VISIBLE LWS_EXTERN unsigned int +lws_upng_get_components(const lws_upng_t *upng); +LWS_VISIBLE LWS_EXTERN unsigned int +lws_upng_get_pixelsize(const lws_upng_t *upng); +LWS_VISIBLE LWS_EXTERN lws_upng_format_t +lws_upng_get_format(const lws_upng_t *upng); + diff --git a/lib/misc/CMakeLists.txt b/lib/misc/CMakeLists.txt index 1b16acd1d..a95606615 100644 --- a/lib/misc/CMakeLists.txt +++ b/lib/misc/CMakeLists.txt @@ -60,6 +60,11 @@ if (LWS_WITH_FTS) misc/fts/trie-fd.c) endif() +if (LWS_WITH_UPNG) + list(APPEND SOURCES + misc/upng.c) +endif() + # this is an older, standalone hashed disk cache # implementation unrelated to lws-cache-ttl if (LWS_WITH_DISKCACHE) diff --git a/lib/misc/upng.c b/lib/misc/upng.c new file mode 100644 index 000000000..cdf2bbcd3 --- /dev/null +++ b/lib/misc/upng.c @@ -0,0 +1,1535 @@ +/* + * 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 (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 + +#include +#include +#include +#include + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 + +/*256 literals, the end code, some length codes, and 2 unused codes */ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused */ +#define NUM_DISTANCE_SYMBOLS 32 +/* The code length codes. 0-15: code lengths, 16: copy previous 3-6 times, + * 17: 3-10 zeros, 18: 11-138 zeros */ +#define NUM_CODE_LENGTH_CODES 19 +/* largest number of symbols used by any tree type */ +#define MAX_SYMBOLS 288 + +#define DEFLATE_CODE_BITLEN 15 +#define DISTANCE_BITLEN 15 +#define CODE_LENGTH_BITLEN 7 +/* largest bitlen used by any tree type */ +#define MAX_BIT_LENGTH 15 + +#define DEFLATE_CODE_BUFFER_SIZE (NUM_DEFLATE_CODE_SYMBOLS * 2) +#define DISTANCE_BUFFER_SIZE (NUM_DISTANCE_SYMBOLS * 2) +#define CODE_LENGTH_BUFFER_SIZE (NUM_DISTANCE_SYMBOLS * 2) + +typedef uint16_t huff_t; + +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 struct htree { + huff_t *tree2d; + /*maximum number of bits a single code can get */ + uint16_t maxbitlen; + /*number of symbols in the alphabet = number of codes */ + uint16_t numcodes; +} htree_t; + +typedef enum { + UPNS_ID_BL_GB_DONE, + UPNS_ID_BL_GB_BTYPEb0, + UPNS_ID_BL_GB_BTYPEb1, + + UPNS_ID_BL_GB_BTYPE_0, + UPNS_ID_BL_GB_BTYPE_1, + UPNS_ID_BL_GB_BTYPE_2, + + UPNS_ID_BL_GB_BTYPE_0a, + UPNS_ID_BL_GB_BTYPE_0b, + UPNS_ID_BL_GB_BTYPE_0c, + UPNS_ID_BL_GB_BTYPE_0d, + + UPNS_ID_BL_GB_BTYPE_2a, + UPNS_ID_BL_GB_BTYPE_2b, + UPNS_ID_BL_GB_BTYPE_2c, + UPNS_ID_BL_GB_BTYPE_2d, + UPNS_ID_BL_GB_BTYPE_2e, + + UPNS_ID_BL_GB_BTYPE_2_16, + UPNS_ID_BL_GB_BTYPE_2_17, + UPNS_ID_BL_GB_BTYPE_2_18, + + UPNS_ID_BL_GB_SPIN, + + UPNS_ID_BL_GB_SPINa, + UPNS_ID_BL_GB_SPINb, + UPNS_ID_BL_GB_SPINc, + UPNS_ID_BL_GB_SPINd, + UPNS_ID_BL_GB_SPINe, + +} upng_inflate_states_t; + +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; + +typedef struct inflator_ctx { + unsigned int clenc[NUM_CODE_LENGTH_CODES]; + unsigned int bitlen[NUM_DEFLATE_CODE_SYMBOLS]; + unsigned int bitlenD[NUM_DISTANCE_SYMBOLS]; + huff_t clct_buffer[CODE_LENGTH_BUFFER_SIZE]; + huff_t ct_buffer[DEFLATE_CODE_BUFFER_SIZE]; + huff_t ctD_buffer[DISTANCE_BUFFER_SIZE]; + + lws_upng_t *upng; + + const uint8_t *in; + uint8_t *out; + + htree_t clct; + htree_t ct; + htree_t ctD; + + size_t bp; + size_t inpos; + size_t inlen; + size_t outpos; + size_t outpos_linear; + size_t consumed_linear; + size_t outlen; + size_t length; + size_t start; + size_t forward; + size_t backward; + size_t exbits; + size_t bypl; + + upng_inflate_states_t state; + + unsigned int len; + unsigned int nlen; + unsigned int n; + unsigned int hlit; + unsigned int hdist; + unsigned int hclen; + unsigned int i; + unsigned int t; + unsigned int codeD; + unsigned int distance; + unsigned int exbitsD; + unsigned int code; + unsigned int treepos; + + unsigned int read_bits_shifter; + unsigned int read_bits_limit; + unsigned int read_bits_i; + + unsigned int info_size; + + uint8_t subsequent; + uint8_t btype; + uint8_t done; + + char read_bits_ongoing; +} inflator_ctx_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 const huff_t huff_length_base[] = { + /*the base lengths represented by codes 257-285 */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258 +}; + +static const huff_t huff_length_extra[] = { + /*the extra bits used by codes 257-285 (added to base length) */ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, + 5, 5, 5, 5, 0 +}; + +static const huff_t huff_distance_base[] = { + /* + * The base backwards distances (the bits of distance codes appear + * after length codes and use their own huffman tree) + */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, + 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 +}; + +static const huff_t huff_distance_extra[] = { + /* the extra bits of backwards distances (added to base) */ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, + 10, 11, 11, 12, 12, 13, 13 +}; + +static const huff_t huff_cl_cl[] = { + /* + * The order in which "code length alphabet code lengths" are stored, + * out of this the huffman tree of the dynamic huffman tree lengths + * is generated + */ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 +}; + +static const huff_t FIXED_DEFLATE_CODE_TREE[NUM_DEFLATE_CODE_SYMBOLS * 2] = { + 289, 370, 290, 307, 546, 291, 561, 292, 293, 300, 294, 297, 295, 296, + 0, 1, 2, 3, 298, 299, 4, 5, 6, 7, 301, 304, 302, 303, 8, 9, 10, 11, 305, + 306, 12, 13, 14, 15, 308, 339, 309, 324, 310, 317, 311, 314, 312, 313, + 16, 17, 18, 19, 315, 316, 20, 21, 22, 23, 318, 321, 319, 320, 24, 25, + 26, 27, 322, 323, 28, 29, 30, 31, 325, 332, 326, 329, 327, 328, 32, 33, + 34, 35, 330, 331, 36, 37, 38, 39, 333, 336, 334, 335, 40, 41, 42, 43, + 337, 338, 44, 45, 46, 47, 340, 355, 341, 348, 342, 345, 343, 344, 48, + 49, 50, 51, 346, 347, 52, 53, 54, 55, 349, 352, 350, 351, 56, 57, 58, + 59, 353, 354, 60, 61, 62, 63, 356, 363, 357, 360, 358, 359, 64, 65, 66, + 67, 361, 362, 68, 69, 70, 71, 364, 367, 365, 366, 72, 73, 74, 75, 368, + 369, 76, 77, 78, 79, 371, 434, 372, 403, 373, 388, 374, 381, 375, 378, + 376, 377, 80, 81, 82, 83, 379, 380, 84, 85, 86, 87, 382, 385, 383, 384, + 88, 89, 90, 91, 386, 387, 92, 93, 94, 95, 389, 396, 390, 393, 391, 392, + 96, 97, 98, 99, 394, 395, 100, 101, 102, 103, 397, 400, 398, 399, 104, + 105, 106, 107, 401, 402, 108, 109, 110, 111, 404, 419, 405, 412, 406, + 409, 407, 408, 112, 113, 114, 115, 410, 411, 116, 117, 118, 119, 413, + 416, 414, 415, 120, 121, 122, 123, 417, 418, 124, 125, 126, 127, 420, + 427, 421, 424, 422, 423, 128, 129, 130, 131, 425, 426, 132, 133, 134, + 135, 428, 431, 429, 430, 136, 137, 138, 139, 432, 433, 140, 141, 142, + 143, 435, 483, 436, 452, 568, 437, 438, 445, 439, 442, 440, 441, 144, + 145, 146, 147, 443, 444, 148, 149, 150, 151, 446, 449, 447, 448, 152, + 153, 154, 155, 450, 451, 156, 157, 158, 159, 453, 468, 454, 461, 455, + 458, 456, 457, 160, 161, 162, 163, 459, 460, 164, 165, 166, 167, 462, + 465, 463, 464, 168, 169, 170, 171, 466, 467, 172, 173, 174, 175, 469, + 476, 470, 473, 471, 472, 176, 177, 178, 179, 474, 475, 180, 181, 182, + 183, 477, 480, 478, 479, 184, 185, 186, 187, 481, 482, 188, 189, 190, + 191, 484, 515, 485, 500, 486, 493, 487, 490, 488, 489, 192, 193, 194, + 195, 491, 492, 196, 197, 198, 199, 494, 497, 495, 496, 200, 201, 202, + 203, 498, 499, 204, 205, 206, 207, 501, 508, 502, 505, 503, 504, 208, + 209, 210, 211, 506, 507, 212, 213, 214, 215, 509, 512, 510, 511, 216, + 217, 218, 219, 513, 514, 220, 221, 222, 223, 516, 531, 517, 524, 518, + 521, 519, 520, 224, 225, 226, 227, 522, 523, 228, 229, 230, 231, 525, + 528, 526, 527, 232, 233, 234, 235, 529, 530, 236, 237, 238, 239, 532, + 539, 533, 536, 534, 535, 240, 241, 242, 243, 537, 538, 244, 245, 246, + 247, 540, 543, 541, 542, 248, 249, 250, 251, 544, 545, 252, 253, 254, + 255, 547, 554, 548, 551, 549, 550, 256, 257, 258, 259, 552, 553, 260, + 261, 262, 263, 555, 558, 556, 557, 264, 265, 266, 267, 559, 560, 268, + 269, 270, 271, 562, 565, 563, 564, 272, 273, 274, 275, 566, 567, 276, + 277, 278, 279, 569, 572, 570, 571, 280, 281, 282, 283, 573, 574, 284, + 285, 286, 287, 0, 0 +}; + +static const huff_t FIXED_DISTANCE_TREE[] = { + 33, 48, 34, 41, 35, 38, 36, 37, + 0, 1, 2, 3, 39, 40, 4, 5, + 6, 7, 42, 45, 43, 44, 8, 9, + 10, 11, 46, 47, 12, 13, 14, 15, + 49, 56, 50, 53, 51, 52, 16, 17, + 18, 19, 54, 55, 20, 21, 22, 23, + 57, 60, 58, 59, 24, 25, 26, 27, + 61, 62, 28, 29, 30, 31, 0, 0 +}; + +static lws_stateful_ret_t +read_bit(inflator_ctx_t *inf, uint8_t *bits) +{ + size_t bo = inf->bp >> 3; + + if (bo + inf->inpos >= inf->inlen) + return LWS_SRET_WANT_INPUT; + + *bits = (uint8_t)((*(inf->in + inf->inpos + bo) >> (inf->bp & 7)) & 1); + + inf->bp++; + + return LWS_SRET_OK; +} + +/* Stateful, so it can pick up after running out of input partway thru */ + +static lws_stateful_ret_t +read_bits(inflator_ctx_t *inf, unsigned int nbits, unsigned int *bits) +{ + lws_stateful_ret_t r; + uint8_t b; + + if (!inf->read_bits_ongoing) { + inf->read_bits_ongoing = 1; + inf->read_bits_shifter = 0; + inf->read_bits_limit = nbits; + inf->read_bits_i = 0; + } + + while (inf->read_bits_i < inf->read_bits_limit) { + r =read_bit(inf, &b); + if (r) + return r; + + inf->read_bits_shifter = inf->read_bits_shifter | (unsigned int)(b << inf->read_bits_i); + + inf->read_bits_i++; + } + + inf->read_bits_ongoing = 0; + *bits = inf->read_bits_shifter; + + return LWS_SRET_OK; +} + +static lws_stateful_ret_t +read_byte(inflator_ctx_t *inf, uint8_t *byte) +{ + size_t bo; + + while (inf->bp & 7) + inf->bp++; + + bo = inf->bp >> 3; + + if (bo + inf->inpos >= inf->inlen) + return LWS_SRET_WANT_INPUT; + + *byte = *(inf->in + inf->inpos + bo); + + inf->bp += 8; + + return LWS_SRET_OK; +} + +/* buffer must be numcodes*2 in size! */ +static void +huffman_tree_init(htree_t *tree, huff_t *buffer, uint16_t numcodes, + uint16_t maxbitlen) +{ + tree->tree2d = buffer; + + tree->numcodes = numcodes; + tree->maxbitlen = maxbitlen; +} + +#define EMPTY 32767 + +/* + * Given the code lengths (as stored in the PNG file), generate the tree as + * defined by Deflate. maxbitlen is the maximum bits that a code in the tree + * can have. + */ +static lws_stateful_ret_t +huffman_tree_create_lengths(lws_upng_t *upng, htree_t *tree, + const unsigned *bitlen) +{ + unsigned int tree1d[MAX_SYMBOLS], blcount[MAX_BIT_LENGTH], + nextcode[MAX_BIT_LENGTH + 1]; + unsigned int bits, n, i, nodefilled = 0, treepos = 0; + + memset(blcount, 0, sizeof(blcount)); + memset(nextcode, 0, sizeof(nextcode)); + + for (bits = 0; bits < tree->numcodes; bits++) + blcount[bitlen[bits]]++; + + for (bits = 1; bits <= (unsigned int)tree->maxbitlen; bits++) + nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1; + + for (n = 0; n < tree->numcodes; n++) + if (bitlen[n]) + tree1d[n] = nextcode[bitlen[n]]++; + + for (n = 0; n < (unsigned int)tree->numcodes * 2u; n++) + tree->tree2d[n] = EMPTY; + + for (n = 0; n < tree->numcodes; n++) { /* the codes */ + for (i = 0; i < bitlen[n]; i++) { /* the bits for this code */ + uint8_t bit = (uint8_t)((tree1d[n] >> + (bitlen[n] - i - 1)) & 1); + + /* check if oversubscribed */ + if (treepos > tree->numcodes - 2u) + return LWS_SRET_FATAL + 1; + + if (tree->tree2d[2 * treepos + bit] == EMPTY) { + if (i + 1 == bitlen[n]) { /* ... last bit */ + tree->tree2d[2 * treepos + bit] = (huff_t)n; + treepos = 0; + } else { + nodefilled++; + tree->tree2d[2 * treepos + bit] = + (huff_t)(nodefilled + tree->numcodes); + treepos = nodefilled; + } + } else + treepos = (unsigned int)(tree->tree2d[2 * treepos + bit] - + tree->numcodes); + } + } + + for (n = 0; n < tree->numcodes * 2u; n++) + if (tree->tree2d[n] == EMPTY) + tree->tree2d[n] = 0; + + return LWS_SRET_OK; +} + +static lws_stateful_ret_t +huffman_decode_symbol(inflator_ctx_t *inf, const htree_t *ct, unsigned int *uct) +{ + lws_stateful_ret_t r; + uint8_t bit; + + do { + r = read_bit(inf, &bit); + if (r) + return r; + + *uct = ct->tree2d[(inf->treepos << 1) | bit]; + if (*uct < ct->numcodes) + return LWS_SRET_OK; + + inf->treepos = *uct - ct->numcodes; + if (inf->treepos >= ct->numcodes) + return LWS_SRET_FATAL + 2; + } while (1); +} + +static lws_stateful_ret_t +uz_inflate_data(inflator_ctx_t *inf) +{ + unsigned int count, val, tu; + uint8_t t, done = 0; + lws_stateful_ret_t r; + size_t virt; + + while (!done) { + switch (inf->state) { + case UPNS_ID_BL_GB_DONE: + r = read_bit(inf, &inf->done); + if (r) + return r; + inf->state++; + + /* fallthru */ + case UPNS_ID_BL_GB_BTYPEb0: + r = read_bit(inf, &inf->btype); + if (r) + return r; + inf->state++; + + /* fallthru */ + case UPNS_ID_BL_GB_BTYPEb1: + r = read_bit(inf, &t); + if (r) + return r; + + inf->btype |= (uint8_t)(t << 1); + + if (inf->btype == 3) + return LWS_SRET_FATAL + 3; + + inf->i = 0; + + inf->state = UPNS_ID_BL_GB_BTYPE_0 + inf->btype; + continue; + + case UPNS_ID_BL_GB_BTYPE_0: /* no compression */ + r = read_byte(inf, &t); + if (r) + return r; + + inf->len = t; + inf->state = UPNS_ID_BL_GB_BTYPE_0a; + + /* fallthru */ + case UPNS_ID_BL_GB_BTYPE_0a: + r = read_byte(inf, &t); + if (r) + return r; + + inf->len = inf->len | (unsigned int)(t << 8); + inf->state++; + /* fallthru */ + + case UPNS_ID_BL_GB_BTYPE_0b: + r = read_byte(inf, &t); + if (r) + return r; + + inf->nlen = t; + inf->state++; + + /* fallthru */ + case UPNS_ID_BL_GB_BTYPE_0c: + r = read_byte(inf, &t); + if (r) + return r; + + inf->nlen = inf->nlen | (unsigned int)(t << 8); + + if (inf->len + inf->nlen != 65535) + return LWS_SRET_FATAL + 4; + + inf->state++; + inf->n = 0; + + /* fallthru */ + case UPNS_ID_BL_GB_BTYPE_0d: + + if (inf->n < inf->len) { + + r = read_byte(inf, &t); + if (r) + return r; + + inf->out[inf->outpos++] = t; + if (inf->outpos >= inf->outlen) + inf->outpos = 0; + inf->outpos_linear++; + + if (inf->outpos_linear - inf->consumed_linear >= + inf->bypl + 1) + return LWS_SRET_WANT_OUTPUT; + + inf->n++; + continue; + } + + inf->treepos = 0; + inf->state = UPNS_ID_BL_GB_SPIN; + continue; + + case UPNS_ID_BL_GB_BTYPE_1: /* fixed trees */ + huffman_tree_init(&inf->ct, + (huff_t *)FIXED_DEFLATE_CODE_TREE, + NUM_DEFLATE_CODE_SYMBOLS, + DEFLATE_CODE_BITLEN); + huffman_tree_init(&inf->ctD, + (huff_t *)FIXED_DISTANCE_TREE, + NUM_DISTANCE_SYMBOLS, + DISTANCE_BITLEN); + + lwsl_notice("%s: fixed tree init\n", __func__); + inf->treepos = 0; + inf->state = UPNS_ID_BL_GB_SPIN; + continue; + + case UPNS_ID_BL_GB_BTYPE_2: /* dynamic trees */ + huffman_tree_init(&inf->ct, inf->ct_buffer, + NUM_DEFLATE_CODE_SYMBOLS, + DEFLATE_CODE_BITLEN); + huffman_tree_init(&inf->ctD, inf->ctD_buffer, + NUM_DISTANCE_SYMBOLS, + DISTANCE_BITLEN); + huffman_tree_init(&inf->clct, inf->clct_buffer, + NUM_CODE_LENGTH_CODES, + CODE_LENGTH_BITLEN); + + /* clear bitlen arrays */ + memset(inf->bitlen, 0, sizeof(inf->bitlen)); + memset(inf->bitlenD, 0, sizeof(inf->bitlenD)); + + inf->state = UPNS_ID_BL_GB_BTYPE_2a; + + /* fallthru */ + case UPNS_ID_BL_GB_BTYPE_2a: + r = read_bits(inf, 5, &inf->hlit); + if (r) + return r; + inf->hlit += 257; + inf->state++; + + /* fallthru */ + case UPNS_ID_BL_GB_BTYPE_2b: + r = read_bits(inf, 5, &inf->hdist); + if (r) + return r; + inf->hdist++; + inf->state++; + + /* fallthru */ + case UPNS_ID_BL_GB_BTYPE_2c: + r = read_bits(inf, 4, &inf->hclen); + if (r) + return r; + inf->hclen += 4; + inf->state = UPNS_ID_BL_GB_BTYPE_2d; + inf->i = 0; + + /* fallthru */ + case UPNS_ID_BL_GB_BTYPE_2d: + if (inf->i < NUM_CODE_LENGTH_CODES) { + if (inf->i < inf->hclen) { + r = read_bits(inf, 3, + &inf->clenc[huff_cl_cl[inf->i]]); + if (r) + return r; + } else + /*if not, it must stay 0 */ + inf->clenc[huff_cl_cl[inf->i]] = 0; + + inf->i++; + continue; + } + + r = huffman_tree_create_lengths(inf->upng, &inf->clct, + inf->clenc); + if (r) + return r; + + inf->i = 0; + inf->state = UPNS_ID_BL_GB_BTYPE_2e; + inf->treepos = 0; + + /* fallthru */ + case UPNS_ID_BL_GB_BTYPE_2e: + + if (inf->i >= inf->hlit + inf->hdist) { + if (inf->bitlen[256] == 0) + return LWS_SRET_FATAL + 6; + + if (huffman_tree_create_lengths(inf->upng, + &inf->ct, + inf->bitlen)) + return LWS_SRET_FATAL + 7; + + if (huffman_tree_create_lengths(inf->upng, + &inf->ctD, + inf->bitlenD)) + return LWS_SRET_FATAL + 8; + + inf->treepos = 0; + inf->state = UPNS_ID_BL_GB_SPIN; + continue; + } + + r = huffman_decode_symbol(inf, &inf->clct, &inf->code); + if (r) + return r; + + switch (inf->code) { + case 16: + inf->state = UPNS_ID_BL_GB_BTYPE_2_16; + continue; + case 17: + inf->state = UPNS_ID_BL_GB_BTYPE_2_17; + continue; + case 18: + inf->state = UPNS_ID_BL_GB_BTYPE_2_18; + continue; + default: + if (inf->code > 15) + return LWS_SRET_FATAL + 9; + + if (inf->i < inf->hlit) + inf->bitlen[inf->i] = inf->code; + else + inf->bitlenD[inf->i - inf->hlit] = + inf->code; + + inf->i++; + inf->treepos = 0; + + /* stay in 2e */ + continue; + } + + case UPNS_ID_BL_GB_BTYPE_2_16: /* repeat previous */ + r = read_bits(inf, 2, &tu); + if (r) + return r; + count = tu + 3; + + if ((inf->i - 1) < inf->hlit) + val = inf->bitlen[inf->i - 1]; + else + val = inf->bitlenD[inf->i - inf->hlit - 1]; + + goto fill; + + case UPNS_ID_BL_GB_BTYPE_2_17: /*repeat "0" 3-10 times */ + r = read_bits(inf, 3, &tu); + if (r) + return r; + count = tu + 3; + + val = 0; + goto fill; + + case UPNS_ID_BL_GB_BTYPE_2_18: /*repeat "0" 11-138 times */ + r = read_bits(inf, 7, &tu); + if (r) + return r; + count = tu + 11; + val = 0; +fill: + if (inf->i + count > inf->hlit + inf->hdist) + return LWS_SRET_FATAL + 10; + + { + unsigned int n; + + for (n = 0; n < count; n++) { + + if (inf->i < inf->hlit) + inf->bitlen[inf->i] = val; + else + inf->bitlenD[inf->i - inf->hlit] = val; + + inf->i++; + } + } + inf->state = UPNS_ID_BL_GB_BTYPE_2e; + inf->treepos = 0; + continue; + + + case UPNS_ID_BL_GB_SPIN: + + r = huffman_decode_symbol(inf, &inf->ct, &inf->code); + if (r) + return r; + + if (inf->code >= FIRST_LENGTH_CODE_INDEX && + inf->code - FIRST_LENGTH_CODE_INDEX < + LWS_ARRAY_SIZE(huff_length_base)) + inf->length = huff_length_base[inf->code - + FIRST_LENGTH_CODE_INDEX]; + else + inf->length = 0; + + if (inf->code == 256) { + /* + * We're finished with this huffman block, we + * need to go back up a level + */ + done = inf->done; + inf->state = UPNS_ID_BL_GB_DONE; + continue; + } + + if (inf->code <= 255) { + inf->state = UPNS_ID_BL_GB_SPINa; + continue; + } + + if (inf->code < FIRST_LENGTH_CODE_INDEX || + inf->code > LAST_LENGTH_CODE_INDEX) { + inf->treepos = 0; + continue; + } + + inf->exbits = huff_length_extra[inf->code - + FIRST_LENGTH_CODE_INDEX]; + inf->state = UPNS_ID_BL_GB_SPINb; + + /* fallthru */ + case UPNS_ID_BL_GB_SPINb: + r = read_bits(inf, (unsigned int)inf->exbits, &tu); + if (r) + return r; + + inf->length += tu; + inf->state++; + inf->treepos = 0; + + /* fallthru */ + case UPNS_ID_BL_GB_SPINc: + + /* part 3: get distance code */ + + r = huffman_decode_symbol(inf, &inf->ctD, &inf->codeD); + if (r) + return r; + + /* invalid distance code (30-31 are never used) */ + if (inf->codeD > 29) + return LWS_SRET_FATAL + 11; + + inf->distance = huff_distance_base[inf->codeD]; + + /* part 4: get extra bits from distance */ + + inf->exbitsD = huff_distance_extra[inf->codeD]; + inf->state++; + + /* fallthru */ + case UPNS_ID_BL_GB_SPINd: + + r = read_bits(inf, inf->exbitsD, &tu); + if (r) + return r; + + inf->distance += tu; + + if (inf->distance > inf->info_size) { + lwsl_err("%s: distance %lu\n", __func__, + (unsigned long)inf->distance); + assert(0); + } + + /* + * Part 5: fill in all the out[n] values based + * on the length and dist + */ + inf->start = inf->outpos; + inf->forward = 0; + inf->backward = inf->distance; /* from inf->start */ + + inf->state++; + + /* fallthru */ + case UPNS_ID_BL_GB_SPINe: + + if (inf->forward >= inf->length) { + inf->treepos = 0; + inf->state = UPNS_ID_BL_GB_SPIN; + continue; + } + + if (inf->backward <= inf->start) + virt = inf->start - inf->backward; + else /* wrapped... backward >= start */ + virt = inf->info_size - + (inf->backward - inf->start); + + if (virt >= inf->info_size) + lwsl_err("virt %d\n", (int)virt); + + inf->out[inf->outpos++] = inf->out[virt]; + if (inf->outpos >= inf->outlen) + inf->outpos = 0; + + inf->outpos_linear++; + inf->backward--; + + if (!inf->backward) + inf->backward = inf->distance; + + inf->forward++; + + if (inf->outpos_linear - inf->consumed_linear >= + inf->bypl + 1) + return LWS_SRET_WANT_OUTPUT; + + continue; + + case UPNS_ID_BL_GB_SPINa: + + inf->out[inf->outpos++] = (uint8_t)inf->code; + if (inf->outpos >= inf->outlen) + inf->outpos = 0; + + inf->outpos_linear++; + inf->treepos = 0; + inf->state = UPNS_ID_BL_GB_SPIN; + + if (inf->outpos_linear - inf->consumed_linear >= + inf->bypl + 1) + return LWS_SRET_WANT_OUTPUT; + + continue; + } + } + + return LWS_SRET_OK; +} + +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; + 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->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 = uz_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; +} diff --git a/minimal-examples-lowlevel/api-tests/api-test-upng/CMakeLists.txt b/minimal-examples-lowlevel/api-tests/api-test-upng/CMakeLists.txt new file mode 100644 index 000000000..547d3a224 --- /dev/null +++ b/minimal-examples-lowlevel/api-tests/api-test-upng/CMakeLists.txt @@ -0,0 +1,25 @@ +project(lws-api-test-upng C) +cmake_minimum_required(VERSION 2.8.12) +find_package(libwebsockets CONFIG REQUIRED) +list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR}) +include(CheckCSourceCompiles) +include(LwsCheckRequirements) + +set(SAMP lws-api-test-upng) +set(SRCS main.c ) + +set(requirements 1) +require_lws_config(LWS_WITH_UPNG 1 requirements) +require_lws_config(LWS_WITH_CLIENT 1 requirements) + +if (requirements) + + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS}) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets ${LIBWEBSOCKETS_DEP_LIBS}) + endif() +endif() diff --git a/minimal-examples-lowlevel/api-tests/api-test-upng/main.c b/minimal-examples-lowlevel/api-tests/api-test-upng/main.c new file mode 100644 index 000000000..31791e22e --- /dev/null +++ b/minimal-examples-lowlevel/api-tests/api-test-upng/main.c @@ -0,0 +1,138 @@ +/* + * lws-api-test-upng + * + * Written in 2010-2022 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include +#include +#include +#include +#include + +int fdin = 0, fdout = 1; + +int +main(int argc, const char **argv) +{ + int result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + lws_stateful_ret_t r = LWS_SRET_WANT_INPUT; + const char *p; + lws_upng_t *u; + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS UPNG test tool\n"); + + if ((p = lws_cmdline_option(argc, argv, "--stdin"))) { + fdin = open(p, LWS_O_RDONLY, 0); + if (fdin < 0) { + result = 1; + lwsl_err("%s: unable to open stdin file\n", __func__); + goto bail; + } + } + + if ((p = lws_cmdline_option(argc, argv, "--stdout"))) { + fdout = open(p, LWS_O_WRONLY | LWS_O_CREAT | LWS_O_TRUNC, 0600); + if (fdout < 0) { + result = 1; + lwsl_err("%s: unable to open stdout file\n", __func__); + goto bail; + } + } + + if (!fdin) { + struct timeval timeout; + fd_set fds; + + FD_ZERO(&fds); + FD_SET(0, &fds); + + timeout.tv_sec = 0; + timeout.tv_usec = 1000; + + if (select(fdin + 1, &fds, NULL, NULL, &timeout) < 0 || + !FD_ISSET(0, &fds)) { + result = 1; + lwsl_err("%s: pass PNG " + "on stdin or use --stdin\n", __func__); + goto bail; + } + } + + + u = lws_upng_new(); + if (!u) { + lwsl_err("%s: failed to allocate\n", __func__); + goto bail; + } + + do { + const uint8_t *pix; + uint8_t ib[256]; + const uint8_t *pib = (const uint8_t *)ib; + ssize_t s, os; + size_t ps; + + if (r == LWS_SRET_WANT_INPUT) { + s = read(fdin, ib, sizeof(ib)); + + if (s <= 0) { + lwsl_err("%s: failed to read: %d\n", __func__, errno); + goto bail1; + } + + ps = (size_t)s; + + // lwsl_notice("%s: fetched %d\n", __func__, (int)s); + } + + do { + r = lws_upng_emit_next_line(u, &pix, &pib, &ps, 0); + if (r == LWS_SRET_WANT_INPUT) + break; + + if (r > LWS_SRET_FATAL) { + lwsl_err("%s: emit returned FATAL %d\n", __func__, r &0xff); + result = 1; + goto bail1; + } + + if (!pix) + goto bail1; + + os = (ssize_t)(lws_upng_get_width(u) * (lws_upng_get_pixelsize(u) / 8)); + + if (write(fdout, pix, +#if defined(WIN32) + (unsigned int) +#endif + (size_t)os) < os) { + lwsl_err("%s: write %d failed %d\n", __func__, (int)os, errno); + goto bail1; + } + + lwsl_notice("%s: wrote %d\n", __func__, (int)os); + } while (ps); + + } while (1); + +bail1: + if (fdin) + close(fdin); + if (fdout != 1) + close(fdout); + + lws_upng_free(&u); + +bail: + lwsl_user("Completed: %s\n", result ? "FAIL" : "PASS"); + + return result; +}