From 88fd4e8c1bed067fe274a7debb8ce6e8758d0e79 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sat, 14 Nov 2026 17:26:14 +0900 Subject: [PATCH] =?UTF-8?q?mcpplibs.mwrap.libjpeg=5Fturbo=203.2.1:=20libjp?= =?UTF-8?q?eg-turbo-m=20(the=20TurboJPEG=20API=20=E2=80=94=20JPEG=20compre?= =?UTF-8?q?ssion,=20decompression,=20lossless=20transforms=20=E2=80=94=20a?= =?UTF-8?q?s=20a=20C++23=20module)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...2026-11-14-add-libjpeg-turbo-mwrap-plan.md | 43 ++++++++++++++ .github/workflows/validate.yml | 5 ++ CHANGELOG.md | 8 +++ mcpp.toml | 1 + pkgs/mwrap/libjpeg_turbo.lua | 52 +++++++++++++++++ tests/mwrap/check_installed_template.py | 1 + tests/mwrap/libjpeg-turbo-decode/mcpp.toml | 11 ++++ .../libjpeg-turbo-decode/tests/behavior.cpp | 57 +++++++++++++++++++ 8 files changed, 178 insertions(+) create mode 100644 .agents/docs/2026-11-14-add-libjpeg-turbo-mwrap-plan.md create mode 100644 pkgs/mwrap/libjpeg_turbo.lua create mode 100644 tests/mwrap/libjpeg-turbo-decode/mcpp.toml create mode 100644 tests/mwrap/libjpeg-turbo-decode/tests/behavior.cpp diff --git a/.agents/docs/2026-11-14-add-libjpeg-turbo-mwrap-plan.md b/.agents/docs/2026-11-14-add-libjpeg-turbo-mwrap-plan.md new file mode 100644 index 00000000..652927c3 --- /dev/null +++ b/.agents/docs/2026-11-14-add-libjpeg-turbo-mwrap-plan.md @@ -0,0 +1,43 @@ +# Add mcpplibs.mwrap.libjpeg_turbo (libjpeg-turbo-m 3.2.1) + +## Form + +- Third-party upstream [libjpeg-turbo/libjpeg-turbo](https://github.com/libjpeg-turbo/libjpeg-turbo), release 3.2.1, + under the IJG License and the Modified (3-clause) BSD License (zlib License for parts; the in-tree libspng is BSD + 2-Clause). +- Form A external mwrap module repository [mcpplibs/libjpeg-turbo-m](https://github.com/mcpplibs/libjpeg-turbo-m), + package `mcpplibs.mwrap.libjpeg_turbo`, version 3.2.1, tag `mwrap-3.2.1`; release asset + `libjpeg-turbo-m-3.2.1.tar.gz` (`83cfc60f…e8ca`), CN mirror `gitcode.com/mcpp-res/mwrap-libjpeg-turbo` with identical + bytes. +- Implementation ownership: the index has no JPEG package, so the wrapper vendors what upstream's TurboJPEG static + library is built from (upstream's `TURBOJPEG_SOURCES`: the libjpeg and TurboJPEG sources, the 8/12/16-bit precision + wrappers, the image-file readers and writers `tj3LoadImage*` and `tj3SaveImage*` use, the in-tree libspng; 188 files, + checksums in `upstream/documents.json`) and builds them with three choices: no SIMD extensions (the x86 ones are + NASM assembly; the C code gives the same results on the default path), libspng against `compat.zlib` instead of the + in-tree zlib (upstream's `WITH_SYSTEM_ZLIB`), and floating-point contraction off. The three headers upstream's build + configures (`jconfig.h`, `jconfigint.h`, `jversion.h`) are written out under `port/include` for GCC and Clang on the + three platforms; that directory, with forwarding headers to the public ones, is the only include directory consumers + see. It deploys upstream's README, LICENSE.md, README.ijg and libspng's license files with its own LICENSE/NOTICE + (`runtime.deploy`). + +## Module + +- `mcpplibs.mwrap.libjpeg_turbo`: every name of `` — 7 enumerations, 68 enumerators, 4 types, 80 + functions (the TurboJPEG 3 API and the 2.x functions upstream still declares) — the upstream globals by `using`. +- Adaptation: a module cannot export a macro nor an internal-linkage constant, so the header's 43 constant macros, its 9 + `static const` tables and values (`tjPixelSize`, `tjRedOffset`, …, `TJUNSCALED`, `TJUNCROPPED`) and its 3 macros that + take arguments (`tj3Init`, `TJSCALED`, `TJPAD`) are declared under their names, with the header's values, in + `mcpplibs::mwrap::libjpeg_turbo`. +- The libjpeg API (``) is built and linked but not exported as a module: it is used through macros and + `setjmp`. Its headers are on the include path. +- The export list is derived by `tools/surface.py` from the header and audited in CI (`tools/check_exports.py`). + +## Verification + +- Wrapper CI (Linux x86_64 GCC 16.1 and LLVM 23.1.3, macOS LLVM, Windows LLVM): upstream's `testorig.jpg` decodes to + the MD5 upstream's regression test expects (`MD5_PPM_EXAMPLE_DECOMPRESS`, `dea1d7bb…6641`) on every row; a picture + compressed, its bytes and its decoded pixels pinned by MD5 (the same on every row); a scaled decode into another + pixel format; a quarter turn and back without decoding; refusal of what is no JPEG; the adapted constants against the + header's; the export audit; the default template from an installed copy with a strict build. +- This index: consumer `tests/mwrap/libjpeg-turbo-decode`; the installed-template check with `--deploy` + (`STATIC_NOTICES`). diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index fa573aa2..46d891c9 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1733,6 +1733,11 @@ jobs: shell: bash run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.manifold@3.5.5.1 --deploy + - name: Installed libjpeg-turbo wrapper archive, actual default decoding template and its relocated notices + if: contains(format(' {0} ', env.MEMBERS), ' libjpeg-turbo-decode ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.libjpeg_turbo@3.2.1 --deploy + - name: Installed libarchive wrapper archive, actual default archive template and its relocated notices if: contains(format(' {0} ', env.MEMBERS), ' libarchive-bundle ') && matrix.toolchain == 'default' shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 55401a66..14ed8bc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,14 @@ ### Added +- **mcpplibs.mwrap.libjpeg_turbo 3.2.1(新包)。** libjpeg-turbo-m:libjpeg-turbo 的 TurboJPEG API(JPEG 压缩、解压、无损变换)的 + C++23 模块,`` 的 7 个枚举、68 个枚举值、4 个类型、80 个函数以原名导出;头文件里的 43 个常量宏、9 个 + `static const` 常量与表、3 个带参数的宏(`tj3Init`、`TJSCALED`、`TJPAD`)以原名位于 `mcpplibs::mwrap::libjpeg_turbo`。 + 封装仓库逐字节收录 3.2.1 发布包中 TurboJPEG 静态库的源文件,按上游的清单构建:不带 SIMD 扩展(C 代码,默认路径全程 + 整数运算,各平台结果相同)、libspng 链接 `compat.zlib`(上游的 `WITH_SYSTEM_ZLIB`)、关闭浮点收缩。封装的测试里上游的 + `testorig.jpg` 解码结果与上游回归测试期望的 MD5 一致。消费者测试 `tests/mwrap/libjpeg-turbo-decode`,安装后模板检查带 + `--deploy`。GLOBAL 为 GitHub 发布资产,CN 为 GitCode `mcpp-res/mwrap-libjpeg-turbo` 镜像,sha256 相同。 + - **mcpplibs.mwrap.manifold 3.5.5.1(新包)。** manifold-m:Manifold(始终保持流形的实体几何库)的 C++23 模块,以 `export using` 在上游的命名空间里导出 `` 与 `` 的全部公开名字(`manifold` 36 个类型、10 个函数, `manifold::math` 16 个函数,`linalg` 10 个类型、116 个函数与运算符);7 个内部链接的 `constexpr` 常量(`kPi`、 diff --git a/mcpp.toml b/mcpp.toml index de977232..d6d6a644 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -38,6 +38,7 @@ members = [ "tests/mwrap/meshoptimizer-simplify", "tests/mwrap/cgltf-inspect", "tests/mwrap/manifold-boolean", + "tests/mwrap/libjpeg-turbo-decode", "tests/mwrap/libarchive-bundle", "tests/mwrap/libc-stdio", "tests/mwrap/miniaudio-engine", diff --git a/pkgs/mwrap/libjpeg_turbo.lua b/pkgs/mwrap/libjpeg_turbo.lua new file mode 100644 index 00000000..ede29c59 --- /dev/null +++ b/pkgs/mwrap/libjpeg_turbo.lua @@ -0,0 +1,52 @@ +-- Immutable upstream-aligned mwrap source release (libjpeg-turbo-m mwrap-3.2.1); +-- GLOBAL and CN bytes are identical. +-- +-- The wrapper is Apache-2.0; the library it vendors and links statically, +-- libjpeg-turbo 3.2.1, is under the IJG License and the Modified (3-clause) +-- BSD License (its in-tree libspng BSD 2-Clause): the package deploys +-- upstream's README, LICENSE.md, README.ijg and libspng's license files beside +-- programs (runtime.deploy). It depends on compat.zlib (libspng's inflate). +package = { + spec = "1", + namespace = "mcpplibs.mwrap", + name = "libjpeg_turbo", + description = "libjpeg-turbo-m: libjpeg-turbo 3.2.1 (the TurboJPEG API: JPEG compression, decompression, lossless transforms) as a C++23 module", + licenses = { "Apache-2.0" }, + repo = "https://github.com/mcpplibs/libjpeg-turbo-m", + type = "package", + extensions = { + autoupdate = { + tag = "mwrap-${version}", + url = "https://github.com/mcpplibs/libjpeg-turbo-m/releases/download/${tag}/libjpeg-turbo-m-${version}.tar.gz", + }, + }, + xpm = { + linux = { + ["3.2.1"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/libjpeg-turbo-m/releases/download/mwrap-3.2.1/libjpeg-turbo-m-3.2.1.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-libjpeg-turbo/releases/download/3.2.1/libjpeg-turbo-m-3.2.1.tar.gz", + }, + sha256 = "83cfc60fea1c309506e4994361c06f0f4f62dd297fc9a5dea50bb2c644c4e8ca", + }, + }, + macosx = { + ["3.2.1"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/libjpeg-turbo-m/releases/download/mwrap-3.2.1/libjpeg-turbo-m-3.2.1.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-libjpeg-turbo/releases/download/3.2.1/libjpeg-turbo-m-3.2.1.tar.gz", + }, + sha256 = "83cfc60fea1c309506e4994361c06f0f4f62dd297fc9a5dea50bb2c644c4e8ca", + }, + }, + windows = { + ["3.2.1"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/libjpeg-turbo-m/releases/download/mwrap-3.2.1/libjpeg-turbo-m-3.2.1.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-libjpeg-turbo/releases/download/3.2.1/libjpeg-turbo-m-3.2.1.tar.gz", + }, + sha256 = "83cfc60fea1c309506e4994361c06f0f4f62dd297fc9a5dea50bb2c644c4e8ca", + }, + }, + }, +} diff --git a/tests/mwrap/check_installed_template.py b/tests/mwrap/check_installed_template.py index 2e843b8b..f3e22fcd 100644 --- a/tests/mwrap/check_installed_template.py +++ b/tests/mwrap/check_installed_template.py @@ -23,6 +23,7 @@ "mcpplibs.mwrap.meshoptimizer": ("meshoptimizer.cppm", "upstream/meshoptimizer/LICENSE.md", b"MIT License", b"Arseny Kapoulkine"), "mcpplibs.mwrap.cgltf": ("cgltf.cppm", "upstream/cgltf/LICENSE", b"Permission is hereby granted", b"Kuhlmann"), "mcpplibs.mwrap.manifold": ("manifold.cppm", "upstream/manifold/LICENSE", b"Apache License", b"The Manifold Authors"), + "mcpplibs.mwrap.libjpeg_turbo": ("libjpeg_turbo.cppm", "upstream/libjpeg-turbo/README.ijg", b"Independent JPEG Group", b"libjpeg-turbo Project"), "mcpplibs.mwrap.libc": ("libc.cppm", None, b"", b"ISO/IEC 9899:2018"), } diff --git a/tests/mwrap/libjpeg-turbo-decode/mcpp.toml b/tests/mwrap/libjpeg-turbo-decode/mcpp.toml new file mode 100644 index 00000000..b4dd5ed8 --- /dev/null +++ b/tests/mwrap/libjpeg-turbo-decode/mcpp.toml @@ -0,0 +1,11 @@ +[package] +name = "libjpeg-turbo-decode" +version = "3.2.1" +description = "Consume the immutable libjpeg-turbo wrapper: a picture compressed and read back, its size and sampling, a scaled decode, a lossless turn, the adapted constants" + +[indices] +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. + +[dependencies] +mcpplibs.mwrap.libjpeg_turbo = "3.2.1" diff --git a/tests/mwrap/libjpeg-turbo-decode/tests/behavior.cpp b/tests/mwrap/libjpeg-turbo-decode/tests/behavior.cpp new file mode 100644 index 00000000..4420aec3 --- /dev/null +++ b/tests/mwrap/libjpeg-turbo-decode/tests/behavior.cpp @@ -0,0 +1,57 @@ +// The installed libjpeg-turbo wrapper: a picture compressed in memory, its +// header read back, decoded whole and at half size, turned a quarter without +// decoding, and the header's macros and tables under their adapted names. +import std; +import mcpplibs.mwrap.libjpeg_turbo; + +namespace jpeg = mcpplibs::mwrap::libjpeg_turbo; + +int main() { + static_assert(jpeg::TJ_NUMPF == 12 && jpeg::tjPixelSize[TJPF_RGBA] == 4 && jpeg::TJPAD(5) == 8); + constexpr int width = 80, height = 48; + std::vector pixels(static_cast(width) * height * 3); + for (int row = 0; row < height; ++row) + for (int column = 0; column < width; ++column) { + pixels[(static_cast(row) * width + column) * 3 + 0] = static_cast(column * 3); + pixels[(static_cast(row) * width + column) * 3 + 1] = static_cast(row * 5); + pixels[(static_cast(row) * width + column) * 3 + 2] = 100; + } + tjhandle encoder = jpeg::tj3Init(TJINIT_COMPRESS); + if (!encoder || tj3Set(encoder, TJPARAM_QUALITY, 92) != 0 || tj3Set(encoder, TJPARAM_SUBSAMP, TJSAMP_444) != 0) return 1; + unsigned char* file = nullptr; + std::size_t size = 0; + if (tj3Compress8(encoder, pixels.data(), width, 0, height, TJPF_RGB, &file, &size) != 0 || size == 0 || size > tj3JPEGBufSize(width, height, TJSAMP_444)) return 2; + tj3Destroy(encoder); + + tjhandle decoder = jpeg::tj3Init(TJINIT_DECOMPRESS); + if (tj3DecompressHeader(decoder, file, size) != 0) return 3; + if (tj3Get(decoder, TJPARAM_JPEGWIDTH) != width || tj3Get(decoder, TJPARAM_JPEGHEIGHT) != height || tj3Get(decoder, TJPARAM_SUBSAMP) != TJSAMP_444) return 4; + std::vector back(pixels.size()); + if (tj3Decompress8(decoder, file, size, back.data(), 0, TJPF_RGB) != 0) return 5; + int worst = 0; + for (std::size_t index = 0; index < pixels.size(); ++index) worst = std::max(worst, std::abs(int(pixels[index]) - int(back[index]))); + if (worst > 12) return 6; + + const tjscalingfactor half{1, 2}; + if (tj3SetScalingFactor(decoder, half) != 0 || jpeg::TJSCALED(width, half) != 40) return 7; + std::vector small(static_cast(40) * 24 * jpeg::tjPixelSize[TJPF_BGRA]); + if (tj3Decompress8(decoder, file, size, small.data(), 0, TJPF_BGRA) != 0 || small[jpeg::tjAlphaOffset[TJPF_BGRA]] != 255) return 8; + + tjhandle turner = jpeg::tj3Init(TJINIT_TRANSFORM); + tjtransform turn{}; + turn.op = TJXOP_ROT90; + turn.options = jpeg::TJXOPT_PERFECT; + unsigned char* turned = nullptr; + std::size_t turned_size = 0; + if (tj3Transform(turner, file, size, 1, &turned, &turned_size, &turn) != 0) return 9; + if (tj3DecompressHeader(decoder, turned, turned_size) != 0 || tj3Get(decoder, TJPARAM_JPEGWIDTH) != height || tj3Get(decoder, TJPARAM_JPEGHEIGHT) != width) return 10; + + const std::array nothing{1, 2, 3, 4}; + if (tj3DecompressHeader(decoder, nothing.data(), nothing.size()) != -1 || tj3GetErrorCode(decoder) != TJERR_FATAL) return 11; + tj3Free(turned); + tj3Free(file); + tj3Destroy(turner); + tj3Destroy(decoder); + std::println("libjpeg-turbo {}: {} bytes for {} x {}, worst difference {}", jpeg::TURBOJPEG_VERSION_NUMBER, size, width, height, worst); + return 0; +}