@arraypress/covers 1.1.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/README.md +18 -809
- package/package.json +2 -1
- package/recipes/flyers/afterglow-motion.json +540 -0
- package/recipes/flyers/afterglow.json +70 -0
- package/recipes/flyers/arcfield-mirror-motion.json +397 -0
- package/recipes/flyers/arcfield-mirror.json +364 -0
- package/recipes/flyers/bauhaus-oktav.json +319 -0
- package/recipes/flyers/bauhaus-ritmo.json +735 -0
- package/recipes/flyers/bauhaus-volta.json +394 -0
- package/recipes/flyers/bloomfall.json +99 -0
- package/recipes/flyers/drag-solenne-motion.json +444 -0
- package/recipes/flyers/drag-solenne.json +426 -0
- package/recipes/flyers/fennick-patches.json +874 -0
- package/recipes/flyers/ferro-pass.json +398 -0
- package/recipes/flyers/ferrova.json +812 -0
- package/recipes/flyers/grid-rest-rhythm.json +125 -0
- package/recipes/flyers/groovy-honeycomb.json +816 -0
- package/recipes/flyers/groovy-marigold.json +479 -0
- package/recipes/flyers/groovy-peachfuzz.json +974 -0
- package/recipes/flyers/halden-orbit-motion.json +381 -0
- package/recipes/flyers/halden-orbit.json +356 -0
- package/recipes/flyers/halvard.json +120 -0
- package/recipes/flyers/kairo-weekender.json +696 -0
- package/recipes/flyers/linger-motion.json +518 -0
- package/recipes/flyers/linger.json +67 -0
- package/recipes/flyers/little-orchard.json +105 -0
- package/recipes/flyers/lofi-hexmoth.json +912 -0
- package/recipes/flyers/lofi-lowbit-motion.json +888 -0
- package/recipes/flyers/lofi-lowbit.json +880 -0
- package/recipes/flyers/lofi-nullpoint.json +3013 -0
- package/recipes/flyers/lumio-stack-motion.json +648 -0
- package/recipes/flyers/lumio-stack.json +438 -0
- package/recipes/flyers/magazine-cover.json +168 -0
- package/recipes/flyers/magazine-extended.json +269 -0
- package/recipes/flyers/magazine-knockout.json +40 -0
- package/recipes/flyers/northfold-varsity.json +618 -0
- package/recipes/flyers/open-studio.json +69 -0
- package/recipes/flyers/orrin-merch.json +715 -0
- package/recipes/flyers/oxyde.json +113 -0
- package/recipes/flyers/quiet-fields.json +610 -0
- package/recipes/flyers/scrapbook-halcyon.json +948 -0
- package/recipes/flyers/solenne-ticket.json +730 -0
- package/recipes/flyers/solvane-motion.json +576 -0
- package/recipes/flyers/solvane.json +98 -0
- package/recipes/flyers/subduct.json +99 -0
- package/recipes/flyers/sunvale-lineup.json +584 -0
- package/recipes/flyers/tidewell.json +91 -0
- package/recipes/flyers/tour-kairo.json +535 -0
- package/recipes/flyers/velora-stickers.json +885 -0
- package/recipes/flyers/wheatpaste-black.json +313 -0
- package/recipes/flyers/wheatpaste-mark.json +198 -0
- package/recipes/flyers/wheatpaste-torn.json +114 -0
- package/recipes/flyers/wheatpaste-wall.json +984 -0
- package/recipes/flyers/wheatpaste-words.json +233 -0
- package/recipes/release/aura.json +137 -0
- package/recipes/release/chrome.json +433 -0
- package/recipes/release/collage.json +372 -0
- package/recipes/release/duotone.json +113 -0
- package/recipes/release/film-still.json +102 -0
- package/recipes/release/headline.json +70 -0
- package/recipes/release/instant.json +170 -0
- package/recipes/release/label.json +364 -0
- package/recipes/release/liquid-glass.json +301 -0
- package/recipes/release/odd-tile-dark.json +688 -0
- package/recipes/release/odd-tile-dense.json +1056 -0
- package/recipes/release/odd-tile-shift.json +541 -0
- package/recipes/release/odd-tile-split.json +571 -0
- package/recipes/release/odd-tile.json +633 -0
- package/recipes/release/off-edge.json +96 -0
- package/recipes/release/plinth.json +794 -0
- package/recipes/release/riso.json +108 -0
- package/recipes/release/series/fluid-label.json +561 -0
- package/recipes/release/series/fluid-pixel.json +334 -0
- package/recipes/release/series/hairline.json +305 -0
- package/recipes/release/series/halo.json +282 -0
- package/recipes/release/series/mark-crop.json +135 -0
- package/recipes/release/series/mark-window.json +150 -0
- package/recipes/release/series/photo-label.json +475 -0
- package/recipes/release/series/smear-field.json +345 -0
- package/recipes/release/series/smear.json +202 -0
- package/recipes/release/series/spin.json +172 -0
- package/recipes/release/series/split-ghost.json +230 -0
- package/recipes/release/series/zoom.json +168 -0
- package/recipes/release/small-print.json +114 -0
- package/recipes/release/stack.json +156 -0
- package/recipes/release/swiss.json +117 -0
- package/schema/recipe.schema.json +332 -65
- package/src/animate/index.d.ts +9 -19
- package/src/animate/index.js +6 -111
- package/src/animate/keyframes.d.ts +21 -0
- package/src/animate/keyframes.js +119 -0
- package/src/animate/marquee.d.ts +30 -0
- package/src/animate/marquee.js +93 -0
- package/src/animate/morph.d.ts +8 -0
- package/src/animate/morph.js +99 -0
- package/src/animate/motion.d.ts +59 -0
- package/src/animate/motion.js +63 -0
- package/src/animate/sheet.d.ts +19 -0
- package/src/animate/sheet.js +61 -0
- package/src/animate/timing.d.ts +28 -0
- package/src/animate/timing.js +146 -0
- package/src/animate/tracks.d.ts +18 -0
- package/src/animate/tracks.js +139 -0
- package/src/diagram.js +4 -3
- package/src/extract.js +2 -2
- package/src/fonts.js +1 -2
- package/src/geometry.js +4 -34
- package/src/grid.js +1 -1
- package/src/handmade.js +9 -7
- package/src/icons.js +1 -2
- package/src/index.d.ts +39 -48
- package/src/index.js +5 -5
- package/src/inter-data.js +1 -1
- package/src/json-recipe.js +23 -7
- package/src/layer-options.js +21 -1
- package/src/layers/barcode.js +3 -3
- package/src/layers/geometry.js +5 -5
- package/src/layers/perforation.js +3 -3
- package/src/layers/repeat.js +2 -2
- package/src/layers/shape.d.ts +2 -2
- package/src/layers/shape.js +5 -5
- package/src/layers/soft.js +2 -2
- package/src/layers/svg.js +2 -3
- package/src/layers/table.js +3 -3
- package/src/layers/tear.d.ts +1 -1
- package/src/layers/tear.js +4 -4
- package/src/layers/texture.js +3 -3
- package/src/layers/util.js +15 -2
- package/src/layers.js +10 -26
- package/src/{effects/paint.js → paint/index.js} +5 -5
- package/src/{effects/paint-presets.d.ts → paint/presets.d.ts} +1 -1
- package/src/{effects/paint-presets.js → paint/presets.js} +1 -1
- package/src/{effects → paint}/tile.d.ts +1 -1
- package/src/{effects → paint}/tile.js +4 -4
- package/src/palette.js +1 -1
- package/src/platform/files.d.ts +6 -0
- package/src/platform/files.js +41 -0
- package/src/platform/harfbuzz.js +1 -1
- package/src/platform/index.d.ts +3 -3
- package/src/platform/index.js +6 -4
- package/src/platform/load.js +1 -2
- package/src/print.js +9 -7
- package/src/recipes.js +1 -1
- package/src/render/backdrop.js +2 -2
- package/src/render/grain.js +1 -1
- package/src/render/group.js +3 -3
- package/src/render/key.js +1 -2
- package/src/{effects → render}/transform.d.ts +2 -1
- package/src/{effects → render}/transform.js +17 -5
- package/src/render/use.d.ts +1 -1
- package/src/render/use.js +2 -2
- package/src/render/wrap.js +5 -60
- package/src/render.js +55 -18
- package/src/text.js +1 -1
- package/src/type/auto-color.js +1 -1
- package/src/type/glyphs.js +2 -2
- package/src/type/line.js +1 -1
- package/src/type/live.js +1 -1
- package/src/type/path.js +2 -2
- package/src/type/pixel.d.ts +0 -2
- package/src/type/pixel.js +3 -33
- package/src/type/spans.js +2 -2
- package/src/type/typeset.js +19 -8
- package/src/version.js +1 -1
- package/src/color.js +0 -246
- package/src/effects/ascii.d.ts +0 -7
- package/src/effects/ascii.js +0 -93
- package/src/effects/buffer.d.ts +0 -60
- package/src/effects/buffer.js +0 -472
- package/src/effects/detail.d.ts +0 -12
- package/src/effects/detail.js +0 -124
- package/src/effects/digital.d.ts +0 -14
- package/src/effects/digital.js +0 -216
- package/src/effects/distort.d.ts +0 -20
- package/src/effects/distort.js +0 -314
- package/src/effects/dither.d.ts +0 -8
- package/src/effects/dither.js +0 -147
- package/src/effects/drop-shadow.d.ts +0 -4
- package/src/effects/drop-shadow.js +0 -45
- package/src/effects/extrude.d.ts +0 -5
- package/src/effects/extrude.js +0 -55
- package/src/effects/film.d.ts +0 -11
- package/src/effects/film.js +0 -204
- package/src/effects/glow.d.ts +0 -8
- package/src/effects/glow.js +0 -56
- package/src/effects/grade.d.ts +0 -30
- package/src/effects/grade.js +0 -274
- package/src/effects/halftone.d.ts +0 -8
- package/src/effects/halftone.js +0 -95
- package/src/effects/index.d.ts +0 -125
- package/src/effects/index.js +0 -277
- package/src/effects/ink.d.ts +0 -12
- package/src/effects/ink.js +0 -70
- package/src/effects/lens.d.ts +0 -14
- package/src/effects/lens.js +0 -277
- package/src/effects/light.d.ts +0 -10
- package/src/effects/light.js +0 -59
- package/src/effects/looks.d.ts +0 -9
- package/src/effects/looks.js +0 -74
- package/src/effects/mask.d.ts +0 -52
- package/src/effects/mask.js +0 -314
- package/src/effects/motion.d.ts +0 -6
- package/src/effects/motion.js +0 -60
- package/src/effects/newsprint.d.ts +0 -6
- package/src/effects/newsprint.js +0 -89
- package/src/effects/optics.d.ts +0 -10
- package/src/effects/optics.js +0 -213
- package/src/effects/options.d.ts +0 -6
- package/src/effects/options.js +0 -106
- package/src/effects/pixels.d.ts +0 -24
- package/src/effects/pixels.js +0 -138
- package/src/effects/polar.d.ts +0 -10
- package/src/effects/polar.js +0 -265
- package/src/effects/region.d.ts +0 -8
- package/src/effects/region.js +0 -136
- package/src/effects/round-border.d.ts +0 -5
- package/src/effects/round-border.js +0 -62
- package/src/effects/screen.d.ts +0 -14
- package/src/effects/screen.js +0 -212
- package/src/effects/sort.d.ts +0 -8
- package/src/effects/sort.js +0 -125
- package/src/effects/symmetry.d.ts +0 -6
- package/src/effects/symmetry.js +0 -74
- package/src/effects/tone.d.ts +0 -16
- package/src/effects/tone.js +0 -117
- package/src/effects/util.d.ts +0 -16
- package/src/effects/util.js +0 -85
- package/src/platform/bytes.d.ts +0 -22
- package/src/platform/bytes.js +0 -103
- package/src/platform/compress.d.ts +0 -12
- package/src/platform/compress.js +0 -81
- package/src/platform/node.d.ts +0 -26
- package/src/platform/node.js +0 -86
- package/src/png.js +0 -126
- package/src/raster/index.d.ts +0 -24
- package/src/raster/index.js +0 -171
- package/src/render/content.d.ts +0 -5
- package/src/render/content.js +0 -58
- package/src/render/tiles.d.ts +0 -6
- package/src/render/tiles.js +0 -77
- package/src/rng.js +0 -95
- package/src/svg.js +0 -69
- /package/src/{effects/paint.d.ts → paint/index.d.ts} +0 -0
package/src/effects/digital.js
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Digital damage: `chromatic` aberration (an RGB split along a direction,
|
|
3
|
-
* as a filter; or radial, red and blue scaled apart toward the corners as
|
|
4
|
-
* a cheap lens does, on pixels), `glitch` (channel shift, torn slices and
|
|
5
|
-
* displaced blocks, seeded) and `pixelate` (square cells, or hexagons).
|
|
6
|
-
*
|
|
7
|
-
* @module effects/digital
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { num } from '../svg.js';
|
|
11
|
-
import { len } from '../geometry.js';
|
|
12
|
-
import { createRng } from '../rng.js';
|
|
13
|
-
import { mat } from './util.js';
|
|
14
|
-
import { px, pointPx, seedFor, premultiplied, unpremultiply } from './buffer.js';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* `chromatic` along a direction (and `rgbSplit`): red moved `amount` (a
|
|
18
|
-
* length) along `angle`, blue the other way, green kept.
|
|
19
|
-
*
|
|
20
|
-
* @param {import('./index.js').Effect & { kind: 'chromatic' | 'rgbSplit' }} fx
|
|
21
|
-
* @param {import('../render/context.js').RenderContext} ctx
|
|
22
|
-
* @returns {string}
|
|
23
|
-
*/
|
|
24
|
-
export function chromatic(fx, ctx) {
|
|
25
|
-
const d = len(fx.amount, ctx.canvas, '0.5vmin');
|
|
26
|
-
const a = ((fx.angle ?? 0) * Math.PI) / 180;
|
|
27
|
-
const dx = Math.cos(a) * d;
|
|
28
|
-
const dy = Math.sin(a) * d;
|
|
29
|
-
/** @param {number} c @returns {string} One channel alone, at the source's alpha. */
|
|
30
|
-
const only = (c) => mat([c === 0 ? 1 : 0, 0, 0, 0, 0, 0, c === 1 ? 1 : 0, 0, 0, 0, 0, 0, c === 2 ? 1 : 0, 0, 0, 0, 0, 0, 1, 0]);
|
|
31
|
-
return `<feColorMatrix in="SourceGraphic" type="matrix" values="${only(0)}"/><feOffset dx="${num(dx)}" dy="${num(dy)}" result="cr"/>`
|
|
32
|
-
+ `<feColorMatrix in="SourceGraphic" type="matrix" values="${only(1)}" result="cg"/>`
|
|
33
|
-
+ `<feColorMatrix in="SourceGraphic" type="matrix" values="${only(2)}"/><feOffset dx="${num(-dx)}" dy="${num(-dy)}" result="cb"/>`
|
|
34
|
-
+ '<feComposite in="cr" in2="cg" operator="arithmetic" k2="1" k3="1"/><feComposite in2="cb" operator="arithmetic" k2="1" k3="1"/>';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* `chromatic` with `mode: 'radial'`: red scaled out from `center` and blue
|
|
39
|
-
* in, `amount` apart at the corners.
|
|
40
|
-
*
|
|
41
|
-
* @param {import('./buffer.js').Pixels} img
|
|
42
|
-
* @param {import('./index.js').Effect & { kind: 'chromatic' }} fx
|
|
43
|
-
* @param {any} ctx
|
|
44
|
-
* @returns {Uint8ClampedArray}
|
|
45
|
-
*/
|
|
46
|
-
export function chromaticRadial(img, fx, ctx) {
|
|
47
|
-
const { width: w, height: h } = img;
|
|
48
|
-
const src = premultiplied(img);
|
|
49
|
-
const [cx0, cy0] = pointPx(fx.center, img);
|
|
50
|
-
const cx = cx0 - 0.5;
|
|
51
|
-
const cy = cy0 - 0.5;
|
|
52
|
-
const reach = Math.max(Math.hypot(cx, cy), Math.hypot(w - cx, cy), Math.hypot(cx, h - cy), Math.hypot(w - cx, h - cy));
|
|
53
|
-
const k = px(fx.amount, ctx, img, '0.6vmin') / reach;
|
|
54
|
-
const out = new Uint8ClampedArray(src.length);
|
|
55
|
-
/** @param {number} x @param {number} y @param {number} c @returns {number} One channel, bilinear, edge held. */
|
|
56
|
-
const at = (x, y, c) => {
|
|
57
|
-
x = Math.min(w - 1, Math.max(0, x));
|
|
58
|
-
y = Math.min(h - 1, Math.max(0, y));
|
|
59
|
-
const x0 = x | 0;
|
|
60
|
-
const y0 = y | 0;
|
|
61
|
-
const x1 = Math.min(w - 1, x0 + 1);
|
|
62
|
-
const y1 = Math.min(h - 1, y0 + 1);
|
|
63
|
-
const fx0 = x - x0;
|
|
64
|
-
const fy0 = y - y0;
|
|
65
|
-
const top = src[(y0 * w + x0) * 4 + c] * (1 - fx0) + src[(y0 * w + x1) * 4 + c] * fx0;
|
|
66
|
-
const bot = src[(y1 * w + x0) * 4 + c] * (1 - fx0) + src[(y1 * w + x1) * 4 + c] * fx0;
|
|
67
|
-
return top + (bot - top) * fy0;
|
|
68
|
-
};
|
|
69
|
-
for (let y = 0, i = 0; y < h; y++) {
|
|
70
|
-
for (let x = 0; x < w; x++, i += 4) {
|
|
71
|
-
const dx = x - cx;
|
|
72
|
-
const dy = y - cy;
|
|
73
|
-
out[i] = at(cx + dx * (1 - k), cy + dy * (1 - k), 0);
|
|
74
|
-
out[i + 1] = src[i + 1];
|
|
75
|
-
out[i + 2] = at(cx + dx * (1 + k), cy + dy * (1 + k), 2);
|
|
76
|
-
out[i + 3] = Math.max(src[i + 3], at(cx + dx * (1 - k), cy + dy * (1 - k), 3), at(cx + dx * (1 + k), cy + dy * (1 + k), 3));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return img.opaque ? out : unpremultiply(out);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* `glitch`, seeded: the channels split by `shift` (a length), `slices`
|
|
84
|
-
* of the frame torn sideways (wrapping), `blocks` copied from elsewhere,
|
|
85
|
-
* some with their channels swapped; `amount` (0–1) scales it all.
|
|
86
|
-
*
|
|
87
|
-
* @param {import('./buffer.js').Pixels} img
|
|
88
|
-
* @param {import('./index.js').Effect & { kind: 'glitch' }} fx
|
|
89
|
-
* @param {any} ctx
|
|
90
|
-
* @returns {Uint8ClampedArray}
|
|
91
|
-
*/
|
|
92
|
-
export function glitch(img, fx, ctx) {
|
|
93
|
-
const { data, width: w, height: h } = img;
|
|
94
|
-
const rng = createRng(`glitch:${seedFor(fx, ctx)}`);
|
|
95
|
-
const amount = Math.min(1, Math.max(0, fx.amount ?? 0.5));
|
|
96
|
-
const shift = Math.round(px(fx.shift, ctx, img, '0.8vmin') * (0.5 + amount));
|
|
97
|
-
const wrap = (/** @type {number} */ x) => ((x % w) + w) % w;
|
|
98
|
-
// Channel split, wrapping round.
|
|
99
|
-
const out = new Uint8ClampedArray(data.length);
|
|
100
|
-
for (let y = 0; y < h; y++) {
|
|
101
|
-
const row = y * w;
|
|
102
|
-
for (let x = 0; x < w; x++) {
|
|
103
|
-
const i = (row + x) * 4;
|
|
104
|
-
out[i] = data[(row + wrap(x - shift)) * 4];
|
|
105
|
-
out[i + 1] = data[i + 1];
|
|
106
|
-
out[i + 2] = data[(row + wrap(x + shift)) * 4 + 2];
|
|
107
|
-
out[i + 3] = data[i + 3];
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
const u32 = new Uint32Array(out.buffer);
|
|
111
|
-
const line = new Uint32Array(w);
|
|
112
|
-
// Slices torn sideways.
|
|
113
|
-
const slices = fx.slices ?? Math.round(4 + 14 * amount);
|
|
114
|
-
for (let s = 0; s < slices; s++) {
|
|
115
|
-
const y0 = rng.int(0, h - 1);
|
|
116
|
-
const sh = Math.max(1, Math.round(h * rng.range(0.004, 0.06)));
|
|
117
|
-
const off = Math.round(w * rng.range(0.01, 0.22) * amount * (rng.chance() ? 1 : -1));
|
|
118
|
-
const swap = rng.chance(0.12);
|
|
119
|
-
for (let y = y0; y < Math.min(h, y0 + sh); y++) {
|
|
120
|
-
line.set(u32.subarray(y * w, y * w + w));
|
|
121
|
-
for (let x = 0; x < w; x++) u32[y * w + x] = line[wrap(x - off)];
|
|
122
|
-
if (swap) for (let x = 0; x < w; x++) { const i = (y * w + x) * 4; const r = out[i]; out[i] = out[i + 2]; out[i + 2] = out[i + 1]; out[i + 1] = r; }
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// Blocks lifted from one place and dropped in another.
|
|
126
|
-
const blocks = fx.blocks ?? Math.round(2 + 10 * amount);
|
|
127
|
-
for (let b = 0; b < blocks; b++) {
|
|
128
|
-
const bw = Math.round(w * rng.range(0.04, 0.3));
|
|
129
|
-
const bh = Math.round(h * rng.range(0.01, 0.08));
|
|
130
|
-
const sx = rng.int(0, w - 1);
|
|
131
|
-
const sy = rng.int(0, Math.max(0, h - bh));
|
|
132
|
-
const dx = rng.int(0, w - 1);
|
|
133
|
-
const dy = Math.min(h - bh, Math.max(0, sy + Math.round(h * rng.range(-0.05, 0.05))));
|
|
134
|
-
const tone = rng.chance(0.35);
|
|
135
|
-
for (let y = 0; y < bh; y++) {
|
|
136
|
-
for (let x = 0; x < bw; x++) {
|
|
137
|
-
const v = u32[(sy + y) * w + wrap(sx + x)];
|
|
138
|
-
const o = (dy + y) * w + wrap(dx + x);
|
|
139
|
-
u32[o] = v;
|
|
140
|
-
if (tone) { const i = o * 4; out[i] = out[i] & 0xe0; out[i + 1] = out[i + 1] & 0xe0; out[i + 2] = 255 - out[i + 2]; }
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return out;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* `pixelate` (and `mosaic`): cells of `size` (a length), `shape`
|
|
149
|
-
* `'square'` (each the average of what it covers, embedded at one pixel a
|
|
150
|
-
* cell and scaled up crisp) or `'hex'`.
|
|
151
|
-
*
|
|
152
|
-
* @param {import('./buffer.js').Pixels} img
|
|
153
|
-
* @param {import('./index.js').Effect & { kind: 'pixelate' | 'mosaic' }} fx
|
|
154
|
-
* @param {any} ctx
|
|
155
|
-
* @returns {import('./pixels.js').PixelResult}
|
|
156
|
-
*/
|
|
157
|
-
export function pixelate(img, fx, ctx) {
|
|
158
|
-
const { width: w, height: h } = img;
|
|
159
|
-
const size = Math.max(1, px(fx.size, ctx, img, '2.4vmin'));
|
|
160
|
-
const shape = fx.shape ?? 'square';
|
|
161
|
-
if (shape !== 'square' && shape !== 'hex') throw new TypeError(`Unknown pixelate shape "${shape}" (square, hex)`);
|
|
162
|
-
const src = premultiplied(img);
|
|
163
|
-
if (shape === 'square') {
|
|
164
|
-
const cols = Math.max(1, Math.round(w / size));
|
|
165
|
-
const rows = Math.max(1, Math.round(h / size));
|
|
166
|
-
const acc = new Float64Array(cols * rows * 5);
|
|
167
|
-
for (let y = 0; y < h; y++) {
|
|
168
|
-
const cy = Math.min(rows - 1, ((y * rows) / h) | 0);
|
|
169
|
-
for (let x = 0; x < w; x++) {
|
|
170
|
-
const c = (cy * cols + Math.min(cols - 1, ((x * cols) / w) | 0)) * 5;
|
|
171
|
-
const i = (y * w + x) * 4;
|
|
172
|
-
acc[c] += src[i]; acc[c + 1] += src[i + 1]; acc[c + 2] += src[i + 2]; acc[c + 3] += src[i + 3]; acc[c + 4]++;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
const out = new Uint8ClampedArray(cols * rows * 4);
|
|
176
|
-
for (let c = 0; c < cols * rows; c++) {
|
|
177
|
-
const n = acc[c * 5 + 4] || 1;
|
|
178
|
-
for (let k = 0; k < 4; k++) out[c * 4 + k] = acc[c * 5 + k] / n;
|
|
179
|
-
}
|
|
180
|
-
return { data: img.opaque ? out : unpremultiply(out), width: cols, height: rows, pixelated: true };
|
|
181
|
-
}
|
|
182
|
-
// Pointy-top hexagons, `size` across.
|
|
183
|
-
const s = size / Math.sqrt(3);
|
|
184
|
-
const qOff = Math.ceil(h / (1.5 * s)) + 2;
|
|
185
|
-
const qn = Math.ceil(w / (Math.sqrt(3) * s)) + qOff + 4;
|
|
186
|
-
const rn = Math.ceil(h / (1.5 * s)) + 4;
|
|
187
|
-
const ids = new Int32Array(w * h);
|
|
188
|
-
const acc = new Float64Array(qn * rn * 5);
|
|
189
|
-
for (let y = 0; y < h; y++) {
|
|
190
|
-
for (let x = 0; x < w; x++) {
|
|
191
|
-
const fq = ((Math.sqrt(3) / 3) * (x + 0.5) - (y + 0.5) / 3) / s;
|
|
192
|
-
const fr = ((2 / 3) * (y + 0.5)) / s;
|
|
193
|
-
const fz = -fq - fr;
|
|
194
|
-
let q = Math.round(fq);
|
|
195
|
-
let r = Math.round(fr);
|
|
196
|
-
const z = Math.round(fz);
|
|
197
|
-
const dq = Math.abs(q - fq);
|
|
198
|
-
const dr = Math.abs(r - fr);
|
|
199
|
-
const dz = Math.abs(z - fz);
|
|
200
|
-
if (dq > dr && dq > dz) q = -r - z;
|
|
201
|
-
else if (dr > dz) r = -q - z;
|
|
202
|
-
const id = (r + 1) * qn + (q + qOff);
|
|
203
|
-
ids[y * w + x] = id;
|
|
204
|
-
const i = (y * w + x) * 4;
|
|
205
|
-
const c = id * 5;
|
|
206
|
-
acc[c] += src[i]; acc[c + 1] += src[i + 1]; acc[c + 2] += src[i + 2]; acc[c + 3] += src[i + 3]; acc[c + 4]++;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
const out = new Uint8ClampedArray(w * h * 4);
|
|
210
|
-
for (let p = 0; p < ids.length; p++) {
|
|
211
|
-
const c = ids[p] * 5;
|
|
212
|
-
const n = acc[c + 4] || 1;
|
|
213
|
-
for (let k = 0; k < 4; k++) out[p * 4 + k] = acc[c + k] / n;
|
|
214
|
-
}
|
|
215
|
-
return { data: img.opaque ? out : unpremultiply(out), width: w, height: h, lossless: true };
|
|
216
|
-
}
|
package/src/effects/distort.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// Distortions on pixels.
|
|
2
|
-
import type { Effect } from './index.js';
|
|
3
|
-
import type { Pixels } from './buffer.js';
|
|
4
|
-
|
|
5
|
-
/** `wave`. */
|
|
6
|
-
export function wave(img: Pixels, fx: Extract<Effect, { kind: 'wave' }>, ctx: object): Uint8ClampedArray;
|
|
7
|
-
/** `ripple`. */
|
|
8
|
-
export function ripple(img: Pixels, fx: Extract<Effect, { kind: 'ripple' }>, ctx: object): Uint8ClampedArray;
|
|
9
|
-
/** `twirl`. */
|
|
10
|
-
export function twirl(img: Pixels, fx: Extract<Effect, { kind: 'twirl' }>, ctx: object): Uint8ClampedArray;
|
|
11
|
-
/** `bulge` and `pinch`. */
|
|
12
|
-
export function bulge(img: Pixels, fx: Extract<Effect, { kind: 'bulge' | 'pinch' }>, ctx: object): Uint8ClampedArray;
|
|
13
|
-
/** `fisheye`. */
|
|
14
|
-
export function fisheye(img: Pixels, fx: Extract<Effect, { kind: 'fisheye' }>): Uint8ClampedArray;
|
|
15
|
-
/** `kaleidoscope`. */
|
|
16
|
-
export function kaleidoscope(img: Pixels, fx: Extract<Effect, { kind: 'kaleidoscope' }>): Uint8ClampedArray;
|
|
17
|
-
/** `glass`. */
|
|
18
|
-
export function glass(img: Pixels, fx: Extract<Effect, { kind: 'glass' }>, ctx: object): Uint8ClampedArray;
|
|
19
|
-
/** `displace`. */
|
|
20
|
-
export function displace(img: Pixels, fx: Extract<Effect, { kind: 'displace' }>, ctx: object): Promise<Uint8ClampedArray>;
|
package/src/effects/distort.js
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Distortions on pixels, each an inverse map (every output pixel reads
|
|
3
|
-
* from where the distortion says it came from, bilinear, the edge held):
|
|
4
|
-
* `wave`, `ripple` (rings on water), `twirl`, `bulge` and `pinch`,
|
|
5
|
-
* `fisheye` (barrel distortion over the frame), `kaleidoscope` (a wedge
|
|
6
|
-
* folded round a centre, the frame filled by reflection), `glass`
|
|
7
|
-
* (frosted, reeded or pebbled glass in front of the layer) and `displace`
|
|
8
|
-
* (by seeded noise, or by the luminance of layers: a texture, a photo,
|
|
9
|
-
* type).
|
|
10
|
-
*
|
|
11
|
-
* @module effects/distort
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { px, pointPx, remap, noiseField, seedFor, gaussian, luma, smoothstep } from './buffer.js';
|
|
15
|
-
import { drawLayer } from './pixels.js';
|
|
16
|
-
|
|
17
|
-
/** @param {any} fx @param {import('./buffer.js').Pixels} img @returns {[number, number]} The centre in pixel coordinates. */
|
|
18
|
-
const centre = (fx, img) => { const [x, y] = pointPx(fx.center, img); return [x - 0.5, y - 0.5]; };
|
|
19
|
-
|
|
20
|
-
/** Wave shapes, one period over 0–1, -1–1. @type {Record<string, (t: number) => number>} */
|
|
21
|
-
const WAVES = {
|
|
22
|
-
sine: (t) => Math.sin(2 * Math.PI * t),
|
|
23
|
-
triangle: (t) => 1 - 4 * Math.abs(t - Math.floor(t + 0.5)),
|
|
24
|
-
square: (t) => (t - Math.floor(t) < 0.5 ? 1 : -1),
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* `wave`: the layer displaced across a wave travelling at `angle`
|
|
29
|
-
* (degrees, 0 along x): `amplitude`, `wavelength`, `phase` (0–1 of a
|
|
30
|
-
* period), `shape` (`'sine'`, `'triangle'`, `'square'`).
|
|
31
|
-
*
|
|
32
|
-
* @param {import('./buffer.js').Pixels} img
|
|
33
|
-
* @param {import('./index.js').Effect & { kind: 'wave' }} fx
|
|
34
|
-
* @param {any} ctx
|
|
35
|
-
* @returns {Uint8ClampedArray}
|
|
36
|
-
*/
|
|
37
|
-
export function wave(img, fx, ctx) {
|
|
38
|
-
const A = px(fx.amplitude, ctx, img, '1.5vmin');
|
|
39
|
-
const L = Math.max(1, px(fx.wavelength, ctx, img, '14vmin'));
|
|
40
|
-
const shape = WAVES[fx.shape ?? 'sine'];
|
|
41
|
-
if (!shape) throw new TypeError(`Unknown wave shape "${fx.shape}" (${Object.keys(WAVES).join(', ')})`);
|
|
42
|
-
const a = ((fx.angle ?? 0) * Math.PI) / 180;
|
|
43
|
-
const ux = Math.cos(a);
|
|
44
|
-
const uy = Math.sin(a);
|
|
45
|
-
const ph = fx.phase ?? 0;
|
|
46
|
-
return remap(img, (x, y, at) => {
|
|
47
|
-
const d = A * shape((x * ux + y * uy) / L + ph);
|
|
48
|
-
at[0] = x - uy * d;
|
|
49
|
-
at[1] = y + ux * d;
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* `ripple`: rings on water round `center`: `amplitude`, `wavelength`,
|
|
55
|
-
* `phase`, fading out by `radius`.
|
|
56
|
-
*
|
|
57
|
-
* @param {import('./buffer.js').Pixels} img
|
|
58
|
-
* @param {import('./index.js').Effect & { kind: 'ripple' }} fx
|
|
59
|
-
* @param {any} ctx
|
|
60
|
-
* @returns {Uint8ClampedArray}
|
|
61
|
-
*/
|
|
62
|
-
export function ripple(img, fx, ctx) {
|
|
63
|
-
const [cx, cy] = centre(fx, img);
|
|
64
|
-
const A = px(fx.amplitude, ctx, img, '0.8vmin');
|
|
65
|
-
const L = Math.max(1, px(fx.wavelength, ctx, img, '6vmin'));
|
|
66
|
-
const R = Math.max(1, px(fx.radius, ctx, img, '70vmin'));
|
|
67
|
-
const ph = fx.phase ?? 0;
|
|
68
|
-
return remap(img, (x, y, at) => {
|
|
69
|
-
const dx = x - cx;
|
|
70
|
-
const dy = y - cy;
|
|
71
|
-
const r = Math.sqrt(dx * dx + dy * dy) || 1e-6;
|
|
72
|
-
const d = A * Math.sin(2 * Math.PI * (r / L - ph)) * (1 - smoothstep(0, R, r));
|
|
73
|
-
at[0] = x + (dx / r) * d;
|
|
74
|
-
at[1] = y + (dy / r) * d;
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* `twirl`: turned `angle` degrees at `center`, less and less out to
|
|
80
|
-
* `radius`, where it stops.
|
|
81
|
-
*
|
|
82
|
-
* @param {import('./buffer.js').Pixels} img
|
|
83
|
-
* @param {import('./index.js').Effect & { kind: 'twirl' }} fx
|
|
84
|
-
* @param {any} ctx
|
|
85
|
-
* @returns {Uint8ClampedArray}
|
|
86
|
-
*/
|
|
87
|
-
export function twirl(img, fx, ctx) {
|
|
88
|
-
const [cx, cy] = centre(fx, img);
|
|
89
|
-
const R = Math.max(1, px(fx.radius, ctx, img, '45vmin'));
|
|
90
|
-
const turn = ((fx.angle ?? 180) * Math.PI) / 180;
|
|
91
|
-
return remap(img, (x, y, at) => {
|
|
92
|
-
const dx = x - cx;
|
|
93
|
-
const dy = y - cy;
|
|
94
|
-
const r = Math.sqrt(dx * dx + dy * dy);
|
|
95
|
-
if (r >= R) { at[0] = x; at[1] = y; return; }
|
|
96
|
-
const k = 1 - r / R;
|
|
97
|
-
const t = -turn * k * k;
|
|
98
|
-
const c = Math.cos(t);
|
|
99
|
-
const s = Math.sin(t);
|
|
100
|
-
at[0] = cx + dx * c - dy * s;
|
|
101
|
-
at[1] = cy + dx * s + dy * c;
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* `bulge` and `pinch`: magnified (or shrunk) toward `center` within
|
|
107
|
-
* `radius`, by `amount` (0–1; `pinch` is a bulge the other way), smooth at
|
|
108
|
-
* the edge.
|
|
109
|
-
*
|
|
110
|
-
* @param {import('./buffer.js').Pixels} img
|
|
111
|
-
* @param {import('./index.js').Effect & { kind: 'bulge' | 'pinch' }} fx
|
|
112
|
-
* @param {any} ctx
|
|
113
|
-
* @returns {Uint8ClampedArray}
|
|
114
|
-
*/
|
|
115
|
-
export function bulge(img, fx, ctx) {
|
|
116
|
-
const [cx, cy] = centre(fx, img);
|
|
117
|
-
const R = Math.max(1, px(fx.radius, ctx, img, '35vmin'));
|
|
118
|
-
const amt = (fx.kind === 'pinch' ? -1 : 1) * (fx.amount ?? 0.5);
|
|
119
|
-
const a = Math.max(-1, Math.min(0.95, amt));
|
|
120
|
-
return remap(img, (x, y, at) => {
|
|
121
|
-
const dx = x - cx;
|
|
122
|
-
const dy = y - cy;
|
|
123
|
-
const d = Math.sqrt(dx * dx + dy * dy) / R;
|
|
124
|
-
if (d >= 1) { at[0] = x; at[1] = y; return; }
|
|
125
|
-
const f = 1 - a * (1 - d) * (1 - d);
|
|
126
|
-
at[0] = cx + dx * f;
|
|
127
|
-
at[1] = cy + dy * f;
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* `fisheye`: barrel distortion over the whole frame, `amount` 0–1 (the
|
|
133
|
-
* centre swells, the edges bow; the corners stay put).
|
|
134
|
-
*
|
|
135
|
-
* @param {import('./buffer.js').Pixels} img
|
|
136
|
-
* @param {import('./index.js').Effect & { kind: 'fisheye' }} fx
|
|
137
|
-
* @returns {Uint8ClampedArray}
|
|
138
|
-
*/
|
|
139
|
-
export function fisheye(img, fx) {
|
|
140
|
-
const [cx, cy] = centre(fx, img);
|
|
141
|
-
const Rn = Math.hypot(img.width, img.height) / 2;
|
|
142
|
-
const th = Math.max(1e-3, Math.min(0.99, fx.amount ?? 0.6)) * (Math.PI / 2) * 0.92;
|
|
143
|
-
const norm = 1 / Math.tan(th);
|
|
144
|
-
return remap(img, (x, y, at) => {
|
|
145
|
-
const dx = x - cx;
|
|
146
|
-
const dy = y - cy;
|
|
147
|
-
const r = Math.sqrt(dx * dx + dy * dy);
|
|
148
|
-
if (!r) { at[0] = x; at[1] = y; return; }
|
|
149
|
-
const f = (Math.tan((r / Rn) * th) * norm * Rn) / r;
|
|
150
|
-
at[0] = cx + dx * f;
|
|
151
|
-
at[1] = cy + dy * f;
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** @param {number} v @param {number} n @returns {number} A coordinate reflected back into 0–(n-1). */
|
|
156
|
-
function reflect(v, n) {
|
|
157
|
-
const p = 2 * (n - 1) || 1;
|
|
158
|
-
const m = ((v % p) + p) % p;
|
|
159
|
-
return m > n - 1 ? p - m : m;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* `kaleidoscope`: the wedge of `360 / segments` from `angle` (degrees, 0 =
|
|
164
|
-
* right, clockwise; default up) round `center`, folded round it with every
|
|
165
|
-
* other copy reflected; `scale` zooms the source (below 1 shows more of
|
|
166
|
-
* it). Beyond the layer's edge the source is reflected, so the frame fills.
|
|
167
|
-
*
|
|
168
|
-
* @param {import('./buffer.js').Pixels} img
|
|
169
|
-
* @param {import('./index.js').Effect & { kind: 'kaleidoscope' }} fx
|
|
170
|
-
* @returns {Uint8ClampedArray}
|
|
171
|
-
*/
|
|
172
|
-
export function kaleidoscope(img, fx) {
|
|
173
|
-
const { width: w, height: h } = img;
|
|
174
|
-
const [cx, cy] = centre(fx, img);
|
|
175
|
-
const n = Math.max(2, Math.round(fx.segments ?? 8));
|
|
176
|
-
const phi = (2 * Math.PI) / n;
|
|
177
|
-
const a0 = ((fx.angle ?? -90) * Math.PI) / 180;
|
|
178
|
-
const k = 1 / Math.max(0.05, fx.scale ?? 1);
|
|
179
|
-
return remap(img, (x, y, at) => {
|
|
180
|
-
const dx = x - cx;
|
|
181
|
-
const dy = y - cy;
|
|
182
|
-
const r = Math.sqrt(dx * dx + dy * dy) * k;
|
|
183
|
-
let t = (Math.atan2(dy, dx) - a0) % (2 * phi);
|
|
184
|
-
if (t < 0) t += 2 * phi;
|
|
185
|
-
if (t > phi) t = 2 * phi - t;
|
|
186
|
-
at[0] = reflect(cx + r * Math.cos(a0 + t), w);
|
|
187
|
-
at[1] = reflect(cy + r * Math.sin(a0 + t), h);
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* `glass`: the layer through glass. `texture` `'frosted'` (fine scatter),
|
|
193
|
-
* `'reeded'` (fluted ribs `size` wide at `angle`, each a narrow lens, with
|
|
194
|
-
* `shade` on their edges) or `'pebbled'` (soft rounded lumps); `amount`
|
|
195
|
-
* is how far light bends, `seed` the texture.
|
|
196
|
-
*
|
|
197
|
-
* @param {import('./buffer.js').Pixels} img
|
|
198
|
-
* @param {import('./index.js').Effect & { kind: 'glass' }} fx
|
|
199
|
-
* @param {any} ctx
|
|
200
|
-
* @returns {Uint8ClampedArray}
|
|
201
|
-
*/
|
|
202
|
-
export function glass(img, fx, ctx) {
|
|
203
|
-
const { width: w, height: h } = img;
|
|
204
|
-
const texture = fx.texture ?? 'frosted';
|
|
205
|
-
if (texture === 'reeded') return reeded(img, fx, ctx);
|
|
206
|
-
if (texture !== 'frosted' && texture !== 'pebbled') throw new TypeError(`Unknown glass texture "${texture}" (frosted, reeded, pebbled)`);
|
|
207
|
-
const frosted = texture === 'frosted';
|
|
208
|
-
const size = Math.max(1, px(fx.size, ctx, img, frosted ? '0.25vmin' : '5vmin'));
|
|
209
|
-
const amount = px(fx.amount, ctx, img, frosted ? '0.7vmin' : '1.6vmin');
|
|
210
|
-
const seed = seedFor(fx, ctx);
|
|
211
|
-
const nx = noiseField(w, h, size, seed, frosted ? 2 : 3);
|
|
212
|
-
const ny = noiseField(w, h, size, seed + 7919, frosted ? 2 : 3);
|
|
213
|
-
const out = remap(img, (x, y, at) => {
|
|
214
|
-
const i = y * w + x;
|
|
215
|
-
at[0] = x + nx[i] * amount;
|
|
216
|
-
at[1] = y + ny[i] * amount;
|
|
217
|
-
});
|
|
218
|
-
// Frost also scatters: a touch of softness.
|
|
219
|
-
return frosted ? gaussian(out, w, h, 4, Math.max(0.5, size * 0.4)) : out;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Reeded (fluted) glass: ribs, each a cylinder lens that shows its strip
|
|
224
|
-
* of the layer narrowed (and, past `amount` 0.5, mirrored), lit at its
|
|
225
|
-
* edges by `shade`.
|
|
226
|
-
*
|
|
227
|
-
* @param {import('./buffer.js').Pixels} img
|
|
228
|
-
* @param {any} fx
|
|
229
|
-
* @param {any} ctx
|
|
230
|
-
* @returns {Uint8ClampedArray}
|
|
231
|
-
*/
|
|
232
|
-
function reeded(img, fx, ctx) {
|
|
233
|
-
const size = Math.max(2, px(fx.size, ctx, img, '3.2vmin'));
|
|
234
|
-
const k = 1 - 2 * Math.min(1, Math.max(0, fx.amount ?? 0.35));
|
|
235
|
-
const shade = fx.shade ?? 0.25;
|
|
236
|
-
const a = ((fx.angle ?? 0) * Math.PI) / 180;
|
|
237
|
-
const ux = Math.cos(a);
|
|
238
|
-
const uy = Math.sin(a);
|
|
239
|
-
const out = remap(img, (x, y, at) => {
|
|
240
|
-
const s = (x * ux + y * uy) / size;
|
|
241
|
-
const u = s - Math.floor(s) - 0.5;
|
|
242
|
-
const d = (u * k - u) * size;
|
|
243
|
-
at[0] = x + ux * d;
|
|
244
|
-
at[1] = y + uy * d;
|
|
245
|
-
});
|
|
246
|
-
const { width: w, height: h } = img;
|
|
247
|
-
// Fluted glass is never quite sharp.
|
|
248
|
-
gaussian(out, w, h, 4, size * 0.05);
|
|
249
|
-
if (!shade) return out;
|
|
250
|
-
for (let y = 0, i = 0; y < h; y++) {
|
|
251
|
-
for (let x = 0; x < w; x++, i += 4) {
|
|
252
|
-
const s = (x * ux + y * uy) / size;
|
|
253
|
-
const u = s - Math.floor(s) - 0.5;
|
|
254
|
-
// Bright on one shoulder, dark in the groove.
|
|
255
|
-
const l = 1 + shade * (0.6 * Math.sin(u * Math.PI * 2 + 0.6) - 0.8 * Math.abs(u) ** 3 * 8 + 0.3);
|
|
256
|
-
out[i] *= l; out[i + 1] *= l; out[i + 2] *= l;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return out;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* `displace`: moved by a map, `amount` at most. The map is seeded noise
|
|
264
|
-
* (`size`, `octaves`, `seed`) or `map: { layers }`, their luminance
|
|
265
|
-
* (`mode: 'gradient'`, the default, bends the layer along the map's
|
|
266
|
-
* slopes like light through a surface; `'channels'` moves by red along x
|
|
267
|
-
* and green along y, as Photoshop's Displace).
|
|
268
|
-
*
|
|
269
|
-
* @param {import('./buffer.js').Pixels} img
|
|
270
|
-
* @param {import('./index.js').Effect & { kind: 'displace' }} fx
|
|
271
|
-
* @param {any} ctx
|
|
272
|
-
* @returns {Promise<Uint8ClampedArray>}
|
|
273
|
-
*/
|
|
274
|
-
export async function displace(img, fx, ctx) {
|
|
275
|
-
const { width: w, height: h } = img;
|
|
276
|
-
const amount = px(fx.amount, ctx, img, '2vmin');
|
|
277
|
-
/** @type {Float32Array} */
|
|
278
|
-
let dx;
|
|
279
|
-
/** @type {Float32Array} */
|
|
280
|
-
let dy;
|
|
281
|
-
if (fx.map?.layers && ctx.stack) {
|
|
282
|
-
const parts = await ctx.stack(fx.map.layers, { tag: 'dm' });
|
|
283
|
-
const m = await drawLayer({ defs: parts.map((/** @type {any} */ p) => p.defs).join(''), body: parts.map((/** @type {any} */ p) => p.body).join('') }, ctx, w, h);
|
|
284
|
-
if (!m) return img.data;
|
|
285
|
-
dx = new Float32Array(w * h);
|
|
286
|
-
dy = new Float32Array(w * h);
|
|
287
|
-
if (fx.mode === 'channels') {
|
|
288
|
-
for (let i = 0, p = 0; p < dx.length; i += 4, p++) { dx[p] = (m[i] - 128) / 128; dy[p] = (m[i + 1] - 128) / 128; }
|
|
289
|
-
} else {
|
|
290
|
-
const l = new Float32Array(w * h);
|
|
291
|
-
for (let i = 0, p = 0; p < l.length; i += 4, p++) l[p] = (luma(m, i) * m[i + 3]) / (255 * 255);
|
|
292
|
-
gaussian(l, w, h, 1, Math.max(1, amount / 6));
|
|
293
|
-
// Slopes, scaled so a full-height edge moves `amount`.
|
|
294
|
-
const k = Math.max(1, amount / 2);
|
|
295
|
-
for (let y = 0; y < h; y++) {
|
|
296
|
-
for (let x = 0; x < w; x++) {
|
|
297
|
-
const p = y * w + x;
|
|
298
|
-
dx[p] = (l[y * w + Math.min(w - 1, x + 1)] - l[y * w + Math.max(0, x - 1)]) * k;
|
|
299
|
-
dy[p] = (l[Math.min(h - 1, y + 1) * w + x] - l[Math.max(0, y - 1) * w + x]) * k;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
} else {
|
|
304
|
-
const size = Math.max(1, px(fx.size, ctx, img, '12vmin'));
|
|
305
|
-
const seed = seedFor(fx, ctx);
|
|
306
|
-
dx = noiseField(w, h, size, seed, fx.octaves ?? 3);
|
|
307
|
-
dy = noiseField(w, h, size, seed + 7919, fx.octaves ?? 3);
|
|
308
|
-
}
|
|
309
|
-
return remap(img, (x, y, at) => {
|
|
310
|
-
const p = y * w + x;
|
|
311
|
-
at[0] = x + dx[p] * amount;
|
|
312
|
-
at[1] = y + dy[p] * amount;
|
|
313
|
-
});
|
|
314
|
-
}
|
package/src/effects/dither.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// An ordered (Bayer) dither.
|
|
2
|
-
import type { Effect } from './index.js';
|
|
3
|
-
import type { RenderContext, Markup } from '../render/context.js';
|
|
4
|
-
|
|
5
|
-
/** The `dither` effect's filter primitives. */
|
|
6
|
-
export function dither(fx: Extract<Effect, { kind: 'dither' }>, ctx: RenderContext): string;
|
|
7
|
-
/** The dither on pixels (the layer drawn once per cell, thresholded, embedded nearest-neighbour); undefined where nothing can rasterise. */
|
|
8
|
-
export function ditherRaster(fx: Extract<Effect, { kind: 'dither' }>, m: Markup, ctx: RenderContext, id: string): Promise<Markup | undefined>;
|