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
43 changes: 43 additions & 0 deletions .agents/docs/2026-11-14-add-libjpeg-turbo-mwrap-plan.md
Original file line number Diff line number Diff line change
@@ -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 `<turbojpeg.h>` — 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 (`<jpeglib.h>`) 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`).
5 changes: 5 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@

### Added

- **mcpplibs.mwrap.libjpeg_turbo 3.2.1(新包)。** libjpeg-turbo-m:libjpeg-turbo 的 TurboJPEG API(JPEG 压缩、解压、无损变换)的
C++23 模块,`<turbojpeg.h>` 的 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/manifold.h>` 与 `<manifold/polygon.h>` 的全部公开名字(`manifold` 36 个类型、10 个函数,
`manifold::math` 16 个函数,`linalg` 10 个类型、116 个函数与运算符);7 个内部链接的 `constexpr` 常量(`kPi`、
Expand Down
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
52 changes: 52 additions & 0 deletions pkgs/mwrap/libjpeg_turbo.lua
Original file line number Diff line number Diff line change
@@ -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",
},
},
},
}
1 change: 1 addition & 0 deletions tests/mwrap/check_installed_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
}

Expand Down
11 changes: 11 additions & 0 deletions tests/mwrap/libjpeg-turbo-decode/mcpp.toml
Original file line number Diff line number Diff line change
@@ -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"
57 changes: 57 additions & 0 deletions tests/mwrap/libjpeg-turbo-decode/tests/behavior.cpp
Original file line number Diff line number Diff line change
@@ -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<unsigned char> pixels(static_cast<std::size_t>(width) * height * 3);
for (int row = 0; row < height; ++row)
for (int column = 0; column < width; ++column) {
pixels[(static_cast<std::size_t>(row) * width + column) * 3 + 0] = static_cast<unsigned char>(column * 3);
pixels[(static_cast<std::size_t>(row) * width + column) * 3 + 1] = static_cast<unsigned char>(row * 5);
pixels[(static_cast<std::size_t>(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<unsigned char> 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<unsigned char> small(static_cast<std::size_t>(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<unsigned char, 4> 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;
}
Loading