@thi.ng/imago 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -1
- package/README.md +38 -16
- package/api.d.ts +63 -19
- 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 +6 -3
- package/proc.d.ts +24 -6
- package/proc.js +64 -340
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,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
|
|
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
|
|
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
|
-
{ "
|
|
84
|
-
{ "
|
|
85
|
-
{ "
|
|
85
|
+
{ "op": "rotate" },
|
|
86
|
+
{ "op": "extend", "border": 5, "unit": "%", "ref": "min", "bg": "#fff" },
|
|
87
|
+
{ "op": "resize", "size": 1920 },
|
|
86
88
|
{
|
|
87
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
110
|
+
"op": "output",
|
|
111
|
+
"id": "hires",
|
|
109
112
|
"path": "{name}-{sha256}-{w}x{h}.avif",
|
|
110
113
|
"avif": { "quality": 80 }
|
|
111
114
|
},
|
|
112
|
-
{ "
|
|
113
|
-
{ "
|
|
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
|
|
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
|
-
|
|
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
|
|
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,7 +302,7 @@ 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:
|
|
305
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 4.17 KB
|
|
285
306
|
|
|
286
307
|
## Dependencies
|
|
287
308
|
|
|
@@ -294,6 +315,7 @@ Package sizes (brotli'd, pre-treeshake): ESM: 3.29 KB
|
|
|
294
315
|
- [@thi.ng/logger](https://github.com/thi-ng/umbrella/tree/develop/packages/logger)
|
|
295
316
|
- [@thi.ng/pixel](https://github.com/thi-ng/umbrella/tree/develop/packages/pixel)
|
|
296
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)
|
|
297
319
|
- [sharp](https://sharp.pixelplumbing.com)
|
|
298
320
|
|
|
299
321
|
## 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
|
-
|
|
27
|
+
op: string;
|
|
26
28
|
}
|
|
27
29
|
export interface BlurSpec extends ProcSpec {
|
|
28
|
-
|
|
30
|
+
op: "blur";
|
|
29
31
|
radius: number;
|
|
30
32
|
}
|
|
31
33
|
export interface CompSpec extends ProcSpec {
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
+
op: "exif";
|
|
72
86
|
tags: Exif;
|
|
73
87
|
}
|
|
74
88
|
export interface ExtendSpec extends ProcSpec {
|
|
75
|
-
|
|
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
|
-
|
|
97
|
+
op: "gamma";
|
|
84
98
|
gamma: number;
|
|
85
99
|
}
|
|
86
100
|
export interface GrayscaleSpec extends ProcSpec {
|
|
87
|
-
|
|
101
|
+
op: "gray";
|
|
88
102
|
gamma?: number | boolean;
|
|
89
103
|
}
|
|
90
104
|
export interface HSBLSpec extends ProcSpec {
|
|
91
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>;
|
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
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
package/ops/exif.js
ADDED
package/ops/extend.d.ts
ADDED