png-moon

    PNG encoder/decoder written in MoonBit

    png
    image
    Download zip
    Version
    0.1.2
    License
    Apache-2.0
    Last updated
    9 days ago
    Downloads
    74

    #TryAndRun-TvT/png-moon

    基于 MoonBit 的 PNG 编解码库。 纯 MoonBit 实现;唯一第三方依赖是已实测过的 moonbit-community/flate(压缩层)。

    license

    状态:解码(Identity / Expand)、编码(Identity 口径)、容器 / PLTE / tRNS / Adam7 / 16bit 均已落地,七层外部真值在守。已发布到 registry (mooncakes 落地页 为准), 装得上、解得开、编得出都在干净模块里验过。

    支持的后端:只有 wasm-gc(默认)与 wasm(MVP)。 两个后端跑同一批测试与 同一条 CLI 自检。moon.mod 的 supported_targets 里还登记了 js——那是本仓 门禁宿主(L4/L5 编码产物要落成文件)的构建目标,不是对外承诺的后端: 不提供调用方式、不保证可用、也不随文档承诺任何行为。

    #能做什么

    能力说明
    解码 Identity任意合法位深 / 颜色类型,含 Adam7 隔行;输出文件里写的通道形态与打包字节
    解码 Expand调色板 / 1·2·4bit / tRNS 合成 → 恒 8bit RGBA;16bit 取高字节
    编码 Identitydecode 的结果原样写回;五种行滤波 + 自适应选型
    两条压缩后端Flate(默认,真实压缩)/ Stored(逐字节等于 CPython compressobj(0))
    容器写出PLTE / tRNS(调色板 alpha 表、灰度 / 真彩指定透明色)、Adam7 七趟编码
    错误面解码 6 类 + 编码 7 类,按「调用方该做什么」分类,tag() 是稳定契约

    不做(显式拒绝,不产错结果):色彩管理(iCCP / gAMA 的解释)、APNG、流式解码、 16bit 的 strip_16 / PACKSWAP / BGR 变换、Expand 自动回转原形态、压缩率追平 zlib。

    #快速开始

    安装工具链:

    curl -fsSL https://cli.moonbitlang.cn/install/unix.sh | bash export PATH="$HOME/.moon/bin:$PATH"

    装本库(依赖会自动带上 moonbit-community/flate):

    moon add TryAndRun-TvT/png-moon

    # 在你的 moon.pkg 里引用:库在 lib/ 包,模块根是空包(README 文档测试宿主) import { "TryAndRun-TvT/png-moon/lib" @png, }

    最小调用形态:

    // 解码:Identity 拿到原始位深 / 通道;Expand 拿到 8bit RGBA
    match decode(png) {
    Ok(img) => img.pixels // width / height / color / depth / pixels
    Err(e) => e.tag() // "bad_chunk:crc_mismatch" / "truncated" / "limit:pixels" …
    }

    // 编码:默认 Flate;要逐字节可复算就显式 Stored
    match encode(img) { Ok(bytes) => .. Err(e) => e.tag() .. }
    match encode(img, options=EncodeOptions::new(compression=Stored)) { .. }

    // 调色板图必须显式给 PLTE:Identity 口径下 decode 不返回调色板
    match encode(img, options=EncodeOptions::new(palette=Some(plte), trns=Some(alpha))) { .. }

    两条默认值需要知道:

    • compression 默认 Flate:产物字节会变(体积通常小一个量级)。要指纹 / 金丝雀 / 逐字节对照,就显式传 compression=Stored。
    • interlace 默认 None,且不打算换:Adam7 只在「边下边显示」有收益, 要的人显式传 interlace=Adam7。

    #最小用例

    ///|
    test "解码一张 1x1 RGBA8 PNG" {
    let 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"
    // 文档测试宿主(模块根包)不 import 库,故这里只验字节层面的不变量;
    // 真正跑 decode 的用例在 lib/decode_test.mbt(黑盒,黄金值来自 Pillow/CPython)。
    inspect(png.length(), content="70")
    inspect(png[0], content="b'\\x89'")
    inspect(png[12], content="b'\\x49'")
    }

    ///|
    test "编解码调用形态(伪代码,真用例见 lib/decode_test.mbt / lib/expand_test.mbt / lib/encode_test.mbt)" {
    // 解出来的 DecodedImage 是:宽 / 高 / 颜色类型 / 位深 / 紧凑像素字节
    // match @lib.decode(png, options? = DecodeOptions::new(mode=Identity)) {
    // Ok(img) => img.pixels // 行优先、无 filter 字节;Identity 下是原始打包字节
    // Err(e) => e.tag() // 稳定标签,如 "bad_chunk:crc_mismatch"
    // }
    //
    // DecodeOptions::new(mode=Expand) 的契约:输出恒为 8bit RGBA
    // (调色板查 PLTE,tRNS 合成 alpha,低位深按 v*255/(2^n-1) 展开)。
    //
    // 编回去(Identity 口径进、Identity 口径出):
    // match @lib.encode(img) {
    // Ok(png) => png // signature + IHDR + [PLTE] + [tRNS] + IDAT + IEND
    // // IDAT 默认是 flate 真实压缩;要逐字节可复算就传 Stored
    // Err(e) => e.tag() // "bad_dimensions" / "bad_pixel_length" /
    // // "bad_palette:*" / "bad_trns:*" / "bad_pixel_value"
    // }
    //
    // 调色板图要显式给 PLTE(Identity 口径下 decode 拿不到它):
    // EncodeOptions::new(palette=Some(plte), trns=Some(alpha_table))
    // 要「产物可复算」(指纹 / 金丝雀 / 逐字节对照):EncodeOptions::new(compression=Stored)
    // 渐进式图:EncodeOptions::new(interlace=Adam7)(默认仍是 None,且不打算换)
    inspect(1, content="1")
    }

    这个 mbt check 块会被 moon test 真编译、真运行(README.mbt.md 是符号链接)。 它只用内置类型——文档测试宿主是模块根的空包,不 import 本库。

    #构建与运行

    moon build lib # 编译库包 moon build cmd/main --release # CLI(默认 wasm-gc) moon build cmd/main --target wasm --release # wasm MVP 后端 moon run cmd/main # CLI 自检(Identity + Expand + Adam7 + 往返) moon test # 单元 / 文档测试 bash scripts/gates.sh # 一键门禁(本地唯一闸门) bash scripts/wasm-backend.sh # 第二后端轴:wasm 的 check/test/自检 + 外部真值 bash scripts/release-check.sh # 发布前:归档面 / 干净环境 / 归档内真实编解码

    本仓不挂 push / PR CI:.cnb.yml 只保留 vscode 开发环境的工具链初始化, 提交前请在本地跑 bash scripts/gates.sh。各阶段守什么、哪些开关能跳过, 见工程约定与配置台账。

    #文档

    文档内容
    项目总览现状、API 契约、边界、七层真值、门禁、复跑命令、演进史
    规格与移植方案目标与非目标、分层与文件职责、上游映射、生态选型
    执行方案与验收真值任务包、chunk 顺序约束、里程碑 U/V、L1–L7 真值细节
    工程约定与配置台账配置来源、门禁阶段、工具链、逐文件处置
    解码与编码落地实测探针、flate 接入、Identity、Expand、编码 / 容器 / Adam7
    压缩性能与尺子实测真实压缩、体积 / 耗时、默认值换挡、尺子的重建
    验收完备性与发布实测往返四关、坏图生成器、16bit、完成度复核、wasm 后端

    完整导航与全量清单:docs/README.md。

    #项目结构

    . ├── moon.mod # MoonBit 模块配置 ├── moon.pkg # 模块根空包:README 文档测试宿主(不写库代码) ├── lib/ # 库包(公共 API 外壳;实现细节见 lib/internal/) ├── cmd/main/ # 可执行包:CLI 自检 ├── cmd/{conformance,encode,roundtrip,bench}/ # 门禁宿主(不进发布归档) ├── probe/ # 可行性探针(证据面,非库代码) ├── docs/ # 文档(入口 = docs/README.md) ├── scripts/ # 构建 / 门禁脚本 ├── AGENTS.md # AI 协作代理指南(贡献前必读) ├── README.mbt.md -> README.md # 文档测试入口(符号链接) └── LICENSE # Apache-2.0

    布局纪律:模块根只放元数据;库代码一律 lib/,实现细节 lib/internal/(不要建 src/); 模块根空 moon.pkg 只为文档测试存在;*_test.mbt(黑盒)与 *_wbtest.mbt(白盒)不可混用。

    #工程约定与贡献

    • 硬性约定:AGENTS.md——密钥 / Token 永不入库、不提交本地配置与构建产物、 文档死链零容忍、README 只保留结论与出处。
    • 门禁:bash scripts/gates.sh(本地唯一闸门;跳过阶段不算通过)。
    • 问题与建议:仓库 Issue。
    • Git 钩子(可选):git config core.hooksPath .githooks(个人本地配置,仓库不代设)。

    #致谢与许可

    • 本库许可:Apache-2.0。
    • 工程配置参考同组织 base64-moon (参考形态,不复制业务脚本)。