@thi.ng/imago 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/index.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./api.js";
2
+ export * from "./ops.js";
3
+ export * from "./path.js";
4
+ export * from "./proc.js";
5
+ export * from "./units.js";
6
+ //# sourceMappingURL=index.d.ts.map
package/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./api.js";
2
+ export * from "./ops.js";
3
+ export * from "./path.js";
4
+ export * from "./proc.js";
5
+ export * from "./units.js";
package/ops.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { BlurSpec, CompSpec, CropSpec, DitherSpec, EXIFSpec, ExtendSpec, GammaSpec, GrayscaleSpec, HSBLSpec, NestSpec, OutputSpec, ProcSpec, ResizeSpec, RotateSpec } from "./api.js";
2
+ export declare const defSpec: <T extends ProcSpec>(type: T["type"]) => (opts: Omit<T, "type">) => T;
3
+ export declare const blur: (opts: Omit<BlurSpec, "type">) => BlurSpec;
4
+ export declare const composite: (opts: Omit<CompSpec, "type">) => CompSpec;
5
+ export declare const crop: (opts: Omit<CropSpec, "type">) => CropSpec;
6
+ export declare const dither: (opts: Omit<DitherSpec, "type">) => DitherSpec;
7
+ export declare const exif: (opts: Omit<EXIFSpec, "type">) => EXIFSpec;
8
+ export declare const extend: (opts: Omit<ExtendSpec, "type">) => ExtendSpec;
9
+ export declare const gamma: (opts: Omit<GammaSpec, "type">) => GammaSpec;
10
+ export declare const grayscale: (opts: Omit<GrayscaleSpec, "type">) => GrayscaleSpec;
11
+ export declare const hsbl: (opts: Omit<HSBLSpec, "type">) => HSBLSpec;
12
+ export declare const nest: (opts: Omit<NestSpec, "type">) => NestSpec;
13
+ export declare const output: (opts: Omit<OutputSpec, "type">) => OutputSpec;
14
+ export declare const resize: (opts: Omit<ResizeSpec, "type">) => ResizeSpec;
15
+ export declare const rotate: (opts: Omit<RotateSpec, "type">) => RotateSpec;
16
+ //# sourceMappingURL=ops.d.ts.map
package/ops.js ADDED
@@ -0,0 +1,30 @@
1
+ const defSpec = (type) => (opts) => ({ type, ...opts });
2
+ const blur = defSpec("blur");
3
+ const composite = defSpec("composite");
4
+ const crop = defSpec("crop");
5
+ const dither = defSpec("dither");
6
+ const exif = defSpec("exif");
7
+ const extend = defSpec("extend");
8
+ const gamma = defSpec("gamma");
9
+ const grayscale = defSpec("gray");
10
+ const hsbl = defSpec("hsbl");
11
+ const nest = defSpec("nest");
12
+ const output = defSpec("output");
13
+ const resize = defSpec("resize");
14
+ const rotate = defSpec("rotate");
15
+ export {
16
+ blur,
17
+ composite,
18
+ crop,
19
+ defSpec,
20
+ dither,
21
+ exif,
22
+ extend,
23
+ gamma,
24
+ grayscale,
25
+ hsbl,
26
+ nest,
27
+ output,
28
+ resize,
29
+ rotate
30
+ };
package/package.json ADDED
@@ -0,0 +1,125 @@
1
+ {
2
+ "name": "@thi.ng/imago",
3
+ "version": "0.1.0",
4
+ "description": "JSON & API-based declarative and extensible image processing trees/pipelines",
5
+ "type": "module",
6
+ "module": "./index.js",
7
+ "typings": "./index.d.ts",
8
+ "sideEffects": false,
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/thi-ng/umbrella.git"
12
+ },
13
+ "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/imago#readme",
14
+ "funding": [
15
+ {
16
+ "type": "github",
17
+ "url": "https://github.com/sponsors/postspectacular"
18
+ },
19
+ {
20
+ "type": "patreon",
21
+ "url": "https://patreon.com/thing_umbrella"
22
+ }
23
+ ],
24
+ "author": "Karsten Schmidt (https://thi.ng)",
25
+ "license": "Apache-2.0",
26
+ "scripts": {
27
+ "build": "yarn build:esbuild && yarn build:decl",
28
+ "build:decl": "tsc --declaration --emitDeclarationOnly",
29
+ "build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
30
+ "clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",
31
+ "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
32
+ "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
33
+ "doc:readme": "bun ../../tools/src/module-stats.ts && bun ../../tools/src/readme.ts",
34
+ "pub": "yarn npm publish --access public",
35
+ "test": "bun test"
36
+ },
37
+ "dependencies": {
38
+ "@thi.ng/api": "^8.9.25",
39
+ "@thi.ng/checks": "^3.5.0",
40
+ "@thi.ng/date": "^2.6.0",
41
+ "@thi.ng/defmulti": "^3.0.25",
42
+ "@thi.ng/errors": "^2.4.18",
43
+ "@thi.ng/file-io": "^1.3.2",
44
+ "@thi.ng/logger": "^3.0.2",
45
+ "@thi.ng/pixel": "^6.1.10",
46
+ "@thi.ng/pixel-dither": "^1.1.108",
47
+ "sharp": "^0.33.2"
48
+ },
49
+ "devDependencies": {
50
+ "@microsoft/api-extractor": "^7.40.1",
51
+ "@thi.ng/vectors": "^7.10.10",
52
+ "esbuild": "^0.20.0",
53
+ "rimraf": "^5.0.5",
54
+ "typedoc": "^0.25.7",
55
+ "typescript": "^5.3.3"
56
+ },
57
+ "keywords": [
58
+ "avif",
59
+ "batch",
60
+ "bitmap",
61
+ "blur",
62
+ "color",
63
+ "crop",
64
+ "composite",
65
+ "dither",
66
+ "exif",
67
+ "fileformat",
68
+ "gif",
69
+ "grayscale",
70
+ "image",
71
+ "jpeg",
72
+ "nested",
73
+ "no-browser",
74
+ "nodejs",
75
+ "pipeline",
76
+ "png",
77
+ "process",
78
+ "resize",
79
+ "svg",
80
+ "tiff",
81
+ "transformation",
82
+ "tree",
83
+ "typescript",
84
+ "webp"
85
+ ],
86
+ "publishConfig": {
87
+ "access": "public"
88
+ },
89
+ "browser": {
90
+ "process": false,
91
+ "setTimeout": false
92
+ },
93
+ "engines": {
94
+ "node": ">=18"
95
+ },
96
+ "files": [
97
+ "./*.js",
98
+ "./*.d.ts"
99
+ ],
100
+ "exports": {
101
+ ".": {
102
+ "default": "./index.js"
103
+ },
104
+ "./api": {
105
+ "default": "./api.js"
106
+ },
107
+ "./ops": {
108
+ "default": "./ops.js"
109
+ },
110
+ "./path": {
111
+ "default": "./path.js"
112
+ },
113
+ "./proc": {
114
+ "default": "./proc.js"
115
+ },
116
+ "./units": {
117
+ "default": "./units.js"
118
+ }
119
+ },
120
+ "thi.ng": {
121
+ "status": "alpha",
122
+ "year": 2024
123
+ },
124
+ "gitHead": "4513a1c703bdbf0f0867f03e547e47692e415fac\n"
125
+ }
package/path.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /// <reference types="node" />
2
+ import type { TypedArray } from "@thi.ng/api";
3
+ import type { ImgProcCtx } from "./api.js";
4
+ /**
5
+ * Expands/replaces all `{xyz}`-templated identifiers in given file path.
6
+ *
7
+ * @remarks
8
+ * The following IDs are supported. Any others will remain as is.
9
+ *
10
+ * - date: yyyyMMdd
11
+ * - time: HHmmss
12
+ * - name: original base filename (w/o ext)
13
+ * - sha1/224/256/384/512: truncated hash of output
14
+ * - w: current width
15
+ * - h: current height
16
+ *
17
+ * @param path
18
+ * @param buf
19
+ * @param ctx
20
+ */
21
+ export declare const formatPath: (path: string, buf: Buffer | TypedArray, ctx: ImgProcCtx) => string;
22
+ //# sourceMappingURL=path.d.ts.map
package/path.js ADDED
@@ -0,0 +1,34 @@
1
+ import { FMT_HHmmss_ALT, FMT_yyyyMMdd_ALT } from "@thi.ng/date";
2
+ import { illegalArgs as unsupported } from "@thi.ng/errors";
3
+ import { createHash } from "node:crypto";
4
+ import { basename } from "node:path";
5
+ const formatPath = (path, buf, ctx) => path.replace(/\{(\w+)\}/g, (match, id) => {
6
+ switch (id) {
7
+ case "name": {
8
+ !path && unsupported(
9
+ "cannot format `{name}`, image has no file source"
10
+ );
11
+ const name = basename(ctx.path);
12
+ const idx = name.lastIndexOf(".");
13
+ return idx > 0 ? name.substring(0, idx) : name;
14
+ }
15
+ case "sha1":
16
+ case "sha224":
17
+ case "sha256":
18
+ case "sha384":
19
+ case "sha512":
20
+ return createHash(id).update(buf).digest("hex").substring(0, 8);
21
+ case "w":
22
+ return String(ctx.size[0]);
23
+ case "h":
24
+ return String(ctx.size[1]);
25
+ case "date":
26
+ return FMT_yyyyMMdd_ALT();
27
+ case "time":
28
+ return FMT_HHmmss_ALT();
29
+ }
30
+ return match;
31
+ });
32
+ export {
33
+ formatPath
34
+ };
package/proc.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ /// <reference types="node" />
2
+ import sharp, { type Sharp } from "sharp";
3
+ import { type CompLayer, type ImgProcCtx, type ImgProcOpts, type ProcSpec } from "./api.js";
4
+ export declare const LOGGER: import("@thi.ng/logger").ProxyLogger;
5
+ export declare const processImage: (src: string | Buffer | Sharp, procs: ProcSpec[], opts?: Partial<ImgProcOpts>, parentCtx?: ImgProcCtx) => Promise<sharp.Sharp>;
6
+ /**
7
+ * Extensible polymorphic function performing a single image processing step.
8
+ *
9
+ * @remarks
10
+ * The function returns a tuple of `[img, bake-flag]`. If the flag (2nd value)
11
+ * is true, the returned image will be serialized/baked to an internal buffer
12
+ * (also wiping any EXIF!) after the current processing step and then used as
13
+ * input for the next processing step.
14
+ *
15
+ * Due to most ops in sharp's API merely setting internal state rather than
16
+ * applying changes directly, all size or channel changing procs will require
17
+ * "baking" in order to produce predictable results... The {@link processImage}
18
+ * function checks this flag after each processing step and its down to each
19
+ * processor to determine if baking is required or not...
20
+ *
21
+ * @param spec
22
+ * @param img
23
+ * @param ctx
24
+ **/
25
+ export declare const process: import("@thi.ng/defmulti").MultiFn3<ProcSpec, sharp.Sharp, ImgProcCtx, Promise<[sharp.Sharp, boolean]>>;
26
+ export declare const defLayer: import("@thi.ng/defmulti").MultiFn3<CompLayer, sharp.Sharp, ImgProcCtx, Promise<sharp.OverlayOptions>>;
27
+ //# sourceMappingURL=proc.d.ts.map
package/proc.js ADDED
@@ -0,0 +1,362 @@
1
+ import { typedArray } from "@thi.ng/api";
2
+ import { isArrayBufferView, isNumber, isString } from "@thi.ng/checks";
3
+ import { defmulti } from "@thi.ng/defmulti";
4
+ import { illegalArgs } from "@thi.ng/errors";
5
+ import { readText, writeFile, writeJSON } from "@thi.ng/file-io";
6
+ import { ROOT } from "@thi.ng/logger";
7
+ import { ABGR8888, GRAY8, Lane, intBuffer } from "@thi.ng/pixel";
8
+ import {
9
+ ATKINSON,
10
+ BURKES,
11
+ DIFFUSION_2D,
12
+ DIFFUSION_COLUMN,
13
+ DIFFUSION_ROW,
14
+ FLOYD_STEINBERG,
15
+ JARVIS_JUDICE_NINKE,
16
+ SIERRA2,
17
+ STUCKI,
18
+ defBayer,
19
+ ditherWith,
20
+ orderedDither
21
+ } from "@thi.ng/pixel-dither";
22
+ import { join, resolve } from "node:path";
23
+ import sharp, {} from "sharp";
24
+ import {
25
+ GRAVITY_POSITION
26
+ } from "./api.js";
27
+ import { formatPath } from "./path.js";
28
+ import {
29
+ coerceColor,
30
+ computeMargins,
31
+ computeSize,
32
+ ensureSize,
33
+ gravityPosition,
34
+ positionOrGravity
35
+ } from "./units.js";
36
+ const LOGGER = ROOT.childLogger("imgproc");
37
+ const DITHER_KERNELS = {
38
+ atkinson: ATKINSON,
39
+ burkes: BURKES,
40
+ column: DIFFUSION_COLUMN,
41
+ diffusion: DIFFUSION_2D,
42
+ floyd: FLOYD_STEINBERG,
43
+ jarvis: JARVIS_JUDICE_NINKE,
44
+ row: DIFFUSION_ROW,
45
+ sierra: SIERRA2,
46
+ stucki: STUCKI
47
+ };
48
+ const processImage = async (src, procs, opts = {}, parentCtx) => {
49
+ let img = isString(src) || isArrayBufferView(src.buffer) ? sharp(src) : src;
50
+ const meta = await img.metadata();
51
+ ensureSize(meta);
52
+ const ctx = {
53
+ path: isString(src) ? src : parentCtx?.path,
54
+ logger: opts?.logger || LOGGER,
55
+ size: [meta.width, meta.height],
56
+ channels: meta.channels,
57
+ meta,
58
+ opts
59
+ };
60
+ let bake;
61
+ for (let proc of procs) {
62
+ ctx.logger.debug("processing spec:", proc);
63
+ [img, bake] = await process(proc, img, ctx);
64
+ if (!bake) {
65
+ ctx.logger.debug("skip baking processor's results...");
66
+ continue;
67
+ }
68
+ const { data, info } = await img.raw().toBuffer({ resolveWithObject: true });
69
+ ctx.size = [info.width, info.height];
70
+ ctx.channels = info.channels;
71
+ img = sharp(data, {
72
+ raw: {
73
+ width: info.width,
74
+ height: info.height,
75
+ channels: info.channels
76
+ }
77
+ });
78
+ }
79
+ return img;
80
+ };
81
+ const process = defmulti(
82
+ (spec) => spec.type,
83
+ {},
84
+ {
85
+ blur: async (spec, input) => {
86
+ const { radius } = spec;
87
+ return [input.blur(1 + radius / 2), false];
88
+ },
89
+ composite: async (spec, input, ctx) => {
90
+ const { layers } = spec;
91
+ const layerSpecs = await Promise.all(
92
+ layers.map((l) => defLayer(l, input, ctx))
93
+ );
94
+ return [input.composite(layerSpecs), false];
95
+ },
96
+ crop: async (spec, input, ctx) => {
97
+ const { border, gravity, pos, size, ref, unit } = spec;
98
+ if (border == null && size == null)
99
+ illegalArgs("require `border` or `size` option");
100
+ if (border != null) {
101
+ const sides = computeMargins(border, ctx.size, ref, unit);
102
+ const [left2, right, top2, bottom] = sides;
103
+ return [
104
+ input.extract({
105
+ left: left2,
106
+ top: top2,
107
+ width: ctx.size[0] - left2 - right,
108
+ height: ctx.size[1] - top2 - bottom
109
+ }),
110
+ true
111
+ ];
112
+ }
113
+ const $size = computeSize(size, ctx.size, unit);
114
+ let left = 0, top = 0;
115
+ if (pos) {
116
+ ({ left = 0, top = 0 } = positionOrGravity(pos, gravity, $size, ctx.size, unit) || {});
117
+ } else {
118
+ [left, top] = gravityPosition(gravity || "c", $size, ctx.size);
119
+ }
120
+ return [
121
+ input.extract({
122
+ left,
123
+ top,
124
+ width: $size[0],
125
+ height: $size[1]
126
+ }),
127
+ true
128
+ ];
129
+ },
130
+ dither: async (spec, input, ctx) => {
131
+ let { mode, num = 2, rgb = false, size = 8 } = spec;
132
+ const [w, h] = ctx.size;
133
+ let raw;
134
+ if (rgb) {
135
+ const tmp = await input.clone().ensureAlpha(1).toColorspace("srgb").raw().toBuffer({ resolveWithObject: true });
136
+ raw = tmp.data.buffer;
137
+ rgb = tmp.info.channels === 4;
138
+ } else {
139
+ raw = (await input.clone().grayscale().raw().toBuffer()).buffer;
140
+ }
141
+ let img = intBuffer(
142
+ w,
143
+ h,
144
+ rgb ? ABGR8888 : GRAY8,
145
+ typedArray(rgb ? "u32" : "u8", raw)
146
+ );
147
+ if (mode === "bayer") {
148
+ orderedDither(
149
+ img,
150
+ defBayer(size),
151
+ rgb ? [num, num, num] : [num]
152
+ );
153
+ } else {
154
+ ditherWith(DITHER_KERNELS[mode], img, {
155
+ channels: rgb ? [Lane.RED, Lane.GREEN, Lane.BLUE] : void 0
156
+ });
157
+ }
158
+ if (!rgb)
159
+ img = img.as(ABGR8888);
160
+ return [
161
+ sharp(new Uint8Array(img.data.buffer), {
162
+ raw: {
163
+ width: img.width,
164
+ height: img.height,
165
+ channels: 4
166
+ }
167
+ }),
168
+ true
169
+ ];
170
+ },
171
+ exif: async (spec, input) => {
172
+ const { tags } = spec;
173
+ return [input.withExif(tags), false];
174
+ },
175
+ extend: async (spec, input, ctx) => {
176
+ const { bg, border, mode, ref, unit } = spec;
177
+ const sides = computeMargins(border, ctx.size, ref, unit);
178
+ const [left, right, top, bottom] = sides;
179
+ return [
180
+ input.extend({
181
+ left,
182
+ right,
183
+ top,
184
+ bottom,
185
+ background: coerceColor(bg || "#000"),
186
+ extendWith: mode
187
+ }),
188
+ true
189
+ ];
190
+ },
191
+ gamma: async (spec, input) => {
192
+ const { gamma } = spec;
193
+ return [input.gamma(gamma, 1), false];
194
+ },
195
+ gray: async (spec, input) => {
196
+ const { gamma } = spec;
197
+ if (gamma !== false) {
198
+ input = input.gamma(isNumber(gamma) ? gamma : void 0);
199
+ }
200
+ return [input.grayscale(), true];
201
+ },
202
+ hsbl: async (spec, input) => {
203
+ const { h = 0, s = 1, b = 1, l = 0 } = spec;
204
+ return [
205
+ input.modulate({
206
+ hue: h,
207
+ brightness: b,
208
+ saturation: s,
209
+ lightness: l * 255
210
+ }),
211
+ true
212
+ ];
213
+ },
214
+ nest: async (spec, input, ctx) => {
215
+ const { procs } = spec;
216
+ ctx.logger.debug("--- nest start ---");
217
+ await processImage(input.clone(), procs, ctx.opts, ctx);
218
+ ctx.logger.debug("--- nest end ---");
219
+ return [input, false];
220
+ },
221
+ output: async (spec, input, ctx) => {
222
+ const opts = spec;
223
+ const outDir = resolve(ctx.opts.outDir || ".");
224
+ let output = input.clone();
225
+ if (opts.raw) {
226
+ const { alpha = false, meta = false } = opts.raw !== true ? opts.raw : {};
227
+ if (alpha)
228
+ output = output.ensureAlpha();
229
+ const { data, info } = await output.raw().toBuffer({ resolveWithObject: true });
230
+ const path = join(outDir, formatPath(opts.path, data, ctx));
231
+ writeFile(path, data, null, ctx.logger);
232
+ if (meta) {
233
+ writeJSON(
234
+ path + ".meta.json",
235
+ info,
236
+ void 0,
237
+ void 0,
238
+ ctx.logger
239
+ );
240
+ }
241
+ return [input, false];
242
+ }
243
+ let format = /\.(\w+)$/.exec(opts.path)?.[1];
244
+ switch (format) {
245
+ case "avif":
246
+ if (opts.avif)
247
+ output = output.avif(opts.avif);
248
+ break;
249
+ case "gif":
250
+ if (opts.gif)
251
+ output = output.gif(opts.gif);
252
+ break;
253
+ case "jpg":
254
+ case "jpeg":
255
+ if (opts.jpeg)
256
+ output = output.jpeg(opts.jpeg);
257
+ break;
258
+ case "jp2":
259
+ if (opts.jp2)
260
+ output = output.jp2(opts.jp2);
261
+ break;
262
+ case "jxl":
263
+ if (opts.jxl)
264
+ output = output.jxl(opts.jxl);
265
+ break;
266
+ case "png":
267
+ if (opts.png)
268
+ output = output.png(opts.png);
269
+ break;
270
+ case "tiff":
271
+ if (opts.tiff)
272
+ output = output.tiff(opts.tiff);
273
+ break;
274
+ case "webp":
275
+ if (opts.webp)
276
+ output = output.webp(opts.webp);
277
+ break;
278
+ }
279
+ if (opts.tile)
280
+ output = output.tile(opts.tile);
281
+ if (format)
282
+ output = output.toFormat(format);
283
+ const result = await output.toBuffer();
284
+ writeFile(
285
+ join(outDir, formatPath(opts.path, result, ctx)),
286
+ result,
287
+ null,
288
+ ctx.logger
289
+ );
290
+ return [input, false];
291
+ },
292
+ resize: async (spec, input, ctx) => {
293
+ const { bg, filter, fit, gravity, size, unit } = spec;
294
+ const [width, height] = computeSize(size, ctx.size, unit);
295
+ return [
296
+ input.resize({
297
+ width,
298
+ height,
299
+ fit,
300
+ kernel: filter,
301
+ position: gravity ? GRAVITY_POSITION[gravity] : void 0,
302
+ background: bg ? coerceColor(bg) : void 0
303
+ }),
304
+ true
305
+ ];
306
+ },
307
+ rotate: async (spec, input, _) => {
308
+ const { angle, bg, flipX, flipY } = spec;
309
+ if (flipX)
310
+ input = input.flop();
311
+ if (flipY)
312
+ input = input.flip();
313
+ return [input.rotate(angle, { background: coerceColor(bg) }), true];
314
+ }
315
+ }
316
+ );
317
+ const defLayer = defmulti(
318
+ (x) => x.type,
319
+ {},
320
+ {
321
+ img: async (layer, _, ctx) => {
322
+ const { gravity, path, pos, size, unit, ...opts } = layer;
323
+ const input = sharp(path);
324
+ const meta = await input.metadata();
325
+ let imgSize = [meta.width, meta.height];
326
+ const $pos = positionOrGravity(
327
+ pos,
328
+ gravity,
329
+ imgSize,
330
+ ctx.size,
331
+ unit
332
+ );
333
+ if (!size)
334
+ return { input: path, ...$pos, ...opts };
335
+ ensureSize(meta);
336
+ imgSize = computeSize(size, imgSize, unit);
337
+ return {
338
+ input: await input.resize(imgSize[0], imgSize[1]).png({ compressionLevel: 0 }).toBuffer(),
339
+ ...$pos,
340
+ ...opts
341
+ };
342
+ },
343
+ svg: async (layer, _, ctx) => {
344
+ let { body, gravity, path, pos, unit, ...opts } = layer;
345
+ if (path)
346
+ body = readText(path, ctx.logger);
347
+ const w = +(/width="(\d+)"/.exec(body)?.[1] || 0);
348
+ const h = +(/height="(\d+)"/.exec(body)?.[1] || 0);
349
+ return {
350
+ input: Buffer.from(body),
351
+ ...positionOrGravity(pos, gravity, [w, h], ctx.size, unit),
352
+ ...opts
353
+ };
354
+ }
355
+ }
356
+ );
357
+ export {
358
+ LOGGER,
359
+ defLayer,
360
+ process,
361
+ processImage
362
+ };
package/units.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { Nullable } from "@thi.ng/api";
2
+ import type { Metadata } from "sharp";
3
+ import { type Color, type CompLayer, type Dim, type Gravity, type Sides, type Size, type SizeRef, type SizeUnit } from "./api.js";
4
+ export declare const ensureSize: (meta: Metadata) => false;
5
+ export declare const coerceColor: (col: Color) => string | {
6
+ r: number;
7
+ g: number;
8
+ b: number;
9
+ alpha?: number | undefined;
10
+ };
11
+ export declare const positionOrGravity: (pos: CompLayer["pos"], gravity: Nullable<Gravity>, [w, h]: Dim, [parentW, parentH]: Dim, unit?: SizeUnit) => {
12
+ gravity: string;
13
+ left?: undefined;
14
+ top?: undefined;
15
+ } | {
16
+ left: number | undefined;
17
+ top: number | undefined;
18
+ gravity?: undefined;
19
+ } | undefined;
20
+ export declare const gravityPosition: (gravity: Gravity, [w, h]: Dim, [parentW, parentH]: Dim) => number[];
21
+ export declare const refSize: ([w, h]: Dim, ref?: SizeRef) => number;
22
+ export declare const computeSize: (size: Size, curr: Dim, unit?: SizeUnit) => Dim;
23
+ export declare const computeMargins: (size: Size | Sides, curr: Dim, ref?: SizeRef, unit?: SizeUnit) => Sides;
24
+ //# sourceMappingURL=units.d.ts.map