#!/usr/bin/env python3 """ amalgamate.py -- generate webpdec.h, a single-file WebP decoder. Collapses libwebp's *decode + demux* path (lossy VP8, lossless VP8L, the VP8X extended container, the ALPH alpha chunk, and the WebPDemux / WebPAnimDecoder animation+metadata API) into one stb-style header, built as portable C99 with all SIMD, threads and host file I/O disabled. Self-contained: with no arguments it clones libwebp at the pinned commit (into ./libwebp, git-ignored), writes the empty config.h that forces the generic-C path, applies the few single-translation-unit symbol renames itself, and writes ./webpdec.h. A fresh checkout + `python3 amalgamate.py` just works. python3 amalgamate.py [OUTPUT.h] Output layout: #ifndef WEBPDEC_H <- public API (types/decode/demux) ...declarations... #endif #ifdef WEBPDEC_IMPLEMENTATION #define HAVE_CONFIG_H <- forces the portable C path (SIMD off) ...all decode+demux .c files + their internal headers, inlined once each... #endif """ import os import re import subprocess import sys HERE = os.path.dirname(os.path.abspath(__file__)) SRC = os.path.join(HERE, "libwebp") UPSTREAM_URL = "https://chromium.googlesource.com/webm/libwebp" PINNED_COMMIT = "b43b2caa710c0c997c066cb32c7fea1391fad70a" # v1.6.0 VERSION = "1.6.0" # ---- the decoder-only, portable-C object set (LIBWEBPDECODER_OBJS + DEMUX_OBJS, # minus every *_sse2/_sse41/_neon/_msa/_mips* SIMD translation unit) ------- DEC = [ "src/dec/alpha_dec.c", "src/dec/buffer_dec.c", "src/dec/frame_dec.c", "src/dec/idec_dec.c", "src/dec/io_dec.c", "src/dec/quant_dec.c", "src/dec/tree_dec.c", "src/dec/vp8_dec.c", "src/dec/vp8l_dec.c", "src/dec/webp_dec.c", ] DSP = [ "src/dsp/alpha_processing.c", "src/dsp/cpu.c", "src/dsp/dec.c", "src/dsp/dec_clip_tables.c", "src/dsp/filters.c", "src/dsp/lossless.c", "src/dsp/rescaler.c", "src/dsp/upsampling.c", "src/dsp/yuv.c", ] UTILS = [ "src/utils/bit_reader_utils.c", "src/utils/color_cache_utils.c", "src/utils/filters_utils.c", "src/utils/huffman_utils.c", "src/utils/palette.c", "src/utils/quant_levels_dec_utils.c", "src/utils/random_utils.c", "src/utils/rescaler_utils.c", "src/utils/thread_utils.c", "src/utils/utils.c", ] # Demux + animation: pure RIFF chunk parser (demux.c) and the frame compositor # WebPAnimDecoder (anim_decode.c). anim_decode reuses WebPDecode and calls no # WebPMux* function, so no muxer/encoder TU is needed. DEMUX = ["src/demux/demux.c", "src/demux/anim_decode.c"] SOURCES = DEC + DSP + UTILS + DEMUX # demux.h declares both the WebPDemux and WebPAnimDecoder public APIs and pulls # in decode.h + mux_types.h, so it covers the whole public surface. PUBLIC = ["src/webp/types.h", "src/webp/decode.h", "src/webp/demux.h"] # A handful of file-local symbols share a name across these TUs, which only # matters once everything is one translation unit. We give the colliding copy # a private prefix by wrapping just that source file's body in #define/#undef -- # leaving the upstream tree pristine (so a fresh clone regenerates identically). RENAME_PREFIX = "webpdec_priv_" RENAMES = { "src/utils/thread_utils.c": ["ChangeState"], "src/utils/quant_levels_dec_utils.c": ["clip_8b"], "src/demux/demux.c": [ "MemBuffer", "InitMemBuffer", "MemDataSize", "RemapMemBuffer", "ParseVP8X", ], } INCLUDE_RE = re.compile(r'^\s*#\s*include\s+"([^"]+)"\s*(?://.*|/\*.*)?$') def ensure_libwebp(): """Clone libwebp at the pinned commit if it isn't already present.""" if os.path.isfile(os.path.join(SRC, "src/dec/webp_dec.c")): return sys.stderr.write("libwebp not found; cloning %s @ %s ...\n" % (UPSTREAM_URL, PINNED_COMMIT[:12])) try: subprocess.check_call(["git", "clone", "--depth", "1", UPSTREAM_URL, SRC]) head = subprocess.check_output( ["git", "-C", SRC, "rev-parse", "HEAD"]).decode().strip() if head != PINNED_COMMIT: subprocess.check_call( ["git", "-C", SRC, "fetch", "--depth", "1", "origin", PINNED_COMMIT]) subprocess.check_call( ["git", "-C", SRC, "checkout", "--detach", PINNED_COMMIT]) except Exception as e: sys.exit("error: could not fetch libwebp (%s).\nClone it manually:\n" " git clone %s %s\n git -C %s checkout %s" % (e, UPSTREAM_URL, SRC, SRC, PINNED_COMMIT)) def write_config_h(): """Empty config.h: with HAVE_CONFIG_H defined this forces libwebp's generic-C path (no WEBP_HAVE_* => no SIMD), even on SIMD-capable hosts.""" with open(os.path.join(SRC, "src/webp/config.h"), "w") as f: f.write("/* intentionally empty: forces libwebp's generic-C path */\n") emitted = set() def resolve(inc, curfile): cands = [] if inc.startswith("src/"): cands.append(os.path.join(SRC, inc)) cands.append(os.path.join(os.path.dirname(curfile), inc)) cands.append(os.path.join(SRC, inc)) for c in cands: c = os.path.normpath(c) if os.path.isfile(c): return c return None def inline(path, out): path = os.path.normpath(path) if path in emitted: return emitted.add(path) rel = os.path.relpath(path, SRC) names = RENAMES.get(rel.replace(os.sep, "/")) out.append("/* >>> %s */\n" % rel) if names: for nm in names: out.append("#define %s %s%s\n" % (nm, RENAME_PREFIX, nm)) with open(path, "r") as f: for line in f: m = INCLUDE_RE.match(line) if m: tgt = resolve(m.group(1), path) if tgt is not None: inline(tgt, out) continue out.append(line) if names: for nm in names: out.append("#undef %s\n" % nm) def main(out_path): ensure_libwebp() write_config_h() out = [] out.append("""\ /* * webpdec.h -- single-file WebP decoder (stb-style). * * libwebp's decode + demux path -- lossy VP8, lossless VP8L, the VP8X extended * container, the ALPH alpha chunk, plus the demux/animation API (WebPDemux, * WebPAnimDecoder) -- amalgamated into one file and built as portable C99 with * all SIMD, threads and host file I/O disabled. The public API is the standard * libwebp one (WebPDecodeRGBA, WebPDemux*, WebPAnimDecoder*). * * Usage: in exactly ONE translation unit, * #define WEBPDEC_IMPLEMENTATION * #include "webpdec.h" * Everywhere else, just #include "webpdec.h". Decode-only (no encoder/muxer). * Link with -lm. * * Generated by amalgamate.py from libwebp %s (see UPSTREAM_COMMIT). * BSD-style license (see LICENSE) + PATENTS grant. Copyright Google LLC. * DO NOT EDIT BY HAND -- rerun `python3 amalgamate.py`. */ """ % VERSION) out.append("#ifndef WEBPDEC_H\n#define WEBPDEC_H\n") for h in PUBLIC: inline(os.path.join(SRC, h), out) out.append("#endif /* WEBPDEC_H */\n\n") out.append("#ifdef WEBPDEC_IMPLEMENTATION\n") out.append("#define HAVE_CONFIG_H\n") for c in SOURCES: inline(os.path.join(SRC, c), out) out.append("#endif /* WEBPDEC_IMPLEMENTATION */\n") text = "".join(out) with open(out_path, "w") as f: f.write(text) # refresh the recorded upstream revision with open(os.path.join(HERE, "UPSTREAM_COMMIT"), "w") as f: f.write("libwebp %s (v%s)\nupstream: %s\n" "decode + demux amalgamation (SIMD/threads/file-IO disabled) " "via amalgamate.py\n" % (PINNED_COMMIT, VERSION, UPSTREAM_URL)) print("wrote %s (%d lines, %d KB) from libwebp %s" % (os.path.relpath(out_path, HERE), text.count("\n"), len(text) // 1024, PINNED_COMMIT[:12])) if __name__ == "__main__": out = sys.argv[1] if len(sys.argv) > 1 else os.path.join(HERE, "webpdec.h") main(out)