@thi.ng/imago 1.2.4 → 1.4.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/CHANGELOG.md +15 -1
- package/README.md +1 -1
- package/api.d.ts +13 -0
- package/ops/output.d.ts +1 -0
- package/ops/output.js +13 -11
- package/package.json +3 -3
- package/path.d.ts +5 -1
- package/path.js +51 -48
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2025-07-
|
|
3
|
+
- **Last updated**: 2025-07-25T00:04:12Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -11,6 +11,20 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
11
11
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
12
12
|
and/or version bumps of transitive dependencies.
|
|
13
13
|
|
|
14
|
+
## [1.4.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/imago@1.4.0) (2025-07-25)
|
|
15
|
+
|
|
16
|
+
#### 🚀 Features
|
|
17
|
+
|
|
18
|
+
- add support for absolute output paths ([c440e25](https://github.com/thi-ng/umbrella/commit/c440e25))
|
|
19
|
+
- update `formatPath()` to always return abs path
|
|
20
|
+
- simplify `outputProc()` path handling
|
|
21
|
+
|
|
22
|
+
## [1.3.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/imago@1.3.0) (2025-07-21)
|
|
23
|
+
|
|
24
|
+
#### 🚀 Features
|
|
25
|
+
|
|
26
|
+
- add `dataURL` output option ([eca0ee3](https://github.com/thi-ng/umbrella/commit/eca0ee3))
|
|
27
|
+
|
|
14
28
|
### [1.2.1](https://github.com/thi-ng/umbrella/tree/@thi.ng/imago@1.2.1) (2025-07-14)
|
|
15
29
|
|
|
16
30
|
#### 🩹 Bug fixes
|
package/README.md
CHANGED
package/api.d.ts
CHANGED
|
@@ -372,6 +372,19 @@ export interface OutputSpec extends ProcSpec {
|
|
|
372
372
|
* WebP output options. See [Sharp docs](https://sharp.pixelplumbing.com/api-output#webp)
|
|
373
373
|
*/
|
|
374
374
|
webp?: WebpOptions;
|
|
375
|
+
/**
|
|
376
|
+
* Only used if {@link OutputSpec.path} is NOT set. If true, output will be
|
|
377
|
+
* captured as data URL, otherwise as binary data/buffer.
|
|
378
|
+
*
|
|
379
|
+
* @remarks
|
|
380
|
+
* Other conditions:
|
|
381
|
+
*
|
|
382
|
+
* - Requires {@link OutputSpec.format} to be set to a data URL compatible
|
|
383
|
+
* image format.
|
|
384
|
+
* - An error will be thrown during processing if the encoded image size
|
|
385
|
+
* exceeds 32KB.
|
|
386
|
+
*/
|
|
387
|
+
dataURL?: boolean;
|
|
375
388
|
}
|
|
376
389
|
export interface ResizeSpec extends ProcSpec {
|
|
377
390
|
op: "resize";
|
package/ops/output.d.ts
CHANGED
package/ops/output.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { encode } from "@thi.ng/blurhash";
|
|
2
2
|
import { isNumber, isPlainObject } from "@thi.ng/checks";
|
|
3
|
+
import { illegalArgs } from "@thi.ng/errors";
|
|
3
4
|
import { writeFile, writeJSON } from "@thi.ng/file-io";
|
|
4
|
-
import { firstNonNullKey } from "@thi.ng/object-utils
|
|
5
|
-
import { join, resolve } from "node:path";
|
|
5
|
+
import { firstNonNullKey } from "@thi.ng/object-utils";
|
|
6
6
|
import { formatPath } from "../path.js";
|
|
7
7
|
const outputProc = async (spec, input, ctx) => {
|
|
8
8
|
const opts = spec;
|
|
9
|
-
const outDir = resolve(ctx.opts.outDir || ".");
|
|
10
9
|
let output = input.clone();
|
|
11
10
|
if (opts.blurhash) {
|
|
12
11
|
await __outputBlurHash(opts, output, ctx);
|
|
13
12
|
return [input, false];
|
|
14
13
|
}
|
|
15
14
|
if (opts.raw) {
|
|
16
|
-
await __outputRaw(opts, output, ctx
|
|
15
|
+
await __outputRaw(opts, output, ctx);
|
|
17
16
|
return [input, false];
|
|
18
17
|
}
|
|
19
18
|
if (ctx.meta.exif && ctx.opts.keepEXIF) {
|
|
@@ -72,18 +71,15 @@ const outputProc = async (spec, input, ctx) => {
|
|
|
72
71
|
if (format) output = output.toFormat(format);
|
|
73
72
|
const result = await output.toBuffer();
|
|
74
73
|
if (opts.path !== void 0) {
|
|
75
|
-
const path =
|
|
76
|
-
outDir,
|
|
77
|
-
formatPath(opts.path, ctx, spec, result)
|
|
78
|
-
);
|
|
74
|
+
const path = formatPath(opts.path, ctx, spec, result);
|
|
79
75
|
writeFile(path, result, null, ctx.logger);
|
|
80
76
|
ctx.outputs[opts.id] = path;
|
|
81
77
|
} else {
|
|
82
|
-
ctx.outputs[opts.id] = result;
|
|
78
|
+
ctx.outputs[opts.id] = format && opts.dataURL ? asDataURL(`image/${format}`, result) : result;
|
|
83
79
|
}
|
|
84
80
|
return [input, false];
|
|
85
81
|
};
|
|
86
|
-
const __outputRaw = async (opts, output, ctx
|
|
82
|
+
const __outputRaw = async (opts, output, ctx) => {
|
|
87
83
|
const { alpha = false, meta = false } = isPlainObject(opts.raw) ? opts.raw : {};
|
|
88
84
|
if (alpha) output = output.ensureAlpha();
|
|
89
85
|
else output = output.removeAlpha();
|
|
@@ -92,7 +88,7 @@ const __outputRaw = async (opts, output, ctx, outDir) => {
|
|
|
92
88
|
ctx.outputMeta[opts.id] = { ...info, exif: ctx.exif };
|
|
93
89
|
}
|
|
94
90
|
if (opts.path !== void 0) {
|
|
95
|
-
const path =
|
|
91
|
+
const path = formatPath(opts.path, ctx, opts, data);
|
|
96
92
|
writeFile(path, data, null, ctx.logger);
|
|
97
93
|
ctx.outputs[opts.id] = path;
|
|
98
94
|
if (meta) {
|
|
@@ -122,6 +118,12 @@ const __outputBlurHash = async (opts, output, ctx) => {
|
|
|
122
118
|
ctx.logger.debug("computed blurhash:", hash);
|
|
123
119
|
ctx.outputs[opts.id] = hash;
|
|
124
120
|
};
|
|
121
|
+
const asDataURL = (mime, data) => {
|
|
122
|
+
if (data.length > 32768)
|
|
123
|
+
illegalArgs("encoded image too large for dataURL (max. 32KB allowed)");
|
|
124
|
+
return `data:${mime};base64,${data.toString("base64")}`;
|
|
125
|
+
};
|
|
125
126
|
export {
|
|
127
|
+
asDataURL,
|
|
126
128
|
outputProc
|
|
127
129
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/imago",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "JSON & API-based declarative and extensible image processing trees/pipelines",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"sharp": "^0.34.3"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@thi.ng/vectors": "^8.
|
|
57
|
+
"@thi.ng/vectors": "^8.5.0",
|
|
58
58
|
"@types/node": "^24.0.15",
|
|
59
59
|
"esbuild": "^0.25.8",
|
|
60
60
|
"typedoc": "^0.28.7",
|
|
@@ -189,5 +189,5 @@
|
|
|
189
189
|
"status": "alpha",
|
|
190
190
|
"year": 2024
|
|
191
191
|
},
|
|
192
|
-
"gitHead": "
|
|
192
|
+
"gitHead": "b8784efbd8beaa2ff296bb8fb802cb73f6d881ac\n"
|
|
193
193
|
}
|
package/path.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { TypedArray } from "@thi.ng/api";
|
|
2
2
|
import type { ImgProcCtx, OutputSpec } from "./api.js";
|
|
3
3
|
/**
|
|
4
|
-
* Expands/replaces all `{xyz}`-templated identifiers in given file path
|
|
4
|
+
* Expands/replaces all `{xyz}`-templated identifiers in given file path and
|
|
5
|
+
* makes result an absolute path (if needed).
|
|
5
6
|
*
|
|
6
7
|
* @remarks
|
|
8
|
+
* If the input path is not yet absolute, it will be joined with the
|
|
9
|
+
* {@link ImgProcOpts.outDir}.
|
|
10
|
+
*
|
|
7
11
|
* The following built-in IDs are supported and custom IDs will be looked up via
|
|
8
12
|
* the {@link ImgProcOpts.pathParts} options provided to {@link processImage}.
|
|
9
13
|
* Any others will remain as is. Custom IDs take precedence over built-in ones.
|
package/path.js
CHANGED
|
@@ -12,57 +12,60 @@ import {
|
|
|
12
12
|
} from "@thi.ng/date";
|
|
13
13
|
import { illegalArgs as unsupported } from "@thi.ng/errors";
|
|
14
14
|
import { createHash } from "node:crypto";
|
|
15
|
-
import { basename } from "node:path";
|
|
15
|
+
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
16
16
|
const _ = void 0;
|
|
17
|
-
const formatPath = (path, ctx, spec, buf) =>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
switch (id) {
|
|
23
|
-
case "name": {
|
|
24
|
-
!path && unsupported(
|
|
25
|
-
"cannot format `{name}`, image has no file source"
|
|
26
|
-
);
|
|
27
|
-
const name = basename(ctx.path);
|
|
28
|
-
const idx = name.lastIndexOf(".");
|
|
29
|
-
return idx > 0 ? name.substring(0, idx) : name;
|
|
17
|
+
const formatPath = (path, ctx, spec, buf) => {
|
|
18
|
+
path = path.replace(/\{(\w+)\}/g, (match, id) => {
|
|
19
|
+
const custom = ctx.opts.pathParts?.[id];
|
|
20
|
+
if (custom != null) {
|
|
21
|
+
return isFunction(custom) ? custom(ctx, spec, buf) : custom;
|
|
30
22
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
23
|
+
switch (id) {
|
|
24
|
+
case "name": {
|
|
25
|
+
!path && unsupported(
|
|
26
|
+
"cannot format `{name}`, image has no file source"
|
|
27
|
+
);
|
|
28
|
+
const name = basename(ctx.path);
|
|
29
|
+
const idx = name.lastIndexOf(".");
|
|
30
|
+
return idx > 0 ? name.substring(0, idx) : name;
|
|
31
|
+
}
|
|
32
|
+
case "sha1":
|
|
33
|
+
case "sha224":
|
|
34
|
+
case "sha256":
|
|
35
|
+
case "sha384":
|
|
36
|
+
case "sha512":
|
|
37
|
+
return createHash(id).update(buf).digest("hex").substring(0, 8);
|
|
38
|
+
case "w":
|
|
39
|
+
return String(ctx.size[0]);
|
|
40
|
+
case "h":
|
|
41
|
+
return String(ctx.size[1]);
|
|
42
|
+
case "aspect": {
|
|
43
|
+
const [w, h] = ctx.size;
|
|
44
|
+
return w > h ? "l" : w < h ? "p" : "sq";
|
|
45
|
+
}
|
|
46
|
+
case "date":
|
|
47
|
+
return FMT_yyyyMMdd_ALT(_, true);
|
|
48
|
+
case "time":
|
|
49
|
+
return FMT_HHmmss_ALT(_, true);
|
|
50
|
+
case "year":
|
|
51
|
+
return FMT_yyyy(_, true);
|
|
52
|
+
case "month":
|
|
53
|
+
return FMT_MM(_, true);
|
|
54
|
+
case "week":
|
|
55
|
+
return FMT_ww(_, true);
|
|
56
|
+
case "day":
|
|
57
|
+
return FMT_dd(_, true);
|
|
58
|
+
case "hour":
|
|
59
|
+
return FMT_HH(_, true);
|
|
60
|
+
case "minute":
|
|
61
|
+
return FMT_mm(_, true);
|
|
62
|
+
case "second":
|
|
63
|
+
return FMT_ss(_, true);
|
|
44
64
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
case "year":
|
|
50
|
-
return FMT_yyyy(_, true);
|
|
51
|
-
case "month":
|
|
52
|
-
return FMT_MM(_, true);
|
|
53
|
-
case "week":
|
|
54
|
-
return FMT_ww(_, true);
|
|
55
|
-
case "day":
|
|
56
|
-
return FMT_dd(_, true);
|
|
57
|
-
case "hour":
|
|
58
|
-
return FMT_HH(_, true);
|
|
59
|
-
case "minute":
|
|
60
|
-
return FMT_mm(_, true);
|
|
61
|
-
case "second":
|
|
62
|
-
return FMT_ss(_, true);
|
|
63
|
-
}
|
|
64
|
-
return match;
|
|
65
|
-
});
|
|
65
|
+
return match;
|
|
66
|
+
});
|
|
67
|
+
return isAbsolute(path) ? path : join(resolve(ctx.opts.outDir || "."), path);
|
|
68
|
+
};
|
|
66
69
|
export {
|
|
67
70
|
formatPath
|
|
68
71
|
};
|