From 28190ce012d78161afb184facb14b874bd4babec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deuc=D0=B5?= <shurd@sasktel.net> Date: Sat, 28 Dec 2024 17:25:57 -0500 Subject: [PATCH] Hack in initial JXL support Uses libjxl, makes the video demo more possible. Does not yet have a feature test sequence, documentation, support in the gmake build system, runtime linking, etc. Just a quick hack. It also looks like I can parallize the decode, should should also help things out. --- src/syncterm/CMakeLists.txt | 16 ++ src/syncterm/term.c | 333 ++++++++++++++++++++++++++++++++++++ 2 files changed, 349 insertions(+) diff --git a/src/syncterm/CMakeLists.txt b/src/syncterm/CMakeLists.txt index a9202caccd..9c812c5561 100644 --- a/src/syncterm/CMakeLists.txt +++ b/src/syncterm/CMakeLists.txt @@ -4,6 +4,7 @@ project (SyncTERM C) set(WITHOUT_CRYPTLIB OFF CACHE BOOL "Disable cryptlib (ssh and telnet over TLS") set(WITHOUT_OOII OFF CACHE BOOL "Disable Operation Overkill ][ Terminal") +set(WITHOUT_JPEG_XL OFF CACHE BOOL "Disable JPEG XL") # CPack stuff... set(CPACK_PACKAGE_NAME SyncTERM) @@ -26,6 +27,11 @@ INCLUDE(CPack) INCLUDE (../build/SynchronetMacros.cmake) INCLUDE (CheckIncludeFiles) +find_package(PkgConfig) +if(NOT WITHOUT_JPEG_XL) + pkg_check_modules(JPEG_XL libjxl) +endif() + set(SOURCE bbslist.c conn.c @@ -124,6 +130,16 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Haiku") add_custom_command(TARGET syncterm POST_BUILD COMMAND xres -o $<TARGET_FILE:syncterm> ${CMAKE_CURRENT_BINARY_DIR}/syncterm.rsrc) endif() +if(NOT WITHOUT_JPEG_XL) + if(JPEG_XL_FOUND) + target_compile_definitions(syncterm PUBLIC WITH_JPEG_XL) + target_include_directories(syncterm PRIVATE ${JPEG_XL_INCLUDE_DIRS}) + target_compile_options(syncterm PRIVATE ${JPEG_XL_CFLAGS}) + target_link_directories(syncterm PRIVATE ${JPEG_XL_LIBRARY_DIRS}) + target_link_libraries(syncterm ${JPEG_XL_LIBRARIES}) + target_link_options(syncterm PRIVATE ${JPEG_XL_LDFLAGS}) + endif() +endif() install(TARGETS syncterm DESTINATION bin) if(UNIX) diff --git a/src/syncterm/term.c b/src/syncterm/term.c index 6318ac57d3..a352e3e240 100644 --- a/src/syncterm/term.c +++ b/src/syncterm/term.c @@ -43,6 +43,12 @@ #include "md5.h" #include "ripper.h" +#ifdef WITH_JPEG_XL +#include <jxl/decode.h> +#include <jxl/encode.h> +#include "xpmap.h" +#endif + #define ANSI_REPLY_BUFSIZE 2048 static char ansi_replybuf[2048]; @@ -3076,6 +3082,320 @@ done: free(maskfn); } +#ifdef WITH_JPEG_XL +static void * +read_jxl(const char *fn) +{ + struct xpmapping *map = xpmap(fn, XPMAP_READ); + struct ciolib_pixels *pret = NULL; + uint8_t *pbuf = NULL; + uintmax_t width; + uintmax_t height; + + if (map == NULL) + return map; + + // Decode + JxlDecoderStatus st; + JxlBasicInfo info; + JxlColorEncoding ce; + size_t sz = 0; + JxlPixelFormat format = { + .num_channels = 3, + .data_type = JXL_TYPE_UINT8, + .endianness = JXL_NATIVE_ENDIAN, + .align = 1 + }; + JxlColorEncodingSetToSRGB(&ce, JXL_FALSE); + JxlDecoder *dec = JxlDecoderCreate(NULL); + if (dec == NULL) { + xpunmap(map); + return NULL; + } + if (JxlDecoderSetInput(dec, map->addr, map->size) != JXL_DEC_SUCCESS) { + xpunmap(map); + JxlDecoderDestroy(dec); + return NULL; + } + JxlDecoderCloseInput(dec); + if (JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) { + xpunmap(map); + JxlDecoderDestroy(dec); + return NULL; + } + for (bool done = false; !done;) { + st = JxlDecoderProcessInput(dec); + switch(st) { + case JXL_DEC_ERROR: + done = true; + break; + case JXL_DEC_BASIC_INFO: + if (JxlDecoderGetBasicInfo(dec, &info) != JXL_DEC_SUCCESS) { + done = true; + break; + } + width = info.xsize; + height = info.ysize; + break; + case JXL_DEC_COLOR_ENCODING: + // TODO... + if (JxlDecoderSetPreferredColorProfile(dec, &ce) != JXL_DEC_SUCCESS) { + done = true; + break; + } + break; + case JXL_DEC_NEED_IMAGE_OUT_BUFFER: + if (JxlDecoderImageOutBufferSize(dec, &format, &sz) != JXL_DEC_SUCCESS) { + done = true; + break; + } + pbuf = malloc(sz); + if (pbuf == NULL) { + done = true; + break; + } + pret = alloc_ciolib_pixels(width, height); + if (pret == NULL) { + done = true; + break; + } + JxlDecoderSetImageOutBuffer(dec, &format, pbuf, sz); + break; + case JXL_DEC_FULL_IMAGE: + // Got a single frame... this is not necessarily the whole image though. + break; + case JXL_DEC_SUCCESS: + done = true; + break; + default: + // Everything else is fail. + done = true; + break; + } + } + if (st != JXL_DEC_SUCCESS) { + freepixels(pret); + pret = NULL; + } + else { + for (size_t i = 0; i < sz; i += 3) { + size_t j = i / 3; + pret->pixels[j] = 0x80000000; + pret->pixels[j] |= pbuf[i] << 16; + pret->pixels[j] |= pbuf[i+1] << 8; + pret->pixels[j] |= pbuf[i+2]; + } + } + free(pbuf); + JxlDecoderReleaseInput(dec); + xpunmap(map); + JxlDecoderDestroy(dec); + return pret; +} + +static void +draw_jxl_str_handler(char *str, size_t slen, char *fn, void *apcd) +{ + struct ciolib_mask *ctmask = NULL; + char *p; + char *p2; + void *mask = NULL; + char *maskfn = NULL; + char *jxlfn = NULL; + struct ciolib_pixels *jxlp = NULL; + unsigned long *val; + unsigned long sx = 0; // Source X to start at + unsigned long sy = 0; // Source Y to start at + unsigned long sw = 0; // Source width to show + unsigned long sh = 0; // Source height to show + unsigned long dx = 0; // Destination X to start at + unsigned long dy = 0; // Destination Y to start at + unsigned long mx = 0; // Mask X to start at + unsigned long my = 0; // Mask Y to start at + unsigned long mw = 0; // Width of the mask + unsigned long mh = 0; // Height of the mask + size_t mlen = 0; + bool mbuf = false; + + for (p = str + 18; p && *p == ';'; p = strchr(p + 1, ';')) { + val = NULL; + switch (p[1]) { + case 'S': + switch (p[2]) { + case 'X': + val = &sx; + break; + case 'Y': + val = &sy; + break; + case 'W': + val = &sw; + break; + case 'H': + val = &sh; + break; + } + break; + case 'D': + switch (p[2]) { + case 'X': + val = &dx; + break; + case 'Y': + val = &dy; + break; + } + break; + case 'M': + if (p[2] == 'X') { + val = &mx; + break; + } + if (p[2] == 'Y') { + val = &my; + break; + } + if (p[2] == 'W') { + val = &mw; + break; + } + if (p[2] == 'H') { + val = &mh; + break; + } + if (strncmp(p + 2, "FILE=", 5) == 0) { + p2 = strchr(p + 7, ';'); + if (p2 == NULL) + goto done; + if (!mbuf) + freemask(ctmask); + mbuf = false; + ctmask = NULL; + free(mask); + mask = strndup(p + 7, p2 - p - 7); + continue; // Avoid val check + } + else if (strncmp(p + 2, "ASK=", 4) == 0) { + p2 = strchr(p + 6, ';'); + if (p2 == NULL) + goto done; + FREE_AND_NULL(mask); + if (!mbuf) + freemask(ctmask); + mbuf = false; + ctmask = alloc_ciolib_mask(0, 0); + ctmask->bits = b64_decode_alloc(p + 6, p2 - p + 5, &mlen); + if (ctmask->bits == NULL) + goto done; + continue; // Avoid val check + } + else if (strncmp(p + 2, "BUF", 3) == 0) { + freemask(ctmask); + ctmask = NULL; + mbuf = true; + continue; // Avoid val check + } + break; + } + if (val == NULL || p[3] != '=') + break; + *val = strtoul(p + 4, NULL, 10); + } + + if (asprintf(&jxlfn, "%s%s", fn, p + 1) == -1) + goto done; + jxlp = read_jxl(jxlfn); + if (jxlp == NULL) + goto done; + + if (sw == 0) + sw = jxlp->width - sx; + if (sh == 0) + sh = jxlp->height - sy; + + if (ctmask != NULL) { + if (mlen < (sw * sh + 7) / 8) + goto done; + if (mw == 0) + mw = sw; + if (mh == 0) + mh = sh; + if (mlen < (mw * mh + 7) / 8) + goto done; + ctmask->width = mw; + ctmask->height = mh; + } + + if (mask != NULL) { + if (asprintf(&maskfn, "%s%s", fn, (char*)mask) < 0) + goto done; + } + + if (maskfn != NULL) { + freemask(ctmask); + ctmask = read_pbm(maskfn, true); + if (ctmask == NULL) + goto done; + if (ctmask->width < sw || ctmask->height < sh) + goto done; + } + + if (mbuf) + ctmask = mask_buffer; + + if (jxlp != NULL) + setpixels(dx, dy, dx + sw - 1, dy + sh - 1, sx, sy, mx, my, jxlp, ctmask); +done: + free(mask); + free(maskfn); + if (!mbuf) + freemask(ctmask); + free(jxlfn); + freepixels(jxlp); +} + +static void +load_jxl_str_handler(char *str, size_t slen, char *fn, void *apcd) +{ + char *p; + char *jxlfn = NULL; + struct ciolib_pixels *jxlp = NULL; + unsigned long bufnum = 0; + unsigned long *val; + + for (p = str + 18; p && *p == ';'; p = strchr(p + 1, ';')) { + val = NULL; + switch (p[1]) { + case 'B': + val = &bufnum; + break; + } + if (val == NULL || p[2] != '=') + break; + *val = strtoul(p + 3, NULL, 10); + } + + if (bufnum >= sizeof(pixmap_buffer) / sizeof(pixmap_buffer[0])) + goto done; + + freepixels(pixmap_buffer[bufnum]); + pixmap_buffer[bufnum] = NULL; + + if (asprintf(&jxlfn, "%s%s", fn, p + 1) == -1) + goto done; + jxlp = read_jxl(jxlfn); + if (jxlp == NULL) + goto done; + pixmap_buffer[bufnum] = jxlp; + free(jxlfn); + return; + +done: + free(jxlfn); + freepixels(jxlp); +} +#endif + static void copy_pixmap(char *str, size_t slen, char *fn, void *apcd) { @@ -3358,6 +3678,12 @@ apc_handler(char *strbuf, size_t slen, void *apcd) // Load PPM into memory buffer load_pbm_str_handler(strbuf, slen, fn, apcd); } +#ifdef WITH_JPEG_XL + else if (strncmp(strbuf, "SyncTERM:C;LoadJXL", 18) == 0) { + // Load PPM into memory buffer + load_jxl_str_handler(strbuf, slen, fn, apcd); + } +#endif else if (strncmp(strbuf, "SyncTERM:C;L", 12) == 0) { // Cache list if ((strbuf[12] != 0) && (strbuf[12] != ';')) @@ -3476,6 +3802,13 @@ apc_handler(char *strbuf, size_t slen, void *apcd) //SyncTERM:C;DrawPPM;SX=x;SY=y;SW=w;SH=hDX=x;Dy=y; draw_ppm_str_handler(strbuf, slen, fn, apcd); } +#ifdef WITH_JPEG_XL + else if (strncmp(strbuf, "SyncTERM:C;DrawJXL", 18) == 0) { + // Request to draw a 255 max PPM file from cache + //SyncTERM:C;DrawPPM;SX=x;SY=y;SW=w;SH=hDX=x;Dy=y; + draw_jxl_str_handler(strbuf, slen, fn, apcd); + } +#endif else if (strncmp(strbuf, "SyncTERM:P;Copy", 15) == 0) { copy_pixmap(strbuf, slen, fn, apcd); } -- GitLab