diff --git a/codecs/tiff/.dockerignore b/codecs/tiff/.dockerignore new file mode 100644 index 000000000..f63018cea --- /dev/null +++ b/codecs/tiff/.dockerignore @@ -0,0 +1,2 @@ +** +!/patches diff --git a/codecs/tiff/.gitignore b/codecs/tiff/.gitignore new file mode 100644 index 000000000..e9c30529a --- /dev/null +++ b/codecs/tiff/.gitignore @@ -0,0 +1,2 @@ +compile_flags.txt +*.wat diff --git a/codecs/tiff/Makefile b/codecs/tiff/Makefile new file mode 100644 index 000000000..31d0fcead --- /dev/null +++ b/codecs/tiff/Makefile @@ -0,0 +1,160 @@ +CODEC_URL = https://download.osgeo.org/libtiff/tiff-4.6.0.tar.xz +CODEC_PACKAGE = node_modules/tiff.tar.xz +CODEC_PACKAGE_HASH = e178649607d1e22b51cf361dd20a3753f244f022eefab1f2f218fc62ebaf87d2 + +ZLIB_URL = https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.xz +ZLIB_PACKAGE = node_modules/zlib.tar.xz +ZLIB_PACKAGE_HASH = 38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32 + +JPEG_URL = https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/3.0.2/libjpeg-turbo-3.0.2.tar.gz +JPEG_PACKAGE = node_modules/libjpeg-turbo.tar.gz +JPEG_PACKAGE_HASH = c2ce515a78d91b09023773ef2770d6b0df77d674e144de80d63e0389b3a15ca6 + +export CODEC_DIR = node_modules/tiff +export ZLIB_DIR = node_modules/zlib +export JPEG_DIR = node_modules/jpeg + +.PHONY all: clean_binary dec/tiff_dec.wasm + +# =============================================================== +# zlib + +$(ZLIB_PACKAGE): + mkdir -p "$(@D)" + curl -sSL "$(ZLIB_URL)" -o "$@" + echo "$(ZLIB_PACKAGE_HASH) $(ZLIB_PACKAGE)" | sha256sum -c - + +$(ZLIB_DIR)/CMakeLists.txt: $(ZLIB_PACKAGE) + mkdir -p "$(@D)" + tar xJm --strip 1 -C "$(@D)" -f "$(ZLIB_PACKAGE)" +# remove dummy Makefile + rm -f "$(ZLIB_DIR)/Makefile" + +$(ZLIB_DIR)/Makefile: $(ZLIB_DIR)/CMakeLists.txt + cmake -S "$(ZLIB_DIR)" -B "$(ZLIB_DIR)" \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DBUILD_SHARED_LIBS=OFF + +ZLIB_LIB=$(ZLIB_DIR)/libz.a + +$(ZLIB_LIB): $(ZLIB_DIR)/Makefile + $(MAKE) -C "$(ZLIB_DIR)" zlibstatic + mv -f "$(ZLIB_DIR)/libzlibstatic.a" "$(ZLIB_LIB)" + +# =============================================================== +# libjpeg-turbo + +$(JPEG_PACKAGE): + mkdir -p "$(@D)" + curl -sSL "$(JPEG_URL)" -o "$@" + echo "$(JPEG_PACKAGE_HASH) $(JPEG_PACKAGE)" | sha256sum -c - + +$(JPEG_DIR)/CMakeLists.txt: $(JPEG_PACKAGE) + mkdir -p "$(@D)" + tar xzm --strip 1 -C "$(@D)" -f "$(JPEG_PACKAGE)" + for i in ./patches/libjpeg-turbo/*.patch; do patch -d "$(@D)" -N -p1 < "$$i"; done + +$(JPEG_DIR)/Makefile: $(JPEG_DIR)/CMakeLists.txt + cmake -S "$(JPEG_DIR)" -B "$(JPEG_DIR)" \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DBUILD_SHARED_LIBS=OFF \ + -DENABLE_SHARED=OFF \ + -DWITH_SIMD=OFF \ + -DWITH_TURBOJPEG=OFF + +JPEG_LIB=$(JPEG_DIR)/libjpeg.a + +$(JPEG_LIB): $(JPEG_DIR)/Makefile + $(MAKE) -C "$(JPEG_DIR)" jpeg-static + +# =============================================================== +# libtiff + +$(CODEC_PACKAGE): + mkdir -p "$(@D)" + curl -sSL "$(CODEC_URL)" -o "$@" + echo "$(CODEC_PACKAGE_HASH) $(CODEC_PACKAGE)" | sha256sum -c - + +$(CODEC_DIR)/configure: $(CODEC_PACKAGE) + mkdir -p "$(@D)" + tar xJm --strip 1 -C "$(@D)" -f "$(CODEC_PACKAGE)" + for i in ./patches/libtiff/*.patch; do patch -d "$(@D)" -N -p1 < "$$i"; done + +# It's a huge pain telling CMake to find our custom-built libraries, use autoconf instead... +$(CODEC_DIR)/Makefile: $(CODEC_DIR)/configure + cd $(CODEC_DIR); \ + ./configure \ + --host=wasm32-wasi \ + --enable-shared=no \ + --enable-cxx=no \ + --disable-tools \ + --disable-tests \ + --disable-contrib \ + --disable-docs \ + --disable-thunder \ + --disable-next \ + --disable-mdi \ + --disable-old-jpeg \ + --disable-jbig \ + --disable-lerc \ + --disable-lzma \ + --disable-zstd \ + --disable-webp \ + --disable-sphinx \ + --with-zlib-include-dir="$(shell realpath $(ZLIB_DIR))" \ + --with-zlib-lib-dir="$(shell realpath $(ZLIB_DIR))" \ + --with-jpeg-include-dir="$(shell realpath $(JPEG_DIR))" \ + --with-jpeg-lib-dir="$(shell realpath $(JPEG_DIR))" + +CODEC_LIB=$(CODEC_DIR)/libtiff/.libs/libtiff.a + +$(CODEC_LIB): $(CODEC_DIR)/Makefile + $(MAKE) -C "$(CODEC_DIR)" + +# =============================================================== + +dec/tiff_dec.wasm: $(ZLIB_LIB) $(JPEG_LIB) $(CODEC_LIB) + $(CC) \ + $(CFLAGS) \ + -Wall \ + -Wextra \ + -Wconversion \ + -I"$(CODEC_DIR)/libtiff" \ + -g \ + -Wl,-z,stack-size=1048576 \ + -Wl,--fatal-warnings \ + -Wl,--no-entry \ + -Wl,--export=malloc \ + -Wl,--export=free \ + -Wl,--trace-symbol=stderr \ + -mexec-model=reactor \ + -o dec/tiff_dec.wasm \ + dec/tiff_dec.c \ + "$(CODEC_LIB)" \ + "$(JPEG_LIB)" \ + "$(ZLIB_LIB)" \ + -lm + +# =============================================================== + +distclean_zlib: + rm -rf "$(ZLIB_DIR)" + +distclean_jpeg: + rm -rf "$(JPEG_DIR)" + +distclean_codec: + rm -rf "$(CODEC_DIR)" + +.PHONY distclean: distclean_zlib distclean_jpeg distclean_codec + +clean_jpeg: + [ -f "$(JPEG_DIR)/Makefile" ] && $(MAKE) -C "$(JPEG_DIR)" clean + +clean_codec: + [ -f "$(CODEC_DIR)/Makefile" ] && $(MAKE) -C "$(CODEC_DIR)" clean + +clean_binary: + rm -f dec/tiff_dec.wasm + +.PHONY clean: clean_binary clean_codec clean_jpeg diff --git a/codecs/tiff/build.sh b/codecs/tiff/build.sh new file mode 100644 index 000000000..4e55a7e0d --- /dev/null +++ b/codecs/tiff/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -euo pipefail + +docker build -t squoosh-wasi --network=host -f wasi.Dockerfile . +docker run -it --rm -v "$(pwd)":/src -u "$(id -u):$(id -g)" squoosh-wasi "$@" + +# wasm2wat --enable-annotation --enable-code-metadata dec/tiff_dec.wasm > dec/tiff_dec.wat || true +wasm-opt -O3 --strip-debug -o dec/tiff_dec.wasm dec/tiff_dec.wasm diff --git a/codecs/tiff/dec/tiff_dec.c b/codecs/tiff/dec/tiff_dec.c new file mode 100644 index 000000000..6298c07ea --- /dev/null +++ b/codecs/tiff/dec/tiff_dec.c @@ -0,0 +1,195 @@ +#include +#include +#include +#include +#include + +typedef struct cursor { + const char* data; + tmsize_t size; + tmsize_t off; +} cursor_t; + +typedef union cd_as_cursor { + thandle_t client; + cursor_t* cursor; +} cd_as_cursor_t; + +static tmsize_t _cursor_read_proc(thandle_t clientdata, void* buf, tmsize_t len) { + cd_as_cursor_t cdc; + cdc.client = clientdata; + cursor_t* c = cdc.cursor; + + tmsize_t nremain = c->size - c->off; + tmsize_t nread = nremain > len ? len : nremain; + + memcpy(buf, (const void*)(c->data + c->off), (size_t)nread); + c->off += nread; + return (tmsize_t)nread; +} + +static tmsize_t _cursor_write_proc(thandle_t clientdata, void* buf, tmsize_t size) { + (void)clientdata, (void)buf, (void)size; + return 0; +} + +static toff_t _cursor_seek_proc(thandle_t clientdata, toff_t off, int whence) { + cd_as_cursor_t cdc; + cdc.client = clientdata; + cursor_t* c = cdc.cursor; + + tmsize_t off_m = (tmsize_t)off; + if ((toff_t)off_m != off) { + errno = EINVAL; + return (toff_t)-1; + } + + switch (whence) { + case SEEK_SET: + c->off = off_m; + break; + case SEEK_CUR: + c->off += off_m; + break; + case SEEK_END: + if (off_m <= c->size) { + c->off = c->size - off_m; + break; + } + __attribute__((fallthrough)); + default: + errno = EINVAL; + return (toff_t)-1; + } + return (toff_t)c->off; +} + +static int _cursor_close_proc(thandle_t clientdata) { + cd_as_cursor_t cdc; + cdc.client = clientdata; + free(cdc.cursor); + return 0; +} + +static uint64_t _cursor_size_proc(thandle_t clientdata) { + cd_as_cursor_t cdc; + cdc.client = clientdata; + return (uint64_t)cdc.cursor->size; +} + +static int _cursor_map_proc(thandle_t clientdata, void** pbase, toff_t* psize) { + cd_as_cursor_t cdc; + cdc.client = clientdata; + cursor_t* c = cdc.cursor; + + // `tif_unix.c` only passes `PROT_READ` to `mmap()`, so dropping `const` should be fine here. + *pbase = (void*)c->data; + *psize = (toff_t)c->size; + return 1; +} + +static void _cursor_unmap_proc(thandle_t clientdata, void* base, toff_t size) { + (void)clientdata, (void)base, (void)size; +} + +extern int _return_decoded_image(const uint32_t* raster, uint32_t width, uint32_t height) + __attribute__((import_name("return_decoded_image"))); + +__attribute__((export_name("decode"))) int decode(const char* data, const size_t size) { + static const char* module_name = "tiff_dec"; + if (!data) + return 0; + + tmsize_t size_m = (tmsize_t)size; + if (((size_t)size_m) != size) { + TIFFErrorExtR(NULL, module_name, "Too large image buffer"); + return 0; + } + + cursor_t* c = malloc(sizeof(cursor_t)); + if (!c) + return 0; + c->data = data; + c->size = size_m; + c->off = 0; + + cd_as_cursor_t cdc; + cdc.cursor = c; + TIFF* tif = TIFFClientOpen("dummy.tif", "r", cdc.client, _cursor_read_proc, _cursor_write_proc, + _cursor_seek_proc, _cursor_close_proc, _cursor_size_proc, + _cursor_map_proc, _cursor_unmap_proc); + if (!tif) { + free(cdc.cursor); + return 0; + } + + int ret = 0; + + uint32_t width, height; + if (TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width) == 0 || + TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height) == 0) { + TIFFErrorExtR(tif, module_name, "Missing image dimension tag"); + goto cleanup_tif; + } + + uint32_t npixels; + if (__builtin_umul_overflow(width, height, &npixels)) { + TIFFErrorExtR(tif, module_name, "Too large image dimension"); + goto cleanup_tif; + } + + uint32_t* raster = (uint32_t*)malloc(npixels * sizeof(uint32_t)); + if (!raster) { + TIFFErrorExtR(tif, module_name, "Failed to allocate memory of %d*%d*4 bytes", width, height); + goto cleanup_tif; + } + + if (!TIFFReadRGBAImageOriented(tif, width, height, raster, 1, ORIENTATION_TOPLEFT)) { + goto cleanup_raster; + } + + ret = _return_decoded_image(raster, width, height); + +cleanup_raster: + free(raster); +cleanup_tif: + TIFFClose(tif); + return ret; +} + +extern int _log_warning(const char* s, size_t len) __attribute__((import_name("log_warning"))); + +extern int _log_error(const char* s, size_t len) __attribute__((import_name("log_error"))); + +// Override unix error handlers from `tiff_unix.c`. +static void _extern_warning_handler(const char* module, const char* fmt, va_list ap) { + (void)module; + char buf[256]; + vsnprintf(buf, sizeof(buf), fmt, ap); + _log_warning(buf, strlen(buf)); +} +TIFFErrorHandler _TIFFwarningHandler = _extern_warning_handler; + +static void _extern_error_handler(const char* module, const char* fmt, va_list ap) { + (void)module; + char buf[256]; + vsnprintf(buf, sizeof(buf), fmt, ap); + _log_error(buf, strlen(buf)); +} +TIFFErrorHandler _TIFFerrorHandler = _extern_error_handler; + +// Stubbing libc to remove `environ_get*` WASI imports. +char* getenv(const char* name) { + (void)name; + return NULL; +} + +// Stubbing libc to remove `fd_*` WASI imports. +_Noreturn void __assert_fail(const char* expr, const char* file, int line, const char* func) { + // Modified from + // https://github.com/WebAssembly/wasi-libc/blob/wasi-sdk-20/libc-top-half/musl/src/exit/assert.c + char buf[256]; + snprintf(buf, sizeof(buf), "Assertion failed: %s (%s: %s: %d)\n", expr, file, func, line); + _log_error(buf, strlen(buf)); + abort(); +} diff --git a/codecs/tiff/dec/tiff_dec.wasm b/codecs/tiff/dec/tiff_dec.wasm new file mode 100644 index 000000000..c57e06c36 Binary files /dev/null and b/codecs/tiff/dec/tiff_dec.wasm differ diff --git a/codecs/tiff/package.json b/codecs/tiff/package.json new file mode 100644 index 000000000..fc0d07df7 --- /dev/null +++ b/codecs/tiff/package.json @@ -0,0 +1,6 @@ +{ + "name": "tiff", + "scripts": { + "build": "./build.sh make" + } +} diff --git a/codecs/tiff/patches/libjpeg-turbo/0001-Remove-fprintf-call.patch b/codecs/tiff/patches/libjpeg-turbo/0001-Remove-fprintf-call.patch new file mode 100644 index 000000000..a8f710064 --- /dev/null +++ b/codecs/tiff/patches/libjpeg-turbo/0001-Remove-fprintf-call.patch @@ -0,0 +1,35 @@ +From add3a91a2cafdba74b6fa65fb525007909a69b7c Mon Sep 17 00:00:00 2001 +From: andylizi +Date: Sat, 3 Feb 2024 01:04:17 -0400 +Subject: [PATCH] Remove `fprintf` call + +--- + jerror.c | 14 +------------- + 1 file changed, 1 insertion(+), 13 deletions(-) + +diff --git a/jerror.c b/jerror.c +index d0ab5b8..ef745df 100644 +--- a/jerror.c ++++ b/jerror.c +@@ -97,17 +97,5 @@ METHODDEF(void) + output_message(j_common_ptr cinfo) + { +- char buffer[JMSG_LENGTH_MAX]; +- +- /* Create the message */ +- (*cinfo->err->format_message) (cinfo, buffer); +- +-#ifdef USE_WINDOWS_MESSAGEBOX +- /* Display it in a message dialog box */ +- MessageBox(GetActiveWindow(), buffer, "JPEG Library Error", +- MB_OK | MB_ICONERROR); +-#else +- /* Send it to stderr, adding a newline */ +- fprintf(stderr, "%s\n", buffer); +-#endif ++ (void)cinfo; + } + +-- +2.43.0 + diff --git a/codecs/tiff/patches/libtiff/0001-Remove-setjmp-longjmp.patch b/codecs/tiff/patches/libtiff/0001-Remove-setjmp-longjmp.patch new file mode 100644 index 000000000..a3d770c77 --- /dev/null +++ b/codecs/tiff/patches/libtiff/0001-Remove-setjmp-longjmp.patch @@ -0,0 +1,47 @@ +From 3a36794496e595f2a9883630c7c6f7acb3a53493 Mon Sep 17 00:00:00 2001 +From: andylizi +Date: Sat, 3 Feb 2024 01:36:05 -0400 +Subject: [PATCH] Remove setjmp/longjmp + +--- + libtiff/tif_jpeg.c | 7 +++---- + libtiff/tif_ojpeg.c | 7 +++---- + 2 files changed, 6 insertions(+), 8 deletions(-) + +diff --git a/libtiff/tif_jpeg.c b/libtiff/tif_jpeg.c +index 39bd813..15dcdce 100644 +--- a/libtiff/tif_jpeg.c ++++ b/libtiff/tif_jpeg.c +@@ -44,3 +44,2 @@ + */ +-#include + +@@ -143,5 +142,5 @@ int TIFFJPEGIsFullStripRequired_12(TIFF *tif); + */ +-#define SETJMP(jbuf) setjmp(jbuf) +-#define LONGJMP(jbuf, code) longjmp(jbuf, code) +-#define JMP_BUF jmp_buf ++#define SETJMP(jbuf) 0 ++#define LONGJMP(jbuf, code) exit(1) ++#define JMP_BUF int + +diff --git a/libtiff/tif_ojpeg.c b/libtiff/tif_ojpeg.c +index e4547cf..28b0f3e 100644 +--- a/libtiff/tif_ojpeg.c ++++ b/libtiff/tif_ojpeg.c +@@ -154,5 +154,5 @@ + /* define LIBJPEG_ENCAP_EXTERNAL */ +-#define SETJMP(jbuf) setjmp(jbuf) +-#define LONGJMP(jbuf, code) longjmp(jbuf, code) +-#define JMP_BUF jmp_buf ++#define SETJMP(jbuf) 0 ++#define LONGJMP(jbuf, code) exit(1) ++#define JMP_BUF int + #define OJPEG_BUFFER 2048 +@@ -206,3 +206,2 @@ static const TIFFField ojpegFields[] = { + #ifndef LIBJPEG_ENCAP_EXTERNAL +-#include + #endif +-- +2.43.0 + diff --git a/codecs/tiff/patches/libtiff/0002-Remove-default-error-handler.patch b/codecs/tiff/patches/libtiff/0002-Remove-default-error-handler.patch new file mode 100644 index 000000000..1a58459fd --- /dev/null +++ b/codecs/tiff/patches/libtiff/0002-Remove-default-error-handler.patch @@ -0,0 +1,24 @@ +From c3ad67265b045f9a0b266d2137bc06043a34d207 Mon Sep 17 00:00:00 2001 +From: andylizi +Date: Sat, 3 Feb 2024 01:07:15 -0400 +Subject: [PATCH] Remove default error handler + +This removes the reference to `fprintf(stderr)`. +--- + libtiff/tif_unix.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/libtiff/tif_unix.c b/libtiff/tif_unix.c +index d9254d4..a9bc588 100644 +--- a/libtiff/tif_unix.c ++++ b/libtiff/tif_unix.c +@@ -356,2 +356,3 @@ int _TIFFmemcmp(const void *p1, const void *p2, tmsize_t c) + ++/* + static void unixWarningHandler(const char *module, const char *fmt, va_list ap) +@@ -374 +375,2 @@ static void unixErrorHandler(const char *module, const char *fmt, va_list ap) + TIFFErrorHandler _TIFFerrorHandler = unixErrorHandler; ++*/ +-- +2.43.0 + diff --git a/codecs/tiff/patches/libtiff/0003-Remove-printdir-functions.patch b/codecs/tiff/patches/libtiff/0003-Remove-printdir-functions.patch new file mode 100644 index 000000000..9467b09f3 --- /dev/null +++ b/codecs/tiff/patches/libtiff/0003-Remove-printdir-functions.patch @@ -0,0 +1,51 @@ +From da302a2fd48aac7c67e7382ead1ed59d603071a5 Mon Sep 17 00:00:00 2001 +From: andylizi +Date: Sat, 3 Feb 2024 01:11:38 -0400 +Subject: [PATCH] Remove printdir functions + +--- + libtiff/tif_fax3.c | 2 +- + libtiff/tif_jpeg.c | 2 +- + libtiff/tif_ojpeg.c | 2 +- + libtiff/tif_predict.c | 2 +- + 4 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/libtiff/tif_fax3.c b/libtiff/tif_fax3.c +index 668e96a..59a6b74 100644 +--- a/libtiff/tif_fax3.c ++++ b/libtiff/tif_fax3.c +@@ -1471,3 +1471,3 @@ static int InitCCITTFax3(TIFF *tif) + sp->printdir = tif->tif_tagmethods.printdir; +- tif->tif_tagmethods.printdir = Fax3PrintDir; /* hook for codec tags */ ++ tif->tif_tagmethods.printdir = NULL; /* hook for codec tags */ + sp->groupoptions = 0; +diff --git a/libtiff/tif_jpeg.c b/libtiff/tif_jpeg.c +index 39bd813..35a14f8 100644 +--- a/libtiff/tif_jpeg.c ++++ b/libtiff/tif_jpeg.c +@@ -2772,3 +2772,3 @@ static void TIFFInitJPEGCommon(TIFF *tif) + tif->tif_tagmethods.vsetfield = JPEGVSetField; /* hook for codec tags */ +- tif->tif_tagmethods.printdir = JPEGPrintDir; /* hook for codec tags */ ++ tif->tif_tagmethods.printdir = NULL; /* hook for codec tags */ + +diff --git a/libtiff/tif_ojpeg.c b/libtiff/tif_ojpeg.c +index e4547cf..6b433ba 100644 +--- a/libtiff/tif_ojpeg.c ++++ b/libtiff/tif_ojpeg.c +@@ -510,3 +510,3 @@ int TIFFInitOJPEG(TIFF *tif, int scheme) + sp->printdir = tif->tif_tagmethods.printdir; +- tif->tif_tagmethods.printdir = OJPEGPrintDir; ++ tif->tif_tagmethods.printdir = NULL; + /* Some OJPEG files don't have strip or tile offsets or bytecounts tags. +diff --git a/libtiff/tif_predict.c b/libtiff/tif_predict.c +index 386b5fe..c756f30 100644 +--- a/libtiff/tif_predict.c ++++ b/libtiff/tif_predict.c +@@ -1019,3 +1019,3 @@ int TIFFPredictorInit(TIFF *tif) + tif->tif_tagmethods.printdir = +- PredictorPrintDir; /* hook for predictor tag */ ++ NULL; /* hook for predictor tag */ + +-- +2.43.0 + diff --git a/codecs/tiff/patches/wasi-libc/0001-Do-not-print-error-for-disabled-printscan-feature.patch b/codecs/tiff/patches/wasi-libc/0001-Do-not-print-error-for-disabled-printscan-feature.patch new file mode 100644 index 000000000..501b91580 --- /dev/null +++ b/codecs/tiff/patches/wasi-libc/0001-Do-not-print-error-for-disabled-printscan-feature.patch @@ -0,0 +1,38 @@ +From 28d63c1d49bea0c3d50889508ea60550e4fc9843 Mon Sep 17 00:00:00 2001 +From: andylizi +Date: Fri, 2 Feb 2024 23:16:05 -0400 +Subject: [PATCH] Do not print error for disabled printscan feature + +Previously functions such as `sprintf`, even though +they do not perform I/O themselves, will cause +I/O-related machinery to be included in the final wasm binary. +This is undesirable for minimizing code size and WASI imports. +--- + libc-top-half/headers/private/printscan.h | 4 ---- + 1 file changed, 4 deletions(-) + +diff --git a/libc-top-half/headers/private/printscan.h b/libc-top-half/headers/private/printscan.h +index 7c21f99..690fce3 100644 +--- a/libc-top-half/headers/private/printscan.h ++++ b/libc-top-half/headers/private/printscan.h +@@ -5,8 +5,6 @@ + __attribute__((__cold__, __noreturn__)) + static void floating_point_not_supported(void) { + void abort(void) __attribute__((__noreturn__)); +- fputs("Support for floating-point formatting is currently disabled.\n" +- "To enable it, " __wasilibc_printscan_floating_point_support_option ".\n", stderr); + abort(); + } + +@@ -46,8 +44,6 @@ typedef double long_double; + __attribute__((__cold__, __noreturn__)) + static void long_double_not_supported(void) { + void abort(void) __attribute__((__noreturn__)); +- fputs("Support for formatting long double values is currently disabled.\n" +- "To enable it, " __wasilibc_printscan_full_support_option ".\n", &__stderr_FILE); + abort(); + } + +-- +2.43.0 + diff --git a/codecs/tiff/wasi.Dockerfile b/codecs/tiff/wasi.Dockerfile new file mode 100644 index 000000000..e4aa92503 --- /dev/null +++ b/codecs/tiff/wasi.Dockerfile @@ -0,0 +1,35 @@ +ARG SDK_VERSION=wasi-sdk-21 +FROM ghcr.io/webassembly/wasi-sdk:$SDK_VERSION AS wasi-sdk-official +ARG SDK_VERSION + +ARG EXTRA_CFLAGS="-Os -msign-ext -mbulk-memory -mnontrapping-fptoint" +ARG EXTRA_CXXFLAGS="-std=c++17" + +# NM is required by wasi-libc symbol check +ENV NM llvm-nm-${LLVM_VERSION} + +RUN --mount=source=./,target=/context \ + set -eux; \ + apt-get update; \ + apt-get install -y curl patch xz-utils; \ + # Remove the old sysroot - we want to compile it ourselves + rm -rf /wasi-sysroot; \ + # Originally the /share folder was created by `check-symbols` target, but we skipped that + mkdir -p /wasi-libc/sysroot/share; \ + curl -sSL "https://github.com/WebAssembly/wasi-libc/archive/refs/tags/${SDK_VERSION}.tar.gz" | \ + tar xzf - --strip 1 -C /wasi-libc; \ + # See the patch file for details + for i in /context/patches/wasi-libc/*.patch; do patch -d /wasi-libc -N -p1 < "$i"; done; \ + # Skip `check-symbols` because we changed CFLAGS and the test won't pass + make -C /wasi-libc -o check-symbols install \ + INSTALL_DIR=/wasi-sysroot/ \ + EXTRA_CFLAGS="${EXTRA_CFLAGS}" \ + ; \ + rm -rf /wasi-libc;\ + apt-get autoremove -y; \ + rm -rf /var/lib/apt/lists/* + +ENV CFLAGS "${CFLAGS} ${EXTRA_CFLAGS}" +ENV CXXFLAGS "${CFLAGS} ${EXTRA_CXXFLAGS}" + +WORKDIR /src diff --git a/src/client/lazy-app/Compress/index.tsx b/src/client/lazy-app/Compress/index.tsx index 104a4c5f3..90ddc2cc0 100644 --- a/src/client/lazy-app/Compress/index.tsx +++ b/src/client/lazy-app/Compress/index.tsx @@ -114,6 +114,9 @@ async function decodeImage( if (mimeType === 'image/qoi') { return await workerBridge.qoiDecode(signal, blob); } + if (mimeType === 'image/tiff') { + return await workerBridge.tiffDecode(signal, blob); + } } // Otherwise fall through and try built-in decoding for a laugh. return await builtinDecode(signal, blob); diff --git a/src/features/decoders/tiff/worker/tiffDecode.ts b/src/features/decoders/tiff/worker/tiffDecode.ts new file mode 100644 index 000000000..025cf8522 --- /dev/null +++ b/src/features/decoders/tiff/worker/tiffDecode.ts @@ -0,0 +1,82 @@ +/** + * Copyright 2020 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import wasmUrl from 'url:codecs/tiff/dec/tiff_dec.wasm'; +import { blobToArrayBuffer } from 'features/worker-utils'; + +interface WasmExports { + _initialize(): void; + malloc(size: number): number; + free(ptr: number): void; + decode(data: number, size: number): number; +} + +let mem: WebAssembly.Memory; +let decodedImage: ImageData | undefined; + +const textDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +const decodeString = (ptr: number, len: number) => + textDecoder.decode(new Uint8Array(mem.buffer, ptr, len)); + +const wasmPromise = WebAssembly.instantiateStreaming(fetch(wasmUrl), { + env: { + return_decoded_image(data: number, width: number, height: number) { + const raster = new Uint8ClampedArray( + mem.buffer, + data, + width * height * 4, + ); + decodedImage = new ImageData(raster, width, height); + return 1; + }, + log_warning(ptr: number, len: number) { + console.warn(decodeString(ptr, len)); + }, + log_error(ptr: number, len: number) { + console.error(decodeString(ptr, len)); + }, + }, + wasi_snapshot_preview1: { + proc_exit(code: number) { + throw new Error( + 'TIFF exited with ' + code + ', probably caused by JPEG decode error', + ); + }, + }, +}).then(({ instance }) => { + mem = instance.exports.memory as any; + const module = instance.exports as unknown as WasmExports; + module._initialize?.(); + return module; +}); + +export default async function decode(blob: Blob): Promise { + const [module, data] = await Promise.all([ + wasmPromise, + blobToArrayBuffer(blob), + ]); + + const ptr = module.malloc(data.byteLength); + try { + new Uint8Array(mem.buffer, ptr).set(new Uint8Array(data)); + + const result = module.decode(ptr, data.byteLength); + if (!result || result <= 0) throw new Error('Decoding error'); + + const image = decodedImage; + decodedImage = undefined; + if (!image) throw new Error('Decoding error'); + return image; + } finally { + module.free(ptr); + } +}