@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 +26 -1
- package/README.md +69 -16
- package/api.d.ts +78 -18
- package/layers/image.d.ts +3 -0
- package/layers/image.js +29 -0
- package/layers/svg.d.ts +3 -0
- package/layers/svg.js +17 -0
- package/layers/text.d.ts +3 -0
- package/layers/text.js +46 -0
- package/ops/blur.d.ts +3 -0
- package/ops/blur.js +7 -0
- package/ops/composite.d.ts +5 -0
- package/ops/composite.js +25 -0
- package/ops/crop.d.ts +3 -0
- package/ops/crop.js +44 -0
- package/ops/dither.d.ts +3 -0
- package/ops/dither.js +68 -0
- package/ops/exif.d.ts +3 -0
- package/ops/exif.js +9 -0
- package/ops/extend.d.ts +3 -0
- package/ops/extend.js +20 -0
- package/ops/gamma.d.ts +3 -0
- package/ops/gamma.js +7 -0
- package/ops/grayscale.d.ts +3 -0
- package/ops/grayscale.js +11 -0
- package/ops/hsbl.d.ts +3 -0
- package/ops/hsbl.js +15 -0
- package/ops/nest.d.ts +3 -0
- package/ops/nest.js +13 -0
- package/ops/output.d.ts +3 -0
- package/ops/output.js +95 -0
- package/ops/resize.d.ts +3 -0
- package/ops/resize.js +20 -0
- package/ops/rotate.d.ts +3 -0
- package/ops/rotate.js +12 -0
- package/ops.d.ts +14 -14
- package/ops.js +1 -1
- package/package.json +11 -8
- package/path.d.ts +18 -6
- package/path.js +34 -4
- package/proc.d.ts +27 -5
- package/proc.js +65 -336
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2024-02-
|
|
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
|
|
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
|
|
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
|
-
{ "
|
|
83
|
-
{ "
|
|
84
|
-
{ "
|
|
85
|
+
{ "op": "rotate" },
|
|
86
|
+
{ "op": "extend", "border": 5, "unit": "%", "ref": "min", "bg": "#fff" },
|
|
87
|
+
{ "op": "resize", "size": 1920 },
|
|
85
88
|
{
|
|
86
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
110
|
+
"op": "output",
|
|
111
|
+
"id": "hires",
|
|
108
112
|
"path": "{name}-{sha256}-{w}x{h}.avif",
|
|
109
113
|
"avif": { "quality": 80 }
|
|
110
114
|
},
|
|
111
|
-
{ "
|
|
112
|
-
{ "
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
27
|
+
op: string;
|
|
25
28
|
}
|
|
26
29
|
export interface BlurSpec extends ProcSpec {
|
|
27
|
-
|
|
30
|
+
op: "blur";
|
|
28
31
|
radius: number;
|
|
29
32
|
}
|
|
30
33
|
export interface CompSpec extends ProcSpec {
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
+
op: "exif";
|
|
71
86
|
tags: Exif;
|
|
72
87
|
}
|
|
73
88
|
export interface ExtendSpec extends ProcSpec {
|
|
74
|
-
|
|
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
|
-
|
|
97
|
+
op: "gamma";
|
|
83
98
|
gamma: number;
|
|
84
99
|
}
|
|
85
100
|
export interface GrayscaleSpec extends ProcSpec {
|
|
86
|
-
|
|
101
|
+
op: "gray";
|
|
87
102
|
gamma?: number | boolean;
|
|
88
103
|
}
|
|
89
104
|
export interface HSBLSpec extends ProcSpec {
|
|
90
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>;
|
package/layers/image.js
ADDED
|
@@ -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
|
+
};
|
package/layers/svg.d.ts
ADDED
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
|
+
};
|
package/layers/text.d.ts
ADDED
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
package/ops/blur.js
ADDED
|
@@ -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
|
package/ops/composite.js
ADDED
|
@@ -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
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
|
+
};
|
package/ops/dither.d.ts
ADDED