libavif-with-gainmap 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # libavif-with-gainmap
2
+
3
+ 一个准备发布到 npm 的 Node.js 包,用 libavif 把带 gain map 的 JPEG 转成带 gain map 的 AVIF,并支持设置质量和输出尺寸。
4
+
5
+ ## 特性
6
+
7
+ - 使用 libavif 的 `avifgainmaputil convert` 做 JPEG gain map -> AVIF gain map。
8
+ - 支持 `quality`、`gainMapQuality`、`speed`、`jobs`、`depth`、`yuv` 等编码参数。
9
+ - 支持 `width`/`height` 精确尺寸,或 `maxWidth`/`maxHeight` 等比缩小。
10
+ - GitHub Actions matrix 构筑这些平台的原生工具:`darwin-arm64`、`darwin-x64`、`linux-arm64`、`linux-x64`、`win32-x64`。
11
+ - npm 包运行时按 `process.platform` + `process.arch` 自动选择 `vendor/<platform>-<arch>/` 下的二进制。
12
+
13
+ ## 安装
14
+
15
+ ```sh
16
+ npm install libavif-with-gainmap
17
+ ```
18
+
19
+ 发布前需要先运行本仓库的 GitHub Actions release workflow,生成并打包 `vendor/` 下的原生二进制。
20
+
21
+ ## CLI
22
+
23
+ ```sh
24
+ avif-gainmap convert input.jpg output.avif --quality 82 --gain-map-quality 70
25
+ ```
26
+
27
+ 调整尺寸:
28
+
29
+ ```sh
30
+ avif-gainmap convert input.jpg output.avif --max-width 1600 --max-height 1200
31
+ avif-gainmap convert input.jpg output.avif --width 1200
32
+ ```
33
+
34
+ 常用参数:
35
+
36
+ ```sh
37
+ avif-gainmap convert input.jpg output.avif \
38
+ --quality 80 \
39
+ --gain-map-quality 65 \
40
+ --speed 6 \
41
+ --jobs all \
42
+ --depth 10 \
43
+ --yuv 420
44
+ ```
45
+
46
+ ## JS API
47
+
48
+ ```js
49
+ const { convertJpegGainMap } = require('libavif-with-gainmap');
50
+
51
+ await convertJpegGainMap('input.jpg', 'output.avif', {
52
+ quality: 82,
53
+ gainMapQuality: 70,
54
+ maxWidth: 1600,
55
+ maxHeight: 1200,
56
+ jobs: 'all'
57
+ });
58
+ ```
59
+
60
+ 如果只给 `width` 或 `height`,另一边会按比例计算。`maxWidth` / `maxHeight` 只会缩小,不会放大。
61
+
62
+ ## 原生工具覆盖
63
+
64
+ 默认使用 npm 包内的二进制。也可以用环境变量覆盖:
65
+
66
+ - `AVIF_GAINMAP_BIN_DIR`: 同时包含 `avifgainmaputil` 和 `avifgainmapresize` 的目录。
67
+ - `AVIF_GAINMAPUTIL_PATH`: 指向自定义 `avifgainmaputil`。
68
+ - `AVIF_GAINMAPRESIZE_PATH`: 指向自定义 `avifgainmapresize`。
69
+
70
+ ## 构筑
71
+
72
+ 本项目使用 libavif `v1.4.2`,构筑时开启:
73
+
74
+ - `AVIF_BUILD_APPS=ON`
75
+ - `AVIF_CODEC_AOM=LOCAL`
76
+ - `AVIF_JPEG=LOCAL`
77
+ - `AVIF_LIBXML2=LOCAL`
78
+ - `AVIF_LIBYUV=LOCAL`
79
+ - `AVIF_LIBSHARPYUV=LOCAL`
80
+ - `AVIF_ZLIBPNG=LOCAL`
81
+
82
+ 本地当前平台构筑:
83
+
84
+ ```sh
85
+ npm run build:libavif
86
+ npm run check-prebuilt
87
+ ```
88
+
89
+ 完整发布请看 [docs/BUILDING.md](docs/BUILDING.md)。
90
+
91
+ ## 为什么有 `avifgainmapresize`
92
+
93
+ 普通图片 resize 工具通常不了解 JPEG/AVIF gain map 的主图、辅助图和元数据关系,容易丢失或破坏 gain map。这里先用 libavif 官方工具转换,再用一个链接 libavif 的小工具同时缩放 AVIF base image 和 gain map image,最后按目标质量重新编码。
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { convertJpegGainMap } = require('../src');
5
+ const { normalizeJobs } = require('../src/options');
6
+ const { version } = require('../package.json');
7
+
8
+ function printHelp() {
9
+ process.stdout.write(`avif-gainmap ${version}
10
+
11
+ Usage:
12
+ avif-gainmap convert <input.jpg> <output.avif> [options]
13
+
14
+ Options:
15
+ -q, --quality <0-100> AVIF color quality. Default: 80
16
+ --gain-map-quality <0-100> Gain map quality. Default: 60
17
+ --width <px> Exact output width. Height is kept proportional if omitted.
18
+ --height <px> Exact output height. Width is kept proportional if omitted.
19
+ --max-width <px> Downscale to fit this width.
20
+ --max-height <px> Downscale to fit this height.
21
+ --speed <0-10> Encoder speed. 0 is slowest, 10 is fastest. Default: 6
22
+ --jobs <n|all> Worker threads.
23
+ --swap-base Make the HDR image the AVIF base image.
24
+ --cicp <P/T/M> Override input CICP values.
25
+ --clli <MaxCLL,MaxPALL> Set alternate image light level information.
26
+ --depth <8|10|12> Output bit depth passed to libavif.
27
+ --yuv <444|422|420|400> Output YUV format passed to libavif.
28
+ --keep-temp Keep intermediate AVIF when resizing.
29
+ --verbose Stream native tool output.
30
+ -h, --help Show this help.
31
+ --version Show version.
32
+ `);
33
+ }
34
+
35
+ function readValue(args, index, inlineValue, flag) {
36
+ if (inlineValue !== undefined) {
37
+ return [inlineValue, index];
38
+ }
39
+ if (index + 1 >= args.length) {
40
+ throw new Error(`${flag} requires a value.`);
41
+ }
42
+ return [args[index + 1], index + 1];
43
+ }
44
+
45
+ function parseConvertArgs(args) {
46
+ const options = {};
47
+ const positional = [];
48
+
49
+ for (let i = 0; i < args.length; i += 1) {
50
+ const raw = args[i];
51
+ const eq = raw.indexOf('=');
52
+ const flag = eq === -1 ? raw : raw.slice(0, eq);
53
+ const inlineValue = eq === -1 ? undefined : raw.slice(eq + 1);
54
+
55
+ switch (flag) {
56
+ case '-q':
57
+ case '--quality': {
58
+ const result = readValue(args, i, inlineValue, flag);
59
+ options.quality = result[0];
60
+ i = result[1];
61
+ break;
62
+ }
63
+ case '--gain-map-quality':
64
+ case '--qgain-map': {
65
+ const result = readValue(args, i, inlineValue, flag);
66
+ options.gainMapQuality = result[0];
67
+ i = result[1];
68
+ break;
69
+ }
70
+ case '--width':
71
+ case '--height':
72
+ case '--max-width':
73
+ case '--max-height':
74
+ case '--speed':
75
+ case '--depth':
76
+ case '--yuv':
77
+ case '--cicp':
78
+ case '--clli': {
79
+ const result = readValue(args, i, inlineValue, flag);
80
+ const key = flag
81
+ .replace(/^--/, '')
82
+ .replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
83
+ options[key] = result[0];
84
+ i = result[1];
85
+ break;
86
+ }
87
+ case '--jobs': {
88
+ const result = readValue(args, i, inlineValue, flag);
89
+ options.jobs = normalizeJobs(result[0]);
90
+ i = result[1];
91
+ break;
92
+ }
93
+ case '--swap-base':
94
+ options.swapBase = true;
95
+ break;
96
+ case '--keep-temp':
97
+ options.keepTemp = true;
98
+ break;
99
+ case '--verbose':
100
+ options.verbose = true;
101
+ break;
102
+ default:
103
+ if (raw.startsWith('-')) {
104
+ throw new Error(`Unknown option ${raw}.`);
105
+ }
106
+ positional.push(raw);
107
+ }
108
+ }
109
+
110
+ if (positional.length !== 2) {
111
+ throw new Error('convert requires <input.jpg> and <output.avif>.');
112
+ }
113
+
114
+ return { input: positional[0], options, output: positional[1] };
115
+ }
116
+
117
+ async function main(argv) {
118
+ const command = argv[2];
119
+ if (!command || command === '-h' || command === '--help') {
120
+ printHelp();
121
+ return;
122
+ }
123
+ if (command === '--version') {
124
+ process.stdout.write(`${version}\n`);
125
+ return;
126
+ }
127
+ if (command !== 'convert') {
128
+ throw new Error(`Unknown command ${command}.`);
129
+ }
130
+
131
+ const { input, output, options } = parseConvertArgs(argv.slice(3));
132
+ await convertJpegGainMap(input, output, options);
133
+ }
134
+
135
+ main(process.argv).catch((error) => {
136
+ process.stderr.write(`${error.message}\n`);
137
+ if (error.stderr) {
138
+ process.stderr.write(error.stderr);
139
+ }
140
+ process.exitCode = 1;
141
+ });
package/docs/API.md ADDED
@@ -0,0 +1,47 @@
1
+ # API
2
+
3
+ ## `convertJpegGainMap(input, output, options)`
4
+
5
+ 把带 gain map 的 JPEG 转成 AVIF gain map。
6
+
7
+ ```js
8
+ const { convertJpegGainMap } = require('libavif-with-gainmap');
9
+
10
+ await convertJpegGainMap('input.jpg', 'output.avif', {
11
+ quality: 80,
12
+ gainMapQuality: 60,
13
+ maxWidth: 1600,
14
+ maxHeight: 1200
15
+ });
16
+ ```
17
+
18
+ ## Options
19
+
20
+ - `quality`: 主图质量,`0..100`,默认 `80`。
21
+ - `gainMapQuality`: gain map 质量,`0..100`,默认 `60`。
22
+ - `width`: 输出宽度。单独设置时保持比例。
23
+ - `height`: 输出高度。单独设置时保持比例。
24
+ - `maxWidth`: 最大宽度,只缩小不放大。
25
+ - `maxHeight`: 最大高度,只缩小不放大。
26
+ - `speed`: libavif 编码速度,`0..10`,默认 `6`。
27
+ - `jobs`: 线程数,正整数或 `'all'`。
28
+ - `depth`: 输出 bit depth,`8`、`10`、`12`。
29
+ - `yuv`: 输出 YUV 格式,`444`、`422`、`420`、`400`。
30
+ - `swapBase`: 传给 `avifgainmaputil --swap-base`。
31
+ - `cicp`: 传给 `avifgainmaputil --cicp`,格式 `P/T/M`。
32
+ - `clli`: 传给 `avifgainmaputil --clli`,格式 `MaxCLL,MaxPALL`。
33
+ - `binDir`: 自定义二进制目录。
34
+ - `toolPaths`: `{ avifgainmaputil, avifgainmapresize }` 自定义工具路径。
35
+ - `verbose`: 把原生工具输出转发到当前 stdout/stderr。
36
+
37
+ 返回值包含原生工具执行结果:
38
+
39
+ ```js
40
+ {
41
+ input: '/abs/input.jpg',
42
+ output: '/abs/output.avif',
43
+ resized: true,
44
+ convert: { command, args, stdout, stderr, exitCode },
45
+ resize: { command, args, stdout, stderr, exitCode }
46
+ }
47
+ ```
@@ -0,0 +1,66 @@
1
+ # 构筑和发布
2
+
3
+ ## GitHub Actions
4
+
5
+ `.github/workflows/release.yml` 会做三件事:
6
+
7
+ 1. 跑 JS 测试。
8
+ 2. 用 matrix 分别构筑五个平台的 libavif 工具:
9
+ - `linux-x64` on `ubuntu-24.04`
10
+ - `linux-arm64` on `ubuntu-24.04-arm`
11
+ - `darwin-x64` on `macos-15-intel`
12
+ - `darwin-arm64` on `macos-15`
13
+ - `win32-x64` on `windows-2025`
14
+ 3. 汇总 `vendor/<platform>-<arch>/` 后执行 `npm pack`,tag 触发时再 `npm publish`。
15
+
16
+ 发布到 npm 前,在仓库 secrets 里配置:
17
+
18
+ ```text
19
+ NPM_TOKEN=<npm automation token>
20
+ ```
21
+
22
+ 然后创建 tag:
23
+
24
+ ```sh
25
+ git tag v0.1.0
26
+ git push origin v0.1.0
27
+ ```
28
+
29
+ ## 本地构筑当前平台
30
+
31
+ 需要 `git`、`cmake`、`ninja`、`nasm`、Node.js 18+。
32
+
33
+ ```sh
34
+ npm test
35
+ npm run build:libavif
36
+ npm run check-prebuilt
37
+ ```
38
+
39
+ 输出目录:
40
+
41
+ ```text
42
+ vendor/<platform>-<arch>/
43
+ avifgainmaputil(.exe)
44
+ avifgainmapresize(.exe)
45
+ ```
46
+
47
+ ## 可配置环境变量
48
+
49
+ - `LIBAVIF_VERSION`: 默认 `v1.4.2`。
50
+ - `LIBAVIF_SOURCE_DIR`: 使用已有 libavif 源码目录,而不是自动 clone。
51
+ - `LIBAVIF_BUILD_DIR`: 自定义 CMake build 目录。
52
+ - `LIBAVIF_INSTALL_DIR`: 自定义 CMake install 目录。
53
+ - `LIBAVIF_CMAKE_ARGS`: 追加 CMake 参数。
54
+ - `TARGET_PLATFORM_KEY`: 写入哪个 `vendor/<key>` 目录,默认当前平台。
55
+
56
+ 示例:
57
+
58
+ ```sh
59
+ LIBAVIF_VERSION=v1.4.2 TARGET_PLATFORM_KEY=linux-x64 npm run build:libavif
60
+ ```
61
+
62
+ ## 构筑策略
63
+
64
+ libavif 是 C/C++ 原生项目,不能构筑一个跨所有系统通用的二进制。本仓库选择在 GitHub Actions 上按平台分别构筑,然后把产物放入 npm tarball。运行时由 Node.js 选择当前平台的二进制。
65
+
66
+ `avifgainmaputil` 来自 libavif;`avifgainmapresize` 是本仓库的很小一层 C helper,通过 public libavif API 解码、缩放、重编码 AVIF gain map。