Skip to content

Commit 8f3572f

Browse files
authored
mcpplibs.mwrap.libpng 1.6.43: libpng-m (libpng as C++23 modules) (#592)
1 parent 0469654 commit 8f3572f

6 files changed

Lines changed: 196 additions & 0 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Add mcpplibs.mwrap.libpng (libpng-m 1.6.43)
2+
3+
## Form
4+
5+
- Third-party upstream [pnggroup/libpng](https://github.com/pnggroup/libpng), tag `v1.6.43`, PNG Reference Library
6+
License version 2. The tarball (`fecc95b4…f1a03a`) is the one compat.libpng 1.6.43 already pins.
7+
- Form A external mwrap module repository: [mcpplibs/libpng-m](https://github.com/mcpplibs/libpng-m), package
8+
`mcpplibs.mwrap.libpng`. The version is the upstream one (first release of the identity, no revision suffix);
9+
tag series `mwrap-${version}`.
10+
- Implementation ownership: the library is compat.libpng's (over compat.zlib 1.3.2; its configuration is upstream's
11+
`scripts/pnglibconf.h.prebuilt`, which is sufficient). The wrapper vendors no upstream code. It retains
12+
upstream's README and license under `upstream/libpng/`, with checksums in `upstream/documents.json`. No
13+
implementation is linked twice.
14+
15+
## Modules
16+
17+
- `mcpplibs.mwrap.libpng` exports, by `export using`, every type (104), struct tag (12) and function (246) that
18+
png.h declares in this configuration. `tools/gen_exports.py` generates the list from the pinned headers: it
19+
evaluates png.h's `#if` conditionals with the build's pnglibconf.h and normalises the `PNG_EXPORT*`,
20+
`PNG_CALLBACK` and `PNG_FUNCTION` declaration macros.
21+
- `mcpplibs.mwrap.libpng.macros`, in `mcpplibs::mwrap::libpng::macros`, holds:
22+
- the constants, as `inline constexpr` of each macro's type;
23+
- the 177 pnglibconf.h options as `constexpr bool` (generated partition `:options`);
24+
- the `PNG_IMAGE_*` size computations, the Adam7 `PNG_PASS_*` helpers, `PNG_get_*` and `png_composite*`, as
25+
constexpr functions with the macros' results and types;
26+
- `png_check_sig`, `png_image_write_get_memory_size`, `png_jmpbuf` (it returns the `jmp_buf&` for the caller's
27+
`setjmp`) and `png_libpng_ver`.
28+
- `<mwrap/libpng/macros.hpp>` (= png.h) remains for `#if` tests of the options and for macro-form code.
29+
- Not exported: `png_err` and `png_set_strip_error_numbers`, which this configuration does not declare, and
30+
`png_get_io_chunk_name`, which is `PNG_REMOVED`.
31+
32+
`tools/check_exports.py` fails when a declaration is not exported, or when one of the 492 macros the headers define
33+
here is neither mapped, nor the name of an exported function (`png_get_uint_32` …), nor excluded with a reason. It
34+
was negative-checked by removing an export and a mapping.
35+
36+
## Verification
37+
38+
Wrapper CI runs on four rows, all required: Linux GCC 16.1, Linux LLVM 23.1.3, macOS LLVM and Windows LLVM. It
39+
covers:
40+
41+
- the build;
42+
- the gtest suites: the API through the module, the constants at compile time, and the header mixed with the import,
43+
with every function-like macro compared with its mapping;
44+
- the export audit;
45+
- conformance: libpng's `pngtest.c`, ported to C++ against the module (`examples/pngtest`). It runs on upstream's
46+
`tests/pngtest-all` cases, on `-m` over all of `contrib/pngsuite` and `contrib/testpngs`, and on `--strict` per
47+
pngsuite image: 96 cases and 167 images. Each case's exit status and output must equal those of upstream's
48+
`pngtest.c`, built unchanged from the same tarball against png.h. Every crasher is an expected failure, and the
49+
bas*.png zero-sample counts match `contrib/pngsuite/README`.
50+
51+
The templates job scaffolds the default `image` template as an installed package, builds it with `--strict` and runs
52+
it.
53+
54+
Local checks, with the CI-pinned mcpp 2026.10.26.1 and `MCPP_INDEX_MIRROR=GLOBAL`:
55+
56+
- `bash tests/run_members.sh libpng-image` installs the real release archive through the candidate index. The
57+
consumer runs a simplified-API round trip and a low-level read with transforms. It also reads a truncated file:
58+
png_error longjmps to the caller's setjmp on `png_jmpbuf`. Result: `test result ok`.
59+
- `python3 tests/mwrap/check_installed_template.py --package mcpplibs.mwrap.libpng@1.6.43` reports "Verified real
60+
indexed archive installation and unmodified template consumer".
61+
62+
## Mirror and updates
63+
64+
This change has no `mcpp-res` access. The descriptor therefore uses the GitHub release URL as a plain string, and CN
65+
users fall back to it. A maintainer can add the GitCode mirror with identical bytes. `extensions.autoupdate` follows
66+
the repository's own `mwrap-${version}` tags.
67+
68+
## Motivation
69+
70+
openmui.image encodes and decodes PNG with libpng's simplified API. Through this module its implementation unit
71+
imports `mcpplibs.mwrap.libpng` instead of including png.h.

‎.github/workflows/validate.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,6 +1618,11 @@ jobs:
16181618
shell: bash
16191619
run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.quickjs_ng@0.17.0
16201620

1621+
- name: Installed libpng wrapper archive and actual default image template
1622+
if: contains(format(' {0} ', env.MEMBERS), ' libpng-image ') && matrix.toolchain == 'default'
1623+
shell: bash
1624+
run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.libpng@1.6.43
1625+
16211626
- name: Installed Godot wrapper archive and actual standalone math template
16221627
if: contains(format(' {0} ', env.MEMBERS), ' godot-math ') && matrix.toolchain == 'default'
16231628
shell: bash

‎mcpp.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ members = [
1919
"tests/mwrap/md4c-render",
2020
"tests/mwrap/lua-embed",
2121
"tests/mwrap/quickjs-embed",
22+
"tests/mwrap/libpng-image",
2223
"tests/mwrap/grpc-cpu",
2324
"tests/mwrap/grpc-tools",
2425
"tests/mwrap/godot-math",

‎pkgs/mwrap/libpng.lua‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
-- Immutable upstream-aligned mwrap source release (libpng-m mwrap-1.6.43).
2+
-- The CN mirror is added by a maintainer with mcpp-res access; until then CN
3+
-- users fall back to the GitHub release (docs/cn-mirror.md).
4+
package = {
5+
spec = "1",
6+
namespace = "mcpplibs.mwrap",
7+
name = "libpng",
8+
description = "libpng-m: libpng (the PNG reference library) as C++23 modules",
9+
licenses = { "Apache-2.0" },
10+
repo = "https://github.com/mcpplibs/libpng-m",
11+
type = "package",
12+
extensions = {
13+
autoupdate = {
14+
tag = "mwrap-${version}",
15+
url = "https://github.com/mcpplibs/libpng-m/releases/download/${tag}/libpng-m-${version}.tar.gz",
16+
},
17+
},
18+
xpm = {
19+
linux = {
20+
["1.6.43"] = {
21+
url = "https://github.com/mcpplibs/libpng-m/releases/download/mwrap-1.6.43/libpng-m-1.6.43.tar.gz",
22+
sha256 = "44b62160e99e9febf908da87774447376c847f304674c4e3654c9bc2ab8aeb85",
23+
},
24+
},
25+
macosx = {
26+
["1.6.43"] = {
27+
url = "https://github.com/mcpplibs/libpng-m/releases/download/mwrap-1.6.43/libpng-m-1.6.43.tar.gz",
28+
sha256 = "44b62160e99e9febf908da87774447376c847f304674c4e3654c9bc2ab8aeb85",
29+
},
30+
},
31+
windows = {
32+
["1.6.43"] = {
33+
url = "https://github.com/mcpplibs/libpng-m/releases/download/mwrap-1.6.43/libpng-m-1.6.43.tar.gz",
34+
sha256 = "44b62160e99e9febf908da87774447376c847f304674c4e3654c9bc2ab8aeb85",
35+
},
36+
},
37+
},
38+
}

‎tests/mwrap/libpng-image/mcpp.toml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "libpng-image"
3+
version = "1.6.43"
4+
description = "Consume the immutable libpng wrapper: a simplified-API round trip, a low-level read with transforms, the longjmp error path, the mapped macros"
5+
6+
[indices]
7+
# Use the complete candidate built-in index prepared by tests/run_members.sh.
8+
# An explicit empty table overrides the legacy workspace redirect.
9+
10+
[dependencies]
11+
"mcpplibs.mwrap.libpng" = "1.6.43"
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#include <csetjmp> // setjmp, for png_jmpbuf
2+
3+
import std;
4+
import mcpplibs.mwrap.libpng;
5+
6+
using namespace mcpplibs::mwrap::libpng::macros;
7+
8+
namespace {
9+
struct Source {
10+
const png_byte* data;
11+
std::size_t size, at = 0;
12+
};
13+
void read_from(png_structp png, png_bytep out, std::size_t n) {
14+
auto* s = static_cast<Source*>(png_get_io_ptr(png));
15+
if (s->at + n > s->size) png_error(png, "truncated");
16+
std::memcpy(out, s->data + s->at, n);
17+
s->at += n;
18+
}
19+
// Expand to RGBA and read every row; png_error longjmps back here.
20+
int read_rgba(png_structp png, png_infop info, png_bytep rows, png_uint_32 stride) {
21+
if (setjmp(png_jmpbuf(png))) return -1;
22+
png_read_info(png, info);
23+
png_set_expand(png);
24+
png_set_filler(png, 0xff, PNG_FILLER_AFTER);
25+
png_read_update_info(png, info);
26+
for (png_uint_32 y = 0; y < png_get_image_height(png, info); ++y) png_read_row(png, rows + y * stride, nullptr);
27+
png_read_end(png, nullptr);
28+
return 0;
29+
}
30+
} // namespace
31+
32+
int main() {
33+
static_assert(PNG_SIMPLIFIED_READ_SUPPORTED && PNG_SETJMP_SUPPORTED && PNG_LIBPNG_VER == 10643);
34+
// A grey ramp written with the simplified API, to memory.
35+
constexpr png_uint_32 w = 9, h = 4;
36+
png_image image{};
37+
image.version = PNG_IMAGE_VERSION;
38+
image.width = w;
39+
image.height = h;
40+
image.format = PNG_FORMAT_GRAY;
41+
std::vector<png_byte> grey(PNG_IMAGE_SIZE(image));
42+
for (std::size_t i = 0; i < grey.size(); ++i) grey[i] = static_cast<png_byte>(i * 7);
43+
png_alloc_size_t size = 0;
44+
if (!png_image_write_get_memory_size(image, size, 0, grey.data(), 0, nullptr)) return 1;
45+
std::vector<png_byte> file(size);
46+
if (!png_image_write_to_memory(&image, file.data(), &size, 0, grey.data(), 0, nullptr)) return 2;
47+
if (!png_check_sig(file.data(), 8)) return 3;
48+
// Read it back with the low-level reader, expanded to RGBA.
49+
std::vector<png_byte> rgba(w * h * 4);
50+
Source source{file.data(), size};
51+
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
52+
png_infop info = png_create_info_struct(png);
53+
png_set_read_fn(png, &source, read_from);
54+
png_set_gray_to_rgb(png);
55+
const int status = read_rgba(png, info, rgba.data(), w * 4);
56+
png_destroy_read_struct(&png, &info, nullptr);
57+
if (status != 0) return 4;
58+
for (std::size_t p = 0; p < grey.size(); ++p)
59+
if (rgba[p * 4] != grey[p] || rgba[p * 4 + 2] != grey[p] || rgba[p * 4 + 3] != 255) return 5;
60+
// A truncated file: png_error in the read callback, longjmp to read_rgba.
61+
Source cut{file.data(), size / 2};
62+
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
63+
info = png_create_info_struct(png);
64+
png_set_read_fn(png, &cut, read_from);
65+
const int failed = read_rgba(png, info, rgba.data(), w * 4);
66+
png_destroy_read_struct(&png, &info, nullptr);
67+
if (failed != -1) return 6;
68+
std::println("libpng-image: ok (libpng {}, {} bytes for {}x{})", png_libpng_ver, size, w, h);
69+
return 0;
70+
}

0 commit comments

Comments
 (0)