Wuffs' base-interface dispatch calls the format functions through function
pointers of a different type (void* self vs. the decoder's own type), which
is undefined behavior: clang -fsanitize=function (part of
-fsanitize=undefined, so BASISU_SAN=ON with clang) aborts on every load,
and CFI builds would trap. No behavior change; Wuffs is unmodified.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner directive. With BASISU_USE_WUFFS_LDR_LOADER=1, print_png_info() uses only
get_ldr_image_info(): it prints the true dimensions (even beyond the decode cap,
plus a "can't be loaded" line when they exceed BASISU_WUFFS_LDR_MAX_DIM) or
"Failed parsing PNG file" if Wuffs rejects the header. pv_png::get_png_info and
print_png_loader_info() are no longer used for -info in that configuration;
channels/bit depth/color type/tRNS/gAMA lines are not printed (the adapter stays
thin). With =0 the function body is public master's, unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bounds the memory a small corrupt/truncated file claiming a huge image can make the loader allocate
before Wuffs fails. Images within the 16384 dimension cap that need a larger work buffer (the largest
progressive JPEGs, ~>9.4K^2 CMYK 4:4:4 / ~>10.9K^2 YCbCr 4:4:4, and 8-bit RGBA PNGs right at 16384^2)
are now rejected with the work-buffer error. Still overridable with -DBASISU_WUFFS_LDR_MAX_WORKBUF_BYTES.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With BASISU_USE_WUFFS_LDR_LOADER=1, -info on a PNG keeps the pvpngreader
header details and adds one line from get_ldr_image_info(): the true
dimensions, whether they exceed the loader's decode cap, or that Wuffs
rejects the header (so -info no longer reports a PNG as fine that the
loader then refuses). When pvpngreader's own header parser fails (its 32768
max dimension, ancillary chunk CRCs) the loader's view is still printed, so
e.g. a 65535x65535 PNG header reports its true dimensions ("exceeds the
maximum loadable dimension") instead of only "Failed parsing PNG file".
=0 is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
load_png(mem), load_tga(file), load_jpg(file/mem) and load_qoi(mem/file) now
go through a thin Wuffs C-API adapter (wuffs_load_ldr) when
BASISU_USE_WUFFS_LDR_LOADER is 1 (the default, basisu_enc.h). Everything
built on them (load_image, load_image_hdr's LDR paths, basis_compressor,
the basisu tool, KTX2 Studio's WASM encoder) switches automatically. The
old pvpngreader / read_tga() / jpgd / qoi.h decode bodies stay in #else
branches (=0 gives today's code; qoi.h is still used to write .QOI).
The adapter only converts to basisu's RGBA image, enforces the caps and
reports errors. Wuffs' accept/reject and pixels are final: no fallback,
no fixups. decode_image_config reads the header, then a 0 width/height
("empty image"), width/height > BASISU_WUFFS_LDR_MAX_DIM (default
basist::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION = 16384) or a work buffer >
BASISU_WUFFS_LDR_MAX_WORKBUF_BYTES (default 3 GiB here, the measured worst
case at 16384^2; tradeoff documented in basisu_enc.h) is refused BEFORE any
pixel or work buffer is allocated. All memory is basisu's own
(image::try_alloc - added here - and try_resize, initialized); img is only
replaced on success; nothing asserts on file data.
get_ldr_image_info() is a header-only query (no cap) that returns the
true dimensions, even of files beyond the decode cap.
Wuffs is declared here (wuffs-v0.4.c included without
WUFFS_IMPLEMENTATION, same config header) and compiled in
encoder/3rdparty/basisu_wuffs.cpp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
basisu_wuffs.cpp defines WUFFS_IMPLEMENTATION and #includes the unmodified
encoder/3rdparty/wuffs-v0.4.c with all warnings silenced in that TU only
(MSVC #pragma warning(push, 0)/pop; gcc/clang diagnostic push/ignored/pop).
basisu_wuffs_config.h holds the Wuffs configuration (modules BASE core/
interfaces/pixconv, ADLER32, CRC32, DEFLATE, ZLIB, PNG, JPEG, TARGA, QOI;
RGBA_NONPREMUL destination only; WUFFS_CONFIG__AVOID_CPU_ARCH = no SIMD), shared
with basisu_enc.cpp's declarations-only include (next commit). With
BASISU_USE_WUFFS_LDR_LOADER=0 the TU compiles to nothing.
Added to every build that compiles the encoder: root CMakeLists.txt
(native, -m32, WASI, Python module) with a BASISU_USE_WUFFS_LDR_LOADER option
(default ON), webgl/encoder/CMakeLists.txt (all Emscripten variants, same
option), and encoder_lib.vcxproj/.filters (basisu.sln: basisu, examples).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
encoder/3rdparty/wuffs-v0.4.c is the verbatim release/c/wuffs-v0.4.c from
github.com/google/wuffs at commit 4be865da258c (WUFFS_VERSION_STRING
"0.4.0-alpha.12+4048.20261020"), sha256
458066457347bd6d9bc3c906aa8292bea4488577727569ddd9436fdd89d5f04d.
Wuffs is dual-licensed Apache-2.0 OR MIT; basis_universal elects
Apache-2.0 (same as the rest of the repo, LICENSES/Apache-2.0.txt):
added to .reuse/dep5 and the README's 3rd-party sentence. Wuffs ships no
NOTICE file. Not compiled yet (the next commits add the build files and
the loader adapter).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Newer Emscripten (6.0.x) no longer includes mainScriptUrlOrBlob in its default INCOMING_MODULE_JS_API,
so the threaded encoder loaded via importScripts() in ktx2_studio/encode-worker.js ignored it and the
pthread pool never came up (the worker hangs while loading). Add it explicitly (Emscripten's default list
plus mainScriptUrlOrBlob), as in the development tree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the default DYNAMIC_EXECUTION=1, embind generates its invokers at
runtime with new Function(). A Content-Security-Policy without
'unsafe-eval' blocks this, so the transcoder and encoder fail to start
with an EvalError.
Add two CMake options to webgl/transcoder and webgl/encoder:
- NO_DYNAMIC_EXECUTION (default OFF) adds -s DYNAMIC_EXECUTION=0.
- EMBIND_AOT (default ON, only used with NO_DYNAMIC_EXECUTION) adds
-s EMBIND_AOT=1. This generates the invokers at build time, so
embind calls stay as fast as with the default build.
The default build does not change. The .wasm output is byte-identical
in all configurations; only the JS glue changes.
Fixes#464
The cPackUASTC* values are members of an anonymous enum. embind's
constant() registers a constant against the type of its value, and no
binding exists for an anonymous enum type. embind therefore waits for
that type forever, and the constants never appear on the module.
Module.cPackUASTCLevelDefault and the others are undefined in the
shipped basis_encoder.js.
Cast the values to uint32_t so that they bind as plain numbers.
EMBIND_AOT=1 makes this a link error ("Missing binding for type"), so
this change is also necessary to build the encoder with that flag.
The sanity check on a level's m_uncompressed_byte_length (>= 2 GiB)
logged "Invalid level offset (too large)", which names the wrong field.
Correct it to "Invalid level uncompressed byte length (too large)".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
--slow uses `make -j1` (one file at a time, the classic manual `make`)
instead of the default `make -j$(nproc)`. Useful for readable output or
constrained machines; parallel remains the default.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
For a more thorough clean, --clean now runs `make clean` (removing the
build outputs and object files via the existing build system) before
deleting CMakeCache.txt/CMakeFiles and reconfiguring.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convenience script (place: webgl/build.sh) that runs emcmake/cmake/make
in webgl/transcoder/build and webgl/encoder/build. Stops on the first
error (set -euo pipefail), builds both by default or a named target
(transcoder/encoder), supports --clean and -D... cmake pass-through, is
runnable from any directory, and auto-sources emsdk if emcmake is not on
PATH.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BasisThumbProvider::GetThumbnail() allocates rgbBuf with malloc() but
freed it with delete, which is a mismatched-allocator (and delete on a
void*) undefined behavior. Use free() to match the allocation, as is
already done for the sibling 'data' buffer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regenerate contrib/previewers/lib/basisu_transcoder.cpp from the current
transcoder sources with the single_file_transcoder combine.py script,
replacing a long-stale copy. KTX2 and Zstd are enabled.
Only the Basis Universal sources are amalgamated; Zstd is kept as an
ordinary #include "../zstd/zstd.h" (via combine.py's -k option) rather
than being inlined, and zstd/zstd.c is compiled and linked separately,
so Zstd is neither touched nor duplicated.
Update the previewers project to build the current (C++17) transcoder:
- set LanguageStandard to stdcpp17 (the transcoder uses inline variables
and other C++17 features);
- define NOMINMAX (the Windows shell headers, included before the
transcoder, otherwise define min/max macros that break the
std::numeric_limits<>::max() calls);
- add the zstd directory to the include paths and compile
..\..\..\zstd\zstd.c as a separate compilation unit.
The DLL builds for Debug/Release, x64 and Win32. The previewer still
only registers .basis files; enabling KTX2/Zstd just makes the
transcoder compile cleanly out of the box.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two things prevented the transcoder from compiling with
BASISD_SUPPORT_KTX2 set to 0:
- ktx2_image_level_info was defined inside the #if BASISD_SUPPORT_KTX2
block, but dds_transcoder::get_image_level_info() takes it by
reference and is compiled unconditionally. Move the struct ahead of
that block so it is always defined; it has no KTX2-specific members.
- The "#include ../zstd/zstd.h" was nested inside #if BASISD_SUPPORT_KTX2,
but the Zstd decompressor (zstd_decompress()) is compiled whenever
BASISD_SUPPORT_KTX2_ZSTD is set, independent of KTX2 (Zstd is also used
by other codecs). With KTX2 off but Zstd on, the ZSTD_* symbols were
referenced without the header being included. Gate the include on
BASISD_SUPPORT_KTX2_ZSTD alone, matching where the symbols are used.
BASISD_SUPPORT_KTX2_ZSTD still defaults to 1 (unchanged). The default
build (KTX2 and Zstd both enabled) is unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>