webpdec.h: single-file WebP decoder
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>
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# Upstream libwebp checkout -- fetched on demand by amalgamate.py
|
||||||
|
/libwebp/
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
*.o
|
||||||
|
*.raw
|
||||||
|
*.ppm
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
# webpdec.h IS committed (it's the deliverable, regenerate with amalgamate.py)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
Copyright (c) 2010, Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in
|
||||||
|
the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
* Neither the name of Google nor the names of its contributors may
|
||||||
|
be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
"These implementations" means the copyrightable works that implement the WebM
|
||||||
|
codecs distributed by Google as part of the WebM Project.
|
||||||
|
|
||||||
|
Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge,
|
||||||
|
royalty-free, irrevocable (except as stated in this section) patent license to
|
||||||
|
make, have made, use, offer to sell, sell, import, transfer, and otherwise
|
||||||
|
run, modify and propagate the contents of these implementations of WebM, where
|
||||||
|
such license applies only to those patent claims, both currently owned by
|
||||||
|
Google and acquired in the future, licensable by Google that are necessarily
|
||||||
|
infringed by these implementations of WebM. This grant does not include claims
|
||||||
|
that would be infringed only as a consequence of further modification of these
|
||||||
|
implementations. If you or your agent or exclusive licensee institute or order
|
||||||
|
or agree to the institution of patent litigation or any other patent
|
||||||
|
enforcement activity against any entity (including a cross-claim or
|
||||||
|
counterclaim in a lawsuit) alleging that any of these implementations of WebM
|
||||||
|
or any code incorporated within any of these implementations of WebM
|
||||||
|
constitute direct or contributory patent infringement, or inducement of
|
||||||
|
patent infringement, then any patent rights granted to you under this License
|
||||||
|
for these implementations of WebM shall terminate as of the date such
|
||||||
|
litigation is filed.
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# webpdec.h — single-file WebP decoder
|
||||||
|
|
||||||
|
A standalone, dependency-free **WebP decoder** in one C header, in the spirit of
|
||||||
|
the [stb](https://github.com/nothings/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:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define WEBPDEC_IMPLEMENTATION
|
||||||
|
#include "webpdec.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
Everywhere else, just `#include "webpdec.h"`. The public API is the standard
|
||||||
|
libwebp decode + demux API.
|
||||||
|
|
||||||
|
Still image:
|
||||||
|
|
||||||
|
```c
|
||||||
|
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):
|
||||||
|
|
||||||
|
```c
|
||||||
|
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
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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
|
||||||
|
sh tests/run.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
With [Pillow](https://python-pillow.org/) 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.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
libwebp b43b2caa710c0c997c066cb32c7fea1391fad70a (v1.6.0)
|
||||||
|
upstream: https://chromium.googlesource.com/webm/libwebp
|
||||||
|
decode + demux amalgamation (SIMD/threads/file-IO disabled) via amalgamate.py
|
||||||
+209
@@ -0,0 +1,209 @@
|
|||||||
|
#!/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)
|
||||||
Executable
+59
@@ -0,0 +1,59 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Build and run the webpdec.h tests. If Pillow is available, samples are
|
||||||
|
# generated and decode output is checked bit-exactly against reference libwebp;
|
||||||
|
# otherwise the tests just decode whatever .webp files are passed/present.
|
||||||
|
#
|
||||||
|
# sh tests/run.sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.." # repo root
|
||||||
|
CC=${CC:-cc}
|
||||||
|
CFLAGS="-O2 -std=c99 -Wall -I."
|
||||||
|
TMP=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TMP"' EXIT
|
||||||
|
|
||||||
|
# Generate samples with Pillow if present (covers lossy, lossless, alpha,
|
||||||
|
# odd dimensions, and a multi-frame animation).
|
||||||
|
HAVE_PIL=0
|
||||||
|
if python3 - "$TMP" >/dev/null 2>&1 <<'PY'
|
||||||
|
import sys
|
||||||
|
from PIL import Image
|
||||||
|
d = sys.argv[1]
|
||||||
|
w, h = 137, 83
|
||||||
|
img = Image.new("RGBA", (w, h))
|
||||||
|
px = img.load()
|
||||||
|
for y in range(h):
|
||||||
|
for x in range(w):
|
||||||
|
px[x, y] = ((x*255)//w, (y*255)//h, (x*y) % 256, (x+y) % 256)
|
||||||
|
img.save(d+"/lossless.webp", lossless=True)
|
||||||
|
img.save(d+"/lossy.webp", quality=90)
|
||||||
|
frames = [Image.new("RGBA", (48, 32), (i*50 % 256, 80, 200, 255)) for i in range(5)]
|
||||||
|
frames[0].save(d+"/anim.webp", save_all=True, append_images=frames[1:],
|
||||||
|
duration=[100, 120, 140, 160, 180], loop=0, lossless=True)
|
||||||
|
PY
|
||||||
|
then HAVE_PIL=1; fi
|
||||||
|
|
||||||
|
echo "== build =="
|
||||||
|
$CC $CFLAGS -o "$TMP/test_decode" tests/test_decode.c -lm
|
||||||
|
$CC $CFLAGS -o "$TMP/test_demux" tests/test_demux.c -lm
|
||||||
|
|
||||||
|
if [ "$HAVE_PIL" = 1 ]; then
|
||||||
|
echo "== decode (still) =="
|
||||||
|
for k in lossless lossy; do
|
||||||
|
"$TMP/test_decode" "$TMP/$k.webp" "$TMP/$k.raw"
|
||||||
|
python3 - "$TMP/$k.webp" "$TMP/$k.raw" <<'PY'
|
||||||
|
import sys
|
||||||
|
from PIL import Image
|
||||||
|
ref = Image.open(sys.argv[1]).convert("RGBA").tobytes()
|
||||||
|
mine = open(sys.argv[2], "rb").read()
|
||||||
|
assert ref == mine, "%s: pixels differ from reference libwebp!" % sys.argv[1]
|
||||||
|
print(" %s: bit-exact vs reference libwebp" % sys.argv[1].split('/')[-1])
|
||||||
|
PY
|
||||||
|
done
|
||||||
|
echo "== demux + animation =="
|
||||||
|
"$TMP/test_demux" "$TMP/anim.webp"
|
||||||
|
else
|
||||||
|
echo "(Pillow not found -- decoding any .webp passed as args)"
|
||||||
|
for f in "$@"; do "$TMP/test_decode" "$f"; done
|
||||||
|
fi
|
||||||
|
echo "ALL TESTS PASSED"
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/* test harness: decode a .webp to RGBA, report, dump a PPM for eyeballing */
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define WEBPDEC_IMPLEMENTATION
|
||||||
|
#include "webpdec.h"
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
if (argc < 2) { fprintf(stderr, "usage: %s file.webp [out.ppm]\n", argv[0]); return 2; }
|
||||||
|
FILE *f = fopen(argv[1], "rb");
|
||||||
|
if (!f) { perror("open"); return 1; }
|
||||||
|
fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET);
|
||||||
|
unsigned char *buf = malloc(n);
|
||||||
|
if (fread(buf, 1, n, f) != (size_t)n) { perror("read"); return 1; }
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
int w = 0, h = 0;
|
||||||
|
if (!WebPGetInfo(buf, n, &w, &h)) { fprintf(stderr, "not a webp / bad header\n"); return 1; }
|
||||||
|
printf("WebPGetInfo: %dx%d\n", w, h);
|
||||||
|
|
||||||
|
uint8_t *rgba = WebPDecodeRGBA(buf, n, &w, &h);
|
||||||
|
if (!rgba) { fprintf(stderr, "decode FAILED\n"); return 1; }
|
||||||
|
printf("decoded: %dx%d, RGBA (R,G,B,A top-to-bottom)\n", w, h);
|
||||||
|
|
||||||
|
/* sample a few pixels */
|
||||||
|
long center = ((long)h/2 * w + w/2) * 4;
|
||||||
|
printf("center px RGBA = %d %d %d %d\n",
|
||||||
|
rgba[center], rgba[center+1], rgba[center+2], rgba[center+3]);
|
||||||
|
|
||||||
|
/* checksum + alpha presence */
|
||||||
|
unsigned long sum = 0; int has_alpha = 0;
|
||||||
|
for (long i = 0; i < (long)w*h; i++) {
|
||||||
|
sum += rgba[i*4] + rgba[i*4+1] + rgba[i*4+2];
|
||||||
|
if (rgba[i*4+3] != 255) has_alpha = 1;
|
||||||
|
}
|
||||||
|
printf("rgb checksum=%lu non-opaque-alpha=%s\n", sum, has_alpha ? "yes" : "no");
|
||||||
|
|
||||||
|
if (argc >= 3) { /* .raw => raw RGBA dump; else opaque PPM */
|
||||||
|
const char *p = argv[2];
|
||||||
|
size_t L = 0; while (p[L]) L++;
|
||||||
|
FILE *o = fopen(argv[2], "wb");
|
||||||
|
if (L >= 4 && p[L-4]=='.' && p[L-3]=='r' && p[L-2]=='a' && p[L-1]=='w') {
|
||||||
|
fwrite(rgba, 4, (size_t)w*h, o);
|
||||||
|
} else {
|
||||||
|
fprintf(o, "P6\n%d %d\n255\n", w, h);
|
||||||
|
for (long i = 0; i < (long)w*h; i++) fwrite(&rgba[i*4], 1, 3, o);
|
||||||
|
}
|
||||||
|
fclose(o);
|
||||||
|
printf("wrote %s\n", argv[2]);
|
||||||
|
}
|
||||||
|
WebPFree(rgba);
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/* exercises the demux + animation API folded into the single header */
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define WEBPDEC_IMPLEMENTATION
|
||||||
|
#include "webpdec.h"
|
||||||
|
|
||||||
|
static unsigned char* slurp(const char* p, size_t* n){
|
||||||
|
FILE* f=fopen(p,"rb"); if(!f) return 0;
|
||||||
|
fseek(f,0,SEEK_END); long L=ftell(f); fseek(f,0,SEEK_SET);
|
||||||
|
unsigned char* b=malloc(L); if(fread(b,1,L,f)!=(size_t)L){} fclose(f); *n=L; return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc,char**argv){
|
||||||
|
size_t n; unsigned char* buf=slurp(argv[1],&n);
|
||||||
|
if(!buf){ printf("no file\n"); return 1; }
|
||||||
|
|
||||||
|
WebPData data = { buf, n };
|
||||||
|
|
||||||
|
/* --- demux: report canvas + frame/loop count --- */
|
||||||
|
WebPDemuxer* dmux = WebPDemux(&data);
|
||||||
|
if(!dmux){ printf("WebPDemux failed\n"); return 1; }
|
||||||
|
unsigned cw = WebPDemuxGetI(dmux, WEBP_FF_CANVAS_WIDTH);
|
||||||
|
unsigned ch = WebPDemuxGetI(dmux, WEBP_FF_CANVAS_HEIGHT);
|
||||||
|
unsigned nf = WebPDemuxGetI(dmux, WEBP_FF_FRAME_COUNT);
|
||||||
|
unsigned lp = WebPDemuxGetI(dmux, WEBP_FF_LOOP_COUNT);
|
||||||
|
unsigned fl = WebPDemuxGetI(dmux, WEBP_FF_FORMAT_FLAGS);
|
||||||
|
printf("demux: canvas=%ux%u frames=%u loop=%u flags=0x%x\n", cw,ch,nf,lp,fl);
|
||||||
|
WebPDemuxDelete(dmux);
|
||||||
|
|
||||||
|
/* --- animation: decode every frame via WebPAnimDecoder --- */
|
||||||
|
WebPAnimDecoderOptions opt;
|
||||||
|
WebPAnimDecoderOptionsInit(&opt);
|
||||||
|
opt.color_mode = MODE_RGBA;
|
||||||
|
WebPAnimDecoder* adec = WebPAnimDecoderNew(&data, &opt);
|
||||||
|
if(!adec){ printf("WebPAnimDecoderNew failed\n"); return 1; }
|
||||||
|
WebPAnimInfo info;
|
||||||
|
WebPAnimDecoderGetInfo(adec, &info);
|
||||||
|
printf("anim: %ux%u frames=%u loops=%u bgcolor=0x%08x\n",
|
||||||
|
info.canvas_width, info.canvas_height, info.frame_count,
|
||||||
|
info.loop_count, info.bgcolor);
|
||||||
|
int prev = 0, got = 0;
|
||||||
|
while (WebPAnimDecoderHasMoreFrames(adec)) {
|
||||||
|
uint8_t* frgba; int ts;
|
||||||
|
if (!WebPAnimDecoderGetNext(adec, &frgba, &ts)) { printf("GetNext failed\n"); break; }
|
||||||
|
long c = ((long)info.canvas_height/2*info.canvas_width + info.canvas_width/2)*4;
|
||||||
|
printf(" frame %d ts=%dms (+%dms) centerRGBA=%d,%d,%d,%d\n",
|
||||||
|
got, ts, ts-prev, frgba[c],frgba[c+1],frgba[c+2],frgba[c+3]);
|
||||||
|
prev = ts; got++;
|
||||||
|
}
|
||||||
|
printf("decoded %d/%u frames -> %s\n", got, info.frame_count,
|
||||||
|
(got==(int)info.frame_count)?"OK":"MISMATCH");
|
||||||
|
WebPAnimDecoderDelete(adec);
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user