@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,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pixel helpers shared by the effects computed on pixels: RGBA buffers
|
|
3
|
+
* (8-bit, unpremultiplied, row-major), bilinear sampling with the edge
|
|
4
|
+
* held, premultiplying, separable box and Gaussian blurs with running
|
|
5
|
+
* sums (O(1) a pixel whatever the radius), seeded value noise, points and
|
|
6
|
+
* lengths in pixels.
|
|
7
|
+
*
|
|
8
|
+
* @module effects/buffer
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { len } from '../geometry.js';
|
|
12
|
+
import { hashString } from '../rng.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A layer drawn as pixels: RGBA, 8-bit, not premultiplied.
|
|
16
|
+
*
|
|
17
|
+
* @typedef {object} Pixels
|
|
18
|
+
* @property {Uint8ClampedArray} data
|
|
19
|
+
* @property {number} width
|
|
20
|
+
* @property {number} height
|
|
21
|
+
* @property {number} scale - Pixels per layer unit.
|
|
22
|
+
* @property {boolean} opaque - Every pixel has alpha 255.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** sRGB byte → linear light, 0–1. */
|
|
26
|
+
export const TO_LINEAR = (() => {
|
|
27
|
+
const t = new Float32Array(256);
|
|
28
|
+
for (let i = 0; i < 256; i++) {
|
|
29
|
+
const c = i / 255;
|
|
30
|
+
t[i] = c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
31
|
+
}
|
|
32
|
+
return t;
|
|
33
|
+
})();
|
|
34
|
+
|
|
35
|
+
/** Linear light (0–1, in 4096 steps) → sRGB byte. */
|
|
36
|
+
const FROM_LINEAR = (() => {
|
|
37
|
+
const t = new Uint8ClampedArray(4097);
|
|
38
|
+
for (let i = 0; i <= 4096; i++) {
|
|
39
|
+
const c = i / 4096;
|
|
40
|
+
t[i] = Math.round(255 * (c <= 0.0031308 ? c * 12.92 : 1.055 * c ** (1 / 2.4) - 0.055));
|
|
41
|
+
}
|
|
42
|
+
return t;
|
|
43
|
+
})();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Linear light to an sRGB byte (clipped).
|
|
47
|
+
*
|
|
48
|
+
* @param {number} v - Linear, 0–1 (higher clips).
|
|
49
|
+
* @returns {number}
|
|
50
|
+
*/
|
|
51
|
+
export const toSrgb = (v) => FROM_LINEAR[v <= 0 ? 0 : v >= 1 ? 4096 : (v * 4096 + 0.5) | 0];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A length in pixels: the layer's units times the raster's scale.
|
|
55
|
+
*
|
|
56
|
+
* @param {import('../index.js').Length | undefined} v
|
|
57
|
+
* @param {{ canvas: { width: number, height: number } }} ctx
|
|
58
|
+
* @param {Pixels} img
|
|
59
|
+
* @param {import('../index.js').Length} fallback
|
|
60
|
+
* @returns {number}
|
|
61
|
+
*/
|
|
62
|
+
export const px = (v, ctx, img, fallback) => len(v, ctx.canvas, fallback) * img.scale;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A point given as fractions of the canvas (`[x, y]` or `{ x, y }`), in pixels.
|
|
66
|
+
*
|
|
67
|
+
* @param {[number, number] | { x: number, y: number } | undefined} p
|
|
68
|
+
* @param {{ width: number, height: number }} img
|
|
69
|
+
* @param {[number, number]} [fallback=[0.5, 0.5]]
|
|
70
|
+
* @returns {[number, number]}
|
|
71
|
+
*/
|
|
72
|
+
export function pointPx(p, img, fallback = [0.5, 0.5]) {
|
|
73
|
+
const [x, y] = fraction(p, fallback);
|
|
74
|
+
return [x * img.width, y * img.height];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A point as fractions, from `[x, y]` or `{ x, y }`.
|
|
79
|
+
*
|
|
80
|
+
* @param {[number, number] | { x: number, y: number } | undefined} p
|
|
81
|
+
* @param {[number, number]} fallback
|
|
82
|
+
* @returns {[number, number]}
|
|
83
|
+
*/
|
|
84
|
+
export function fraction(p, fallback) {
|
|
85
|
+
if (Array.isArray(p)) return [Number(p[0] ?? fallback[0]), Number(p[1] ?? fallback[1])];
|
|
86
|
+
if (p && typeof p === 'object') return [Number(p.x ?? fallback[0]), Number(p.y ?? fallback[1])];
|
|
87
|
+
return fallback;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** @param {number} v @returns {number} 0–1. */
|
|
91
|
+
export const clamp01 = (v) => (v < 0 ? 0 : v > 1 ? 1 : v);
|
|
92
|
+
|
|
93
|
+
/** @param {number} a @param {number} b @param {number} v @returns {number} Hermite step from a to b. */
|
|
94
|
+
export function smoothstep(a, b, v) {
|
|
95
|
+
const t = clamp01((v - a) / (b - a || 1e-9));
|
|
96
|
+
return t * t * (3 - 2 * t);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A copy premultiplied by alpha (the same buffer when it's opaque).
|
|
101
|
+
*
|
|
102
|
+
* @param {Pixels} img
|
|
103
|
+
* @returns {Uint8ClampedArray}
|
|
104
|
+
*/
|
|
105
|
+
export function premultiplied(img) {
|
|
106
|
+
if (img.opaque) return img.data;
|
|
107
|
+
const d = img.data;
|
|
108
|
+
const out = new Uint8ClampedArray(d.length);
|
|
109
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
110
|
+
const a = d[i + 3];
|
|
111
|
+
out[i] = (d[i] * a + 127) / 255;
|
|
112
|
+
out[i + 1] = (d[i + 1] * a + 127) / 255;
|
|
113
|
+
out[i + 2] = (d[i + 2] * a + 127) / 255;
|
|
114
|
+
out[i + 3] = a;
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Undo {@link premultiplied}, in place.
|
|
121
|
+
*
|
|
122
|
+
* @param {Uint8ClampedArray} d
|
|
123
|
+
* @returns {Uint8ClampedArray}
|
|
124
|
+
*/
|
|
125
|
+
export function unpremultiply(d) {
|
|
126
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
127
|
+
const a = d[i + 3];
|
|
128
|
+
if (a === 255 || a === 0) continue;
|
|
129
|
+
const k = 255 / a;
|
|
130
|
+
d[i] = d[i] * k;
|
|
131
|
+
d[i + 1] = d[i + 1] * k;
|
|
132
|
+
d[i + 2] = d[i + 2] * k;
|
|
133
|
+
}
|
|
134
|
+
return d;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Bilinear RGBA sample with the edge held, written to `out` at `o`.
|
|
139
|
+
*
|
|
140
|
+
* @param {Uint8ClampedArray} src
|
|
141
|
+
* @param {number} w
|
|
142
|
+
* @param {number} h
|
|
143
|
+
* @param {number} x - Pixel coordinates (pixel centres at .5 are exact at integers here: x = column).
|
|
144
|
+
* @param {number} y
|
|
145
|
+
* @param {Uint8ClampedArray | Float32Array} out
|
|
146
|
+
* @param {number} o
|
|
147
|
+
* @returns {void}
|
|
148
|
+
*/
|
|
149
|
+
export function bilinear(src, w, h, x, y, out, o) {
|
|
150
|
+
if (x < 0) x = 0; else if (x > w - 1) x = w - 1;
|
|
151
|
+
if (y < 0) y = 0; else if (y > h - 1) y = h - 1;
|
|
152
|
+
const x0 = x | 0;
|
|
153
|
+
const y0 = y | 0;
|
|
154
|
+
const fx = x - x0;
|
|
155
|
+
const fy = y - y0;
|
|
156
|
+
const x1 = x0 + 1 < w ? x0 + 1 : x0;
|
|
157
|
+
const y1 = y0 + 1 < h ? y0 + 1 : y0;
|
|
158
|
+
const a = (y0 * w + x0) * 4;
|
|
159
|
+
const b = (y0 * w + x1) * 4;
|
|
160
|
+
const c = (y1 * w + x0) * 4;
|
|
161
|
+
const d = (y1 * w + x1) * 4;
|
|
162
|
+
const w00 = (1 - fx) * (1 - fy);
|
|
163
|
+
const w10 = fx * (1 - fy);
|
|
164
|
+
const w01 = (1 - fx) * fy;
|
|
165
|
+
const w11 = fx * fy;
|
|
166
|
+
out[o] = src[a] * w00 + src[b] * w10 + src[c] * w01 + src[d] * w11;
|
|
167
|
+
out[o + 1] = src[a + 1] * w00 + src[b + 1] * w10 + src[c + 1] * w01 + src[d + 1] * w11;
|
|
168
|
+
out[o + 2] = src[a + 2] * w00 + src[b + 2] * w10 + src[c + 2] * w01 + src[d + 2] * w11;
|
|
169
|
+
out[o + 3] = src[a + 3] * w00 + src[b + 3] * w10 + src[c + 3] * w01 + src[d + 3] * w11;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Resample every pixel from where `map` says it comes from (an inverse
|
|
174
|
+
* mapping: distortions). `map(x, y, at)` writes the source position into
|
|
175
|
+
* `at[0]`, `at[1]` (pixel coordinates; the edge is held beyond them).
|
|
176
|
+
* Premultiplied where the layer isn't opaque, so clear pixels don't bleed.
|
|
177
|
+
*
|
|
178
|
+
* @param {Pixels} img
|
|
179
|
+
* @param {(x: number, y: number, at: Float64Array) => void} map
|
|
180
|
+
* @returns {Uint8ClampedArray}
|
|
181
|
+
*/
|
|
182
|
+
export function remap(img, map) {
|
|
183
|
+
const { width: w, height: h } = img;
|
|
184
|
+
const src = premultiplied(img);
|
|
185
|
+
const out = new Uint8ClampedArray(w * h * 4);
|
|
186
|
+
const at = new Float64Array(2);
|
|
187
|
+
for (let y = 0, o = 0; y < h; y++) {
|
|
188
|
+
for (let x = 0; x < w; x++, o += 4) {
|
|
189
|
+
map(x, y, at);
|
|
190
|
+
bilinear(src, w, h, at[0], at[1], out, o);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return img.opaque ? out : unpremultiply(out);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Box radii for three passes that approximate a Gaussian of `sigma`.
|
|
198
|
+
*
|
|
199
|
+
* @param {number} sigma
|
|
200
|
+
* @returns {number[]}
|
|
201
|
+
*/
|
|
202
|
+
export function boxesFor(sigma) {
|
|
203
|
+
const n = 3;
|
|
204
|
+
const ideal = Math.sqrt((12 * sigma * sigma) / n + 1);
|
|
205
|
+
let wl = Math.floor(ideal);
|
|
206
|
+
if (wl % 2 === 0) wl--;
|
|
207
|
+
const wu = wl + 2;
|
|
208
|
+
const m = Math.round((12 * sigma * sigma - n * wl * wl - 4 * n * wl - 3 * n) / (-4 * wl - 4));
|
|
209
|
+
return Array.from({ length: n }, (_, i) => ((i < m ? wl : wu) - 1) / 2);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* A box blur along rows (`dir` 0) or columns (1) of an interleaved
|
|
214
|
+
* buffer, the edge held: running sums, O(1) a sample.
|
|
215
|
+
*
|
|
216
|
+
* @param {Uint8ClampedArray | Float32Array} src
|
|
217
|
+
* @param {Uint8ClampedArray | Float32Array} dst
|
|
218
|
+
* @param {number} w
|
|
219
|
+
* @param {number} h
|
|
220
|
+
* @param {number} ch - Channels per pixel.
|
|
221
|
+
* @param {number} r - Radius, whole pixels.
|
|
222
|
+
* @param {0 | 1} dir
|
|
223
|
+
* @returns {void}
|
|
224
|
+
*/
|
|
225
|
+
export function boxPass(src, dst, w, h, ch, r, dir) {
|
|
226
|
+
const lines = dir === 0 ? h : w;
|
|
227
|
+
const n = dir === 0 ? w : h;
|
|
228
|
+
const step = (dir === 0 ? 1 : w) * ch;
|
|
229
|
+
const lineStep = (dir === 0 ? w : 1) * ch;
|
|
230
|
+
const inv = 1 / (2 * r + 1);
|
|
231
|
+
const acc = new Float64Array(ch);
|
|
232
|
+
for (let l = 0; l < lines; l++) {
|
|
233
|
+
const base = l * lineStep;
|
|
234
|
+
const last = base + (n - 1) * step;
|
|
235
|
+
for (let c = 0; c < ch; c++) acc[c] = src[base + c] * (r + 1);
|
|
236
|
+
for (let k = 1; k <= r; k++) {
|
|
237
|
+
const i = base + Math.min(k, n - 1) * step;
|
|
238
|
+
for (let c = 0; c < ch; c++) acc[c] += src[i + c];
|
|
239
|
+
}
|
|
240
|
+
for (let k = 0; k < n; k++) {
|
|
241
|
+
const o = base + k * step;
|
|
242
|
+
for (let c = 0; c < ch; c++) dst[o + c] = acc[c] * inv;
|
|
243
|
+
const add = k + r + 1 < n ? base + (k + r + 1) * step : last;
|
|
244
|
+
const sub = k - r >= 0 ? base + (k - r) * step : base;
|
|
245
|
+
for (let c = 0; c < ch; c++) acc[c] += src[add + c] - src[sub + c];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* A Gaussian blur (three box passes each way), the edge held.
|
|
252
|
+
*
|
|
253
|
+
* @template {Uint8ClampedArray | Float32Array} T
|
|
254
|
+
* @param {T} data - Interleaved samples (blurred in place).
|
|
255
|
+
* @param {number} w
|
|
256
|
+
* @param {number} h
|
|
257
|
+
* @param {number} ch
|
|
258
|
+
* @param {number} sigma - Pixels (x).
|
|
259
|
+
* @param {number} [sigmaY=sigma] - Pixels (y).
|
|
260
|
+
* @returns {T}
|
|
261
|
+
*/
|
|
262
|
+
export function gaussian(data, w, h, ch, sigma, sigmaY = sigma) {
|
|
263
|
+
/** @type {[number, 0 | 1][]} */
|
|
264
|
+
const passes = [];
|
|
265
|
+
if (sigma > 0.3) for (const r of boxesFor(sigma)) passes.push([Math.round(r), 0]);
|
|
266
|
+
if (sigmaY > 0.3) for (const r of boxesFor(sigmaY)) passes.push([Math.round(r), 1]);
|
|
267
|
+
if (!passes.length) return data;
|
|
268
|
+
// Ping-pong between two buffers; one copy back at the end if needed.
|
|
269
|
+
let a = data;
|
|
270
|
+
let b = /** @type {T} */ (new /** @type {any} */ (data.constructor)(data.length));
|
|
271
|
+
for (const [r, dir] of passes) { boxPass(a, b, w, h, ch, r, dir); [a, b] = [b, a]; }
|
|
272
|
+
if (a !== data) data.set(a);
|
|
273
|
+
return data;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Downsample by a whole factor, averaging each block (premultiplied in,
|
|
278
|
+
* premultiplied out). The last row and column of blocks may be partial.
|
|
279
|
+
*
|
|
280
|
+
* @param {Uint8ClampedArray} src
|
|
281
|
+
* @param {number} w
|
|
282
|
+
* @param {number} h
|
|
283
|
+
* @param {number} f
|
|
284
|
+
* @returns {{ data: Uint8ClampedArray, width: number, height: number }}
|
|
285
|
+
*/
|
|
286
|
+
export function shrink(src, w, h, f) {
|
|
287
|
+
const sw = Math.ceil(w / f);
|
|
288
|
+
const sh = Math.ceil(h / f);
|
|
289
|
+
const out = new Uint8ClampedArray(sw * sh * 4);
|
|
290
|
+
const acc = new Float64Array(sw * 4);
|
|
291
|
+
const cnt = new Float64Array(sw);
|
|
292
|
+
for (let by = 0; by < sh; by++) {
|
|
293
|
+
acc.fill(0);
|
|
294
|
+
cnt.fill(0);
|
|
295
|
+
for (let y = by * f; y < Math.min(h, (by + 1) * f); y++) {
|
|
296
|
+
for (let x = 0; x < w; x++) {
|
|
297
|
+
const i = (y * w + x) * 4;
|
|
298
|
+
const j = ((x / f) | 0) * 4;
|
|
299
|
+
acc[j] += src[i]; acc[j + 1] += src[i + 1]; acc[j + 2] += src[i + 2]; acc[j + 3] += src[i + 3];
|
|
300
|
+
cnt[(x / f) | 0]++;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
for (let bx = 0; bx < sw; bx++) {
|
|
304
|
+
const o = (by * sw + bx) * 4;
|
|
305
|
+
const k = 1 / cnt[bx];
|
|
306
|
+
out[o] = acc[bx * 4] * k; out[o + 1] = acc[bx * 4 + 1] * k; out[o + 2] = acc[bx * 4 + 2] * k; out[o + 3] = acc[bx * 4 + 3] * k;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return { data: out, width: sw, height: sh };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Upscale to a size, bilinear (a smaller working copy back to the layer's pixels).
|
|
314
|
+
*
|
|
315
|
+
* @param {Uint8ClampedArray} src
|
|
316
|
+
* @param {number} sw
|
|
317
|
+
* @param {number} sh
|
|
318
|
+
* @param {number} w
|
|
319
|
+
* @param {number} h
|
|
320
|
+
* @returns {Uint8ClampedArray}
|
|
321
|
+
*/
|
|
322
|
+
export function enlarge(src, sw, sh, w, h) {
|
|
323
|
+
const out = new Uint8ClampedArray(w * h * 4);
|
|
324
|
+
const kx = sw / w;
|
|
325
|
+
const ky = sh / h;
|
|
326
|
+
for (let y = 0, o = 0; y < h; y++) {
|
|
327
|
+
const sy = (y + 0.5) * ky - 0.5;
|
|
328
|
+
for (let x = 0; x < w; x++, o += 4) bilinear(src, sw, sh, (x + 0.5) * kx - 0.5, sy, out, o);
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Upscale to a size, nearest neighbour (cells stay crisp).
|
|
335
|
+
*
|
|
336
|
+
* @param {Uint8ClampedArray} src
|
|
337
|
+
* @param {number} sw
|
|
338
|
+
* @param {number} sh
|
|
339
|
+
* @param {number} w
|
|
340
|
+
* @param {number} h
|
|
341
|
+
* @returns {Uint8ClampedArray}
|
|
342
|
+
*/
|
|
343
|
+
export function nearest(src, sw, sh, w, h) {
|
|
344
|
+
const out = new Uint8ClampedArray(w * h * 4);
|
|
345
|
+
const u = new Uint32Array(src.buffer, src.byteOffset, sw * sh);
|
|
346
|
+
const v = new Uint32Array(out.buffer);
|
|
347
|
+
for (let y = 0; y < h; y++) {
|
|
348
|
+
const row = Math.min(sh - 1, ((y * sh) / h) | 0) * sw;
|
|
349
|
+
for (let x = 0; x < w; x++) v[y * w + x] = u[row + Math.min(sw - 1, ((x * sw) / w) | 0)];
|
|
350
|
+
}
|
|
351
|
+
return out;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Rec. 709 luma of a pixel, 0–255.
|
|
356
|
+
*
|
|
357
|
+
* @param {ArrayLike<number>} d
|
|
358
|
+
* @param {number} i - Index of the pixel's red sample.
|
|
359
|
+
* @returns {number}
|
|
360
|
+
*/
|
|
361
|
+
export const luma = (d, i) => 0.2126 * d[i] + 0.7152 * d[i + 1] + 0.0722 * d[i + 2];
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* A seed for a pixel effect: the effect's own `seed`, else one from the layer's stream.
|
|
365
|
+
*
|
|
366
|
+
* @param {{ seed?: number | string }} fx
|
|
367
|
+
* @param {{ rng: { int: (a: number, b: number) => number } }} ctx
|
|
368
|
+
* @returns {number}
|
|
369
|
+
*/
|
|
370
|
+
export const seedFor = (fx, ctx) => (fx.seed != null ? hashString(String(fx.seed)) : ctx.rng.int(1, 2 ** 31 - 1));
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* A fast integer hash of a lattice point and a seed, 0–1.
|
|
374
|
+
*
|
|
375
|
+
* @param {number} x
|
|
376
|
+
* @param {number} y
|
|
377
|
+
* @param {number} seed
|
|
378
|
+
* @returns {number}
|
|
379
|
+
*/
|
|
380
|
+
export function hash2(x, y, seed) {
|
|
381
|
+
let h = Math.imul(x | 0, 374761393) ^ Math.imul(y | 0, 668265263) ^ Math.imul(seed | 0, 2246822519);
|
|
382
|
+
h = Math.imul(h ^ (h >>> 13), 1274126177);
|
|
383
|
+
h ^= h >>> 16;
|
|
384
|
+
return (h >>> 0) / 4294967296;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Smooth value noise, -1–1, with lattice points every `1` unit.
|
|
389
|
+
*
|
|
390
|
+
* @param {number} x
|
|
391
|
+
* @param {number} y
|
|
392
|
+
* @param {number} seed
|
|
393
|
+
* @returns {number}
|
|
394
|
+
*/
|
|
395
|
+
export function valueNoise(x, y, seed) {
|
|
396
|
+
const xi = Math.floor(x);
|
|
397
|
+
const yi = Math.floor(y);
|
|
398
|
+
const fx = x - xi;
|
|
399
|
+
const fy = y - yi;
|
|
400
|
+
const u = fx * fx * (3 - 2 * fx);
|
|
401
|
+
const v = fy * fy * (3 - 2 * fy);
|
|
402
|
+
const a = hash2(xi, yi, seed);
|
|
403
|
+
const b = hash2(xi + 1, yi, seed);
|
|
404
|
+
const c = hash2(xi, yi + 1, seed);
|
|
405
|
+
const d = hash2(xi + 1, yi + 1, seed);
|
|
406
|
+
return 2 * (a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v) - 1;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Fractal value noise (octaves summed, each twice as fine, half as
|
|
411
|
+
* strong), about -1–1.
|
|
412
|
+
*
|
|
413
|
+
* @param {number} x
|
|
414
|
+
* @param {number} y
|
|
415
|
+
* @param {number} seed
|
|
416
|
+
* @param {number} [octaves=3]
|
|
417
|
+
* @returns {number}
|
|
418
|
+
*/
|
|
419
|
+
export function fractalNoise(x, y, seed, octaves = 3) {
|
|
420
|
+
let sum = 0;
|
|
421
|
+
let amp = 1;
|
|
422
|
+
let norm = 0;
|
|
423
|
+
for (let o = 0; o < octaves; o++) {
|
|
424
|
+
sum += valueNoise(x, y, seed + o * 1013) * amp;
|
|
425
|
+
norm += amp;
|
|
426
|
+
amp *= 0.5;
|
|
427
|
+
x *= 2.03;
|
|
428
|
+
y *= 2.03;
|
|
429
|
+
}
|
|
430
|
+
return sum / norm;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* A noise field sampled on a coarse grid and read back bilinear: fractal
|
|
435
|
+
* noise for every pixel without evaluating it at every pixel.
|
|
436
|
+
*
|
|
437
|
+
* @param {number} w - Pixels.
|
|
438
|
+
* @param {number} h
|
|
439
|
+
* @param {number} period - Pixels between the coarsest lattice points.
|
|
440
|
+
* @param {number} seed
|
|
441
|
+
* @param {number} [octaves=3]
|
|
442
|
+
* @returns {Float32Array} -1–1, w × h.
|
|
443
|
+
*/
|
|
444
|
+
export function noiseField(w, h, period, seed, octaves = 3) {
|
|
445
|
+
// Fine enough for the finest octave: about 4 samples per its period.
|
|
446
|
+
const step = Math.max(1, period / 2 ** (octaves - 1) / 4);
|
|
447
|
+
const gw = Math.ceil(w / step) + 2;
|
|
448
|
+
const gh = Math.ceil(h / step) + 2;
|
|
449
|
+
const grid = new Float32Array(gw * gh);
|
|
450
|
+
for (let j = 0; j < gh; j++) for (let i = 0; i < gw; i++) grid[j * gw + i] = fractalNoise((i * step) / period, (j * step) / period, seed, octaves);
|
|
451
|
+
if (step === 1) {
|
|
452
|
+
const out = new Float32Array(w * h);
|
|
453
|
+
for (let y = 0; y < h; y++) out.set(grid.subarray(y * gw, y * gw + w), y * w);
|
|
454
|
+
return out;
|
|
455
|
+
}
|
|
456
|
+
const out = new Float32Array(w * h);
|
|
457
|
+
for (let y = 0; y < h; y++) {
|
|
458
|
+
const gy = y / step;
|
|
459
|
+
const j = gy | 0;
|
|
460
|
+
const fy = gy - j;
|
|
461
|
+
for (let x = 0; x < w; x++) {
|
|
462
|
+
const gx = x / step;
|
|
463
|
+
const i = gx | 0;
|
|
464
|
+
const fx = gx - i;
|
|
465
|
+
const p = j * gw + i;
|
|
466
|
+
const top = grid[p] + (grid[p + 1] - grid[p]) * fx;
|
|
467
|
+
const bot = grid[p + gw] + (grid[p + gw + 1] - grid[p + gw]) * fx;
|
|
468
|
+
out[y * w + x] = top + (bot - top) * fy;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return out;
|
|
472
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Sharpen, edges, emboss and sketch.
|
|
2
|
+
import type { Effect } from './index.js';
|
|
3
|
+
import type { RenderContext } from '../render/context.js';
|
|
4
|
+
|
|
5
|
+
/** The `sharpen` effect's filter primitives. */
|
|
6
|
+
export function sharpen(fx: Extract<Effect, { kind: 'sharpen' }>, ctx: RenderContext): string;
|
|
7
|
+
/** The `edges` effect's filter primitives. */
|
|
8
|
+
export function edges(fx: Extract<Effect, { kind: 'edges' }>, ctx: RenderContext): string;
|
|
9
|
+
/** The `emboss` effect's filter primitives. */
|
|
10
|
+
export function emboss(fx: Extract<Effect, { kind: 'emboss' }>, ctx: RenderContext): string;
|
|
11
|
+
/** The `sketch` effect's filter primitives. */
|
|
12
|
+
export function sketch(fx: Extract<Effect, { kind: 'sketch' }>, ctx: RenderContext): string;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detail, as SVG filters (their sizes are lengths, so they look the same
|
|
3
|
+
* at every output size): `sharpen` (an unsharp mask), `edges` (difference
|
|
4
|
+
* of Gaussians: dark lines on white, or glowing edges on black),
|
|
5
|
+
* `emboss` (a relief lit from an angle) and `sketch` (a pencil drawing:
|
|
6
|
+
* the image colour-dodged with its own blurred negative).
|
|
7
|
+
*
|
|
8
|
+
* @module effects/detail
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { num } from '../svg.js';
|
|
12
|
+
import { len } from '../geometry.js';
|
|
13
|
+
import { LUMA, rgb, mat } from './util.js';
|
|
14
|
+
|
|
15
|
+
/** A greyscale matrix (luma into R, G and B, alpha kept). */
|
|
16
|
+
const GREY = mat([...LUMA, 0, 0, ...LUMA, 0, 0, ...LUMA, 0, 0, 0, 0, 0, 1, 0]);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `sharpen`: an unsharp mask, `amount` (1 is strong) at `radius`.
|
|
20
|
+
*
|
|
21
|
+
* @param {import('./index.js').Effect & { kind: 'sharpen' }} fx
|
|
22
|
+
* @param {import('../render/context.js').RenderContext} ctx
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
export function sharpen(fx, ctx) {
|
|
26
|
+
const a = fx.amount ?? 0.8;
|
|
27
|
+
return `<feGaussianBlur in="SourceGraphic" stdDeviation="${num(len(fx.radius, ctx.canvas, '0.12vmin'), 3)}" result="sb"/>`
|
|
28
|
+
+ `<feComposite in="SourceGraphic" in2="sb" operator="arithmetic" k2="${num(1 + a, 3)}" k3="${num(-a, 3)}"/>`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Invert R, G and B (alpha kept). */
|
|
32
|
+
const NEGATIVE = '<feComponentTransfer><feFuncR type="table" tableValues="1 0"/><feFuncG type="table" tableValues="1 0"/><feFuncB type="table" tableValues="1 0"/></feComponentTransfer>';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* `a − b` centred on mid-grey, opaque: `0.5 + (a − b) / 2`. (An arithmetic
|
|
36
|
+
* composite of `a` and `−b` would take the alphas' difference too.)
|
|
37
|
+
*
|
|
38
|
+
* @param {string} a @param {string} b @param {string} result
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
const difference = (a, b, result) => `<feOffset in="${b}" dx="0" dy="0"/>${NEGATIVE}<feComposite in="${a}" operator="arithmetic" k2="0.5" k3="0.5" result="${result}"/>`;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A transfer on R, G and B from a function of 0–1, as a table.
|
|
45
|
+
*
|
|
46
|
+
* @param {(v: number) => number} f
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
function curve(f) {
|
|
50
|
+
const t = Array.from({ length: 33 }, (_, i) => num(Math.min(1, Math.max(0, f(i / 32))), 4)).join(' ');
|
|
51
|
+
return `<feComponentTransfer><feFuncR type="table" tableValues="${t}"/><feFuncG type="table" tableValues="${t}"/><feFuncB type="table" tableValues="${t}"/></feComponentTransfer>`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* `edges`: difference of Gaussians at `radius`, `amount` strong.
|
|
56
|
+
* `style: 'ink'` (default): dark lines on white, in `color`; `'glow'`:
|
|
57
|
+
* the edges in their own colours on black (Photoshop's Glowing Edges).
|
|
58
|
+
* `mono` greys the layer first.
|
|
59
|
+
*
|
|
60
|
+
* @param {import('./index.js').Effect & { kind: 'edges' }} fx
|
|
61
|
+
* @param {import('../render/context.js').RenderContext} ctx
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
64
|
+
export function edges(fx, ctx) {
|
|
65
|
+
const r = len(fx.radius, ctx.canvas, '0.1vmin');
|
|
66
|
+
const k = 8 * (fx.amount ?? 1);
|
|
67
|
+
const style = fx.style ?? 'ink';
|
|
68
|
+
if (style !== 'ink' && style !== 'glow') throw new TypeError(`Unknown edges style "${style}" (ink, glow)`);
|
|
69
|
+
const src = fx.mono || style === 'ink' ? `<feColorMatrix in="SourceGraphic" type="matrix" values="${GREY}" result="eg"/>` : '<feOffset in="SourceGraphic" dx="0" dy="0" result="eg"/>';
|
|
70
|
+
const dog = `${src}<feGaussianBlur in="eg" stdDeviation="${num(r, 3)}" result="ea"/><feGaussianBlur in="eg" stdDeviation="${num(r * 2.2, 3)}" result="eb"/>${difference('eb', 'ea', 'ed')}`;
|
|
71
|
+
// Both sides of an edge glow; ink draws where the narrow blur is darker (the line).
|
|
72
|
+
if (style === 'glow') return `${dog}${curve((v) => Math.abs(v - 0.5) * 2 * k)}<feComposite in2="SourceAlpha" operator="in"/>`;
|
|
73
|
+
let out = `${dog}${curve((v) => 1 - (v - 0.5) * 2 * k)}`;
|
|
74
|
+
if (fx.color) {
|
|
75
|
+
const [ir, ig, ib] = rgb(fx.color);
|
|
76
|
+
out += `<feColorMatrix type="matrix" values="${mat([1 - ir, 0, 0, 0, ir, 0, 1 - ig, 0, 0, ig, 0, 0, 1 - ib, 0, ib, 0, 0, 0, 1, 0])}"/>`;
|
|
77
|
+
}
|
|
78
|
+
return `${out}<feComposite in2="SourceAlpha" operator="in"/>`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* `emboss`: a relief, lit from `angle` (degrees, 0 = from the right,
|
|
83
|
+
* default 135: top-left), `amount` deep, at `radius`. Grey, as
|
|
84
|
+
* Photoshop's; `color: true` overlays it on the layer.
|
|
85
|
+
*
|
|
86
|
+
* @param {import('./index.js').Effect & { kind: 'emboss' }} fx
|
|
87
|
+
* @param {import('../render/context.js').RenderContext} ctx
|
|
88
|
+
* @returns {string}
|
|
89
|
+
*/
|
|
90
|
+
export function emboss(fx, ctx) {
|
|
91
|
+
const r = len(fx.radius, ctx.canvas, '0.12vmin');
|
|
92
|
+
const a = (((fx.angle ?? 135) + 180) * Math.PI) / 180;
|
|
93
|
+
const dx = Math.cos(a) * r;
|
|
94
|
+
const dy = -Math.sin(a) * r;
|
|
95
|
+
const k = 3 * (fx.amount ?? 1);
|
|
96
|
+
const relief = `<feColorMatrix in="SourceGraphic" type="matrix" values="${GREY}"/><feGaussianBlur stdDeviation="${num(r / 2, 3)}" result="mg"/>`
|
|
97
|
+
+ `<feOffset in="mg" dx="${num(dx, 3)}" dy="${num(dy, 3)}" result="m1"/><feOffset in="mg" dx="${num(-dx, 3)}" dy="${num(-dy, 3)}" result="m2"/>`
|
|
98
|
+
+ difference('m1', 'm2', 'md') + curve((v) => 0.5 + (v - 0.5) * k)
|
|
99
|
+
+ '<feComposite in2="SourceAlpha" operator="in" result="mr"/>';
|
|
100
|
+
return fx.color ? `${relief}<feBlend in="mr" in2="SourceGraphic" mode="overlay"/><feComposite in2="SourceAlpha" operator="in"/>` : relief;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* `sketch`: a pencil drawing. The layer greyed, colour-dodged with its
|
|
105
|
+
* own negative blurred to `radius`, darkened by `darkness` (0–1); `color:
|
|
106
|
+
* true` keeps the layer's colours (coloured pencil).
|
|
107
|
+
*
|
|
108
|
+
* @param {import('./index.js').Effect & { kind: 'sketch' }} fx
|
|
109
|
+
* @param {import('../render/context.js').RenderContext} ctx
|
|
110
|
+
* @returns {string}
|
|
111
|
+
*/
|
|
112
|
+
export function sketch(fx, ctx) {
|
|
113
|
+
const r = len(fx.radius, ctx.canvas, '0.5vmin');
|
|
114
|
+
const g = 1 + 3 * (fx.darkness ?? 0.4);
|
|
115
|
+
// The darks lifted a little, so they draw as tone rather than solid black.
|
|
116
|
+
const lift = '<feComponentTransfer><feFuncR type="linear" slope="0.82" intercept="0.18"/><feFuncG type="linear" slope="0.82" intercept="0.18"/><feFuncB type="linear" slope="0.82" intercept="0.18"/></feComponentTransfer>';
|
|
117
|
+
const grey = `<feColorMatrix in="SourceGraphic" type="matrix" values="${GREY}"/>${lift}<feOffset dx="0" dy="0" result="kn"/>`;
|
|
118
|
+
const base = fx.color ? `<feOffset in="SourceGraphic" dx="0" dy="0"/>${lift}<feOffset dx="0" dy="0" result="kg"/>` : '<feOffset in="kn" dx="0" dy="0" result="kg"/>';
|
|
119
|
+
return `${grey}${base}<feOffset in="kn" dx="0" dy="0"/>${NEGATIVE}`
|
|
120
|
+
+ `<feGaussianBlur stdDeviation="${num(r, 3)}" result="ki"/>`
|
|
121
|
+
+ '<feBlend in="ki" in2="kg" mode="color-dodge"/>'
|
|
122
|
+
+ `<feComponentTransfer><feFuncR type="gamma" exponent="${num(g, 3)}"/><feFuncG type="gamma" exponent="${num(g, 3)}"/><feFuncB type="gamma" exponent="${num(g, 3)}"/></feComponentTransfer>`
|
|
123
|
+
+ '<feComposite in2="SourceAlpha" operator="in"/>';
|
|
124
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Chromatic aberration, glitch and pixelate.
|
|
2
|
+
import type { Effect } from './index.js';
|
|
3
|
+
import type { RenderContext } from '../render/context.js';
|
|
4
|
+
import type { Pixels } from './buffer.js';
|
|
5
|
+
import type { PixelResult } from './pixels.js';
|
|
6
|
+
|
|
7
|
+
/** The directional `chromatic` (and `rgbSplit`) effect's filter primitives. */
|
|
8
|
+
export function chromatic(fx: Extract<Effect, { kind: 'chromatic' | 'rgbSplit' }>, ctx: RenderContext): string;
|
|
9
|
+
/** Radial `chromatic`, on pixels. */
|
|
10
|
+
export function chromaticRadial(img: Pixels, fx: Extract<Effect, { kind: 'chromatic' }>, ctx: object): Uint8ClampedArray;
|
|
11
|
+
/** `glitch`, on pixels. */
|
|
12
|
+
export function glitch(img: Pixels, fx: Extract<Effect, { kind: 'glitch' }>, ctx: object): Uint8ClampedArray;
|
|
13
|
+
/** `pixelate` (and `mosaic`), on pixels. */
|
|
14
|
+
export function pixelate(img: Pixels, fx: Extract<Effect, { kind: 'pixelate' | 'mosaic' }>, ctx: object): PixelResult;
|