@thi.ng/imago 0.2.0 → 0.3.1

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-22T23:15:26Z
3
+ - **Last updated**: 2024-02-25T14:07:53Z
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,23 @@ 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
+
12
29
  ## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/imago@0.2.0) (2024-02-22)
13
30
 
14
31
  #### 🚀 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)
@@ -46,6 +47,7 @@
46
47
  - [resize](#resize)
47
48
  - [rotate](#rotate)
48
49
  - [Status](#status)
50
+ - [Metadata handling](#metadata-handling)
49
51
  - [Installation](#installation)
50
52
  - [Dependencies](#dependencies)
51
53
  - [API](#api)
@@ -67,11 +69,11 @@ In this new TypeScript version all image I/O and processing is delegated to
67
69
 
68
70
  Transformation trees/pipelines are simple JSON objects (but can be programmatically created):
69
71
 
70
- The following pipeline performs the following steps:
72
+ The following pipeline performs these steps (in sequence):
71
73
 
72
- - auto-rotate image (using EXIF orientation info)
74
+ - auto-rotate image (using EXIF orientation info, if available)
73
75
  - add 5% white border (size relative to shortest side)
74
- - proportionally resize to 1920px (by default longest side)
76
+ - proportionally resize image to 1920px (longest side by default)
75
77
  - overlay bitmap logo layer, positioned at 45% left / 5% bottom
76
78
  - add custom EXIF metadata
77
79
  - output this current stage as high quality AVIF (using templated output path)
@@ -80,11 +82,11 @@ The following pipeline performs the following steps:
80
82
 
81
83
  ```json tangle:export/readme-example1.json
82
84
  [
83
- { "type": "rotate" },
84
- { "type": "extend", "border": 5, "unit": "%", "ref": "min", "bg": "#fff" },
85
- { "type": "resize", "size": 1920 },
85
+ { "op": "rotate" },
86
+ { "op": "extend", "border": 5, "unit": "%", "ref": "min", "bg": "#fff" },
87
+ { "op": "resize", "size": 1920 },
86
88
  {
87
- "type": "composite",
89
+ "op": "composite",
88
90
  "layers": [
89
91
  {
90
92
  "type": "img",
@@ -96,7 +98,7 @@ The following pipeline performs the following steps:
96
98
  ]
97
99
  },
98
100
  {
99
- "type": "exif",
101
+ "op": "exif",
100
102
  "tags": {
101
103
  "IFD0": {
102
104
  "Copyright": "Karsten Schmidt",
@@ -105,12 +107,13 @@ The following pipeline performs the following steps:
105
107
  }
106
108
  },
107
109
  {
108
- "type": "output",
110
+ "op": "output",
111
+ "id": "hires",
109
112
  "path": "{name}-{sha256}-{w}x{h}.avif",
110
113
  "avif": { "quality": 80 }
111
114
  },
112
- { "type": "crop", "size": [240, 240], "gravity": "c" },
113
- { "type": "output", "path": "{name}-thumb.jpg" }
115
+ { "op": "crop", "size": [240, 240], "gravity": "c" },
116
+ { "op": "output", "id": "thumb", "path": "{name}-thumb.jpg" }
114
117
  ]
115
118
  ```
116
119
 
@@ -155,6 +158,14 @@ Compositing multiple layers:
155
158
 
156
159
  - from file or inline doc
157
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
+
158
169
  ### crop
159
170
 
160
171
  Cropping a part of the image
@@ -180,7 +191,7 @@ Supported dither modes from
180
191
 
181
192
  ### exif
182
193
 
183
- Set EXIF metadata (can only be given directly before [output](#output))
194
+ Set custom EXIF metadata (can be given multiple times, will be merged)
184
195
 
185
196
  ### extend
186
197
 
@@ -188,6 +199,7 @@ Add pixels on all sides of the image
188
199
 
189
200
  - supports px or percent units
190
201
  - proportional to a given reference side/size
202
+ - can be individually configured per side
191
203
 
192
204
  ### gamma
193
205
 
@@ -203,8 +215,8 @@ Hue, saturation, brightness and lightness adjustments
203
215
 
204
216
  ### nest
205
217
 
206
- Nested branch/pipeline of operations with no effect on image state of
207
- current/parent pipeline...
218
+ Performing nested branches/pipelines of operations with no effect on image state
219
+ of current/parent pipeline...
208
220
 
209
221
  ### output
210
222
 
@@ -261,7 +273,7 @@ Resizing image
261
273
 
262
274
  ### rotate
263
275
 
264
- Auto-rotate, rotate and/or mirror image
276
+ Auto-rotate, rotate by angle and/or flip image along x/y
265
277
 
266
278
  ## Status
267
279
 
@@ -269,6 +281,15 @@ Auto-rotate, rotate and/or mirror image
269
281
 
270
282
  [Search or submit any issues for this package](https://github.com/thi-ng/umbrella/issues?q=%5Bimago%5D+in%3Atitle)
271
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
+
272
293
  ## Installation
273
294
 
274
295
  ```bash
@@ -281,11 +302,12 @@ For Node.js REPL:
281
302
  const imago = await import("@thi.ng/imago");
282
303
  ```
283
304
 
284
- Package sizes (brotli'd, pre-treeshake): ESM: 3.29 KB
305
+ Package sizes (brotli'd, pre-treeshake): ESM: 4.06 KB
285
306
 
286
307
  ## Dependencies
287
308
 
288
309
  - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api)
310
+ - [@thi.ng/associative](https://github.com/thi-ng/umbrella/tree/develop/packages/associative)
289
311
  - [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
290
312
  - [@thi.ng/date](https://github.com/thi-ng/umbrella/tree/develop/packages/date)
291
313
  - [@thi.ng/defmulti](https://github.com/thi-ng/umbrella/tree/develop/packages/defmulti)
@@ -294,6 +316,7 @@ Package sizes (brotli'd, pre-treeshake): ESM: 3.29 KB
294
316
  - [@thi.ng/logger](https://github.com/thi-ng/umbrella/tree/develop/packages/logger)
295
317
  - [@thi.ng/pixel](https://github.com/thi-ng/umbrella/tree/develop/packages/pixel)
296
318
  - [@thi.ng/pixel-dither](https://github.com/thi-ng/umbrella/tree/develop/packages/pixel-dither)
319
+ - [@thi.ng/prefixes](https://github.com/thi-ng/umbrella/tree/develop/packages/prefixes)
297
320
  - [sharp](https://sharp.pixelplumbing.com)
298
321
 
299
322
  ## API
package/api.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
- import type { Fn3, Keys, TypedArray } from "@thi.ng/api";
2
+ import type { Fn, Fn3, Keys, TypedArray } from "@thi.ng/api";
3
3
  import type { ILogger } from "@thi.ng/logger";
4
- 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";
5
5
  export type Gravity = "c" | "e" | "n" | "ne" | "nw" | "s" | "se" | "sw" | "w";
6
6
  export type DitherMode = "atkinson" | "burkes" | "column" | "diffusion" | "floyd" | "jarvis" | "row" | "sierra" | "stucki" | "bayer";
7
7
  export type Dim = [number, number];
@@ -21,15 +21,17 @@ export interface Position {
21
21
  t?: number;
22
22
  b?: number;
23
23
  }
24
+ export type Processor = Fn3<ProcSpec, Sharp, ImgProcCtx, Promise<[Sharp, boolean]>>;
25
+ export type CompLayerFn = Fn3<CompLayer, Sharp, ImgProcCtx, Promise<OverlayOptions>>;
24
26
  export interface ProcSpec {
25
- type: string;
27
+ op: string;
26
28
  }
27
29
  export interface BlurSpec extends ProcSpec {
28
- type: "blur";
30
+ op: "blur";
29
31
  radius: number;
30
32
  }
31
33
  export interface CompSpec extends ProcSpec {
32
- type: "composite";
34
+ op: "composite";
33
35
  layers: CompLayer[];
34
36
  }
35
37
  export type CompLayer = ImgLayer | SVGLayer;
@@ -51,8 +53,20 @@ export interface SVGLayer extends CompLayerBase {
51
53
  body: string;
52
54
  path: string;
53
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
+ }
54
68
  export interface CropSpec extends ProcSpec {
55
- type: "crop";
69
+ op: "crop";
56
70
  border?: Size | Sides;
57
71
  gravity?: Gravity;
58
72
  pos?: Position;
@@ -61,18 +75,18 @@ export interface CropSpec extends ProcSpec {
61
75
  unit?: SizeUnit;
62
76
  }
63
77
  export interface DitherSpec extends ProcSpec {
64
- type: "dither";
78
+ op: "dither";
65
79
  mode: DitherMode;
66
80
  num: number;
67
81
  rgb?: boolean;
68
82
  size: 2 | 4 | 8;
69
83
  }
70
84
  export interface EXIFSpec extends ProcSpec {
71
- type: "exif";
85
+ op: "exif";
72
86
  tags: Exif;
73
87
  }
74
88
  export interface ExtendSpec extends ProcSpec {
75
- type: "extend";
89
+ op: "extend";
76
90
  bg?: Color;
77
91
  border: Size | Sides;
78
92
  mode?: ExtendWith;
@@ -80,26 +94,39 @@ export interface ExtendSpec extends ProcSpec {
80
94
  unit?: SizeUnit;
81
95
  }
82
96
  export interface GammaSpec extends ProcSpec {
83
- type: "gamma";
97
+ op: "gamma";
84
98
  gamma: number;
85
99
  }
86
100
  export interface GrayscaleSpec extends ProcSpec {
87
- type: "gray";
101
+ op: "gray";
88
102
  gamma?: number | boolean;
89
103
  }
90
104
  export interface HSBLSpec extends ProcSpec {
91
- type: "hsbl";
105
+ op: "hsbl";
92
106
  h?: number;
93
107
  s?: number;
94
108
  b?: number;
95
109
  l?: number;
96
110
  }
97
111
  export interface NestSpec extends ProcSpec {
98
- type: "nest";
99
- 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[][];
100
119
  }
101
120
  export interface OutputSpec extends ProcSpec {
102
- 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
+ */
103
130
  path: string;
104
131
  avif?: AvifOptions;
105
132
  gif?: GifOptions;
@@ -123,7 +150,7 @@ export interface OutputSpec extends ProcSpec {
123
150
  webp?: WebpOptions;
124
151
  }
125
152
  export interface ResizeSpec extends ProcSpec {
126
- type: "resize";
153
+ op: "resize";
127
154
  bg?: Color;
128
155
  filter?: Keys<KernelEnum>;
129
156
  fit?: Keys<FitEnum>;
@@ -132,7 +159,7 @@ export interface ResizeSpec extends ProcSpec {
132
159
  unit?: SizeUnit;
133
160
  }
134
161
  export interface RotateSpec extends ProcSpec {
135
- type: "rotate";
162
+ op: "rotate";
136
163
  angle?: number;
137
164
  bg: Color;
138
165
  flipX?: boolean;
@@ -148,6 +175,22 @@ export interface ImgProcOpts {
148
175
  * Base directory for {@link output} steps
149
176
  */
150
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;
151
194
  /**
152
195
  * An object with custom output path replacements for {@link formatPath}. If
153
196
  * a given replacement value is a function, it will be called with the
@@ -163,14 +206,15 @@ export interface ImgProcOpts {
163
206
  export interface ImgProcCtx {
164
207
  path?: string;
165
208
  size: Dim;
166
- channels: 1 | 2 | 3 | 4;
167
209
  meta: Metadata;
210
+ exif: Exif;
211
+ iccFile?: string;
168
212
  logger: ILogger;
169
213
  opts: Partial<ImgProcOpts>;
170
214
  /**
171
215
  * Paths of all exported images.
172
216
  */
173
- outputs: string[];
217
+ outputs: Record<string, string>;
174
218
  }
175
219
  export declare const GRAVITY_POSITION: Record<Gravity, string>;
176
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
package/ops/dither.js ADDED
@@ -0,0 +1,68 @@
1
+ import { typedArray } from "@thi.ng/api";
2
+ import { ABGR8888, GRAY8, Lane, intBuffer } from "@thi.ng/pixel";
3
+ import {
4
+ ATKINSON,
5
+ BURKES,
6
+ DIFFUSION_2D,
7
+ DIFFUSION_COLUMN,
8
+ DIFFUSION_ROW,
9
+ FLOYD_STEINBERG,
10
+ JARVIS_JUDICE_NINKE,
11
+ SIERRA2,
12
+ STUCKI,
13
+ defBayer,
14
+ ditherWith,
15
+ orderedDither
16
+ } from "@thi.ng/pixel-dither";
17
+ import sharp from "sharp";
18
+ const DITHER_KERNELS = {
19
+ atkinson: ATKINSON,
20
+ burkes: BURKES,
21
+ column: DIFFUSION_COLUMN,
22
+ diffusion: DIFFUSION_2D,
23
+ floyd: FLOYD_STEINBERG,
24
+ jarvis: JARVIS_JUDICE_NINKE,
25
+ row: DIFFUSION_ROW,
26
+ sierra: SIERRA2,
27
+ stucki: STUCKI
28
+ };
29
+ const ditherProc = async (spec, input, ctx) => {
30
+ let { mode, num = 2, rgb = false, size = 8 } = spec;
31
+ const [w, h] = ctx.size;
32
+ let raw;
33
+ if (rgb) {
34
+ const tmp = await input.clone().ensureAlpha(1).toColorspace("srgb").raw().toBuffer({ resolveWithObject: true });
35
+ raw = tmp.data.buffer;
36
+ rgb = tmp.info.channels === 4;
37
+ } else {
38
+ raw = (await input.clone().grayscale().raw().toBuffer()).buffer;
39
+ }
40
+ let img = intBuffer(
41
+ w,
42
+ h,
43
+ rgb ? ABGR8888 : GRAY8,
44
+ typedArray(rgb ? "u32" : "u8", raw)
45
+ );
46
+ if (mode === "bayer") {
47
+ orderedDither(img, defBayer(size), rgb ? [num, num, num] : [num]);
48
+ } else {
49
+ ditherWith(DITHER_KERNELS[mode], img, {
50
+ channels: rgb ? [Lane.RED, Lane.GREEN, Lane.BLUE] : void 0
51
+ });
52
+ }
53
+ if (!rgb)
54
+ img = img.as(ABGR8888);
55
+ return [
56
+ sharp(new Uint8Array(img.data.buffer), {
57
+ raw: {
58
+ width: img.width,
59
+ height: img.height,
60
+ channels: 4
61
+ }
62
+ }),
63
+ true
64
+ ];
65
+ };
66
+ export {
67
+ ditherProc
68
+ };
package/ops/exif.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Processor } from "../api.js";
2
+ export declare const exifProc: Processor;
3
+ //# sourceMappingURL=exif.d.ts.map
package/ops/exif.js ADDED
@@ -0,0 +1,9 @@
1
+ import { meldDeepObj } from "@thi.ng/associative";
2
+ const exifProc = async (spec, input, ctx) => {
3
+ const { tags } = spec;
4
+ meldDeepObj(ctx.exif, tags);
5
+ return [input, false];
6
+ };
7
+ export {
8
+ exifProc
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { Processor } from "../api.js";
2
+ export declare const extendProc: Processor;
3
+ //# sourceMappingURL=extend.d.ts.map