// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 The png-moon Authors
///|
/// 可执行入口:用一张**内联的最小 PNG** 跑一遍 `decode`,把摘要打到 stdout。
///
/// 为什么不是「读文件 → 解码」:MoonBit 的 core 没有文件读写包,`wasm-gc` 下也没有
/// 等价的标准 I/O;硬塞一个「读文件」会让 `build-and-run` 依赖宿主能力,
/// 而它要证明的只是「库在 wasm-gc 下能构建、能跑、能出正确结果」。
///
/// 四条自检,各压一条主路径:
/// ① `Identity`:1×1 RGBA8,像素 `ff0000ff`(与 `lib/decode_test.mbt` 同一张图);
/// ② `Expand`:8×2、2bit 调色板 + 4 字节 `tRNS`(与 `lib/expand_test.mbt` 的 `pal2_trns` 同一张图),
/// 展开后必须是 8 行 × 8 像素 × 4 通道,且第 1 个像素是 `00000000`(alpha=0 的那一项);
/// ③ 隔行(Adam7):1×1 的 `interlace=1` 图必须解出 `ff0000ff`(T11);
/// ④ 往返(encode):把 ① 解出的图编回去,再解一遍,像素必须一致(T7/T8)。
///
/// 自检失败时**退出码非零**——门禁据此判红。
fn main {
check_identity()
check_expand()
check_interlaced()
check_encode_roundtrip()
println(
"png-moon: 自检通过(Identity + Expand + Adam7 + encode 往返)",
)
}
///|
fn check_identity() -> Unit {
match @lib.decode(min_png()) {
Ok(img) => {
println(
"png-moon: [Identity] 1x1 RGBA8 → \{img.width}x\{img.height}, \{img.pixels.length()} 字节像素",
)
if img.pixels.length() != 4 {
println("!! 像素字节数应为 4")
abort("png-moon self-check failed")
}
}
Err(e) => {
println("!! [Identity] 解码失败 [\{e.tag()}]: \{e.message()}")
abort("png-moon self-check failed")
}
}
}
///|
fn check_expand() -> Unit {
let png : Bytes = pal_png()
match @lib.decode(png, options=@lib.DecodeOptions::new(mode=@lib.Expand)) {
Ok(img) => {
println(
"png-moon: [Expand] 8x2 2bit 调色板 → \{img.width}x\{img.height}, \{img.pixels.length()} 字节 RGBA",
)
// 8 像素 × 8 行 × 4 通道 = 256
if img.pixels.length() != 256 {
println("!! Expand 输出应为 256 字节 RGBA")
abort("png-moon self-check failed")
}
// Pillow 的第 1 个像素是 00000000(调色板第 0 项 + tRNS 第 0 字节 = 0)
if img.pixels[0] != b'\x00' || img.pixels[3] != b'\x00' {
println("!! Expand 首像素应为 00000000")
abort("png-moon self-check failed")
}
}
Err(e) => {
println("!! [Expand] 解码失败 [\{e.tag()}]: \{e.message()}")
abort("png-moon self-check failed")
}
}
}
///|
/// ③ 隔行:1×1 的 `interlace=1` 图(Adam7 的第 1 趟恰好覆盖这 1 个像素)。
/// 这条在 T11 之前会走 `unsupported:interlaced`,现在必须真的解出像素。
fn check_interlaced() -> Unit {
match @lib.decode(interlaced_png()) {
Ok(img) =>
if img.pixels != b"\xff\x00\x00\xff" {
println("!! [Adam7] 1x1 隔行图的像素应为 ff0000ff")
abort("png-moon self-check failed")
} else {
println("png-moon: [Adam7] 1x1 隔行图解出 ff0000ff")
}
Err(e) => {
println("!! [Adam7] 解码失败 [\{e.tag()}]: \{e.message()}")
abort("png-moon self-check failed")
}
}
}
///|
/// ④ 往返:把 Identity 解出的图 `encode` 回字节,再解一遍。
///
/// 这条自检的价值在于「跑在 wasm-gc 的 release 构建里」:编码路径要是有平台相关的
/// 行为(比如 `Buffer` 的容量增长),`moon test` 的调试构建不一定暴露。
fn check_encode_roundtrip() -> Unit {
let src = match @lib.decode(min_png()) {
Ok(img) => img
Err(e) => {
println("!! [encode] 前置解码失败 [\{e.tag()}]")
abort("png-moon self-check failed")
return
}
}
let png = match @lib.encode(src) {
@lib.Ok(b) => b
@lib.Err(e) => {
println("!! [encode] 编码失败 [\{e.tag()}]: \{e.message()}")
abort("png-moon self-check failed")
return
}
}
match @lib.decode(png) {
Ok(back) =>
if back.pixels != src.pixels {
println("!! [encode] 往返像素不一致")
abort("png-moon self-check failed")
} else {
println("png-moon: [encode] 往返一致(\{png.length()} 字节)")
}
Err(e) => {
println(
"!! [encode] 自己写的图读不回来 [\{e.tag()}]: \{e.message()}",
)
abort("png-moon self-check failed")
}
}
}
///|
/// 1×1 RGBA8 的**隔行**版本(`interlace=1`)。真值:CPython 现算 + Pillow 反向验证,
/// 见 `lib/decode_test.mbt` 的同名用例。
fn interlaced_png() -> Bytes {
b"\x89PNG\r\n\x1a\n\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x0dIDATx\xdac\xf8\xcf\xc0\xf0\x1f\x00\x05\x00\x01\xffV\xc7/\x0d\x00\x00\x00\x00IEND\xaeB`\x82"
}
///|
/// 8×2、2bit 调色板 + `tRNS`(`lib/expand_test.mbt` 的 `pal2_trns` 同一串字节)。
/// 真值出处:`python3 scripts/gen-expand-vectors.py`。
fn pal_png() -> Bytes {
// 逐字节写成转义串(而不是照抄十六进制串后又手改):`b"…"` 里不能出现裸的 `"` 与反斜杠不可控字符。
b"\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x00\x08\x00\x00\x00\x08\x02\x03\x00\x00\x00\xb9aV\x18\x00\x00\x00\x0cPLTE\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9b\xc0\x13\xdc\x00\x00\x00\x04tRNS\x00\xff\x80@@T\x8b\xac\x00\x00\x00\x0dIDATx\xdac\x90\x96f\xc0\x86\x00\x14X\x01\xb1\xaf@\xb3\x13\x00\x00\x00\x00IEND\xaeB`\x82"
}
///|
/// 1×1 RGBA8(像素 `ff0000ff`)的最小 PNG 字节。
///
/// 真值出处:`scripts/gen-png-vectors.py` 同款构造 + CPython `zlib.compress` 现算;
/// 同一串十六进制在 `lib/decode_test.mbt` 里被 Pillow 的输出反向验过。
fn min_png() -> Bytes {
b"\x89PNG\r\n\x1a\n\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x0dIDATx\xda\x63\xf8\xcf\xc0\xf0\x1f\x00\x05\x00\x01\xff\x56\xc7\x2f\x0d\x00\x00\x00\x00IEND\xae\x42\x60\x82"
}