Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Preserve upstream document bytes and original whitespace on every platform.
upstream/** -text -whitespace
152 changes: 152 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
mcpp:
name: mcpp (${{ matrix.os }}, ${{ matrix.toolchain }})
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-24.04, toolchain: 'gcc@16.1.0' }
- { os: ubuntu-24.04, toolchain: 'llvm@23.1.3' }
- { os: ubuntu-24.04-arm, toolchain: 'gcc@16.1.0' }
- { os: macos-15, toolchain: 'llvm@23.1.3' }
- { os: windows-2025, toolchain: 'llvm@23.1.3' }
runs-on: ${{ matrix.os }}
timeout-minutes: 60
defaults:
run:
shell: bash
env:
XLINGS_NON_INTERACTIVE: 1
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Mwrap identity and bilingual README contract
run: python3 tools/check_mwrap_release.py

- name: Install Xlings (Linux/macOS)
if: runner.os != 'Windows'
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/refs/heads/main/tools/other/quick_install.sh | bash
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"

- name: Install Xlings (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
irm https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.ps1 | iex
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

- name: Install project tools (.xlings.json)
run: |
for attempt in 1 2 3; do
xlings update > /dev/null 2>&1 || true
if xlings install; then break; fi
[ "$attempt" = 3 ] && exit 1
sleep 30
done
mcpp --version

- name: Select the toolchain
run: |
name="${{ matrix.toolchain }}"
mcpp toolchain install "${name%@*}" "${name#*@}"
mcpp toolchain default "$name"

- name: Build the modules (default, and with the demux and mux features)
run: |
mcpp build
mcpp build --features demux,mux

- name: Tests (the decode and encode API, sharpyuv, the macros, header mixing, pinned bytes)
run: mcpp test

- name: Tests, release profile
run: mcpp test --profile release

- name: Animation tests (the demux and mux features)
working-directory: tests/anim
run: |
mcpp test
mcpp test --profile release

- name: Export audit (the generated modules are fresh; every declaration and macro of the public headers)
run: python3 tools/check_exports.py

# Upstream's cwebp and dwebp from the same release, built by its own
# makefile.unix with the system C compiler: needs make (not on Windows).
- name: Conformance (examples/webpcodec byte-identical to upstream cwebp and dwebp)
if: runner.os != 'Windows'
run: python3 tools/run_conformance.py

- name: The example builds (Windows)
if: runner.os == 'Windows'
working-directory: examples/webpcodec
run: mcpp build

# The Web: wasm32-emscripten (emsdk from xlings, through mcpp), the tests
# under node.
web:
name: mcpp (web, wasm32-emscripten)
runs-on: ubuntu-24.04
timeout-minutes: 60
defaults:
run:
shell: bash
env:
XLINGS_NON_INTERACTIVE: 1
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install Xlings
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/refs/heads/main/tools/other/quick_install.sh | bash
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"

- name: Install project tools (.xlings.json)
run: |
for attempt in 1 2 3; do
xlings update > /dev/null 2>&1 || true
if xlings install; then break; fi
[ "$attempt" = 3 ] && exit 1
sleep 30
done
mcpp --version

- name: Install emsdk
run: mcpp toolchain install emsdk 6.0.9

- name: Tests under node
run: mcpp test --toolchain emsdk@6.0.9 --target wasm32-emscripten

- name: Tests under node, release profile
run: mcpp test --toolchain emsdk@6.0.9 --target wasm32-emscripten --profile release

- name: Animation tests under node (the demux and mux features)
working-directory: tests/anim
run: |
mcpp test --toolchain emsdk@6.0.9 --target wasm32-emscripten
mcpp test --toolchain emsdk@6.0.9 --target wasm32-emscripten --profile release
49 changes: 49 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Release
on:
push:
tags: ["mwrap-*"]
permissions:
contents: read
jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
actions: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Check tag, upstream version and bilingual entry pages
run: python3 tools/check_mwrap_release.py --tag "$GITHUB_REF_NAME"
- name: Require successful source CI at this exact commit
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
result=$(gh run list --repo "$GITHUB_REPOSITORY" --workflow ci.yml --commit "$GITHUB_SHA" --limit 1 --json status,conclusion --jq '.[0] | .status == "completed" and .conclusion == "success"')
test "$result" = true || { echo 'No successful source CI at the tag commit'; exit 1; }
- name: Build immutable source asset and publish
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#mwrap-}"
repo="${GITHUB_REPOSITORY#*/}"
asset="${repo}-${version}.tar.gz"
git archive --format=tar.gz --prefix="${repo}-${version}/" "$GITHUB_SHA" > "$RUNNER_TEMP/$asset"
(cd "$RUNNER_TEMP" && sha256sum "$asset" > "$asset.sha256")
if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo 'Release already exists; refusing to change immutable assets'; exit 1
fi
extra=()
if [[ "$version" == *-rc* ]]; then extra+=(--prerelease); fi
gh release create "$GITHUB_REF_NAME" "$RUNNER_TEMP/$asset" "$RUNNER_TEMP/$asset.sha256" \
--repo "$GITHUB_REPOSITORY" --verify-tag --title "$repo $version" \
--notes "Module wrapper for upstream $version. New mwrap package identities become installable after mcpp-index publication; see the bilingual README for the tested support scope." "${extra[@]}"
31 changes: 31 additions & 0 deletions .github/workflows/templates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Actual project templates
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: templates-${{ github.ref }}
cancel-in-progress: true
env:
MCPP_VERSION: "2026.10.27.1"
jobs:
templates:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Install the pinned mcpp
shell: bash
run: |
archive="mcpp-${MCPP_VERSION}-linux-x86_64.tar.gz"
curl -L -fsS -o "$archive" "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/$archive"
tar -xzf "$archive"
echo "$PWD/mcpp-${MCPP_VERSION}-linux-x86_64/bin" >> "$GITHUB_PATH"
- name: Actual scaffold, strict build and run of the default template (image)
run: python3 tools/test_template.py --toolchain gcc@16.1.0

- name: Actual scaffold, strict build and run of the animation template (the demux and mux features)
run: python3 tools/test_template.py --toolchain gcc@16.1.0 --template animation
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
target
compile_commands.json
mcpp.lock
.mcpp/
.xlings/
doctor.log
__pycache__/
5 changes: 5 additions & 0 deletions .xlings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"workspace": {
"xim:mcpp": "2026.10.27.1"
}
}
14 changes: 14 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
libwebp-m: C++ module wrapper
Copyright (c) 2026 mcpp-community contributors

The wrapper contributions are licensed under the Apache License, Version 2.0.
Upstream code, copied interfaces and dependencies retain their original
licenses and copyright notices. libwebp is distributed under the BSD
3-Clause license, with Google's additional patent grant:

Copyright (c) 2010, Google Inc. All rights reserved.

Its original README, license (COPYING), patent grant (PATENTS) and authors
are retained in upstream/libwebp/. The codec itself is built by
compat.libwebp from the same release. Provenance and checksums are recorded
in upstream/documents.json.
110 changes: 108 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,109 @@
# libwebp-m
# libwebp

libwebp as C++23 modules for mcpp (`import mcpplibs.mwrap.libwebp;`). Work in progress.
> libwebp, the WebP image codec, through C++23 modules.

**English** · [简体中文](README.zh.md) · [Upstream](https://github.com/webmproject/libwebp) · [Releases](https://github.com/mcpplibs/libwebp-m/releases) · [mcpp](https://github.com/mcpp-community/mcpp) · [Index](https://github.com/mcpplibs/mcpp-index)

[![C++23](https://img.shields.io/badge/C%2B%2B-23-blue.svg)](https://en.cppreference.com/w/cpp/23)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![libwebp](https://img.shields.io/badge/libwebp-BSD--3--Clause-blue.svg)](upstream/libwebp/COPYING)
[![mcpp-community](https://img.shields.io/badge/mcpp--community-mwrap-blue)](https://github.com/mcpp-community)
[![CI](https://github.com/mcpplibs/libwebp-m/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mcpplibs/libwebp-m/actions/workflows/ci.yml)

libwebp 1.5.0 decodes and encodes WebP: lossy (VP8) and lossless (VP8L),
with alpha, still and animated. The upstream C API is unchanged:
`WebPGetInfo`, `WebPDecodeRGBA`, `WebPEncodeRGBA`, `WebPEncodeLosslessRGBA`,
`WebPEncode` with `WebPConfig` and `WebPPicture`, `WebPAnimEncoder` and
`WebPAnimDecoder` are the upstream globals of the headers. The codec is
`compat.libwebp` 1.5.0. The headers' `static inline` functions
(`WebPConfigInit`, `WebPPictureInit`, `WebPGetFeatures` …) are adapted under
the same names, and the macros are constants. On every CI run the bytes
written and the pixels read are those of upstream's own `cwebp` and `dwebp`,
and pinned hashes hold on every target, the Web included.

## Modules

- `import mcpplibs.mwrap.libwebp;`: decoding (`webp/decode.h`), encoding (`webp/encode.h`), `WebPData` and the animation enums (`webp/mux_types.h`), re-exporting the two below
- `import mcpplibs.mwrap.libwebp.sharpyuv;`: libsharpyuv, the sharp RGB to YUV conversion (`SharpYuvConvert` …)
- `import mcpplibs.mwrap.libwebp.macros;`: `WEBP_MAX_DIMENSION`, the ABI versions, `WEBP_ABI_IS_INCOMPATIBLE` … in `mcpplibs::mwrap::libwebp::macros`
- `import mcpplibs.mwrap.libwebp.demux;`: `WebPDemux`, `WebPAnimDecoder`, with the `demux` feature
- `import mcpplibs.mwrap.libwebp.mux;`: `WebPMux`, `WebPAnimEncoder`, with the `mux` feature

> More Modules API -> [Usage and API](docs/usage.md)

## Getting started

```sh
mcpp new pictures --template mcpplibs.mwrap.libwebp@1.5.0
cd pictures
mcpp run
```

The default template encodes a picture with alpha lossy at three qualities
and lossless, decodes each back to RGBA and prints its size and PSNR. The
`animation` template (`--template mcpplibs.mwrap.libwebp@1.5.0:animation`)
encodes frames with `WebPAnimEncoder` and plays them back with
`WebPAnimDecoder`.

```toml
[dependencies]
"mcpplibs.mwrap.libwebp" = "1.5.0"
```

```cpp
import std;
import mcpplibs.mwrap.libwebp;

int main() {
std::vector<std::uint8_t> rgba(64 * 64 * 4, 200); // a light grey, opaque square
std::uint8_t* webp = nullptr;
const std::size_t size = WebPEncodeRGBA(rgba.data(), 64, 64, 64 * 4, 80.f, &webp); // lossy, quality 80
int width = 0, height = 0;
std::uint8_t* pixels = WebPDecodeRGBA(webp, size, &width, &height);
std::println("{} bytes, {} x {}", size, width, height);
WebPFree(pixels);
WebPFree(webp);
}
```

| Feature | Effect |
|---|---|
| `demux` | libwebpdemux (compat.libwebp's `src/demux`) and the `demux` module: `WebPDemux*`, `WebPAnimDecoder*` |
| `mux` | libwebpmux (compat.libwebp's `src/mux`) and the `mux` module: `WebPMux*`, `WebPAnimEncoder*` |

Neither is on by default. Animated WebP needs `demux` to read and `mux` to
write.

## The codec

`compat.libwebp` builds the release's `src/dec`, `src/enc`, `src/dsp`,
`src/utils` and `sharpyuv` (and `src/demux`, `src/mux` with the features)
with SSE2 on x86-64 and NEON on arm64, SSE4.1 off, and without
`WEBP_USE_THREAD`: `thread_level` and `use_threads` are accepted and run on
the calling thread. Its output is deterministic: the same picture and
settings give the same bytes on Linux, macOS, Windows and wasm32-emscripten,
with GCC or LLVM, dev or release, and the same bytes as upstream's `cwebp`.
Details and limits: [docs/usage.md](docs/usage.md).

## Examples

- [examples/webpcodec](examples/webpcodec): decodes WebP to PPM/PAM, encodes PPM/PAM to WebP (lossy or lossless) and
prints a file's features, written against the module; [tools/run_conformance.py](tools/run_conformance.py) compares
it with upstream's `cwebp` and `dwebp`.

## Build and testing

Linux (x86_64 GCC and LLVM, arm64 GCC), macOS, Windows and wasm32-emscripten
(under node) CI build the modules and run the tests in the dev and release
profiles, with and without the features; the native rows also run the export
audit ([tools/check_exports.py](tools/check_exports.py): the generated modules
are fresh, every declaration exported, every macro mapped or excluded with its
reason) and, but on Windows, the conformance run against upstream's tools. A
separate job scaffolds both templates as an installed package and runs them.

## License

[Apache-2.0](LICENSE) for the wrapper, by mcpp-community; libwebp remains
BSD-3-Clause with Google's patent grant
([upstream/libwebp/COPYING](upstream/libwebp/COPYING),
[upstream/libwebp/PATENTS](upstream/libwebp/PATENTS)).
Loading
Loading