@arraypress/covers 1.0.0 → 1.1.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 +76 -0
- package/README.md +296 -11
- package/package.json +33 -5
- package/recipes/brand.json +1143 -0
- package/schema/recipe.schema.json +7300 -770
- package/src/animate/index.d.ts +19 -0
- package/src/animate/index.js +116 -0
- package/src/builtins.js +4 -0
- package/src/effects/ascii.d.ts +7 -0
- package/src/effects/ascii.js +93 -0
- package/src/effects/buffer.d.ts +60 -0
- package/src/effects/buffer.js +472 -0
- package/src/effects/detail.d.ts +12 -0
- package/src/effects/detail.js +124 -0
- package/src/effects/digital.d.ts +14 -0
- package/src/effects/digital.js +216 -0
- package/src/effects/distort.d.ts +20 -0
- package/src/effects/distort.js +314 -0
- package/src/effects/dither.d.ts +8 -0
- package/src/effects/dither.js +147 -0
- package/src/effects/drop-shadow.d.ts +4 -0
- package/src/effects/drop-shadow.js +45 -0
- package/src/effects/extrude.d.ts +5 -0
- package/src/effects/extrude.js +55 -0
- package/src/effects/film.d.ts +11 -0
- package/src/effects/film.js +204 -0
- package/src/effects/glow.d.ts +8 -0
- package/src/effects/glow.js +56 -0
- package/src/effects/grade.d.ts +30 -0
- package/src/effects/grade.js +274 -0
- package/src/effects/halftone.d.ts +8 -0
- package/src/effects/halftone.js +95 -0
- package/src/effects/index.d.ts +125 -0
- package/src/effects/index.js +277 -0
- package/src/effects/ink.d.ts +12 -0
- package/src/effects/ink.js +70 -0
- package/src/effects/lens.d.ts +14 -0
- package/src/effects/lens.js +277 -0
- package/src/effects/light.d.ts +10 -0
- package/src/effects/light.js +59 -0
- package/src/effects/looks.d.ts +9 -0
- package/src/effects/looks.js +74 -0
- package/src/effects/mask.d.ts +52 -0
- package/src/effects/mask.js +314 -0
- package/src/effects/motion.d.ts +6 -0
- package/src/effects/motion.js +60 -0
- package/src/effects/newsprint.d.ts +6 -0
- package/src/effects/newsprint.js +89 -0
- package/src/effects/optics.d.ts +10 -0
- package/src/effects/optics.js +213 -0
- package/src/effects/options.d.ts +6 -0
- package/src/effects/options.js +106 -0
- package/src/effects/paint-presets.d.ts +10 -0
- package/src/effects/paint-presets.js +47 -0
- package/src/effects/paint.d.ts +49 -0
- package/src/effects/paint.js +66 -0
- package/src/effects/pixels.d.ts +24 -0
- package/src/effects/pixels.js +138 -0
- package/src/effects/polar.d.ts +10 -0
- package/src/effects/polar.js +265 -0
- package/src/effects/region.d.ts +8 -0
- package/src/effects/region.js +136 -0
- package/src/effects/round-border.d.ts +5 -0
- package/src/effects/round-border.js +62 -0
- package/src/effects/screen.d.ts +14 -0
- package/src/effects/screen.js +212 -0
- package/src/effects/sort.d.ts +8 -0
- package/src/effects/sort.js +125 -0
- package/src/effects/symmetry.d.ts +6 -0
- package/src/effects/symmetry.js +74 -0
- package/src/effects/tile.d.ts +7 -0
- package/src/effects/tile.js +55 -0
- package/src/effects/tone.d.ts +16 -0
- package/src/effects/tone.js +117 -0
- package/src/effects/transform.d.ts +35 -0
- package/src/effects/transform.js +127 -0
- package/src/effects/util.d.ts +16 -0
- package/src/effects/util.js +85 -0
- package/src/extract.js +4 -3
- package/src/fonts.d.ts +55 -0
- package/src/fonts.js +246 -53
- package/src/formats/cut.d.ts +10 -0
- package/src/formats/cut.js +77 -0
- package/src/formats/index.d.ts +6 -0
- package/src/formats/index.js +9 -0
- package/src/formats/presets.d.ts +41 -0
- package/src/formats/presets.js +89 -0
- package/src/formats/print.d.ts +25 -0
- package/src/formats/print.js +117 -0
- package/src/formats/zones.d.ts +10 -0
- package/src/formats/zones.js +64 -0
- package/src/geometry.js +1 -1
- package/src/icons.js +43 -9
- package/src/index.d.ts +205 -19
- package/src/index.js +12 -4
- package/src/inter-data.js +27 -0
- package/src/json-recipe.js +181 -23
- package/src/kit/index.d.ts +20 -0
- package/src/kit/index.js +58 -0
- package/src/layer-options.js +23 -4
- package/src/layers/barcode.d.ts +19 -0
- package/src/layers/barcode.js +63 -0
- package/src/layers/code128.d.ts +4 -0
- package/src/layers/code128.js +28 -0
- package/src/layers/expr.d.ts +6 -0
- package/src/layers/expr.js +121 -0
- package/src/layers/geometry.d.ts +6 -0
- package/src/layers/geometry.js +238 -0
- package/src/layers/index.d.ts +18 -0
- package/src/layers/index.js +15 -0
- package/src/layers/perforation.d.ts +14 -0
- package/src/layers/perforation.js +47 -0
- package/src/layers/repeat.d.ts +29 -0
- package/src/layers/repeat.js +90 -0
- package/src/layers/shape.d.ts +46 -0
- package/src/layers/shape.js +115 -0
- package/src/layers/soft.d.ts +5 -0
- package/src/layers/soft.js +43 -0
- package/src/layers/svg.d.ts +23 -0
- package/src/layers/svg.js +109 -0
- package/src/layers/table.d.ts +25 -0
- package/src/layers/table.js +80 -0
- package/src/layers/tear.d.ts +21 -0
- package/src/layers/tear.js +74 -0
- package/src/layers/texture.d.ts +17 -0
- package/src/layers/texture.js +106 -0
- package/src/layers/util.d.ts +8 -0
- package/src/layers/util.js +48 -0
- package/src/layers.js +125 -41
- package/src/lint/image-size.d.ts +4 -0
- package/src/lint/image-size.js +34 -0
- package/src/lint/index.d.ts +16 -0
- package/src/lint/index.js +127 -0
- package/src/lint/rules.d.ts +5 -0
- package/src/lint/rules.js +19 -0
- package/src/node.d.ts +2 -0
- package/src/node.js +20 -0
- package/src/platform/bytes.d.ts +22 -0
- package/src/platform/bytes.js +103 -0
- package/src/platform/compress.d.ts +12 -0
- package/src/platform/compress.js +81 -0
- package/src/platform/harfbuzz.d.ts +9 -0
- package/src/platform/harfbuzz.js +80 -0
- package/src/platform/index.d.ts +8 -0
- package/src/platform/index.js +13 -0
- package/src/platform/load.d.ts +6 -0
- package/src/platform/load.js +72 -0
- package/src/platform/no-harfbuzz.d.ts +2 -0
- package/src/platform/no-harfbuzz.js +11 -0
- package/src/platform/node.d.ts +26 -0
- package/src/platform/node.js +86 -0
- package/src/platform/settings.d.ts +28 -0
- package/src/platform/settings.js +51 -0
- package/src/png.js +77 -28
- package/src/print.js +8 -7
- package/src/raster/index.d.ts +24 -0
- package/src/raster/index.js +171 -0
- package/src/recipes.js +4 -3
- package/src/render/backdrop.d.ts +6 -0
- package/src/render/backdrop.js +55 -0
- package/src/render/content.d.ts +5 -0
- package/src/render/content.js +58 -0
- package/src/render/context.d.ts +82 -0
- package/src/render/grain.d.ts +7 -0
- package/src/render/grain.js +113 -0
- package/src/render/group.d.ts +23 -0
- package/src/render/group.js +67 -0
- package/src/render/key.d.ts +5 -0
- package/src/render/key.js +91 -0
- package/src/render/report.d.ts +5 -0
- package/src/render/report.js +19 -0
- package/src/render/tiles.d.ts +6 -0
- package/src/render/tiles.js +77 -0
- package/src/render/use.d.ts +27 -0
- package/src/render/use.js +73 -0
- package/src/render/wrap.d.ts +14 -0
- package/src/render/wrap.js +199 -0
- package/src/render.js +303 -212
- package/src/text.d.ts +29 -0
- package/src/text.js +22 -15
- package/src/type/auto-color.d.ts +9 -0
- package/src/type/auto-color.js +44 -0
- package/src/type/fallback.d.ts +11 -0
- package/src/type/fallback.js +92 -0
- package/src/type/glyphs.d.ts +24 -0
- package/src/type/glyphs.js +87 -0
- package/src/type/index.d.ts +5 -0
- package/src/type/index.js +10 -0
- package/src/type/kern.d.ts +4 -0
- package/src/type/kern.js +144 -0
- package/src/type/layout-ref.d.ts +10 -0
- package/src/type/layout-ref.js +74 -0
- package/src/type/line.d.ts +11 -0
- package/src/type/line.js +105 -0
- package/src/type/live.d.ts +6 -0
- package/src/type/live.js +39 -0
- package/src/type/name.d.ts +4 -0
- package/src/type/name.js +32 -0
- package/src/type/path.d.ts +18 -0
- package/src/type/path.js +89 -0
- package/src/type/pixel.d.ts +9 -0
- package/src/type/pixel.js +115 -0
- package/src/type/sfnt.d.ts +20 -0
- package/src/type/sfnt.js +258 -0
- package/src/type/spans.d.ts +33 -0
- package/src/type/spans.js +138 -0
- package/src/type/symbols.d.ts +7 -0
- package/src/type/symbols.js +89 -0
- package/src/type/typeset.d.ts +10 -0
- package/src/type/typeset.js +352 -0
- package/src/type/woff2.d.ts +6 -0
- package/src/type/woff2.js +301 -0
- package/src/version.js +4 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colour grading, as SVG filters: `curves` (a smooth tone curve through
|
|
3
|
+
* points, per channel too), `gradientMap` (tones mapped through a
|
|
4
|
+
* gradient of any number of stops), `posterize`, `invert`, `hue` (hue,
|
|
5
|
+
* saturation and lightness), `colorBalance` (shadows, midtones and
|
|
6
|
+
* highlights pushed toward a colour), `channelMixer` and `sepia`; and
|
|
7
|
+
* `vibrance` on pixels (it weighs each pixel by how saturated it already
|
|
8
|
+
* is, which no colour matrix can).
|
|
9
|
+
*
|
|
10
|
+
* @module effects/grade
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { num } from '../svg.js';
|
|
14
|
+
import { mixOklab, toHex } from '../color.js';
|
|
15
|
+
import { LUMA, rgb, mat } from './util.js';
|
|
16
|
+
|
|
17
|
+
/** Entries in a tone table (the filter interpolates between them). */
|
|
18
|
+
const TABLE = 128;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A monotone cubic through points (Fritsch–Carlson), sampled as a table.
|
|
22
|
+
*
|
|
23
|
+
* @param {[number, number][]} points - `[input, output]`, 0–1.
|
|
24
|
+
* @param {number} [n=TABLE]
|
|
25
|
+
* @returns {number[]}
|
|
26
|
+
*/
|
|
27
|
+
export function curveTable(points, n = TABLE) {
|
|
28
|
+
const p = [...points].map(([x, y]) => [Number(x), Number(y)]).sort((a, b) => a[0] - b[0]);
|
|
29
|
+
if (p.length === 0) return Array.from({ length: n }, (_, i) => i / (n - 1));
|
|
30
|
+
if (p.length === 1) return Array.from({ length: n }, () => p[0][1]);
|
|
31
|
+
const k = p.length;
|
|
32
|
+
const d = [];
|
|
33
|
+
for (let i = 0; i < k - 1; i++) d.push((p[i + 1][1] - p[i][1]) / (p[i + 1][0] - p[i][0] || 1e-9));
|
|
34
|
+
const m = [d[0]];
|
|
35
|
+
for (let i = 1; i < k - 1; i++) m.push(d[i - 1] * d[i] <= 0 ? 0 : (d[i - 1] + d[i]) / 2);
|
|
36
|
+
m.push(d[k - 2]);
|
|
37
|
+
for (let i = 0; i < k - 1; i++) {
|
|
38
|
+
if (d[i] === 0) { m[i] = 0; m[i + 1] = 0; continue; }
|
|
39
|
+
const a = m[i] / d[i];
|
|
40
|
+
const b = m[i + 1] / d[i];
|
|
41
|
+
const s = a * a + b * b;
|
|
42
|
+
if (s > 9) { const t = 3 / Math.sqrt(s); m[i] = t * a * d[i]; m[i + 1] = t * b * d[i]; }
|
|
43
|
+
}
|
|
44
|
+
return Array.from({ length: n }, (_, j) => {
|
|
45
|
+
const x = j / (n - 1);
|
|
46
|
+
if (x <= p[0][0]) return clamp(p[0][1]);
|
|
47
|
+
if (x >= p[k - 1][0]) return clamp(p[k - 1][1]);
|
|
48
|
+
let i = 0;
|
|
49
|
+
while (x > p[i + 1][0]) i++;
|
|
50
|
+
const h = p[i + 1][0] - p[i][0];
|
|
51
|
+
const t = (x - p[i][0]) / h;
|
|
52
|
+
const t2 = t * t;
|
|
53
|
+
const t3 = t2 * t;
|
|
54
|
+
return clamp((2 * t3 - 3 * t2 + 1) * p[i][1] + (t3 - 2 * t2 + t) * h * m[i] + (-2 * t3 + 3 * t2) * p[i + 1][1] + (t3 - t2) * h * m[i + 1]);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @param {number} v @returns {number} */
|
|
59
|
+
const clamp = (v) => Math.min(1, Math.max(0, v));
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A component transfer from three tables (R, G, B), alpha untouched.
|
|
63
|
+
*
|
|
64
|
+
* @param {number[][]} tables
|
|
65
|
+
* @returns {string}
|
|
66
|
+
*/
|
|
67
|
+
export const transfer = (tables) => `<feComponentTransfer>${['R', 'G', 'B'].map((ch, i) => `<feFunc${ch} type="table" tableValues="${tables[i].map((v) => num(v, 4)).join(' ')}"/>`).join('')}</feComponentTransfer>`;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* `curves`: `points` for all three channels (`[[0, 0], [0.25, 0.2],
|
|
71
|
+
* [0.75, 0.82], [1, 1]]` is an S-curve), and `red`, `green`, `blue` on
|
|
72
|
+
* top of it, each `[input, output]` pairs, 0–1.
|
|
73
|
+
*
|
|
74
|
+
* @param {import('./index.js').Effect & { kind: 'curves' }} fx
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
export function curves(fx) {
|
|
78
|
+
const master = curveTable(fx.points ?? [[0, 0], [1, 1]]);
|
|
79
|
+
const chan = [fx.red, fx.green, fx.blue].map((pts) => (pts ? curveTable(pts) : null));
|
|
80
|
+
return transfer(chan.map((c) => (c ? master.map((v) => c[Math.round(v * (TABLE - 1))]) : master)));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* `gradientMap`: luminance mapped through a gradient: `colors` (shadows
|
|
85
|
+
* first), at `stops` (0–1, even by default), mixed in `space`
|
|
86
|
+
* (`'srgb'` like Photoshop, or `'oklab'`).
|
|
87
|
+
*
|
|
88
|
+
* @param {import('./index.js').Effect & { kind: 'gradientMap' }} fx
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
export function gradientMap(fx) {
|
|
92
|
+
const colors = (fx.colors ?? ['#120c2b', '#d6336c', '#ffe9a8']).map((c) => toHex(c));
|
|
93
|
+
const stops = fx.stops ?? colors.map((_, i) => i / Math.max(1, colors.length - 1));
|
|
94
|
+
const lum = `<feColorMatrix type="matrix" values="${mat([...LUMA, 0, 0, ...LUMA, 0, 0, ...LUMA, 0, 0, 0, 0, 0, 1, 0])}"/>`;
|
|
95
|
+
const n = 64;
|
|
96
|
+
const samples = Array.from({ length: n }, (_, j) => sampleGradient(colors, stops, j / (n - 1), fx.space ?? 'srgb'));
|
|
97
|
+
return lum + transfer([0, 1, 2].map((c) => samples.map((s) => s[c])));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A gradient's colour at `t`, as 0–1 channels.
|
|
102
|
+
*
|
|
103
|
+
* @param {string[]} colors
|
|
104
|
+
* @param {number[]} stops
|
|
105
|
+
* @param {number} t
|
|
106
|
+
* @param {'srgb' | 'oklab'} space
|
|
107
|
+
* @returns {[number, number, number]}
|
|
108
|
+
*/
|
|
109
|
+
export function sampleGradient(colors, stops, t, space) {
|
|
110
|
+
if (t <= stops[0]) return rgb(colors[0]);
|
|
111
|
+
for (let i = 0; i < colors.length - 1; i++) {
|
|
112
|
+
if (t <= stops[i + 1]) {
|
|
113
|
+
const k = (t - stops[i]) / (stops[i + 1] - stops[i] || 1);
|
|
114
|
+
if (space === 'oklab') return rgb(mixOklab(colors[i], colors[i + 1], k));
|
|
115
|
+
const a = rgb(colors[i]);
|
|
116
|
+
const b = rgb(colors[i + 1]);
|
|
117
|
+
return [a[0] + (b[0] - a[0]) * k, a[1] + (b[1] - a[1]) * k, a[2] + (b[2] - a[2]) * k];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return rgb(colors[colors.length - 1]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* `posterize`: `levels` tones per channel.
|
|
125
|
+
*
|
|
126
|
+
* @param {import('./index.js').Effect & { kind: 'posterize' }} fx
|
|
127
|
+
* @returns {string}
|
|
128
|
+
*/
|
|
129
|
+
export function posterize(fx) {
|
|
130
|
+
const n = Math.max(2, Math.round(fx.levels ?? 4));
|
|
131
|
+
const t = Array.from({ length: n }, (_, i) => num(i / (n - 1), 4)).join(' ');
|
|
132
|
+
return `<feComponentTransfer>${['R', 'G', 'B'].map((ch) => `<feFunc${ch} type="discrete" tableValues="${t}"/>`).join('')}</feComponentTransfer>`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* `invert`: a negative, `amount` of the way (default 1).
|
|
137
|
+
*
|
|
138
|
+
* @param {import('./index.js').Effect & { kind: 'invert' }} fx
|
|
139
|
+
* @returns {string}
|
|
140
|
+
*/
|
|
141
|
+
export function invert(fx) {
|
|
142
|
+
const a = fx.amount ?? 1;
|
|
143
|
+
return `<feComponentTransfer>${['R', 'G', 'B'].map((ch) => `<feFunc${ch} type="table" tableValues="${num(a, 4)} ${num(1 - a, 4)}"/>`).join('')}</feComponentTransfer>`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* `hue`: turn hues by `angle` degrees, `saturation` (-1 grey to 1 double)
|
|
148
|
+
* and `lightness` (-1 black to 1 white), as Photoshop's Hue/Saturation.
|
|
149
|
+
*
|
|
150
|
+
* @param {import('./index.js').Effect & { kind: 'hue' }} fx
|
|
151
|
+
* @returns {string}
|
|
152
|
+
*/
|
|
153
|
+
export function hue(fx) {
|
|
154
|
+
let out = '';
|
|
155
|
+
if (fx.angle) out += `<feColorMatrix type="hueRotate" values="${num(fx.angle, 3)}"/>`;
|
|
156
|
+
if (fx.saturation) out += `<feColorMatrix type="saturate" values="${num(Math.max(0, 1 + fx.saturation), 4)}"/>`;
|
|
157
|
+
const l = fx.lightness ?? 0;
|
|
158
|
+
if (l) {
|
|
159
|
+
const slope = l > 0 ? 1 - l : 1 + l;
|
|
160
|
+
const icpt = l > 0 ? l : 0;
|
|
161
|
+
out += `<feComponentTransfer>${['R', 'G', 'B'].map((ch) => `<feFunc${ch} type="linear" slope="${num(slope, 4)}" intercept="${num(icpt, 4)}"/>`).join('')}</feComponentTransfer>`;
|
|
162
|
+
}
|
|
163
|
+
return out || '<feOffset dx="0" dy="0"/>';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* How much a tone (0–1) belongs to the shadows, midtones and highlights
|
|
168
|
+
* (GIMP's colour-balance weights).
|
|
169
|
+
*
|
|
170
|
+
* @param {number} v
|
|
171
|
+
* @returns {[number, number, number]}
|
|
172
|
+
*/
|
|
173
|
+
function zones(v) {
|
|
174
|
+
const a = 0.25;
|
|
175
|
+
const b = 0.333;
|
|
176
|
+
const s = 0.7;
|
|
177
|
+
const shadows = clamp((v - b) / -a + 0.5) * s;
|
|
178
|
+
const mids = clamp((v - b) / a + 0.5) * clamp((v + b - 1) / -a + 0.5) * s;
|
|
179
|
+
const highs = clamp((v + b - 1) / a + 0.5) * s;
|
|
180
|
+
return [shadows, mids, highs];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The tables of a colour balance: each zone's `[red, green, blue]` push
|
|
185
|
+
* (-1–1; `[0.1, 0, -0.1]` is warmer), per channel.
|
|
186
|
+
*
|
|
187
|
+
* @param {{ shadows?: number[], midtones?: number[], highlights?: number[] }} fx
|
|
188
|
+
* @returns {number[][]}
|
|
189
|
+
*/
|
|
190
|
+
export function balanceTables(fx) {
|
|
191
|
+
const z = [fx.shadows ?? [0, 0, 0], fx.midtones ?? [0, 0, 0], fx.highlights ?? [0, 0, 0]];
|
|
192
|
+
const n = 64;
|
|
193
|
+
return [0, 1, 2].map((c) => Array.from({ length: n }, (_, j) => {
|
|
194
|
+
const v = j / (n - 1);
|
|
195
|
+
const w = zones(v);
|
|
196
|
+
return clamp(v + 0.5 * (w[0] * (z[0][c] ?? 0) + w[1] * (z[1][c] ?? 0) + w[2] * (z[2][c] ?? 0)));
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* `colorBalance`: `shadows`, `midtones` and `highlights`, each a
|
|
202
|
+
* `[red, green, blue]` push, -1–1 (Photoshop's sliders over 100).
|
|
203
|
+
*
|
|
204
|
+
* @param {import('./index.js').Effect & { kind: 'colorBalance' }} fx
|
|
205
|
+
* @returns {string}
|
|
206
|
+
*/
|
|
207
|
+
export const colorBalance = (fx) => transfer(balanceTables(fx));
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* `channelMixer`: each output channel as a mix of the inputs, `red`,
|
|
211
|
+
* `green`, `blue` (`[r, g, b]` or `[r, g, b, constant]`); `monochrome`
|
|
212
|
+
* mixes one grey from `gray` (default Rec. 709).
|
|
213
|
+
*
|
|
214
|
+
* @param {import('./index.js').Effect & { kind: 'channelMixer' }} fx
|
|
215
|
+
* @returns {string}
|
|
216
|
+
*/
|
|
217
|
+
export function channelMixer(fx) {
|
|
218
|
+
const row = (/** @type {number[] | undefined} */ r, /** @type {number[]} */ d) => {
|
|
219
|
+
const v = r ?? d;
|
|
220
|
+
return [v[0] ?? 0, v[1] ?? 0, v[2] ?? 0, 0, v[3] ?? 0];
|
|
221
|
+
};
|
|
222
|
+
if (fx.monochrome) {
|
|
223
|
+
const g = row(fx.gray, LUMA);
|
|
224
|
+
return `<feColorMatrix type="matrix" values="${mat([...g, ...g, ...g, 0, 0, 0, 1, 0])}"/>`;
|
|
225
|
+
}
|
|
226
|
+
return `<feColorMatrix type="matrix" values="${mat([...row(fx.red, [1, 0, 0]), ...row(fx.green, [0, 1, 0]), ...row(fx.blue, [0, 0, 1]), 0, 0, 0, 1, 0])}"/>`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* `sepia`: the classic brown tone, `amount` of the way (default 1).
|
|
231
|
+
*
|
|
232
|
+
* @param {import('./index.js').Effect & { kind: 'sepia' }} fx
|
|
233
|
+
* @returns {string}
|
|
234
|
+
*/
|
|
235
|
+
export function sepia(fx) {
|
|
236
|
+
const a = Math.min(1, Math.max(0, fx.amount ?? 1));
|
|
237
|
+
const s = [0.393, 0.769, 0.189, 0.349, 0.686, 0.168, 0.272, 0.534, 0.131];
|
|
238
|
+
const id = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
|
239
|
+
const m = s.map((v, i) => id[i] + (v - id[i]) * a);
|
|
240
|
+
return `<feColorMatrix type="matrix" values="${mat([m[0], m[1], m[2], 0, 0, m[3], m[4], m[5], 0, 0, m[6], m[7], m[8], 0, 0, 0, 0, 0, 1, 0])}"/>`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* `vibrance` on pixels: saturation raised most where it's lowest (muted
|
|
245
|
+
* colours come up, saturated ones and skin hold), `amount` -1–1, then
|
|
246
|
+
* `saturation` evenly, as Lightroom's pair.
|
|
247
|
+
*
|
|
248
|
+
* @param {import('./buffer.js').Pixels} img
|
|
249
|
+
* @param {import('./index.js').Effect & { kind: 'vibrance' }} fx
|
|
250
|
+
* @returns {Uint8ClampedArray}
|
|
251
|
+
*/
|
|
252
|
+
export function vibrance(img, fx) {
|
|
253
|
+
const d = img.data;
|
|
254
|
+
const v = fx.amount ?? 0.5;
|
|
255
|
+
const s = 1 + (fx.saturation ?? 0);
|
|
256
|
+
const out = new Uint8ClampedArray(d.length);
|
|
257
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
258
|
+
const r = d[i];
|
|
259
|
+
const g = d[i + 1];
|
|
260
|
+
const b = d[i + 2];
|
|
261
|
+
const max = Math.max(r, g, b);
|
|
262
|
+
const min = Math.min(r, g, b);
|
|
263
|
+
const sat = (max - min) / 255;
|
|
264
|
+
// Skin (red above green above blue) is held back.
|
|
265
|
+
const skin = r > g && g > b ? 0.5 : 1;
|
|
266
|
+
const k = s * (1 + v * (1 - sat) * (v > 0 ? skin : 1));
|
|
267
|
+
const l = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
268
|
+
out[i] = l + (r - l) * k;
|
|
269
|
+
out[i + 1] = l + (g - l) * k;
|
|
270
|
+
out[i + 2] = l + (b - l) * k;
|
|
271
|
+
out[i + 3] = d[i + 3];
|
|
272
|
+
}
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The riso halftone.
|
|
2
|
+
import type { Effect } from './index.js';
|
|
3
|
+
import type { RenderContext } from '../render/context.js';
|
|
4
|
+
|
|
5
|
+
/** The `halftone` effect's filter primitives. */
|
|
6
|
+
export function halftone(fx: Extract<Effect, { kind: 'halftone' }>, ctx: RenderContext): string;
|
|
7
|
+
/** Halftone screen shapes. */
|
|
8
|
+
export const HALFTONE_SHAPES: readonly ('dot' | 'line' | 'square' | 'diamond' | 'cross')[];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The riso halftone: darkness against a rotated dot screen, thresholded
|
|
3
|
+
* softly, flooded with the ink.
|
|
4
|
+
*
|
|
5
|
+
* @module effects/halftone
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { attrs, num } from '../svg.js';
|
|
9
|
+
import { len } from '../geometry.js';
|
|
10
|
+
import { encodePng, pngUri } from '../png.js';
|
|
11
|
+
import { deflateSync } from '../platform/index.js';
|
|
12
|
+
import { LUMA, mat, flood } from './util.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A riso halftone in one ink: darkness against a rotated dot screen,
|
|
16
|
+
* thresholded softly, flooded with the ink and clipped to the source.
|
|
17
|
+
* The screen is one canvas-sized image (no tile seams at any angle).
|
|
18
|
+
*
|
|
19
|
+
* @param {any} fx - `{ ink, cell, angle, contrast, lift, softness, shape }`.
|
|
20
|
+
* @param {import('../layers.js').RenderContext} ctx
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
export function halftone(fx, ctx) {
|
|
24
|
+
const cell = len(fx.cell, ctx.canvas, '1.3vmin');
|
|
25
|
+
const angle = fx.angle ?? 15;
|
|
26
|
+
const c = fx.contrast ?? 1.15;
|
|
27
|
+
const lift = fx.lift ?? -0.05;
|
|
28
|
+
const soft = fx.softness ?? 9;
|
|
29
|
+
const { width: w, height: h } = ctx;
|
|
30
|
+
const href = screenHref(w, h, cell, angle, fx.shape ?? 'dot');
|
|
31
|
+
const k = (/** @type {number} */ v) => -c * v;
|
|
32
|
+
return `<feColorMatrix in="SourceGraphic" type="matrix" values="${mat([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, k(LUMA[0]), k(LUMA[1]), k(LUMA[2]), 0, c + lift])}" result="d"/>`
|
|
33
|
+
+ `<feImage${attrs({ href, x: 0, y: 0, width: w, height: h, preserveAspectRatio: 'none', result: 'scr' })}/>`
|
|
34
|
+
+ `<feColorMatrix in="scr" type="matrix" values="${mat([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...LUMA, 0, 0])}" result="t"/>`
|
|
35
|
+
+ '<feComposite in="d" in2="t" operator="arithmetic" k2="1" k3="-1" k4="0.5"/>'
|
|
36
|
+
+ `<feComponentTransfer><feFuncA type="linear" slope="${num(soft, 3)}" intercept="${num(-(soft - 1) / 2, 3)}"/></feComponentTransfer>`
|
|
37
|
+
+ `<feColorMatrix type="matrix" values="${flood(fx.ink ?? '#0078bf')}"/><feComposite in2="SourceAlpha" operator="in"/>`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Screen shapes: distance from a cell's centre (-0.5–0.5 each way) to
|
|
42
|
+
* 0–1, the order in which a cell fills with ink.
|
|
43
|
+
*
|
|
44
|
+
* @type {Record<string, (x: number, y: number) => number>}
|
|
45
|
+
*/
|
|
46
|
+
const SHAPES = {
|
|
47
|
+
dot: (x, y) => Math.min(1, Math.hypot(x, y) / 0.5),
|
|
48
|
+
line: (x, y) => Math.abs(y) / 0.5,
|
|
49
|
+
square: (x, y) => Math.max(Math.abs(x), Math.abs(y)) / 0.5,
|
|
50
|
+
diamond: (x, y) => (Math.abs(x) + Math.abs(y)) / 1,
|
|
51
|
+
cross: (x, y) => Math.min(Math.abs(x), Math.abs(y)) / 0.5,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Halftone screen shapes. */
|
|
55
|
+
export const HALFTONE_SHAPES = Object.freeze(Object.keys(SHAPES));
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The halftone screen: dots as radial ramps (black at the centre to #efefef
|
|
59
|
+
* at the edge) on a grid turned to `angle`, over the whole canvas; or
|
|
60
|
+
* lines, squares, diamonds or crosses (`shape`). A PNG where the platform
|
|
61
|
+
* deflates synchronously (Node, whose librsvg can't load an SVG inside
|
|
62
|
+
* feImage); an SVG data URI elsewhere (browsers draw it).
|
|
63
|
+
*
|
|
64
|
+
* @param {number} w @param {number} h @param {number} cell @param {number} angle
|
|
65
|
+
* @param {string} [shape='dot']
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
function screenHref(w, h, cell, angle, shape = 'dot') {
|
|
69
|
+
if (!Object.hasOwn(SHAPES, shape)) throw new TypeError(`Unknown halftone shape "${shape}" (${HALFTONE_SHAPES.join(', ')})`);
|
|
70
|
+
if (shape !== 'dot' || deflateSync(new Uint8Array(1))) {
|
|
71
|
+
const f = SHAPES[shape];
|
|
72
|
+
// Enough pixels for ~8 per cell, at most ~1.6 MP (the ramps upscale smoothly).
|
|
73
|
+
const s = Math.min(Math.max(1, 8 / cell), Math.sqrt(1.6e6 / (w * h)));
|
|
74
|
+
const pw = Math.max(1, Math.round(w * s));
|
|
75
|
+
const ph = Math.max(1, Math.round(h * s));
|
|
76
|
+
const px = new Uint8Array(pw * ph);
|
|
77
|
+
const a = (-angle * Math.PI) / 180;
|
|
78
|
+
const cos = Math.cos(a);
|
|
79
|
+
const sin = Math.sin(a);
|
|
80
|
+
const c = cell * s;
|
|
81
|
+
for (let y = 0; y < ph; y++) {
|
|
82
|
+
for (let x = 0; x < pw; x++) {
|
|
83
|
+
const u = (x + 0.5) * cos - (y + 0.5) * sin;
|
|
84
|
+
const v = (x + 0.5) * sin + (y + 0.5) * cos;
|
|
85
|
+
const fx = ((u % c) + c) % c - c / 2;
|
|
86
|
+
const fy = ((v % c) + c) % c - c / 2;
|
|
87
|
+
const d = shape === 'dot' ? Math.min(1, Math.hypot(fx, fy) / (c / 2)) : Math.min(1, f(fx / c, fy / c));
|
|
88
|
+
px[y * pw + x] = Math.round(239 * d);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return pngUri(encodePng(px, pw, ph, 1));
|
|
92
|
+
}
|
|
93
|
+
const screen = `<svg xmlns='http://www.w3.org/2000/svg' width='${num(w)}' height='${num(h)}' viewBox='0 0 ${num(w)} ${num(h)}'><defs><radialGradient id='g'><stop offset='0' stop-color='#000'/><stop offset='1' stop-color='#efefef'/></radialGradient><pattern id='p' width='${num(cell, 3)}' height='${num(cell, 3)}' patternUnits='userSpaceOnUse' patternTransform='rotate(${num(angle)})'><rect width='${num(cell, 3)}' height='${num(cell, 3)}' fill='#efefef'/><circle cx='${num(cell / 2, 3)}' cy='${num(cell / 2, 3)}' r='${num(cell / 2, 3)}' fill='url(#g)'/></pattern></defs><rect x='${num(-w / 4)}' y='${num(-h / 4)}' width='${num(w * 1.5)}' height='${num(h * 1.5)}' fill='url(#p)'/></svg>`;
|
|
94
|
+
return `data:image/svg+xml,${encodeURIComponent(screen)}`;
|
|
95
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Effects: filters, pixel effects and copies any layer takes.
|
|
2
|
+
import type { Color, Length, Layer } from '../index.js';
|
|
3
|
+
import type { RenderContext, Markup } from '../render/context.js';
|
|
4
|
+
export { transformAttr } from './transform.js';
|
|
5
|
+
export type { LayerTransform } from './transform.js';
|
|
6
|
+
export { paint } from './paint.js';
|
|
7
|
+
export type { Paint } from './paint.js';
|
|
8
|
+
|
|
9
|
+
/** A point as fractions of the canvas, `[x, y]` or `{ x, y }`. */
|
|
10
|
+
export type Point = [number, number] | { x: number; y: number };
|
|
11
|
+
|
|
12
|
+
/** Where an effect applies (see {@link EffectMask}). */
|
|
13
|
+
export type { EffectMask } from './mask.js';
|
|
14
|
+
import type { EffectMask } from './mask.js';
|
|
15
|
+
import type { LookName } from './looks.js';
|
|
16
|
+
|
|
17
|
+
/** A seed: a number, or any string (hashed). */
|
|
18
|
+
type Seed = number | string;
|
|
19
|
+
|
|
20
|
+
/** An effect (see the README's Effects table for each kind). */
|
|
21
|
+
export type Effect = (
|
|
22
|
+
| { kind: 'blur'; radius?: Length; x?: Length; y?: Length; edge?: 'duplicate' }
|
|
23
|
+
| { kind: 'motion'; angle?: number; length?: Length; spread?: Length }
|
|
24
|
+
| { kind: 'shadow' | 'glow'; x?: Length; y?: Length; blur?: Length; color?: Color; opacity?: number; spread?: Length }
|
|
25
|
+
| { kind: 'border'; width?: Length; color?: Color; line?: boolean | Color; lineWidth?: Length; shape?: 'die' | 'circle'; cx?: Length; cy?: Length }
|
|
26
|
+
| { kind: 'wear'; rough?: Length; uneven?: number; specks?: number; seed?: number }
|
|
27
|
+
| { kind: 'rough' | 'warp'; scale?: Length; frequency?: number | [number, number]; octaves?: number; seed?: number }
|
|
28
|
+
| { kind: 'xerox'; rough?: Length; dropout?: number; seed?: number }
|
|
29
|
+
| { kind: 'photocopy'; ink?: Color; contrast?: number; lift?: number; noise?: number; frequency?: number; seed?: number }
|
|
30
|
+
| { kind: 'duotone'; colors?: Color[]; contrast?: number }
|
|
31
|
+
| { kind: 'grayscale' | 'levels'; slope?: number; intercept?: number; contrast?: number; brightness?: number; black?: number; white?: number; gamma?: number; output?: [number, number] }
|
|
32
|
+
| { kind: 'saturate'; amount?: number }
|
|
33
|
+
| { kind: 'matrix'; values: string | number[] }
|
|
34
|
+
| { kind: 'grit'; amount?: number; frequency?: number; seed?: number }
|
|
35
|
+
| { kind: 'halftone'; ink?: Color; cell?: Length; angle?: number; contrast?: number; lift?: number; softness?: number; shape?: 'dot' | 'line' | 'square' | 'diamond' | 'cross' }
|
|
36
|
+
| { kind: 'dither'; ink?: Color; paper?: Color; cell?: Length; contrast?: number; brightness?: number; pixelate?: boolean; weights?: [number, number, number]; matrix?: 'bayer4' | 'bayer8' }
|
|
37
|
+
| { kind: 'threshold'; level?: number; ink?: Color; paper?: Color | 'none' }
|
|
38
|
+
| { kind: 'filter'; markup: string }
|
|
39
|
+
| { kind: 'extrude'; depth?: Length; angle?: number; offset?: [Length, Length]; steps?: number; color?: Color; to?: Color }
|
|
40
|
+
// Blurs (pixels).
|
|
41
|
+
| { kind: 'zoom'; center?: Point; amount?: number }
|
|
42
|
+
| { kind: 'spin'; center?: Point; angle?: number }
|
|
43
|
+
| { kind: 'tilt'; center?: Point; angle?: number; width?: Length; falloff?: Length; radius?: Length; saturate?: number }
|
|
44
|
+
| { kind: 'lens'; radius?: Length; blades?: number; rotation?: number; highlights?: number; threshold?: number }
|
|
45
|
+
// Distortions (pixels; mirror is copies).
|
|
46
|
+
| { kind: 'wave'; amplitude?: Length; wavelength?: Length; angle?: number; phase?: number; shape?: 'sine' | 'triangle' | 'square' }
|
|
47
|
+
| { kind: 'ripple'; center?: Point; amplitude?: Length; wavelength?: Length; phase?: number; radius?: Length }
|
|
48
|
+
| { kind: 'twirl'; center?: Point; angle?: number; radius?: Length }
|
|
49
|
+
| { kind: 'bulge' | 'pinch'; center?: Point; amount?: number; radius?: Length }
|
|
50
|
+
| { kind: 'fisheye'; center?: Point; amount?: number }
|
|
51
|
+
| { kind: 'glass'; texture?: 'frosted' | 'reeded' | 'pebbled'; size?: Length; amount?: Length; angle?: number; shade?: number; seed?: Seed }
|
|
52
|
+
| { kind: 'displace'; amount?: Length; map?: { layers: Layer[] }; mode?: 'gradient' | 'channels'; size?: Length; octaves?: number; seed?: Seed }
|
|
53
|
+
| { kind: 'kaleidoscope'; segments?: number; center?: Point; angle?: number; scale?: number }
|
|
54
|
+
| { kind: 'mirror'; keep?: 'left' | 'right' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; at?: number | Point }
|
|
55
|
+
| { kind: 'stretch'; at?: number; direction?: 'down' | 'up' | 'left' | 'right'; length?: number; fade?: number }
|
|
56
|
+
| { kind: 'sort'; direction?: 'horizontal' | 'vertical'; threshold?: [number, number]; by?: 'luma' | 'hue' | 'saturation'; reverse?: boolean; length?: number; seed?: Seed }
|
|
57
|
+
// Colour (filters; vibrance on pixels).
|
|
58
|
+
| { kind: 'curves'; points?: [number, number][]; red?: [number, number][]; green?: [number, number][]; blue?: [number, number][] }
|
|
59
|
+
| { kind: 'gradientMap'; colors?: Color[]; stops?: number[]; space?: 'srgb' | 'oklab' }
|
|
60
|
+
| { kind: 'posterize'; levels?: number }
|
|
61
|
+
| { kind: 'invert'; amount?: number }
|
|
62
|
+
| { kind: 'hue'; angle?: number; saturation?: number; lightness?: number }
|
|
63
|
+
| { kind: 'vibrance'; amount?: number; saturation?: number }
|
|
64
|
+
| { kind: 'colorBalance'; shadows?: [number, number, number]; midtones?: [number, number, number]; highlights?: [number, number, number] }
|
|
65
|
+
| { kind: 'channelMixer'; red?: number[]; green?: number[]; blue?: number[]; monochrome?: boolean; gray?: number[] }
|
|
66
|
+
| { kind: 'sepia'; amount?: number }
|
|
67
|
+
| { kind: 'look'; name?: LookName; amount?: number }
|
|
68
|
+
// Light.
|
|
69
|
+
| { kind: 'bloom'; threshold?: number; radius?: Length; intensity?: number; color?: Color }
|
|
70
|
+
| { kind: 'halation'; threshold?: number; radius?: Length; intensity?: number; color?: Color }
|
|
71
|
+
| { kind: 'vignette'; shape?: 'ellipse' | 'circle' | 'rect'; size?: number; softness?: number; amount?: number; color?: Color; center?: Point }
|
|
72
|
+
| { kind: 'lightLeak'; edge?: 'left' | 'right' | 'top' | 'bottom'; colors?: Color[]; intensity?: number; seed?: Seed }
|
|
73
|
+
| { kind: 'lensFlare'; at?: Point; size?: Length; intensity?: number; color?: Color; ghosts?: number; streak?: boolean | number; seed?: Seed }
|
|
74
|
+
| { kind: 'rays'; center?: Point; threshold?: number; length?: number; intensity?: number; color?: Color }
|
|
75
|
+
// Texture and print.
|
|
76
|
+
| { kind: 'grain'; amount?: number; size?: Length; color?: boolean; seed?: number }
|
|
77
|
+
| { kind: 'dust'; amount?: number; scratches?: number; color?: 'light' | 'dark' | 'mixed'; seed?: Seed }
|
|
78
|
+
| { kind: 'paper'; texture?: 'paper' | 'canvas'; scale?: Length; amount?: number; angle?: number; seed?: number }
|
|
79
|
+
| { kind: 'scanlines'; spacing?: Length; thickness?: number; amount?: number }
|
|
80
|
+
| { kind: 'crt'; mask?: 'aperture' | 'shadow'; size?: Length; amount?: number; scanlines?: number; curvature?: number }
|
|
81
|
+
| { kind: 'vhs'; shift?: Length; bleed?: Length; jitter?: Length; tracking?: number; noise?: number; seed?: Seed }
|
|
82
|
+
| { kind: 'jpeg'; quality?: number; block?: Length; passes?: number; subsample?: boolean }
|
|
83
|
+
| { kind: 'newsprint'; cell?: Length; angles?: [number, number, number, number]; paper?: Color; gain?: number; gcr?: number }
|
|
84
|
+
// Digital.
|
|
85
|
+
| { kind: 'chromatic'; amount?: Length; angle?: number; mode?: 'directional' | 'radial'; center?: Point }
|
|
86
|
+
| { kind: 'rgbSplit'; amount?: Length; angle?: number }
|
|
87
|
+
| { kind: 'glitch'; amount?: number; shift?: Length; slices?: number; blocks?: number; seed?: Seed }
|
|
88
|
+
| { kind: 'pixelate' | 'mosaic'; size?: Length; shape?: 'square' | 'hex' }
|
|
89
|
+
| { kind: 'ascii'; cell?: Length; ramp?: string; color?: 'source' | Color; background?: Color | 'none'; invert?: boolean; contrast?: number }
|
|
90
|
+
| { kind: 'emboss'; angle?: number; amount?: number; radius?: Length; color?: boolean }
|
|
91
|
+
| { kind: 'edges'; radius?: Length; amount?: number; style?: 'ink' | 'glow'; color?: Color; mono?: boolean }
|
|
92
|
+
| { kind: 'sketch'; radius?: Length; darkness?: number; color?: boolean }
|
|
93
|
+
| { kind: 'sharpen'; amount?: number; radius?: Length }
|
|
94
|
+
) & {
|
|
95
|
+
/** Where the effect applies: a gradient or layers (white: full). */
|
|
96
|
+
mask?: EffectMask;
|
|
97
|
+
/** Where filter primitives do their arithmetic. Default `'sRGB'`; `'linearRGB'` is the SVG default browsers' own filters use. */
|
|
98
|
+
colorSpace?: 'sRGB' | 'linearRGB';
|
|
99
|
+
/** Filter region padding, a fraction of the canvas on each side. */
|
|
100
|
+
pad?: number;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/** An effect's kind. */
|
|
104
|
+
export type EffectKind = Effect['kind'];
|
|
105
|
+
|
|
106
|
+
/** Every effect kind. */
|
|
107
|
+
export const EFFECTS: readonly EffectKind[];
|
|
108
|
+
/** Every kind's options, with descriptions (validation, the schema, docs). */
|
|
109
|
+
export { EFFECT_OPTIONS, EFFECT_COMMON } from './options.js';
|
|
110
|
+
export { checkMask, RESOLVED, maskImage } from './mask.js';
|
|
111
|
+
|
|
112
|
+
/** Effects always drawn without an SVG filter (on pixels, or as copies). */
|
|
113
|
+
export const RASTER_EFFECTS: readonly EffectKind[];
|
|
114
|
+
/** Whether an effect is computed on pixels (`chromatic` only radial; `motion` only when rasterising). */
|
|
115
|
+
export function isPixel(fx: Effect | EffectKind | undefined, ctx?: { raster?: boolean }): boolean;
|
|
116
|
+
/** Whether an effect needs something that draws pixels (sharp, a canvas). */
|
|
117
|
+
export function needsPixels(fx: Effect | EffectKind | undefined): boolean;
|
|
118
|
+
/** Whether an effect is drawn without an SVG filter (pixels, copies, a round `border`). */
|
|
119
|
+
export function isRaster(fx: Effect | EffectKind | undefined, ctx?: { raster?: boolean }): boolean;
|
|
120
|
+
/** Such an effect's markup, with its mask; undefined where nothing can draw pixels (its filter is used). */
|
|
121
|
+
export function rasterEffect(fx: Effect, m: Markup, ctx: RenderContext, i: number): Promise<Markup | undefined>;
|
|
122
|
+
export { expandDropShadows } from './drop-shadow.js';
|
|
123
|
+
|
|
124
|
+
/** Wrap markup in a layer's filter effects, in order (innermost first); `content`: the box the layer paints; `prior`: the defs so far. */
|
|
125
|
+
export function applyEffects(body: string, list: Effect | Effect[], ctx: RenderContext, start?: number, content?: { x: number; y: number; width: number; height: number }, prior?: string): { defs: string; body: string };
|