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; +}