libwebp 1.6.0's decode + demux path (lossy VP8, lossless VP8L, VP8X container, ALPH alpha, and the WebPDemux/WebPAnimDecoder animation API) amalgamated into one stb-style header, built as portable C99 with all SIMD, threads and host file I/O disabled. amalgamate.py regenerates webpdec.h from a pinned, auto-cloned libwebp checkout; tests/run.sh checks decode output bit-exactly against reference libwebp. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
webpdec.h — single-file WebP decoder
A standalone, dependency-free WebP decoder in one C header, in the spirit of the stb libraries. It is libwebp's decode + demux path — not a reimplementation — amalgamated into a single file and built as portable C99 with all SIMD, threads and host file I/O disabled. So it is as correct as upstream libwebp (it is libwebp) while staying trivial to drop into any project.
Handles everything real WebP files use:
- Lossy (VP8) and lossless (VP8L)
- The VP8X extended container
- The ALPH alpha chunk (transparency on lossy images)
- Animation + metadata via the demux API (
WebPDemux,WebPAnimDecoder)
Usage
In exactly one translation unit:
#define WEBPDEC_IMPLEMENTATION
#include "webpdec.h"
Everywhere else, just #include "webpdec.h". The public API is the standard
libwebp decode + demux API.
Still image:
int w, h;
uint8_t *rgba = WebPDecodeRGBA(data, len, &w, &h); /* R,G,B,A, top-to-bottom */
if (rgba) {
/* ... w*h*4 bytes ... */
WebPFree(rgba);
}
Animation (all frames, with blending/disposal handled for you):
WebPData wd = { data, len };
WebPAnimDecoderOptions opt; WebPAnimDecoderOptionsInit(&opt);
opt.color_mode = MODE_RGBA;
WebPAnimDecoder *dec = WebPAnimDecoderNew(&wd, &opt);
WebPAnimInfo info; WebPAnimDecoderGetInfo(dec, &info); /* canvas, frame_count */
while (WebPAnimDecoderHasMoreFrames(dec)) {
uint8_t *rgba; int timestamp_ms;
WebPAnimDecoderGetNext(dec, &rgba, ×tamp_ms); /* rgba owned by dec */
}
WebPAnimDecoderDelete(dec);
WebPGetInfo, WebPDecodeRGB/BGRA/ARGB, the advanced WebPDecode +
WebPDecoderConfig path (including decode-time downscaling via
config.options.use_scaling), and the full WebPDemux* chunk/metadata
iterator are all available — see the declarations near the top of webpdec.h.
Link with -lm (libwebp uses pow() for gamma tables). That's the only
dependency.
What's not included
Encoding and the mux (assembly/write) API — this is decode-only, by design.
Portability
webpdec.h defines HAVE_CONFIG_H and bakes in an empty config.h, which is
libwebp's own switch for "no detected SIMD": every architecture dispatcher falls
back to the reference C path. This works even on SIMD-capable targets — e.g. on
ARMv8 where the toolchain defines __ARM_NEON, NEON still stays off. No
pthreads, no <stdio.h> file calls. Tested decoding bit-exactly against
upstream libwebp on x86-64 and aarch64.
Repo layout
webpdec.h the single-file library (committed; this is all you need)
amalgamate.py regenerates webpdec.h from libwebp
UPSTREAM_COMMIT the exact pinned libwebp revision
LICENSE, PATENTS upstream BSD-style license + patent grant
tests/ test_decode.c, test_demux.c, run.sh
libwebp/ upstream checkout (git-ignored; fetched on demand)
Regenerating
python3 amalgamate.py # writes webpdec.h (+ refreshes UPSTREAM_COMMIT)
With no libwebp/ present it shallow-clones libwebp at the pinned commit, so a
fresh checkout just works — no submodules, no manual steps. The amalgamation is
LIBWEBPDECODER_OBJS + DEMUX_OBJS minus every *_sse2/_neon/_msa/... SIMD
translation unit; the few single-TU symbol clashes are namespaced by the script
(the upstream tree is left pristine, so regeneration is byte-for-byte
reproducible).
Tests
sh tests/run.sh
With Pillow installed it generates lossy /
lossless / alpha / animated samples and checks decode output bit-exactly
against reference libwebp; otherwise pass it .webp paths to decode.
License
BSD-style, identical to libwebp. See LICENSE and PATENTS. Copyright Google
LLC and the WebM project authors; this file only mechanically concatenates their
sources.