mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-09 00:00:04 +01:00
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.
This commit is contained in:
parent
aadcd3c44a
commit
48907fca0a
10 changed files with 1896 additions and 1 deletions
|
@ -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
|
||||
|
|
3
LICENSE
3
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
|
||||
|
|
56
READMEs/README.png-decoder.md
Normal file
56
READMEs/README.png-decoder.md
Normal file
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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 <libwebsockets/lws-button.h>
|
||||
#include <libwebsockets/lws-led.h>
|
||||
#include <libwebsockets/lws-pwm.h>
|
||||
#include <libwebsockets/lws-upng.h>
|
||||
#include <libwebsockets/lws-display.h>
|
||||
#include <libwebsockets/lws-ssd1306-i2c.h>
|
||||
#include <libwebsockets/lws-ili9341-spi.h>
|
||||
|
@ -774,6 +788,7 @@ lws_fx_string(const lws_fx_t *a, char *buf, size_t size);
|
|||
#if defined(LWS_WITH_NETWORK)
|
||||
#include <libwebsockets/lws-netdev.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
118
include/libwebsockets/lws-upng.h
Normal file
118
include/libwebsockets/lws-upng.h
Normal file
|
@ -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 <andy@warmcat.com>
|
||||
*
|
||||
* 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);
|
||||
|
|
@ -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)
|
||||
|
|
1535
lib/misc/upng.c
Normal file
1535
lib/misc/upng.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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()
|
138
minimal-examples-lowlevel/api-tests/api-test-upng/main.c
Normal file
138
minimal-examples-lowlevel/api-tests/api-test-upng/main.c
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* lws-api-test-upng
|
||||
*
|
||||
* Written in 2010-2022 by Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*/
|
||||
|
||||
#include <libwebsockets.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
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;
|
||||
}
|
Loading…
Add table
Reference in a new issue