Motionix HEIC Decoder ==================== This separate, replaceable library decodes the primary still image of HEVC-based HEIF files. It does not encode HEVC. It does not contain x265, third-party codec plugins, a virtual filesystem, network image input, or multithreaded WASM. Loading the module fetches its sibling WASM asset from your own server. API --- import createHeicDecoder from '/vendor/heic/decoder.mjs'; const decoder = await createHeicDecoder(); const { width, height, channels, data } = decoder.decode(input); input: ArrayBuffer or Uint8Array, at most 10 MiB (10 * 1024 * 1024 bytes). output: width and height integers; channels === 4; data is a tightly packed, owned Uint8ClampedArray of RGBA bytes, transferable through [data.buffer]. Decode is synchronous: run it in a dedicated worker, never the UI thread. Native contexts, image handles, options, planes and input allocations are freed internally. Results need no dispose method. Terminate the worker to release the WASM heap. Fatal WASM/runtime exceptions also require discarding that worker. The async factory accepts standard Emscripten module initialization options (for example wasmBinary in Node tests), not decoder security-limit overrides. HeicDecodeError.code is INVALID_DATA, UNSUPPORTED_FEATURE, LIMIT_EXCEEDED, or OUT_OF_MEMORY. Native errors also supply nativeCode and nativeSubcode integers. LIMIT_EXCEEDED can mean a complexity/resource limit, not just a large image. Factory/network failures and fatal runtime exceptions can be ordinary Errors. decoder.versions reports the linked libheif and libde265 versions at runtime. Safety and Scope ---------------- decoder.cpp uses heif_context_get_security_limits BEFORE reading input. It retains the upstream default security fields, tightening image area to 40,000,000 pixels, any tracked allocation to 192 MiB, and tracked libheif memory to 512 MiB (both upstream defaults are nonzero 4 GiB). Primary signalled dimensions are checked against 8192 per side and 40 MP before pixel decode. Actual decoded plane dimensions, format and stride are checked again before the JS output allocation and row copy. libheif also checks coded HEVC dimensions and passes pixel limits into current libde265. WASM linear memory is capped at 768 MiB, starts at 32 MiB, and may grow. Tracked libheif memory is NOT a bound on all codec allocations, JS memory or browser process memory. Large valid images can fail on constrained devices. The parent worker must enforce cancellation and a wall-clock deadline, plus repeat its own output validation. Isolation is not a substitute for fixes. Cropping, rotation and mirroring encoded by the HEIF container are applied by libheif. Only the primary still is returned; auxiliary images, burst frames, animation, depth and HDR gain maps are not returned. Output is 8-bit RGBA with libheif's default color conversion. This is not a lossless/HDR-preserving or color-proofing workflow. Arbitrary ICC profiles are not color-managed by a separate ICC engine. Upstream's Emscripten libde265 backend disables deblocking and SAO filters for speed; that existing upstream quality tradeoff is retained. Strict decode refuses damaged inputs instead of deliberately returning partial pixels. Broader device/profile coverage still needs ongoing testing. Versions and Security Review ---------------------------- Reviewed against upstream release/advisory pages on 2026-10-30 UTC: libheif v1.23.7, commit 12a259a5882dd99d45ec5fc77e296a3b620d1e2e https://github.com/strukturag/libheif/releases/tag/v1.23.7 https://github.com/strukturag/libheif/security/advisories libde265 v1.1.3, commit ba62bf4cfb3242f3bf0a45617ff09e35236e4d82 https://github.com/strukturag/libde265/releases/tag/v1.1.3 https://github.com/strukturag/libde265/security/advisories libheif 1.23.7 is the security release covering, among others, derived-image decode budgets (GHSA-48p4-gj45-w9v6), excessive coded pictures (GHSA-2g6j-vhr6-6wrj), repeated transformations (GHSA-7qm8-hj35-269r), and quadratic sequence-table parsing (GHSA-74mj-m37q-h3q9). libde265 1.1.3 includes the prior 1.0.17/1.1.1/1.1.2 security fixes and is the stated patched version for GHSA-vm5w-mrw5-q329. These were the latest releases when checked, not a guarantee of future vulnerability-free operation. Recheck advisories before each release. No stale decoder binary from an npm package was used. Source and Replacement ---------------------- The two *-source.tar.gz files in this directory are complete upstream git archives of the pinned commits, with libheif/ and libde265/ root directories. They preserve upstream licenses and notices, including those for unused sample programs. decoder.cpp and decoder.mjs are the complete adapter source. libheif-no-embind.patch is the only upstream modification: it removes the automatic broad Emscripten bindings, using this adapter's explicit C ABI instead. It does not modify codec, parser, security-limit or color-conversion code. build-heic.mjs is a distributed copy of scripts/build-heic.mjs, the build recipe. Keep all these files and licenses available wherever the JS/WASM is distributed. To replace the library, rebuild the source or substitute a compatible decoder.mjs and its sibling assets. The app calls this public module interface; there is no private-pointer dependency, signature check, or DRM preventing replacement. Preserve notices and source/relink rights for modified versions. Do not impose restrictions on reverse engineering needed to debug modifications to this library. This notice is not a legal compliance or patent certification. HEVC patent obligations can be distinct from copyright licenses. Rebuild ------- Prerequisites: Node, Python 3, Git, CMake 3.31.10, Ninja 1.13.0, and Emscripten 6.0.13 (emscripten-releases build eb132757325583b787fe9414b09afea9442b72ad). SDK manager revision used: d7b4130688bfa348392b921ab04015aa15c4f154. Compiler/runtime source: https://github.com/emscripten-core/emscripten/tree/6.0.13 SDK manager: https://github.com/emscripten-core/emsdk Toolchain packages are build prerequisites, not shipped browser dependencies. 1. Create an empty temporary build directory and extract both source archives into it. Their directories must be named libheif and libde265. 2. Provision the pinned SDK in a separate temporary directory. On Windows the build used existing Python 3.12 and Node, plus portable PyPI cmake and ninja: py -3.12 -m pip install --only-binary=:all: --target cmake==3.31.10 ninja==1.13.0 git clone https://github.com/emscripten-core/emsdk.git git -C checkout d7b4130688bfa348392b921ab04015aa15c4f154 py -3.12 /emsdk.py install releases-eb132757325583b787fe9414b09afea9442b72ad-64bit This installs only inside ; no global install or persistent PATH is required. The Windows compiler archive is about 701 MB; expanded temporary sources/toolchain/caches require several GB. First-use runtime compilation can take many minutes. Use a practical resource budget. 3. Supply an isolated Emscripten config file through EM_CONFIG containing: LLVM_ROOT = '/upstream/bin' BINARYEN_ROOT = '/upstream' NODE_JS = '' 4. Set process-local EMSDK=, EMSDK_PYTHON=, HEIC_BUILD_ROOT=, CMAKE=, NINJA=, and optionally EMCC_CORES=2. Ensure Python is available in this process's PATH (CMake invokes emcc's launcher). On Windows the portable CMake executable is /cmake/data/bin/cmake.exe, not its Python console-script wrapper; Ninja is /bin/ninja.exe. 5. In the repository run node scripts/build-heic.mjs. For a standalone download, set HEIC_OUTPUT_DIR to this decoder source directory and run its build-heic.mjs. The recipe builds static libraries, applies the supplied patch, copies full licenses, links heic-wasm.mjs/heic-wasm.wasm, and copies itself beside the output. All flags are explicit in the recipe. There are two library compile jobs, LTO, C++ exceptions, no filesystem, no dynamic execution, no pthread WASM, and no HEVC encoder. 6. Run node public/vendor/heic/smoke-test.mjs [...]. See FIXTURES.txt for fixture provenance, sanitizing commands and expectations. Then test the real application worker in target browsers under production CSP. Rebuilds may vary byte-for-byte with host/toolchain; validate behavior and regenerate distribution hashes rather than assuming reproducibility. Licenses -------- libheif and libde265 are LGPL-3.0-or-later. The complete upstream COPYING documents, including both LGPLv3 and GPLv3 text, are in licenses/. The adapter sources and build recipe are LGPL-3.0-or-later, without warranty. Emscripten JS/runtime glue: MIT or University of Illinois/NCSA, full text in licenses/emscripten-LICENSE.txt. Runtime musl code: licenses/musl-COPYRIGHT.txt. LLVM libc++, libc++abi, libunwind, compiler-rt and LLVM libc portions: Apache-2.0 WITH LLVM-exception and historical notices in their full LICENSE files in licenses/. Unused runtime components' licenses may be included too. Doug Lea's dlmalloc 2.8.6 is dedicated to the public domain under CC0; the full CC0 text is in licenses/CC0-1.0.txt, with Emscripten modifications covered by its own license. The original source is system/lib/dlmalloc.c in the pinned Emscripten source tree. libde265 MD5 implementation notice (also preserved in its source archive): Author: Alexander Peslyak, better known as Solar Designer This software was written by Alexander Peslyak in 2001. No copyright is claimed, and the software is hereby placed in the public domain. In case this attempt to disclaim copyright and place the software in the public domain is deemed null and void, then the software is Copyright (c) 2001 Alexander Peslyak and it is hereby released to the general public under the following terms: Redistribution and use in source and binary forms, with or without modification, are permitted. There's ABSOLUTELY NO WARRANTY, express or implied.