@thi.ng/imago 0.4.0 → 0.5.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.js CHANGED
@@ -3,3 +3,21 @@ export * from "./ops.js";
3
3
  export * from "./path.js";
4
4
  export * from "./proc.js";
5
5
  export * from "./units.js";
6
+ export * from "./layers/color.js";
7
+ export * from "./layers/image.js";
8
+ export * from "./layers/raw.js";
9
+ export * from "./layers/svg.js";
10
+ export * from "./layers/text.js";
11
+ export * from "./ops/blur.js";
12
+ export * from "./ops/composite.js";
13
+ export * from "./ops/crop.js";
14
+ export * from "./ops/dither.js";
15
+ export * from "./ops/exif.js";
16
+ export * from "./ops/extend.js";
17
+ export * from "./ops/gamma.js";
18
+ export * from "./ops/grayscale.js";
19
+ export * from "./ops/hsbl.js";
20
+ export * from "./ops/nest.js";
21
+ export * from "./ops/output.js";
22
+ export * from "./ops/resize.js";
23
+ export * from "./ops/rotate.js";
@@ -0,0 +1,3 @@
1
+ import type { CompLayerFn } from "../api.js";
2
+ export declare const colorLayerImpl: CompLayerFn;
3
+ //# sourceMappingURL=color.d.ts.map
@@ -0,0 +1,31 @@
1
+ import { coerceColor, computeSize, positionOrGravity } from "../units.js";
2
+ const colorLayerImpl = async (layer, _, ctx) => {
3
+ const {
4
+ type: __,
5
+ bg,
6
+ gravity,
7
+ path,
8
+ pos,
9
+ ref,
10
+ size,
11
+ unit,
12
+ ...opts
13
+ } = layer;
14
+ const layerSize = size ? computeSize(size, ctx.size, ref, unit) : ctx.size;
15
+ const $pos = positionOrGravity(layerSize, ctx.size, layer);
16
+ return {
17
+ input: {
18
+ create: {
19
+ width: layerSize[0],
20
+ height: layerSize[1],
21
+ channels: 4,
22
+ background: coerceColor(bg)
23
+ }
24
+ },
25
+ ...$pos,
26
+ ...opts
27
+ };
28
+ };
29
+ export {
30
+ colorLayerImpl
31
+ };
package/layers/image.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import type { CompLayerFn } from "../api.js";
2
- export declare const imageLayer: CompLayerFn;
2
+ export declare const imageLayerImpl: CompLayerFn;
3
3
  //# sourceMappingURL=image.d.ts.map
package/layers/image.js CHANGED
@@ -1,29 +1,41 @@
1
+ import { illegalArgs } from "@thi.ng/errors";
1
2
  import sharp from "sharp";
2
3
  import { computeSize, ensureSize, positionOrGravity } from "../units.js";
3
- const imageLayer = async (layer, _, ctx) => {
4
+ const imageLayerImpl = async (layer, _, ctx) => {
4
5
  const {
5
6
  type: __,
7
+ buffer,
6
8
  gravity,
7
9
  path,
8
10
  pos,
11
+ ref,
9
12
  size,
10
13
  unit,
11
14
  ...opts
12
15
  } = layer;
13
- const input = sharp(path);
16
+ if (!(path || buffer))
17
+ illegalArgs("missing image source");
18
+ const input = sharp(path || buffer);
14
19
  const meta = await input.metadata();
15
20
  let imgSize = [meta.width, meta.height];
16
- const $pos = positionOrGravity(pos, gravity, imgSize, ctx.size, unit);
21
+ if (size)
22
+ imgSize = computeSize(size, imgSize, ref, unit);
23
+ const $pos = positionOrGravity(imgSize, ctx.size, layer);
17
24
  if (!size)
18
25
  return { input: path, ...$pos, ...opts };
19
26
  ensureSize(meta);
20
- imgSize = computeSize(size, imgSize, unit);
27
+ const { data, info } = await input.resize(imgSize[0], imgSize[1], { fit: "fill" }).raw().toBuffer({ resolveWithObject: true });
21
28
  return {
22
- input: await input.resize(imgSize[0], imgSize[1]).png({ compressionLevel: 0 }).toBuffer(),
29
+ input: data,
30
+ raw: {
31
+ width: info.width,
32
+ height: info.height,
33
+ channels: info.channels
34
+ },
23
35
  ...$pos,
24
36
  ...opts
25
37
  };
26
38
  };
27
39
  export {
28
- imageLayer
40
+ imageLayerImpl
29
41
  };
@@ -0,0 +1,3 @@
1
+ import type { CompLayerFn } from "../api.js";
2
+ export declare const rawLayerImpl: CompLayerFn;
3
+ //# sourceMappingURL=raw.d.ts.map
package/layers/raw.js ADDED
@@ -0,0 +1,28 @@
1
+ import { positionOrGravity } from "../units.js";
2
+ const rawLayerImpl = async (layer, _, ctx) => {
3
+ const {
4
+ type: __,
5
+ buffer,
6
+ channels,
7
+ gravity,
8
+ pos,
9
+ ref,
10
+ size,
11
+ unit,
12
+ ...opts
13
+ } = layer;
14
+ const $pos = positionOrGravity(size, ctx.size, layer);
15
+ return {
16
+ input: Buffer.from(buffer.buffer),
17
+ raw: {
18
+ width: size[0],
19
+ height: size[1],
20
+ channels
21
+ },
22
+ ...$pos,
23
+ ...opts
24
+ };
25
+ };
26
+ export {
27
+ rawLayerImpl
28
+ };
package/layers/svg.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import type { CompLayerFn } from "../api.js";
2
- export declare const svgLayer: CompLayerFn;
2
+ export declare const svgLayerImpl: CompLayerFn;
3
3
  //# sourceMappingURL=svg.d.ts.map
package/layers/svg.js CHANGED
@@ -1,17 +1,30 @@
1
1
  import { readText } from "@thi.ng/file-io";
2
2
  import { positionOrGravity } from "../units.js";
3
- const svgLayer = async (layer, _, ctx) => {
4
- let { type: __, body, gravity, path, pos, unit, ...opts } = layer;
3
+ import { illegalArgs } from "@thi.ng/errors";
4
+ const svgLayerImpl = async (layer, _, ctx) => {
5
+ let {
6
+ type: __,
7
+ body,
8
+ gravity,
9
+ origin,
10
+ path,
11
+ pos,
12
+ ref,
13
+ unit,
14
+ ...opts
15
+ } = layer;
5
16
  if (path)
6
17
  body = readText(path, ctx.logger);
18
+ if (!body)
19
+ illegalArgs("missing SVG doc");
7
20
  const w = +(/width="(\d+)"/.exec(body)?.[1] || 0);
8
21
  const h = +(/height="(\d+)"/.exec(body)?.[1] || 0);
9
22
  return {
10
23
  input: Buffer.from(body),
11
- ...positionOrGravity(pos, gravity, [w, h], ctx.size, unit),
24
+ ...positionOrGravity([w, h], ctx.size, layer),
12
25
  ...opts
13
26
  };
14
27
  };
15
28
  export {
16
- svgLayer
29
+ svgLayerImpl
17
30
  };
package/layers/text.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import type { CompLayerFn } from "../api.js";
2
- export declare const textLayer: CompLayerFn;
2
+ export declare const textLayerImpl: CompLayerFn;
3
3
  //# sourceMappingURL=text.d.ts.map
package/layers/text.js CHANGED
@@ -1,32 +1,35 @@
1
1
  import { isFunction } from "@thi.ng/checks";
2
- import { writeText } from "@thi.ng/file-io";
3
2
  import { XML_SVG } from "@thi.ng/prefixes";
4
- import { computeSize, positionOrGravity } from "../units.js";
5
- const textLayer = async (layer, _, ctx) => {
6
- const {
3
+ import { computeSize, gravityFlags, positionOrGravity } from "../units.js";
4
+ import { readText } from "@thi.ng/file-io";
5
+ const textLayerImpl = async (layer, _, ctx) => {
6
+ let {
7
7
  type: __,
8
- bg = "transparent",
9
- color = "white",
8
+ bg = "#0000",
9
+ body = "",
10
+ color = "#fff",
10
11
  font = "sans-serif",
11
12
  fontSize = 16,
12
13
  padding = 0,
13
14
  textGravity = "c",
14
- body,
15
15
  gravity,
16
+ origin,
16
17
  path,
17
18
  pos,
19
+ ref,
18
20
  size,
19
21
  unit,
20
22
  ...opts
21
23
  } = layer;
22
- const [w, h] = computeSize(size, ctx.size, unit);
23
- const [isE, isW, isN, isS] = ["e", "w", "n", "s"].map(
24
- (x2) => textGravity.includes(x2)
25
- );
24
+ let bounds;
25
+ const [w, h] = bounds = computeSize(size, ctx.size, ref, unit);
26
+ const [isE, isW, isN, isS] = gravityFlags(textGravity);
26
27
  const x = isW ? padding : isE ? w - padding : w / 2;
27
28
  const y = isN ? padding : isS ? h - padding : h / 2;
28
29
  const align = isW ? "start" : isE ? "end" : "middle";
29
30
  const valign = isN ? 0.75 : isS ? 0 : 0.25;
31
+ if (path)
32
+ body = readText(path, ctx.logger);
30
33
  const $body = isFunction(body) ? body(ctx) : body;
31
34
  const svg = [
32
35
  `<svg xmlns="${XML_SVG}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">`,
@@ -34,13 +37,12 @@ const textLayer = async (layer, _, ctx) => {
34
37
  `<text x="${x}" y="${y}" text-anchor="${align}" dy="${valign}em" fill="${color}" font-family="${font}" font-size="${fontSize}">${$body}</text>`,
35
38
  `</svg>`
36
39
  ].join("");
37
- writeText("text-debug.svg", svg);
38
40
  return {
39
41
  input: Buffer.from(svg),
40
- ...positionOrGravity(pos, gravity, [w, h], ctx.size, unit),
42
+ ...positionOrGravity(bounds, ctx.size, layer),
41
43
  ...opts
42
44
  };
43
45
  };
44
46
  export {
45
- textLayer
47
+ textLayerImpl
46
48
  };
package/ops/composite.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { defmulti } from "@thi.ng/defmulti";
2
- import { imageLayer } from "../layers/image.js";
3
- import { svgLayer } from "../layers/svg.js";
4
- import { textLayer } from "../layers/text.js";
2
+ import { colorLayerImpl } from "../layers/color.js";
3
+ import { imageLayerImpl } from "../layers/image.js";
4
+ import { rawLayerImpl } from "../layers/raw.js";
5
+ import { svgLayerImpl } from "../layers/svg.js";
6
+ import { textLayerImpl } from "../layers/text.js";
5
7
  const compositeProc = async (spec, input, ctx) => {
6
8
  const { layers } = spec;
7
9
  const layerSpecs = await Promise.all(
@@ -14,9 +16,11 @@ const defLayer = defmulti(
14
16
  (x) => x.type,
15
17
  {},
16
18
  {
17
- img: imageLayer,
18
- svg: svgLayer,
19
- text: textLayer
19
+ color: colorLayerImpl,
20
+ img: imageLayerImpl,
21
+ raw: rawLayerImpl,
22
+ svg: svgLayerImpl,
23
+ text: textLayerImpl
20
24
  }
21
25
  );
22
26
  export {
package/ops/crop.js CHANGED
@@ -1,12 +1,14 @@
1
+ import { isNumber } from "@thi.ng/checks";
1
2
  import { illegalArgs } from "@thi.ng/errors";
2
3
  import {
3
4
  computeMargins,
4
5
  computeSize,
6
+ computeSizeWithAspect,
5
7
  gravityPosition,
6
8
  positionOrGravity
7
9
  } from "../units.js";
8
10
  const cropProc = async (spec, input, ctx) => {
9
- const { border, gravity, pos, size, ref, unit } = spec;
11
+ const { aspect, border, gravity, pos, size, ref, unit } = spec;
10
12
  if (border == null && size == null)
11
13
  illegalArgs("require `border` or `size` option");
12
14
  if (border != null) {
@@ -22,10 +24,17 @@ const cropProc = async (spec, input, ctx) => {
22
24
  true
23
25
  ];
24
26
  }
25
- const $size = computeSize(size, ctx.size, unit);
27
+ let $size;
28
+ if (aspect != void 0) {
29
+ if (!isNumber(size))
30
+ illegalArgs("size must be numeric if aspect is used");
31
+ $size = computeSizeWithAspect(size, ctx.size, aspect, unit);
32
+ } else {
33
+ $size = computeSize(size, ctx.size, ref, unit);
34
+ }
26
35
  let left = 0, top = 0;
27
36
  if (pos) {
28
- ({ left = 0, top = 0 } = positionOrGravity(pos, gravity, $size, ctx.size, unit) || {});
37
+ ({ left = 0, top = 0 } = positionOrGravity($size, ctx.size, spec) || {});
29
38
  } else {
30
39
  [left, top] = gravityPosition(gravity || "c", $size, ctx.size);
31
40
  }
package/ops/extend.js CHANGED
@@ -9,7 +9,7 @@ const extendProc = async (spec, input, ctx) => {
9
9
  right,
10
10
  top,
11
11
  bottom,
12
- background: coerceColor(bg || "#000"),
12
+ background: coerceColor(bg || "#0000"),
13
13
  extendWith: mode
14
14
  }),
15
15
  true
package/ops/output.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { encode } from "@thi.ng/blurhash";
2
+ import { isNumber, isPlainObject } from "@thi.ng/checks";
2
3
  import { writeFile, writeJSON } from "@thi.ng/file-io";
3
4
  import { join, resolve } from "node:path";
4
5
  import { formatPath } from "../path.js";
5
- import { isNumber, isPlainObject } from "@thi.ng/checks";
6
+ import { illegalArgs } from "@thi.ng/errors";
6
7
  const outputProc = async (spec, input, ctx) => {
7
8
  const opts = spec;
8
9
  const outDir = resolve(ctx.opts.outDir || ".");
@@ -11,6 +12,8 @@ const outputProc = async (spec, input, ctx) => {
11
12
  await outputBlurHash(opts, output, ctx);
12
13
  return [input, false];
13
14
  }
15
+ if (!opts.path)
16
+ illegalArgs("output path missing");
14
17
  if (opts.raw) {
15
18
  await outputRaw(opts, output, ctx, outDir);
16
19
  return [input, false];
@@ -81,7 +84,7 @@ const outputRaw = async (opts, output, ctx, outDir) => {
81
84
  const { alpha = false, meta = false } = isPlainObject(opts.raw) ? opts.raw : {};
82
85
  if (alpha)
83
86
  output = output.ensureAlpha();
84
- const { data, info } = await output.ensureAlpha().raw().toBuffer({ resolveWithObject: true });
87
+ const { data, info } = await output.raw().toBuffer({ resolveWithObject: true });
85
88
  const path = join(outDir, formatPath(opts.path, ctx, opts, data));
86
89
  writeFile(path, data, null, ctx.logger);
87
90
  ctx.outputs[opts.id] = path;
@@ -97,7 +100,7 @@ const outputRaw = async (opts, output, ctx, outDir) => {
97
100
  };
98
101
  const outputBlurHash = async (opts, output, ctx) => {
99
102
  const { data, info } = await output.ensureAlpha().raw().toBuffer({ resolveWithObject: true });
100
- const detail = opts.blurhash.detail || 4;
103
+ const detail = opts.blurhash === true ? 4 : opts.blurhash;
101
104
  const [dx, dy] = isNumber(detail) ? [detail, detail] : detail;
102
105
  const hash = encode(
103
106
  new Uint32Array(data.buffer),
@@ -106,8 +109,8 @@ const outputBlurHash = async (opts, output, ctx) => {
106
109
  dx,
107
110
  dy
108
111
  );
109
- ctx.outputs[opts.id] = hash;
110
112
  ctx.logger.debug("computed blurhash:", hash);
113
+ ctx.outputs[opts.id] = hash;
111
114
  };
112
115
  export {
113
116
  outputProc
package/ops/resize.js CHANGED
@@ -1,8 +1,15 @@
1
+ import { isNumber } from "@thi.ng/checks";
1
2
  import { GRAVITY_POSITION } from "../api.js";
2
3
  import { coerceColor, computeSize } from "../units.js";
3
4
  const resizeProc = async (spec, input, ctx) => {
4
- const { bg, filter, fit, gravity, size, unit } = spec;
5
- const [width, height] = computeSize(size, ctx.size, unit);
5
+ const { bg, filter, fit, gravity, ref, size, unit } = spec;
6
+ const aspect = ctx.size[0] / ctx.size[1];
7
+ let $size = size;
8
+ let width, height;
9
+ if (isNumber($size) && unit !== "%") {
10
+ $size = aspect > 1 ? [$size, $size / aspect] : [$size * aspect, $size];
11
+ }
12
+ [width, height] = computeSize($size, ctx.size, ref, unit);
6
13
  return [
7
14
  input.resize({
8
15
  width,
package/ops/rotate.js CHANGED
@@ -5,7 +5,10 @@ const rotateProc = async (spec, input, _) => {
5
5
  input = input.flop();
6
6
  if (flipY)
7
7
  input = input.flip();
8
- return [input.rotate(angle, { background: coerceColor(bg) }), true];
8
+ return [
9
+ input.rotate(angle, { background: coerceColor(bg || "#0000") }),
10
+ true
11
+ ];
9
12
  };
10
13
  export {
11
14
  rotateProc
package/ops.d.ts CHANGED
@@ -1,16 +1,83 @@
1
- import type { BlurSpec, CompSpec, CropSpec, DitherSpec, EXIFSpec, ExtendSpec, GammaSpec, GrayscaleSpec, HSBLSpec, NestSpec, OutputSpec, ProcSpec, ResizeSpec, RotateSpec } from "./api.js";
1
+ import type { BlurSpec, ColorLayer, CompLayer, CompSpec, CropSpec, DitherSpec, EXIFSpec, ExtendSpec, GammaSpec, GrayscaleSpec, HSBLSpec, ImgLayer, NestSpec, OutputSpec, ProcSpec, RawLayer, ResizeSpec, RotateSpec, SVGLayer, TextLayer } from "./api.js";
2
+ /** @internal */
2
3
  export declare const defSpec: <T extends ProcSpec>(op: T["op"]) => (opts: Omit<T, "op">) => T;
4
+ /** @internal */
5
+ export declare const defLayerSpec: <T extends CompLayer>(type: T["type"]) => (opts: Omit<T, "op">) => T;
6
+ /**
7
+ * Creates a new {@link BlurSpec} with given opts.
8
+ */
3
9
  export declare const blur: (opts: Omit<BlurSpec, "op">) => BlurSpec;
10
+ /**
11
+ * Creates a new {@link CompSpec} with given opts.
12
+ */
4
13
  export declare const composite: (opts: Omit<CompSpec, "op">) => CompSpec;
14
+ /**
15
+ * Creates a new {@link ColorLayer} spec with given opts (for use with
16
+ * {@link composite} / {@link CompSpec}).
17
+ */
18
+ export declare const colorLayer: (opts: Omit<ColorLayer, "op">) => ColorLayer;
19
+ /**
20
+ * Creates a new {@link ImgLayer} spec with given opts (for use with
21
+ * {@link composite} / {@link CompSpec}).
22
+ */
23
+ export declare const imageLayer: (opts: Omit<ImgLayer, "op">) => ImgLayer;
24
+ /**
25
+ * Creates a new {@link RawLayer} spec with given opts (for use with
26
+ * {@link composite} / {@link CompSpec}).
27
+ */
28
+ export declare const rawLayer: (opts: Omit<RawLayer, "op">) => RawLayer;
29
+ /**
30
+ * Creates a new {@link SVGLayer} spec with given opts (for use with
31
+ * {@link composite} / {@link CompSpec}).
32
+ */
33
+ export declare const svgLayer: (opts: Omit<SVGLayer, "op">) => SVGLayer;
34
+ /**
35
+ * Creates a new {@link TextLayer} spec with given opts (for use with
36
+ * {@link composite} / {@link CompSpec}).
37
+ */
38
+ export declare const textLayer: (opts: Omit<TextLayer, "op">) => TextLayer;
39
+ /**
40
+ * Creates a new {@link CompSpec} with given opts.
41
+ */
5
42
  export declare const crop: (opts: Omit<CropSpec, "op">) => CropSpec;
43
+ /**
44
+ * Creates a new {@link DitherSpec} with given opts.
45
+ */
6
46
  export declare const dither: (opts: Omit<DitherSpec, "op">) => DitherSpec;
47
+ /**
48
+ * Creates a new {@link EXIFSpec} with given opts.
49
+ */
7
50
  export declare const exif: (opts: Omit<EXIFSpec, "op">) => EXIFSpec;
51
+ /**
52
+ * Creates a new {@link ExtendSpec} with given opts.
53
+ */
8
54
  export declare const extend: (opts: Omit<ExtendSpec, "op">) => ExtendSpec;
55
+ /**
56
+ * Creates a new {@link GammaSpec} with given opts.
57
+ */
9
58
  export declare const gamma: (opts: Omit<GammaSpec, "op">) => GammaSpec;
59
+ /**
60
+ * Creates a new {@link GrayscaleSpec} with given opts.
61
+ */
10
62
  export declare const grayscale: (opts: Omit<GrayscaleSpec, "op">) => GrayscaleSpec;
63
+ /**
64
+ * Creates a new {@link HSBLSpec} with given opts.
65
+ */
11
66
  export declare const hsbl: (opts: Omit<HSBLSpec, "op">) => HSBLSpec;
67
+ /**
68
+ * Creates a new {@link NestSpec} with given opts.
69
+ */
12
70
  export declare const nest: (opts: Omit<NestSpec, "op">) => NestSpec;
71
+ /**
72
+ * Creates a new {@link OutputSpec} with given opts.
73
+ */
13
74
  export declare const output: (opts: Omit<OutputSpec, "op">) => OutputSpec;
75
+ /**
76
+ * Creates a new {@link ResizeSpec} with given opts.
77
+ */
14
78
  export declare const resize: (opts: Omit<ResizeSpec, "op">) => ResizeSpec;
79
+ /**
80
+ * Creates a new {@link RotateSpec} with given opts.
81
+ */
15
82
  export declare const rotate: (opts: Omit<RotateSpec, "op">) => RotateSpec;
16
83
  //# sourceMappingURL=ops.d.ts.map
package/ops.js CHANGED
@@ -1,6 +1,12 @@
1
1
  const defSpec = (op) => (opts) => ({ op, ...opts });
2
+ const defLayerSpec = (type) => (opts) => ({ type, ...opts });
2
3
  const blur = defSpec("blur");
3
4
  const composite = defSpec("composite");
5
+ const colorLayer = defLayerSpec("color");
6
+ const imageLayer = defLayerSpec("img");
7
+ const rawLayer = defLayerSpec("raw");
8
+ const svgLayer = defLayerSpec("svg");
9
+ const textLayer = defLayerSpec("text");
4
10
  const crop = defSpec("crop");
5
11
  const dither = defSpec("dither");
6
12
  const exif = defSpec("exif");
@@ -14,8 +20,10 @@ const resize = defSpec("resize");
14
20
  const rotate = defSpec("rotate");
15
21
  export {
16
22
  blur,
23
+ colorLayer,
17
24
  composite,
18
25
  crop,
26
+ defLayerSpec,
19
27
  defSpec,
20
28
  dither,
21
29
  exif,
@@ -23,8 +31,12 @@ export {
23
31
  gamma,
24
32
  grayscale,
25
33
  hsbl,
34
+ imageLayer,
26
35
  nest,
27
36
  output,
37
+ rawLayer,
28
38
  resize,
29
- rotate
39
+ rotate,
40
+ svgLayer,
41
+ textLayer
30
42
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/imago",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "JSON & API-based declarative and extensible image processing trees/pipelines",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -39,19 +39,19 @@
39
39
  "@thi.ng/associative": "^6.3.43",
40
40
  "@thi.ng/blurhash": "^0.1.11",
41
41
  "@thi.ng/checks": "^3.5.0",
42
- "@thi.ng/date": "^2.7.1",
42
+ "@thi.ng/date": "^2.7.2",
43
43
  "@thi.ng/defmulti": "^3.0.26",
44
44
  "@thi.ng/errors": "^2.4.18",
45
45
  "@thi.ng/file-io": "^1.3.4",
46
46
  "@thi.ng/logger": "^3.0.3",
47
- "@thi.ng/pixel": "^6.1.12",
48
- "@thi.ng/pixel-dither": "^1.1.110",
47
+ "@thi.ng/pixel": "^6.1.13",
48
+ "@thi.ng/pixel-dither": "^1.1.111",
49
49
  "@thi.ng/prefixes": "^2.3.10",
50
50
  "sharp": "^0.33.2"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@microsoft/api-extractor": "^7.40.1",
54
- "@thi.ng/vectors": "^7.10.12",
54
+ "@thi.ng/vectors": "^7.10.13",
55
55
  "esbuild": "^0.20.0",
56
56
  "rimraf": "^5.0.5",
57
57
  "typedoc": "^0.25.7",
@@ -109,6 +109,60 @@
109
109
  "./api": {
110
110
  "default": "./api.js"
111
111
  },
112
+ "./layers/color": {
113
+ "default": "./layers/color.js"
114
+ },
115
+ "./layers/image": {
116
+ "default": "./layers/image.js"
117
+ },
118
+ "./layers/raw": {
119
+ "default": "./layers/raw.js"
120
+ },
121
+ "./layers/svg": {
122
+ "default": "./layers/svg.js"
123
+ },
124
+ "./layers/text": {
125
+ "default": "./layers/text.js"
126
+ },
127
+ "./ops/blur": {
128
+ "default": "./ops/blur.js"
129
+ },
130
+ "./ops/composite": {
131
+ "default": "./ops/composite.js"
132
+ },
133
+ "./ops/crop": {
134
+ "default": "./ops/crop.js"
135
+ },
136
+ "./ops/dither": {
137
+ "default": "./ops/dither.js"
138
+ },
139
+ "./ops/exif": {
140
+ "default": "./ops/exif.js"
141
+ },
142
+ "./ops/extend": {
143
+ "default": "./ops/extend.js"
144
+ },
145
+ "./ops/gamma": {
146
+ "default": "./ops/gamma.js"
147
+ },
148
+ "./ops/grayscale": {
149
+ "default": "./ops/grayscale.js"
150
+ },
151
+ "./ops/hsbl": {
152
+ "default": "./ops/hsbl.js"
153
+ },
154
+ "./ops/nest": {
155
+ "default": "./ops/nest.js"
156
+ },
157
+ "./ops/output": {
158
+ "default": "./ops/output.js"
159
+ },
160
+ "./ops/resize": {
161
+ "default": "./ops/resize.js"
162
+ },
163
+ "./ops/rotate": {
164
+ "default": "./ops/rotate.js"
165
+ },
112
166
  "./ops": {
113
167
  "default": "./ops.js"
114
168
  },
@@ -126,5 +180,5 @@
126
180
  "status": "alpha",
127
181
  "year": 2024
128
182
  },
129
- "gitHead": "41860743a21093aa13b6b7f4e36cd5e2e934d159\n"
183
+ "gitHead": "dab0cb468a4c3d968ee1eea4e5dcd8df4889faa6\n"
130
184
  }
package/path.d.ts CHANGED
@@ -13,6 +13,7 @@ import type { ImgProcCtx, OutputSpec } from "./api.js";
13
13
  * - sha1/224/256/384/512: truncated hash of output
14
14
  * - w: current width
15
15
  * - h: current height
16
+ * - aspect: "p" (portrait), "l" (landscape) or "sq" (square)
16
17
  * - date: yyyyMMdd
17
18
  * - time: HHmmss
18
19
  * - year: 4-digit year
package/path.js CHANGED
@@ -38,6 +38,10 @@ const formatPath = (path, ctx, spec, buf) => path.replace(/\{(\w+)\}/g, (match,
38
38
  return String(ctx.size[0]);
39
39
  case "h":
40
40
  return String(ctx.size[1]);
41
+ case "aspect": {
42
+ const [w, h] = ctx.size;
43
+ return w > h ? "l" : w < h ? "p" : "sq";
44
+ }
41
45
  case "date":
42
46
  return FMT_yyyyMMdd_ALT(_, true);
43
47
  case "time":