@thi.ng/imago 0.2.0 → 0.3.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/ops/extend.js ADDED
@@ -0,0 +1,20 @@
1
+ import { coerceColor, computeMargins } from "../units.js";
2
+ const extendProc = async (spec, input, ctx) => {
3
+ const { bg, border, mode, ref, unit } = spec;
4
+ const sides = computeMargins(border, ctx.size, ref, unit);
5
+ const [left, right, top, bottom] = sides;
6
+ return [
7
+ input.extend({
8
+ left,
9
+ right,
10
+ top,
11
+ bottom,
12
+ background: coerceColor(bg || "#000"),
13
+ extendWith: mode
14
+ }),
15
+ true
16
+ ];
17
+ };
18
+ export {
19
+ extendProc
20
+ };
package/ops/gamma.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Processor } from "../api.js";
2
+ export declare const gammaProc: Processor;
3
+ //# sourceMappingURL=gamma.d.ts.map
package/ops/gamma.js ADDED
@@ -0,0 +1,7 @@
1
+ const gammaProc = async (spec, input) => {
2
+ const { gamma } = spec;
3
+ return [input.gamma(gamma, 1), false];
4
+ };
5
+ export {
6
+ gammaProc
7
+ };
@@ -0,0 +1,3 @@
1
+ import type { Processor } from "../api.js";
2
+ export declare const grayscaleProc: Processor;
3
+ //# sourceMappingURL=grayscale.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { isNumber } from "@thi.ng/checks";
2
+ const grayscaleProc = async (spec, input) => {
3
+ const { gamma } = spec;
4
+ if (gamma !== false) {
5
+ input = input.gamma(isNumber(gamma) ? gamma : void 0);
6
+ }
7
+ return [input.grayscale(), true];
8
+ };
9
+ export {
10
+ grayscaleProc
11
+ };
package/ops/hsbl.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Processor } from "../api.js";
2
+ export declare const hsblProc: Processor;
3
+ //# sourceMappingURL=hsbl.d.ts.map
package/ops/hsbl.js ADDED
@@ -0,0 +1,15 @@
1
+ const hsblProc = async (spec, input) => {
2
+ const { h = 0, s = 1, b = 1, l = 0 } = spec;
3
+ return [
4
+ input.modulate({
5
+ hue: h,
6
+ brightness: b,
7
+ saturation: s,
8
+ lightness: l * 255
9
+ }),
10
+ true
11
+ ];
12
+ };
13
+ export {
14
+ hsblProc
15
+ };
package/ops/nest.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Processor } from "../api.js";
2
+ export declare const nestProc: Processor;
3
+ //# sourceMappingURL=nest.d.ts.map
package/ops/nest.js ADDED
@@ -0,0 +1,13 @@
1
+ import { processImage } from "../proc.js";
2
+ const nestProc = async (spec, input, ctx) => {
3
+ const { procs } = spec;
4
+ ctx.logger.debug("--- nest start ---");
5
+ await Promise.all(
6
+ procs.map((p) => processImage(input.clone(), p, ctx.opts, ctx))
7
+ );
8
+ ctx.logger.debug("--- nest end ---");
9
+ return [input, false];
10
+ };
11
+ export {
12
+ nestProc
13
+ };
@@ -0,0 +1,3 @@
1
+ import type { Processor } from "../api.js";
2
+ export declare const outputProc: Processor;
3
+ //# sourceMappingURL=output.d.ts.map
package/ops/output.js ADDED
@@ -0,0 +1,95 @@
1
+ import { writeFile, writeJSON } from "@thi.ng/file-io";
2
+ import { join, resolve } from "node:path";
3
+ import { formatPath } from "../path.js";
4
+ import { isPlainObject } from "@thi.ng/checks";
5
+ const outputProc = async (spec, input, ctx) => {
6
+ const opts = spec;
7
+ const outDir = resolve(ctx.opts.outDir || ".");
8
+ let output = input.clone();
9
+ if (opts.raw) {
10
+ await outputRaw(opts, output, ctx, outDir);
11
+ return [input, false];
12
+ }
13
+ if (ctx.meta.exif && ctx.opts.keepEXIF) {
14
+ ctx.logger.warn(
15
+ "TODO injecting & merging EXIF in output still not supported"
16
+ );
17
+ }
18
+ if (Object.keys(ctx.exif).length) {
19
+ ctx.logger.debug("setting custom EXIF", ctx.exif);
20
+ output = output.withExif(ctx.exif);
21
+ }
22
+ if (ctx.iccFile && ctx.opts.keepICC) {
23
+ ctx.logger.debug("using stored ICC profile:", ctx.iccFile);
24
+ output = output.withIccProfile(ctx.iccFile);
25
+ }
26
+ let format = /\.(\w+)$/.exec(opts.path)?.[1];
27
+ switch (format) {
28
+ case "avif":
29
+ if (opts.avif)
30
+ output = output.avif(opts.avif);
31
+ break;
32
+ case "gif":
33
+ if (opts.gif)
34
+ output = output.gif(opts.gif);
35
+ break;
36
+ case "jpg":
37
+ case "jpeg":
38
+ if (opts.jpeg)
39
+ output = output.jpeg(opts.jpeg);
40
+ break;
41
+ case "jp2":
42
+ if (opts.jp2)
43
+ output = output.jp2(opts.jp2);
44
+ break;
45
+ case "jxl":
46
+ if (opts.jxl)
47
+ output = output.jxl(opts.jxl);
48
+ break;
49
+ case "png":
50
+ if (opts.png)
51
+ output = output.png(opts.png);
52
+ break;
53
+ case "tiff":
54
+ if (opts.tiff)
55
+ output = output.tiff(opts.tiff);
56
+ break;
57
+ case "webp":
58
+ if (opts.webp)
59
+ output = output.webp(opts.webp);
60
+ break;
61
+ }
62
+ if (opts.tile)
63
+ output = output.tile(opts.tile);
64
+ if (format)
65
+ output = output.toFormat(format);
66
+ const result = await output.toBuffer();
67
+ const path = join(
68
+ outDir,
69
+ formatPath(opts.path, ctx, spec, result)
70
+ );
71
+ writeFile(path, result, null, ctx.logger);
72
+ ctx.outputs[opts.id] = path;
73
+ return [input, false];
74
+ };
75
+ const outputRaw = async (opts, output, ctx, outDir) => {
76
+ const { alpha = false, meta = false } = isPlainObject(opts.raw) ? opts.raw : {};
77
+ if (alpha)
78
+ output = output.ensureAlpha();
79
+ const { data, info } = await output.raw().toBuffer({ resolveWithObject: true });
80
+ const path = join(outDir, formatPath(opts.path, ctx, opts, data));
81
+ writeFile(path, data, null, ctx.logger);
82
+ ctx.outputs[opts.id] = path;
83
+ if (meta) {
84
+ writeJSON(
85
+ path + ".meta.json",
86
+ { ...info, exif: ctx.exif },
87
+ void 0,
88
+ void 0,
89
+ ctx.logger
90
+ );
91
+ }
92
+ };
93
+ export {
94
+ outputProc
95
+ };
@@ -0,0 +1,3 @@
1
+ import { type Processor } from "../api.js";
2
+ export declare const resizeProc: Processor;
3
+ //# sourceMappingURL=resize.d.ts.map
package/ops/resize.js ADDED
@@ -0,0 +1,20 @@
1
+ import { GRAVITY_POSITION } from "../api.js";
2
+ import { coerceColor, computeSize } from "../units.js";
3
+ const resizeProc = async (spec, input, ctx) => {
4
+ const { bg, filter, fit, gravity, size, unit } = spec;
5
+ const [width, height] = computeSize(size, ctx.size, unit);
6
+ return [
7
+ input.resize({
8
+ width,
9
+ height,
10
+ fit,
11
+ kernel: filter,
12
+ position: gravity ? GRAVITY_POSITION[gravity] : void 0,
13
+ background: bg ? coerceColor(bg) : void 0
14
+ }),
15
+ true
16
+ ];
17
+ };
18
+ export {
19
+ resizeProc
20
+ };
@@ -0,0 +1,3 @@
1
+ import type { Processor } from "../api.js";
2
+ export declare const rotateProc: Processor;
3
+ //# sourceMappingURL=rotate.d.ts.map
package/ops/rotate.js ADDED
@@ -0,0 +1,12 @@
1
+ import { coerceColor } from "../units.js";
2
+ const rotateProc = async (spec, input, _) => {
3
+ const { angle, bg, flipX, flipY } = spec;
4
+ if (flipX)
5
+ input = input.flop();
6
+ if (flipY)
7
+ input = input.flip();
8
+ return [input.rotate(angle, { background: coerceColor(bg) }), true];
9
+ };
10
+ export {
11
+ rotateProc
12
+ };
package/ops.d.ts CHANGED
@@ -1,16 +1,16 @@
1
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;
2
+ export declare const defSpec: <T extends ProcSpec>(op: T["op"]) => (opts: Omit<T, "op">) => T;
3
+ export declare const blur: (opts: Omit<BlurSpec, "op">) => BlurSpec;
4
+ export declare const composite: (opts: Omit<CompSpec, "op">) => CompSpec;
5
+ export declare const crop: (opts: Omit<CropSpec, "op">) => CropSpec;
6
+ export declare const dither: (opts: Omit<DitherSpec, "op">) => DitherSpec;
7
+ export declare const exif: (opts: Omit<EXIFSpec, "op">) => EXIFSpec;
8
+ export declare const extend: (opts: Omit<ExtendSpec, "op">) => ExtendSpec;
9
+ export declare const gamma: (opts: Omit<GammaSpec, "op">) => GammaSpec;
10
+ export declare const grayscale: (opts: Omit<GrayscaleSpec, "op">) => GrayscaleSpec;
11
+ export declare const hsbl: (opts: Omit<HSBLSpec, "op">) => HSBLSpec;
12
+ export declare const nest: (opts: Omit<NestSpec, "op">) => NestSpec;
13
+ export declare const output: (opts: Omit<OutputSpec, "op">) => OutputSpec;
14
+ export declare const resize: (opts: Omit<ResizeSpec, "op">) => ResizeSpec;
15
+ export declare const rotate: (opts: Omit<RotateSpec, "op">) => RotateSpec;
16
16
  //# sourceMappingURL=ops.d.ts.map
package/ops.js CHANGED
@@ -1,4 +1,4 @@
1
- const defSpec = (type) => (opts) => ({ type, ...opts });
1
+ const defSpec = (op) => (opts) => ({ op, ...opts });
2
2
  const blur = defSpec("blur");
3
3
  const composite = defSpec("composite");
4
4
  const crop = defSpec("crop");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/imago",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "JSON & API-based declarative and extensible image processing trees/pipelines",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -44,6 +44,7 @@
44
44
  "@thi.ng/logger": "^3.0.2",
45
45
  "@thi.ng/pixel": "^6.1.11",
46
46
  "@thi.ng/pixel-dither": "^1.1.109",
47
+ "exif-reader": "^2.0.1",
47
48
  "sharp": "^0.33.2"
48
49
  },
49
50
  "devDependencies": {
@@ -95,7 +96,9 @@
95
96
  },
96
97
  "files": [
97
98
  "./*.js",
98
- "./*.d.ts"
99
+ "./*.d.ts",
100
+ "layers",
101
+ "ops"
99
102
  ],
100
103
  "exports": {
101
104
  ".": {
@@ -121,5 +124,5 @@
121
124
  "status": "alpha",
122
125
  "year": 2024
123
126
  },
124
- "gitHead": "16f2b92b5410bd35dcde6c2971c8e62783ebc472\n"
127
+ "gitHead": "c744c5f804ca763bc03163a41a0e150d8e7a9e54\n"
125
128
  }
package/proc.d.ts CHANGED
@@ -1,17 +1,32 @@
1
1
  /// <reference types="node" />
2
2
  import sharp, { type Sharp } from "sharp";
3
- import { type CompLayer, type ImgProcCtx, type ImgProcOpts, type ProcSpec } from "./api.js";
3
+ import type { ImgProcCtx, ImgProcOpts, ProcSpec } from "./api.js";
4
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<{
5
+ /**
6
+ * Main API function. Takes an image input (file path, buffer or existing Sharp
7
+ * instance) and applies given processing pipeline specs in sequence. Returns a
8
+ * promise of final processed image, input metadata (if any) and an object of
9
+ * all written output paths (keyed by each output's {@link OutputSpec.id}). The
10
+ * process can be configured via provided options.
11
+ *
12
+ * @remarks
13
+ * The `parentCtx` arg is internal use only!
14
+ *
15
+ * @param src
16
+ * @param specs
17
+ * @param opts
18
+ * @param parentCtx
19
+ */
20
+ export declare const processImage: (src: string | Buffer | Sharp, specs: ProcSpec[], opts?: Partial<ImgProcOpts>, parentCtx?: ImgProcCtx) => Promise<{
6
21
  img: sharp.Sharp;
7
22
  meta: sharp.Metadata;
8
- outputs: string[];
23
+ outputs: Record<string, string>;
9
24
  }>;
10
25
  /**
11
26
  * Extensible polymorphic function performing a single image processing step.
12
27
  *
13
28
  * @remarks
14
- * The function returns a tuple of `[img, bake-flag]`. If the flag (2nd value)
29
+ * The function returns a tuple of `[img, bakeFlag]`. If the flag (2nd value)
15
30
  * is true, the returned image will be serialized/baked to an internal buffer
16
31
  * (also wiping any EXIF!) after the current processing step and then used as
17
32
  * input for the next processing step.
@@ -22,10 +37,13 @@ export declare const processImage: (src: string | Buffer | Sharp, procs: ProcSpe
22
37
  * function checks this flag after each processing step and its down to each
23
38
  * processor to determine if baking is required or not...
24
39
  *
40
+ * To add support for a custom operation/processor, call: `processor.add("myid",
41
+ * myProcessor)`. Note that registered IDs should be unique (but can also
42
+ * override existing impls).
43
+ *
25
44
  * @param spec
26
45
  * @param img
27
46
  * @param ctx
28
47
  **/
29
- export declare const process: import("@thi.ng/defmulti").MultiFn3<ProcSpec, sharp.Sharp, ImgProcCtx, Promise<[sharp.Sharp, boolean]>>;
30
- export declare const defLayer: import("@thi.ng/defmulti").MultiFn3<CompLayer, sharp.Sharp, ImgProcCtx, Promise<sharp.OverlayOptions>>;
48
+ export declare const processor: import("@thi.ng/defmulti").MultiFn3<ProcSpec, sharp.Sharp, ImgProcCtx, Promise<[sharp.Sharp, boolean]>>;
31
49
  //# sourceMappingURL=proc.d.ts.map