@thi.ng/imago 0.1.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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2024-02-22T11:59:16Z
3
+ - **Last updated**: 2024-02-23T13:36:17Z
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.
@@ -9,6 +9,31 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ ## [0.3.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/imago@0.3.0) (2024-02-23)
13
+
14
+ #### 🚀 Features
15
+
16
+ - major update ([f938d60](https://github.com/thi-ng/umbrella/commit/f938d60))
17
+ - restructure package, split out all ops into separate files
18
+ - update `ProcSpec`, rename `type` => `op`
19
+ - add text layer support (via SVG)
20
+ - add/update EXIF handling & opts
21
+ - add ICC profile handling & opts
22
+ - update output path collection to use object
23
+ - update `OutputSpec` to require output `id`
24
+ - update `NestSpec` to support multiple child pipelines
25
+ - spawn children via Promise.all()
26
+ - add/update docstrings
27
+ - update deps & pkg exports
28
+
29
+ ## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/imago@0.2.0) (2024-02-22)
30
+
31
+ #### 🚀 Features
32
+
33
+ - add support for custom path part replacements ([b0419e1](https://github.com/thi-ng/umbrella/commit/b0419e1))
34
+ - add more path part replacements ([9f84a8a](https://github.com/thi-ng/umbrella/commit/9f84a8a))
35
+ - collect all output paths, update processImage() result ([a3ca52f](https://github.com/thi-ng/umbrella/commit/a3ca52f))
36
+
12
37
  ## [0.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/imago@0.1.0) (2024-02-22)
13
38
 
14
39
  #### 🚀 Features
package/README.md CHANGED
@@ -33,6 +33,7 @@
33
33
  - [Common options](#common-options)
34
34
  - [Bitmap layers](#bitmap-layers)
35
35
  - [SVG layers](#svg-layers)
36
+ - [Text layers](#text-layers)
36
37
  - [crop](#crop)
37
38
  - [dither](#dither)
38
39
  - [exif](#exif)
@@ -42,9 +43,11 @@
42
43
  - [hsbl](#hsbl)
43
44
  - [nest](#nest)
44
45
  - [output](#output)
46
+ - [Templated output paths](#templated-output-paths)
45
47
  - [resize](#resize)
46
48
  - [rotate](#rotate)
47
49
  - [Status](#status)
50
+ - [Metadata handling](#metadata-handling)
48
51
  - [Installation](#installation)
49
52
  - [Dependencies](#dependencies)
50
53
  - [API](#api)
@@ -66,11 +69,11 @@ In this new TypeScript version all image I/O and processing is delegated to
66
69
 
67
70
  Transformation trees/pipelines are simple JSON objects (but can be programmatically created):
68
71
 
69
- The following pipeline performs the following steps:
72
+ The following pipeline performs these steps (in sequence):
70
73
 
71
- - auto-rotate image (using EXIF orientation info)
74
+ - auto-rotate image (using EXIF orientation info, if available)
72
75
  - add 5% white border (size relative to shortest side)
73
- - proportionally resize to 1920px (by default longest side)
76
+ - proportionally resize image to 1920px (longest side by default)
74
77
  - overlay bitmap logo layer, positioned at 45% left / 5% bottom
75
78
  - add custom EXIF metadata
76
79
  - output this current stage as high quality AVIF (using templated output path)
@@ -79,11 +82,11 @@ The following pipeline performs the following steps:
79
82
 
80
83
  ```json tangle:export/readme-example1.json
81
84
  [
82
- { "type": "rotate" },
83
- { "type": "extend", "border": 5, "unit": "%", "ref": "min", "bg": "#fff" },
84
- { "type": "resize", "size": 1920 },
85
+ { "op": "rotate" },
86
+ { "op": "extend", "border": 5, "unit": "%", "ref": "min", "bg": "#fff" },
87
+ { "op": "resize", "size": 1920 },
85
88
  {
86
- "type": "composite",
89
+ "op": "composite",
87
90
  "layers": [
88
91
  {
89
92
  "type": "img",
@@ -95,7 +98,7 @@ The following pipeline performs the following steps:
95
98
  ]
96
99
  },
97
100
  {
98
- "type": "exif",
101
+ "op": "exif",
99
102
  "tags": {
100
103
  "IFD0": {
101
104
  "Copyright": "Karsten Schmidt",
@@ -104,12 +107,13 @@ The following pipeline performs the following steps:
104
107
  }
105
108
  },
106
109
  {
107
- "type": "output",
110
+ "op": "output",
111
+ "id": "hires",
108
112
  "path": "{name}-{sha256}-{w}x{h}.avif",
109
113
  "avif": { "quality": 80 }
110
114
  },
111
- { "type": "crop", "size": [240, 240], "gravity": "c" },
112
- { "type": "output", "path": "{name}-thumb.jpg" }
115
+ { "op": "crop", "size": [240, 240], "gravity": "c" },
116
+ { "op": "output", "id": "thumb", "path": "{name}-thumb.jpg" }
113
117
  ]
114
118
  ```
115
119
 
@@ -154,6 +158,14 @@ Compositing multiple layers:
154
158
 
155
159
  - from file or inline doc
156
160
 
161
+ #### Text layers
162
+
163
+ - optional background color (alpha supported)
164
+ - text color
165
+ - horizontal/vertical text align
166
+ - font family & size
167
+ - constrained to text box
168
+
157
169
  ### crop
158
170
 
159
171
  Cropping a part of the image
@@ -179,7 +191,7 @@ Supported dither modes from
179
191
 
180
192
  ### exif
181
193
 
182
- Set EXIF metadata (can only be given directly before [output](#output))
194
+ Set custom EXIF metadata (can be given multiple times, will be merged)
183
195
 
184
196
  ### extend
185
197
 
@@ -187,6 +199,7 @@ Add pixels on all sides of the image
187
199
 
188
200
  - supports px or percent units
189
201
  - proportional to a given reference side/size
202
+ - can be individually configured per side
190
203
 
191
204
  ### gamma
192
205
 
@@ -202,8 +215,8 @@ Hue, saturation, brightness and lightness adjustments
202
215
 
203
216
  ### nest
204
217
 
205
- Nested branch/pipeline of operations with no effect on image state of
206
- current/parent pipeline...
218
+ Performing nested branches/pipelines of operations with no effect on image state
219
+ of current/parent pipeline...
207
220
 
208
221
  ### output
209
222
 
@@ -219,6 +232,36 @@ File output in any of these formats:
219
232
  - tiff
220
233
  - webp
221
234
 
235
+ #### Templated output paths
236
+
237
+ Output paths can contain `{id}`-templated parts which will be replaced/expanded.
238
+ The following built-in IDs are supported and custom IDs will be looked up via
239
+ the
240
+ [pathParts](https://docs.thi.ng/umbrella/imago/interfaces/ImgProcOpts.html#pathParts)
241
+ options provided to
242
+ [processImage()](https://docs.thi.ng/umbrella/imago/functions/processImage.html).
243
+ Any others will remain as is. Custom IDs take precedence over built-in ones.
244
+
245
+ - `name`: original base filename (w/o ext)
246
+ - `sha1`/`sha224`/`sha256`/`sha384`/`sha512`: truncated hash of output (8 chars)
247
+ - `w`: current image width
248
+ - `h`: current image height
249
+ - `date`: yyyyMMdd date format, e.g. 20240223
250
+ - `time`: HHmmss time format, e.g. 234459
251
+ - `year`: 4-digit year
252
+ - `month`: 2-digit month
253
+ - `week`: 2-digit week
254
+ - `day`: 2-digit day in month
255
+ - `hour`: 2-digit hour (24h system)
256
+ - `minute`: 2-digit minute
257
+ - `second`: 2-digit second
258
+
259
+ Output paths can contain sub-directories which will be automatically created
260
+ (relative to the [configured output
261
+ dir](https://docs.thi.ng/umbrella/imago/interfaces/ImgProcOpts.html#outDir)).
262
+ For example, the path template `{year}/{month}/{day}/{name}-{sha1}.jpg` might
263
+ get replaced to: `2024/02/22/test-123cafe4.jpg`...
264
+
222
265
  ### resize
223
266
 
224
267
  Resizing image
@@ -230,7 +273,7 @@ Resizing image
230
273
 
231
274
  ### rotate
232
275
 
233
- Auto-rotate, rotate and/or mirror image
276
+ Auto-rotate, rotate by angle and/or flip image along x/y
234
277
 
235
278
  ## Status
236
279
 
@@ -238,6 +281,15 @@ Auto-rotate, rotate and/or mirror image
238
281
 
239
282
  [Search or submit any issues for this package](https://github.com/thi-ng/umbrella/issues?q=%5Bimago%5D+in%3Atitle)
240
283
 
284
+ ## Metadata handling
285
+
286
+ By default all input metadata will be lost in the outputs. The `keepEXIF` and
287
+ `keepICC` options can be used to retain EXIF and/or ICC profile information
288
+ (only if also supported in the output format).
289
+
290
+ **Important:** Retaining EXIF and merging it with [custom additions](#exif) is
291
+ still WIP...
292
+
241
293
  ## Installation
242
294
 
243
295
  ```bash
@@ -250,7 +302,7 @@ For Node.js REPL:
250
302
  const imago = await import("@thi.ng/imago");
251
303
  ```
252
304
 
253
- Package sizes (brotli'd, pre-treeshake): ESM: 3.13 KB
305
+ Package sizes (brotli'd, pre-treeshake): ESM: 4.17 KB
254
306
 
255
307
  ## Dependencies
256
308
 
@@ -263,6 +315,7 @@ Package sizes (brotli'd, pre-treeshake): ESM: 3.13 KB
263
315
  - [@thi.ng/logger](https://github.com/thi-ng/umbrella/tree/develop/packages/logger)
264
316
  - [@thi.ng/pixel](https://github.com/thi-ng/umbrella/tree/develop/packages/pixel)
265
317
  - [@thi.ng/pixel-dither](https://github.com/thi-ng/umbrella/tree/develop/packages/pixel-dither)
318
+ - [exif-reader](https://github.com/devongovett/exif-reader)
266
319
  - [sharp](https://sharp.pixelplumbing.com)
267
320
 
268
321
  ## API
package/api.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import type { Keys } from "@thi.ng/api";
1
+ /// <reference types="node" />
2
+ import type { Fn, Fn3, Keys, TypedArray } from "@thi.ng/api";
2
3
  import type { ILogger } from "@thi.ng/logger";
3
- import type { AvifOptions, Blend, Exif, ExtendWith, FitEnum, GifOptions, Jp2Options, JpegOptions, JxlOptions, KernelEnum, Metadata, PngOptions, TiffOptions, TileOptions, WebpOptions } from "sharp";
4
+ import type { AvifOptions, Blend, Exif, ExtendWith, FitEnum, GifOptions, Jp2Options, JpegOptions, JxlOptions, KernelEnum, Metadata, OverlayOptions, PngOptions, Sharp, TiffOptions, TileOptions, WebpOptions } from "sharp";
4
5
  export type Gravity = "c" | "e" | "n" | "ne" | "nw" | "s" | "se" | "sw" | "w";
5
6
  export type DitherMode = "atkinson" | "burkes" | "column" | "diffusion" | "floyd" | "jarvis" | "row" | "sierra" | "stucki" | "bayer";
6
7
  export type Dim = [number, number];
@@ -20,15 +21,17 @@ export interface Position {
20
21
  t?: number;
21
22
  b?: number;
22
23
  }
24
+ export type Processor = Fn3<ProcSpec, Sharp, ImgProcCtx, Promise<[Sharp, boolean]>>;
25
+ export type CompLayerFn = Fn3<CompLayer, Sharp, ImgProcCtx, Promise<OverlayOptions>>;
23
26
  export interface ProcSpec {
24
- type: string;
27
+ op: string;
25
28
  }
26
29
  export interface BlurSpec extends ProcSpec {
27
- type: "blur";
30
+ op: "blur";
28
31
  radius: number;
29
32
  }
30
33
  export interface CompSpec extends ProcSpec {
31
- type: "composite";
34
+ op: "composite";
32
35
  layers: CompLayer[];
33
36
  }
34
37
  export type CompLayer = ImgLayer | SVGLayer;
@@ -50,8 +53,20 @@ export interface SVGLayer extends CompLayerBase {
50
53
  body: string;
51
54
  path: string;
52
55
  }
56
+ export interface TextLayer extends CompLayerBase {
57
+ type: "text";
58
+ textGravity: Gravity;
59
+ bg: string;
60
+ body: string | Fn<ImgProcCtx, string>;
61
+ color: string;
62
+ font: string;
63
+ fontSize: number | string;
64
+ padding: number;
65
+ path: string;
66
+ size: [number, number];
67
+ }
53
68
  export interface CropSpec extends ProcSpec {
54
- type: "crop";
69
+ op: "crop";
55
70
  border?: Size | Sides;
56
71
  gravity?: Gravity;
57
72
  pos?: Position;
@@ -60,18 +75,18 @@ export interface CropSpec extends ProcSpec {
60
75
  unit?: SizeUnit;
61
76
  }
62
77
  export interface DitherSpec extends ProcSpec {
63
- type: "dither";
78
+ op: "dither";
64
79
  mode: DitherMode;
65
80
  num: number;
66
81
  rgb?: boolean;
67
82
  size: 2 | 4 | 8;
68
83
  }
69
84
  export interface EXIFSpec extends ProcSpec {
70
- type: "exif";
85
+ op: "exif";
71
86
  tags: Exif;
72
87
  }
73
88
  export interface ExtendSpec extends ProcSpec {
74
- type: "extend";
89
+ op: "extend";
75
90
  bg?: Color;
76
91
  border: Size | Sides;
77
92
  mode?: ExtendWith;
@@ -79,26 +94,39 @@ export interface ExtendSpec extends ProcSpec {
79
94
  unit?: SizeUnit;
80
95
  }
81
96
  export interface GammaSpec extends ProcSpec {
82
- type: "gamma";
97
+ op: "gamma";
83
98
  gamma: number;
84
99
  }
85
100
  export interface GrayscaleSpec extends ProcSpec {
86
- type: "gray";
101
+ op: "gray";
87
102
  gamma?: number | boolean;
88
103
  }
89
104
  export interface HSBLSpec extends ProcSpec {
90
- type: "hsbl";
105
+ op: "hsbl";
91
106
  h?: number;
92
107
  s?: number;
93
108
  b?: number;
94
109
  l?: number;
95
110
  }
96
111
  export interface NestSpec extends ProcSpec {
97
- type: "nest";
98
- procs: ProcSpec[];
112
+ op: "nest";
113
+ /**
114
+ * Array of one or more arrays of processing pipeline specs. All pipelines
115
+ * are spawned via `Promise.all()` and each one receives a separate clone of
116
+ * the current input image.
117
+ */
118
+ procs: ProcSpec[][];
99
119
  }
100
120
  export interface OutputSpec extends ProcSpec {
101
- type: "output";
121
+ op: "output";
122
+ /**
123
+ * Unique ID of this output, used to record the file path in the `outputs`
124
+ * object returned by {@link processImage}.
125
+ */
126
+ id: string;
127
+ /**
128
+ * Possibly templated output path. See {@link formatPath} for details.
129
+ */
102
130
  path: string;
103
131
  avif?: AvifOptions;
104
132
  gif?: GifOptions;
@@ -122,7 +150,7 @@ export interface OutputSpec extends ProcSpec {
122
150
  webp?: WebpOptions;
123
151
  }
124
152
  export interface ResizeSpec extends ProcSpec {
125
- type: "resize";
153
+ op: "resize";
126
154
  bg?: Color;
127
155
  filter?: Keys<KernelEnum>;
128
156
  fit?: Keys<FitEnum>;
@@ -131,7 +159,7 @@ export interface ResizeSpec extends ProcSpec {
131
159
  unit?: SizeUnit;
132
160
  }
133
161
  export interface RotateSpec extends ProcSpec {
134
- type: "rotate";
162
+ op: "rotate";
135
163
  angle?: number;
136
164
  bg: Color;
137
165
  flipX?: boolean;
@@ -147,14 +175,46 @@ export interface ImgProcOpts {
147
175
  * Base directory for {@link output} steps
148
176
  */
149
177
  outDir: string;
178
+ /**
179
+ * By default all input metadata will be lost in the output(s). If this
180
+ * option is enabled, keeps existing EXIF data and attaches it to output
181
+ * (also where the output format actually supports it).
182
+ *
183
+ * @remarks
184
+ * TODO currently still unsupported
185
+ */
186
+ keepEXIF: boolean;
187
+ /**
188
+ * By default all input metadata will be lost in the output(s). If this
189
+ * option is enabled, keeps existing ICC profile from input image and
190
+ * attaches it to output (also where the output format actually supports
191
+ * it).
192
+ */
193
+ keepICC: boolean;
194
+ /**
195
+ * An object with custom output path replacements for {@link formatPath}. If
196
+ * a given replacement value is a function, it will be called with the
197
+ * current {@link ImgProcCtx}, the current {@link OutputSpec} (e.g. to
198
+ * obtain configured options) and the already serialized image as buffer.
199
+ *
200
+ * @remarks
201
+ * Replacement IDs in this object will take precedence over built-in
202
+ * replacement IDs, e.g. allowing to override `name`, `date` etc.
203
+ */
204
+ pathParts: Record<string, Fn3<ImgProcCtx, OutputSpec, Buffer | TypedArray, string> | string>;
150
205
  }
151
206
  export interface ImgProcCtx {
152
207
  path?: string;
153
208
  size: Dim;
154
- channels: 1 | 2 | 3 | 4;
155
209
  meta: Metadata;
210
+ exif: Exif;
211
+ iccFile?: string;
156
212
  logger: ILogger;
157
213
  opts: Partial<ImgProcOpts>;
214
+ /**
215
+ * Paths of all exported images.
216
+ */
217
+ outputs: Record<string, string>;
158
218
  }
159
219
  export declare const GRAVITY_POSITION: Record<Gravity, string>;
160
220
  export declare const GRAVITY_MAP: Record<Gravity, string>;
@@ -0,0 +1,3 @@
1
+ import type { CompLayerFn } from "../api.js";
2
+ export declare const imageLayer: CompLayerFn;
3
+ //# sourceMappingURL=image.d.ts.map
@@ -0,0 +1,29 @@
1
+ import sharp from "sharp";
2
+ import { computeSize, ensureSize, positionOrGravity } from "../units.js";
3
+ const imageLayer = async (layer, _, ctx) => {
4
+ const {
5
+ type: __,
6
+ gravity,
7
+ path,
8
+ pos,
9
+ size,
10
+ unit,
11
+ ...opts
12
+ } = layer;
13
+ const input = sharp(path);
14
+ const meta = await input.metadata();
15
+ let imgSize = [meta.width, meta.height];
16
+ const $pos = positionOrGravity(pos, gravity, imgSize, ctx.size, unit);
17
+ if (!size)
18
+ return { input: path, ...$pos, ...opts };
19
+ ensureSize(meta);
20
+ imgSize = computeSize(size, imgSize, unit);
21
+ return {
22
+ input: await input.resize(imgSize[0], imgSize[1]).png({ compressionLevel: 0 }).toBuffer(),
23
+ ...$pos,
24
+ ...opts
25
+ };
26
+ };
27
+ export {
28
+ imageLayer
29
+ };
@@ -0,0 +1,3 @@
1
+ import type { CompLayerFn } from "../api.js";
2
+ export declare const svgLayer: CompLayerFn;
3
+ //# sourceMappingURL=svg.d.ts.map
package/layers/svg.js ADDED
@@ -0,0 +1,17 @@
1
+ import { readText } from "@thi.ng/file-io";
2
+ import { positionOrGravity } from "../units.js";
3
+ const svgLayer = async (layer, _, ctx) => {
4
+ let { type: __, body, gravity, path, pos, unit, ...opts } = layer;
5
+ if (path)
6
+ body = readText(path, ctx.logger);
7
+ const w = +(/width="(\d+)"/.exec(body)?.[1] || 0);
8
+ const h = +(/height="(\d+)"/.exec(body)?.[1] || 0);
9
+ return {
10
+ input: Buffer.from(body),
11
+ ...positionOrGravity(pos, gravity, [w, h], ctx.size, unit),
12
+ ...opts
13
+ };
14
+ };
15
+ export {
16
+ svgLayer
17
+ };
@@ -0,0 +1,3 @@
1
+ import type { CompLayerFn } from "../api.js";
2
+ export declare const textLayer: CompLayerFn;
3
+ //# sourceMappingURL=text.d.ts.map
package/layers/text.js ADDED
@@ -0,0 +1,46 @@
1
+ import { isFunction } from "@thi.ng/checks";
2
+ import { writeText } from "@thi.ng/file-io";
3
+ import { XML_SVG } from "@thi.ng/prefixes";
4
+ import { computeSize, positionOrGravity } from "../units.js";
5
+ const textLayer = async (layer, _, ctx) => {
6
+ const {
7
+ type: __,
8
+ bg = "transparent",
9
+ color = "white",
10
+ font = "sans-serif",
11
+ fontSize = 16,
12
+ padding = 0,
13
+ textGravity = "c",
14
+ body,
15
+ gravity,
16
+ path,
17
+ pos,
18
+ size,
19
+ unit,
20
+ ...opts
21
+ } = 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
+ );
26
+ const x = isW ? padding : isE ? w - padding : w / 2;
27
+ const y = isN ? padding : isS ? h - padding : h / 2;
28
+ const align = isW ? "start" : isE ? "end" : "middle";
29
+ const valign = isN ? 0.75 : isS ? 0 : 0.25;
30
+ const $body = isFunction(body) ? body(ctx) : body;
31
+ const svg = [
32
+ `<svg xmlns="${XML_SVG}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">`,
33
+ `<rect x="0" y="0" width="${w}" height="${h}" fill="${bg}"/>`,
34
+ `<text x="${x}" y="${y}" text-anchor="${align}" dy="${valign}em" fill="${color}" font-family="${font}" font-size="${fontSize}">${$body}</text>`,
35
+ `</svg>`
36
+ ].join("");
37
+ writeText("text-debug.svg", svg);
38
+ return {
39
+ input: Buffer.from(svg),
40
+ ...positionOrGravity(pos, gravity, [w, h], ctx.size, unit),
41
+ ...opts
42
+ };
43
+ };
44
+ export {
45
+ textLayer
46
+ };
package/ops/blur.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Processor } from "../api.js";
2
+ export declare const blurProc: Processor;
3
+ //# sourceMappingURL=blur.d.ts.map
package/ops/blur.js ADDED
@@ -0,0 +1,7 @@
1
+ const blurProc = async (spec, input) => {
2
+ const { radius } = spec;
3
+ return [input.blur(1 + radius / 2), false];
4
+ };
5
+ export {
6
+ blurProc
7
+ };
@@ -0,0 +1,5 @@
1
+ import type { OverlayOptions, Sharp } from "sharp";
2
+ import type { CompLayer, ImgProcCtx, Processor } from "../api.js";
3
+ export declare const compositeProc: Processor;
4
+ export declare const defLayer: import("@thi.ng/defmulti").MultiFn3<CompLayer, Sharp, ImgProcCtx, Promise<OverlayOptions>>;
5
+ //# sourceMappingURL=composite.d.ts.map
@@ -0,0 +1,25 @@
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";
5
+ const compositeProc = async (spec, input, ctx) => {
6
+ const { layers } = spec;
7
+ const layerSpecs = await Promise.all(
8
+ layers.map((l) => defLayer(l, input, ctx))
9
+ );
10
+ ctx.logger.debug("layer specs", layerSpecs);
11
+ return [input.composite(layerSpecs), true];
12
+ };
13
+ const defLayer = defmulti(
14
+ (x) => x.type,
15
+ {},
16
+ {
17
+ img: imageLayer,
18
+ svg: svgLayer,
19
+ text: textLayer
20
+ }
21
+ );
22
+ export {
23
+ compositeProc,
24
+ defLayer
25
+ };
package/ops/crop.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Processor } from "../api.js";
2
+ export declare const cropProc: Processor;
3
+ //# sourceMappingURL=crop.d.ts.map
package/ops/crop.js ADDED
@@ -0,0 +1,44 @@
1
+ import { illegalArgs } from "@thi.ng/errors";
2
+ import {
3
+ computeMargins,
4
+ computeSize,
5
+ gravityPosition,
6
+ positionOrGravity
7
+ } from "../units.js";
8
+ const cropProc = async (spec, input, ctx) => {
9
+ const { border, gravity, pos, size, ref, unit } = spec;
10
+ if (border == null && size == null)
11
+ illegalArgs("require `border` or `size` option");
12
+ if (border != null) {
13
+ const sides = computeMargins(border, ctx.size, ref, unit);
14
+ const [left2, right, top2, bottom] = sides;
15
+ return [
16
+ input.extract({
17
+ left: left2,
18
+ top: top2,
19
+ width: ctx.size[0] - left2 - right,
20
+ height: ctx.size[1] - top2 - bottom
21
+ }),
22
+ true
23
+ ];
24
+ }
25
+ const $size = computeSize(size, ctx.size, unit);
26
+ let left = 0, top = 0;
27
+ if (pos) {
28
+ ({ left = 0, top = 0 } = positionOrGravity(pos, gravity, $size, ctx.size, unit) || {});
29
+ } else {
30
+ [left, top] = gravityPosition(gravity || "c", $size, ctx.size);
31
+ }
32
+ return [
33
+ input.extract({
34
+ left,
35
+ top,
36
+ width: $size[0],
37
+ height: $size[1]
38
+ }),
39
+ true
40
+ ];
41
+ };
42
+ export {
43
+ cropProc
44
+ };
@@ -0,0 +1,3 @@
1
+ import type { Processor } from "../api.js";
2
+ export declare const ditherProc: Processor;
3
+ //# sourceMappingURL=dither.d.ts.map