@arraypress/covers 1.1.0 → 1.4.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.
Files changed (259) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +18 -809
  3. package/package.json +3 -1
  4. package/recipes/flyers/afterglow-motion.json +540 -0
  5. package/recipes/flyers/afterglow.json +70 -0
  6. package/recipes/flyers/arcfield-mirror-motion.json +397 -0
  7. package/recipes/flyers/arcfield-mirror.json +364 -0
  8. package/recipes/flyers/bauhaus-oktav.json +319 -0
  9. package/recipes/flyers/bauhaus-ritmo.json +735 -0
  10. package/recipes/flyers/bauhaus-volta.json +394 -0
  11. package/recipes/flyers/bloomfall.json +99 -0
  12. package/recipes/flyers/drag-solenne-motion.json +444 -0
  13. package/recipes/flyers/drag-solenne.json +426 -0
  14. package/recipes/flyers/fennick-patches.json +874 -0
  15. package/recipes/flyers/ferro-pass.json +398 -0
  16. package/recipes/flyers/ferrova.json +812 -0
  17. package/recipes/flyers/grid-poster.json +91 -0
  18. package/recipes/flyers/grid-rest-rhythm.json +125 -0
  19. package/recipes/flyers/groovy-honeycomb.json +816 -0
  20. package/recipes/flyers/groovy-marigold.json +479 -0
  21. package/recipes/flyers/groovy-peachfuzz.json +974 -0
  22. package/recipes/flyers/halden-orbit-motion.json +381 -0
  23. package/recipes/flyers/halden-orbit.json +356 -0
  24. package/recipes/flyers/halvard.json +120 -0
  25. package/recipes/flyers/kairo-weekender.json +696 -0
  26. package/recipes/flyers/linger-motion.json +518 -0
  27. package/recipes/flyers/linger.json +67 -0
  28. package/recipes/flyers/little-orchard.json +105 -0
  29. package/recipes/flyers/lofi-hexmoth.json +912 -0
  30. package/recipes/flyers/lofi-lowbit-motion.json +888 -0
  31. package/recipes/flyers/lofi-lowbit.json +880 -0
  32. package/recipes/flyers/lofi-nullpoint.json +3013 -0
  33. package/recipes/flyers/logofolio.json +60 -0
  34. package/recipes/flyers/lumio-stack-motion.json +648 -0
  35. package/recipes/flyers/lumio-stack.json +438 -0
  36. package/recipes/flyers/magazine-cover.json +168 -0
  37. package/recipes/flyers/magazine-extended.json +269 -0
  38. package/recipes/flyers/magazine-knockout.json +40 -0
  39. package/recipes/flyers/northfold-varsity.json +618 -0
  40. package/recipes/flyers/open-studio.json +69 -0
  41. package/recipes/flyers/orrin-merch.json +715 -0
  42. package/recipes/flyers/oxyde.json +113 -0
  43. package/recipes/flyers/pixel-mosaic.json +58 -0
  44. package/recipes/flyers/quiet-fields.json +610 -0
  45. package/recipes/flyers/scrapbook-halcyon.json +948 -0
  46. package/recipes/flyers/solenne-ticket.json +730 -0
  47. package/recipes/flyers/solvane-motion.json +576 -0
  48. package/recipes/flyers/solvane.json +98 -0
  49. package/recipes/flyers/subduct.json +99 -0
  50. package/recipes/flyers/sunvale-lineup.json +584 -0
  51. package/recipes/flyers/tidewell.json +91 -0
  52. package/recipes/flyers/tour-kairo.json +535 -0
  53. package/recipes/flyers/velora-stickers.json +885 -0
  54. package/recipes/flyers/wheatpaste-black.json +313 -0
  55. package/recipes/flyers/wheatpaste-mark.json +198 -0
  56. package/recipes/flyers/wheatpaste-torn.json +114 -0
  57. package/recipes/flyers/wheatpaste-wall.json +984 -0
  58. package/recipes/flyers/wheatpaste-words.json +233 -0
  59. package/recipes/release/aura.json +137 -0
  60. package/recipes/release/chrome.json +433 -0
  61. package/recipes/release/collage.json +372 -0
  62. package/recipes/release/duotone.json +113 -0
  63. package/recipes/release/film-still.json +102 -0
  64. package/recipes/release/headline.json +70 -0
  65. package/recipes/release/instant.json +170 -0
  66. package/recipes/release/label.json +364 -0
  67. package/recipes/release/liquid-glass.json +301 -0
  68. package/recipes/release/odd-tile-dark.json +688 -0
  69. package/recipes/release/odd-tile-dense.json +1056 -0
  70. package/recipes/release/odd-tile-shift.json +541 -0
  71. package/recipes/release/odd-tile-split.json +571 -0
  72. package/recipes/release/odd-tile.json +633 -0
  73. package/recipes/release/off-edge.json +96 -0
  74. package/recipes/release/plinth.json +794 -0
  75. package/recipes/release/riso.json +108 -0
  76. package/recipes/release/series/fluid-label.json +561 -0
  77. package/recipes/release/series/fluid-pixel.json +334 -0
  78. package/recipes/release/series/hairline.json +305 -0
  79. package/recipes/release/series/halo.json +282 -0
  80. package/recipes/release/series/mark-crop.json +135 -0
  81. package/recipes/release/series/mark-window.json +150 -0
  82. package/recipes/release/series/photo-label.json +475 -0
  83. package/recipes/release/series/smear-field.json +345 -0
  84. package/recipes/release/series/smear.json +202 -0
  85. package/recipes/release/series/spin.json +172 -0
  86. package/recipes/release/series/split-ghost.json +230 -0
  87. package/recipes/release/series/zoom.json +168 -0
  88. package/recipes/release/small-print.json +114 -0
  89. package/recipes/release/stack.json +156 -0
  90. package/recipes/release/swiss.json +117 -0
  91. package/schema/recipe.schema.json +638 -69
  92. package/src/animate/index.d.ts +9 -19
  93. package/src/animate/index.js +6 -111
  94. package/src/animate/keyframes.d.ts +21 -0
  95. package/src/animate/keyframes.js +119 -0
  96. package/src/animate/marquee.d.ts +30 -0
  97. package/src/animate/marquee.js +93 -0
  98. package/src/animate/morph.d.ts +8 -0
  99. package/src/animate/morph.js +99 -0
  100. package/src/animate/motion.d.ts +59 -0
  101. package/src/animate/motion.js +63 -0
  102. package/src/animate/sheet.d.ts +19 -0
  103. package/src/animate/sheet.js +61 -0
  104. package/src/animate/timing.d.ts +28 -0
  105. package/src/animate/timing.js +146 -0
  106. package/src/animate/tracks.d.ts +18 -0
  107. package/src/animate/tracks.js +139 -0
  108. package/src/diagram.js +4 -3
  109. package/src/extract.js +2 -2
  110. package/src/fonts.js +1 -2
  111. package/src/geometry.js +4 -34
  112. package/src/grid.js +1 -1
  113. package/src/handmade.js +9 -7
  114. package/src/icons.js +1 -2
  115. package/src/index.d.ts +47 -54
  116. package/src/index.js +5 -5
  117. package/src/inter-data.js +1 -1
  118. package/src/json-recipe.js +28 -7
  119. package/src/layer-options.js +26 -4
  120. package/src/layers/barcode.js +3 -3
  121. package/src/layers/geometry.js +5 -5
  122. package/src/layers/index.d.ts +6 -0
  123. package/src/layers/index.js +3 -0
  124. package/src/layers/mark-geometry.d.ts +15 -0
  125. package/src/layers/mark-geometry.js +46 -0
  126. package/src/layers/marks.d.ts +25 -0
  127. package/src/layers/marks.js +102 -0
  128. package/src/layers/mosaic.d.ts +32 -0
  129. package/src/layers/mosaic.js +155 -0
  130. package/src/layers/perforation.js +3 -3
  131. package/src/layers/repeat.js +2 -2
  132. package/src/layers/shape.d.ts +2 -2
  133. package/src/layers/shape.js +5 -5
  134. package/src/layers/soft.js +2 -2
  135. package/src/layers/svg.js +2 -3
  136. package/src/layers/table.js +3 -3
  137. package/src/layers/tear.d.ts +1 -1
  138. package/src/layers/tear.js +4 -4
  139. package/src/layers/texture.js +3 -3
  140. package/src/layers/util.js +15 -2
  141. package/src/layers.js +17 -32
  142. package/src/{effects/paint.js → paint/index.js} +5 -5
  143. package/src/{effects/paint-presets.d.ts → paint/presets.d.ts} +1 -1
  144. package/src/{effects/paint-presets.js → paint/presets.js} +1 -1
  145. package/src/{effects → paint}/tile.d.ts +1 -1
  146. package/src/{effects → paint}/tile.js +4 -4
  147. package/src/palette.js +1 -1
  148. package/src/platform/files.d.ts +6 -0
  149. package/src/platform/files.js +41 -0
  150. package/src/platform/harfbuzz.js +1 -1
  151. package/src/platform/index.d.ts +3 -3
  152. package/src/platform/index.js +6 -4
  153. package/src/platform/load.js +1 -2
  154. package/src/print.js +9 -7
  155. package/src/recipes.js +1 -1
  156. package/src/render/backdrop.js +2 -2
  157. package/src/render/grain.js +1 -1
  158. package/src/render/group.js +3 -3
  159. package/src/render/key.js +1 -2
  160. package/src/{effects → render}/transform.d.ts +2 -1
  161. package/src/{effects → render}/transform.js +17 -5
  162. package/src/render/use.d.ts +1 -1
  163. package/src/render/use.js +2 -2
  164. package/src/render/wrap.js +5 -60
  165. package/src/render.js +55 -18
  166. package/src/text.js +1 -1
  167. package/src/type/auto-color.js +1 -1
  168. package/src/type/fallback.js +1 -1
  169. package/src/type/glyphs.js +2 -2
  170. package/src/type/line.js +1 -1
  171. package/src/type/live.js +1 -1
  172. package/src/type/path.js +2 -2
  173. package/src/type/pixel.d.ts +0 -2
  174. package/src/type/pixel.js +3 -33
  175. package/src/type/spans.d.ts +5 -1
  176. package/src/type/spans.js +28 -5
  177. package/src/type/symbols.d.ts +1 -1
  178. package/src/type/symbols.js +20 -2
  179. package/src/type/typeset.js +25 -10
  180. package/src/version.js +1 -1
  181. package/src/color.js +0 -246
  182. package/src/effects/ascii.d.ts +0 -7
  183. package/src/effects/ascii.js +0 -93
  184. package/src/effects/buffer.d.ts +0 -60
  185. package/src/effects/buffer.js +0 -472
  186. package/src/effects/detail.d.ts +0 -12
  187. package/src/effects/detail.js +0 -124
  188. package/src/effects/digital.d.ts +0 -14
  189. package/src/effects/digital.js +0 -216
  190. package/src/effects/distort.d.ts +0 -20
  191. package/src/effects/distort.js +0 -314
  192. package/src/effects/dither.d.ts +0 -8
  193. package/src/effects/dither.js +0 -147
  194. package/src/effects/drop-shadow.d.ts +0 -4
  195. package/src/effects/drop-shadow.js +0 -45
  196. package/src/effects/extrude.d.ts +0 -5
  197. package/src/effects/extrude.js +0 -55
  198. package/src/effects/film.d.ts +0 -11
  199. package/src/effects/film.js +0 -204
  200. package/src/effects/glow.d.ts +0 -8
  201. package/src/effects/glow.js +0 -56
  202. package/src/effects/grade.d.ts +0 -30
  203. package/src/effects/grade.js +0 -274
  204. package/src/effects/halftone.d.ts +0 -8
  205. package/src/effects/halftone.js +0 -95
  206. package/src/effects/index.d.ts +0 -125
  207. package/src/effects/index.js +0 -277
  208. package/src/effects/ink.d.ts +0 -12
  209. package/src/effects/ink.js +0 -70
  210. package/src/effects/lens.d.ts +0 -14
  211. package/src/effects/lens.js +0 -277
  212. package/src/effects/light.d.ts +0 -10
  213. package/src/effects/light.js +0 -59
  214. package/src/effects/looks.d.ts +0 -9
  215. package/src/effects/looks.js +0 -74
  216. package/src/effects/mask.d.ts +0 -52
  217. package/src/effects/mask.js +0 -314
  218. package/src/effects/motion.d.ts +0 -6
  219. package/src/effects/motion.js +0 -60
  220. package/src/effects/newsprint.d.ts +0 -6
  221. package/src/effects/newsprint.js +0 -89
  222. package/src/effects/optics.d.ts +0 -10
  223. package/src/effects/optics.js +0 -213
  224. package/src/effects/options.d.ts +0 -6
  225. package/src/effects/options.js +0 -106
  226. package/src/effects/pixels.d.ts +0 -24
  227. package/src/effects/pixels.js +0 -138
  228. package/src/effects/polar.d.ts +0 -10
  229. package/src/effects/polar.js +0 -265
  230. package/src/effects/region.d.ts +0 -8
  231. package/src/effects/region.js +0 -136
  232. package/src/effects/round-border.d.ts +0 -5
  233. package/src/effects/round-border.js +0 -62
  234. package/src/effects/screen.d.ts +0 -14
  235. package/src/effects/screen.js +0 -212
  236. package/src/effects/sort.d.ts +0 -8
  237. package/src/effects/sort.js +0 -125
  238. package/src/effects/symmetry.d.ts +0 -6
  239. package/src/effects/symmetry.js +0 -74
  240. package/src/effects/tone.d.ts +0 -16
  241. package/src/effects/tone.js +0 -117
  242. package/src/effects/util.d.ts +0 -16
  243. package/src/effects/util.js +0 -85
  244. package/src/platform/bytes.d.ts +0 -22
  245. package/src/platform/bytes.js +0 -103
  246. package/src/platform/compress.d.ts +0 -12
  247. package/src/platform/compress.js +0 -81
  248. package/src/platform/node.d.ts +0 -26
  249. package/src/platform/node.js +0 -86
  250. package/src/png.js +0 -126
  251. package/src/raster/index.d.ts +0 -24
  252. package/src/raster/index.js +0 -171
  253. package/src/render/content.d.ts +0 -5
  254. package/src/render/content.js +0 -58
  255. package/src/render/tiles.d.ts +0 -6
  256. package/src/render/tiles.js +0 -77
  257. package/src/rng.js +0 -95
  258. package/src/svg.js +0 -69
  259. /package/src/{effects/paint.d.ts → paint/index.d.ts} +0 -0
@@ -1,147 +0,0 @@
1
- /**
2
- * An ordered (Bayer) dither in two colours, optionally pixelated first:
3
- * computed on pixels where something can rasterise (the layer drawn once
4
- * at one pixel per cell, thresholded here, embedded with nearest-neighbour
5
- * scaling), else as an SVG filter.
6
- *
7
- * @module effects/dither
8
- */
9
-
10
- import { attrs, num } from '../svg.js';
11
- import { len } from '../geometry.js';
12
- import { encodePng, encodePngAsync, pngUri } from '../png.js';
13
- import { pixelsOf } from '../raster/index.js';
14
- import { LUMA, rgb, mat } from './util.js';
15
-
16
- /** Bayer matrices. */
17
- const BAYER = {
18
- bayer4: [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5],
19
- bayer8: (() => {
20
- const m4 = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5];
21
- const out = new Array(64);
22
- for (let y = 0; y < 8; y++) for (let x = 0; x < 8; x++) {
23
- const base = m4[(y % 4) * 4 + (x % 4)];
24
- const q = (y >> 2) * 2 + (x >> 2);
25
- out[y * 8 + x] = base * 4 + [0, 2, 3, 1][q];
26
- }
27
- return out;
28
- })(),
29
- };
30
-
31
- /**
32
- * An ordered (Bayer) dither in two colours, optionally pixelated to a
33
- * visible cell first. The threshold matrix is a tiny PNG tiled with feTile.
34
- *
35
- * @param {any} fx - `{ ink, paper, cell, contrast, brightness, pixelate, weights, matrix }`.
36
- * @param {import('../layers.js').RenderContext} ctx
37
- * @returns {string}
38
- */
39
- export function dither(fx, ctx) {
40
- const [ir, ig, ib] = rgb(fx.ink ?? '#0b0b0b');
41
- const [pr, pg, pb] = rgb(fx.paper ?? '#f4f1ea');
42
- const c = Math.max(1, len(fx.cell, ctx.canvas, '0.65vmin'));
43
- const contrast = fx.contrast ?? 1.45;
44
- const bright = fx.brightness ?? 0.06;
45
- const wts = fx.weights ?? LUMA;
46
- const { n, px } = thresholds(fx);
47
- const tile = pngUri(encodePng(px, n, n, 1));
48
- let out = '';
49
- let src = 'SourceGraphic';
50
- if (fx.pixelate !== false) {
51
- const d = Math.max(0.3, c / 2 - 0.3);
52
- out += `<feFlood flood-color="#fff" x="${num(c / 2 - 0.3, 3)}" y="${num(c / 2 - 0.3, 3)}" width="0.6" height="0.6" result="dot"/><feComposite in="dot" in2="dot" operator="over" x="0" y="0" width="${num(c, 3)}" height="${num(c, 3)}" result="cell"/><feTile in="cell" result="grid"/>`
53
- + `<feComposite in="SourceGraphic" in2="grid" operator="in" result="sample"/><feMorphology in="sample" operator="dilate" radius="${num(d, 3)}" result="px"/>`;
54
- src = 'px';
55
- }
56
- const o = 0.5 - contrast * 0.5 + bright;
57
- const row = wts.map((/** @type {number} */ x) => x * contrast);
58
- out += `<feColorMatrix in="${src}" type="matrix" values="${mat([...row, 0, o, ...row, 0, o, ...row, 0, o, 0, 0, 0, 1, 0])}" result="lum"/>`
59
- + `<feImage${attrs({ href: tile, x: 0, y: 0, width: c * n, height: c * n, preserveAspectRatio: 'none', 'image-rendering': 'pixelated', result: 'b' })}/><feTile in="b" result="bt"/>`
60
- + '<feComposite in="lum" in2="bt" operator="arithmetic" k2="1" k3="1" k4="-0.5"/>'
61
- + `<feComponentTransfer><feFuncR type="discrete" tableValues="${num(ir, 3)} ${num(pr, 3)}"/><feFuncG type="discrete" tableValues="${num(ig, 3)} ${num(pg, 3)}"/><feFuncB type="discrete" tableValues="${num(ib, 3)} ${num(pb, 3)}"/><feFuncA type="discrete" tableValues="1 1"/></feComponentTransfer>`
62
- + '<feComposite in2="SourceGraphic" operator="in"/>';
63
- return out;
64
- }
65
-
66
- /**
67
- * The threshold matrix as 8-bit greys (the filter's tile, and the raster's thresholds).
68
- *
69
- * @param {any} fx
70
- * @returns {{ n: number, px: Uint8Array }}
71
- */
72
- function thresholds(fx) {
73
- const matrix = /** @type {number[]} */ ((/** @type {any} */ (BAYER))[fx.matrix ?? 'bayer4'] ?? BAYER.bayer4);
74
- const n = Math.round(Math.sqrt(matrix.length));
75
- const px = new Uint8Array(n * n);
76
- matrix.forEach((v, i) => { px[i] = Math.round((1 - (v + 0.5) / (n * n)) * 255); });
77
- return { n, px };
78
- }
79
-
80
- /**
81
- * The dither on pixels: the layer is drawn once at one pixel per cell
82
- * (`pixelate`, the default) or at output resolution, thresholded against
83
- * the matrix here, and embedded scaled up nearest-neighbour. Where the
84
- * layer isn't opaque the result is clipped to its shape. The same
85
- * arithmetic as the filter (sRGB, `contrast`, `brightness`, `weights`).
86
- *
87
- * @param {any} fx
88
- * @param {{ defs: string, body: string }} m - The layer's markup so far.
89
- * @param {import('../layers.js').RenderContext} ctx
90
- * @param {string} id
91
- * @returns {Promise<{ defs: string, body: string } | undefined>} Undefined where nothing can rasterise.
92
- */
93
- export async function ditherRaster(fx, m, ctx, id) {
94
- const c = Math.max(1e-3, len(fx.cell, ctx.canvas, '0.65vmin'));
95
- // Pixels per unit: one per cell, or the output's resolution (at least one per cell).
96
- const ppu = fx.pixelate === false ? Math.max(1 / c, /** @type {any} */ (ctx).res ?? 1) : 1 / c;
97
- const w = Math.max(1, Math.ceil(ctx.width * ppu));
98
- const h = Math.max(1, Math.ceil(ctx.height * ppu));
99
- const uw = w / ppu;
100
- const uh = h / ppu;
101
- const doc = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${num(uw, 4)} ${num(uh, 4)}" preserveAspectRatio="none">${m.defs ? `<defs>${m.defs}</defs>` : ''}${m.body}</svg>`;
102
- let src;
103
- try {
104
- src = await pixelsOf(doc, w, h, /** @type {any} */ (ctx).sharp);
105
- } catch {
106
- src = undefined;
107
- }
108
- if (!src) return undefined;
109
-
110
- const ink = rgb(fx.ink ?? '#0b0b0b').map((v) => Math.round(v * 255));
111
- const paper = rgb(fx.paper ?? '#f4f1ea').map((v) => Math.round(v * 255));
112
- const contrast = fx.contrast ?? 1.45;
113
- const o = 0.5 - contrast * 0.5 + (fx.brightness ?? 0.06);
114
- const [wr, wg, wb] = /** @type {number[]} */ (fx.weights ?? LUMA).map((x) => (x * contrast) / 255);
115
- const { n, px: t } = thresholds(fx);
116
- const cellPx = c * ppu;
117
- const out = new Uint8Array(w * h * 4);
118
- let opaque = true;
119
- for (let y = 0; y < h; y++) {
120
- const ty = (Math.floor(y / cellPx + 1e-6) % n) * n;
121
- for (let x = 0; x < w; x++) {
122
- const i = (y * w + x) * 4;
123
- const a = src[i + 3];
124
- if (a < 255) opaque = false;
125
- if (!a) continue;
126
- // Ink where luminance and threshold fall short of 1 (the filter's discrete step at 0.5 after −0.5).
127
- const lum = src[i] * wr + src[i + 1] * wg + src[i + 2] * wb + o;
128
- const col = lum + t[ty + (Math.floor(x / cellPx + 1e-6) % n)] / 255 < 1 ? ink : paper;
129
- out[i] = col[0]; out[i + 1] = col[1]; out[i + 2] = col[2]; out[i + 3] = 255;
130
- }
131
- }
132
- const png = opaque ? await encodePngAsync(rgbOf(out), w, h, 3) : await encodePngAsync(out, w, h, 4);
133
- const image = `<image${attrs({ x: 0, y: 0, width: num(uw, 4), height: num(uh, 4), preserveAspectRatio: 'none', 'image-rendering': 'pixelated', style: 'image-rendering:pixelated', href: pngUri(png) })}/>`;
134
- if (opaque) return { defs: '', body: image };
135
- // Clipped to the layer's own shape (its alpha, at full resolution).
136
- const region = { x: -uw, y: -uh, width: uw * 3, height: uh * 3 };
137
- const defs = `${m.defs}<filter${attrs({ id: `${id}w`, filterUnits: 'userSpaceOnUse', ...region })}><feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter>`
138
- + `<mask${attrs({ id: `${id}m`, maskUnits: 'userSpaceOnUse', ...region })}><g filter="url(#${id}w)">${m.body}</g></mask>`;
139
- return { defs, body: `<g mask="url(#${id}m)">${image}</g>` };
140
- }
141
-
142
- /** @param {Uint8Array} rgba @returns {Uint8Array} The RGB channels. */
143
- function rgbOf(rgba) {
144
- const out = new Uint8Array((rgba.length / 4) * 3);
145
- for (let i = 0, j = 0; i < rgba.length; i += 4, j += 3) { out[j] = rgba[i]; out[j + 1] = rgba[i + 1]; out[j + 2] = rgba[i + 2]; }
146
- return out;
147
- }
@@ -1,4 +0,0 @@
1
- // feDropShadow as the primitives it stands for.
2
-
3
- /** Replace every `<feDropShadow>` in filter markup with blur, offset, flood, composite and merge (its `in` honoured). */
4
- export function expandDropShadows(markup: string): string;
@@ -1,45 +0,0 @@
1
- /**
2
- * `feDropShadow` spelled out as the primitives it stands for, so its `in`
3
- * is honoured (librsvg ignores `in` on `feDropShadow` and shadows the
4
- * source graphic, which silently drops whatever the chain built before it).
5
- *
6
- * @module effects/drop-shadow
7
- */
8
-
9
- import { attrs } from '../svg.js';
10
-
11
- /** @param {string} s @returns {Record<string, string>} An element's attributes. */
12
- function attributes(s) {
13
- /** @type {Record<string, string>} */
14
- const out = {};
15
- for (const m of s.matchAll(/([\w:-]+)\s*=\s*("([^"]*)"|'([^']*)')/g)) out[m[1]] = m[3] ?? m[4];
16
- return out;
17
- }
18
-
19
- /**
20
- * Replace every `<feDropShadow>` in filter markup with blur, offset, flood,
21
- * composite and merge. Without an `in`, it takes the previous result (or
22
- * the source graphic), exactly as `feDropShadow` does.
23
- *
24
- * @param {string} markup - Filter primitives.
25
- * @returns {string}
26
- *
27
- * @example
28
- * expandDropShadows('<feDropShadow in="felt" dy="2" stdDeviation="1"/>');
29
- */
30
- export function expandDropShadows(markup) {
31
- let n = 0;
32
- return markup.replace(/<feDropShadow\b([^>]*?)\/?>(?:\s*<\/feDropShadow>)?/gi, (_, raw) => {
33
- const a = attributes(raw);
34
- const k = `ds${n++}`;
35
- const shared = { x: a.x, y: a.y, width: a.width, height: a.height, 'color-interpolation-filters': a['color-interpolation-filters'] };
36
- // An identity offset names the input when none is given (the previous result).
37
- const input = a.in ?? `${k}i`;
38
- const pick = a.in ? '' : `<feOffset${attrs({ dx: 0, dy: 0, result: input })}/>`;
39
- return `${pick}<feGaussianBlur${attrs({ in: input, stdDeviation: a.stdDeviation ?? 2, ...shared })}/>`
40
- + `<feOffset${attrs({ dx: a.dx ?? 2, dy: a.dy ?? 2, result: `${k}o`, ...shared })}/>`
41
- + `<feFlood${attrs({ 'flood-color': a['flood-color'] ?? '#000', 'flood-opacity': a['flood-opacity'] ?? 1, ...shared })}/>`
42
- + `<feComposite${attrs({ in2: `${k}o`, operator: 'in', result: `${k}s`, ...shared })}/>`
43
- + `<feMerge${attrs({ result: a.result, ...shared })}><feMergeNode in="${k}s"/><feMergeNode in="${input}"/></feMerge>`;
44
- });
45
- }
@@ -1,5 +0,0 @@
1
- // The `extrude` effect.
2
- import type { RenderContext, Markup } from '../render/context.js';
3
-
4
- /** A layer drawn again behind itself, a step further each time, in a darker colour. */
5
- export function extrude(fx: { kind: 'extrude'; depth?: import('../index.js').Length; angle?: number; offset?: [import('../index.js').Length, import('../index.js').Length]; steps?: number; color?: string; to?: string }, m: Markup, ctx: RenderContext, id: string): Markup;
@@ -1,55 +0,0 @@
1
- /**
2
- * `extrude`: a layer drawn again and again behind itself, a step further
3
- * each time, in a darker colour: block type and 3D marks in one effect.
4
- *
5
- * @module effects/extrude
6
- */
7
-
8
- import { attrs, num } from '../svg.js';
9
- import { len } from '../geometry.js';
10
- import { mixOklab, toHex } from '../color.js';
11
- import { filterElement, flood } from './util.js';
12
-
13
- /**
14
- * The extrusion: `steps` copies (default enough for a solid side at the
15
- * output size, at most 64) along `angle` (degrees, 0 up, 90 right;
16
- * default 135) to `depth` (a length, default `'3vmin'`), or along `[x, y]`
17
- * (`offset`). Copies are `color` (default near-black), fading to `to` at
18
- * the far end; the layer is drawn on top. The markup is written once and
19
- * reused (`<use>`).
20
- *
21
- * @param {any} fx - `{ kind: 'extrude', depth, angle, offset, steps, color, to }`.
22
- * @param {{ defs: string, body: string }} m
23
- * @param {import('../layers.js').RenderContext} ctx
24
- * @param {string} id
25
- * @returns {{ defs: string, body: string }}
26
- */
27
- export function extrude(fx, m, ctx, id) {
28
- const [ox, oy] = fx.offset
29
- ? [len(fx.offset[0], ctx.canvas), len(fx.offset[1], ctx.canvas)]
30
- : (() => {
31
- const d = len(fx.depth, ctx.canvas, '3vmin');
32
- const a = ((fx.angle ?? 135) * Math.PI) / 180;
33
- return [Math.sin(a) * d, -Math.cos(a) * d];
34
- })();
35
- const reach = Math.hypot(ox, oy);
36
- const res = /** @type {any} */ (ctx).res ?? 1;
37
- const steps = Math.max(1, Math.round(fx.steps ?? Math.min(64, Math.max(6, Math.ceil(reach * res)))));
38
- const near = toHex(fx.color ?? '#141414');
39
- const far = fx.to ? toHex(fx.to) : near;
40
- // Colour bands (far first): one recolouring filter per band.
41
- const bands = Math.min(steps, far === near ? 1 : 8);
42
- const pad = reach / Math.min(ctx.width, ctx.height) + 0.05;
43
- let defs = `${m.defs}<g id="${id}s">${m.body}</g>`;
44
- let body = '';
45
- for (let b = 0; b < bands; b++) {
46
- const from = Math.floor((b * steps) / bands);
47
- const to = Math.floor(((b + 1) * steps) / bands);
48
- const color = mixOklab(far, near, bands === 1 ? 1 : b / (bands - 1));
49
- defs += filterElement(`${id}e${b}`, ctx, pad, `<feColorMatrix in="SourceGraphic" type="matrix" values="${flood(color)}"/>`);
50
- let copies = '';
51
- for (let i = steps - from; i > steps - to; i--) copies += `<use${attrs({ href: `#${id}s`, transform: `translate(${num((ox * i) / steps, 3)} ${num((oy * i) / steps, 3)})` })}/>`;
52
- body += `<g filter="url(#${id}e${b})">${copies}</g>`;
53
- }
54
- return { defs, body: `${body}<use href="#${id}s"/>` };
55
- }
@@ -1,11 +0,0 @@
1
- // Film grain, paper, dust and scratches.
2
- import type { Effect } from './index.js';
3
- import type { RenderContext } from '../render/context.js';
4
- import type { Pixels } from './buffer.js';
5
-
6
- /** The `grain` effect's filter primitives. */
7
- export function grain(fx: Extract<Effect, { kind: 'grain' }>, ctx: RenderContext): string;
8
- /** The `paper` effect's filter primitives. */
9
- export function paper(fx: Extract<Effect, { kind: 'paper' }>, ctx: RenderContext): string;
10
- /** `dust`, on pixels. */
11
- export function dust(img: Pixels, fx: Extract<Effect, { kind: 'dust' }>, ctx: object): Uint8ClampedArray;
@@ -1,204 +0,0 @@
1
- /**
2
- * Film and paper: `grain` (film grain as a filter, overlaid so it lives in
3
- * the midtones as real grain does; monochrome or colour), `paper` (the
4
- * layer printed on paper or canvas: a lit relief overlaid) and `dust`
5
- * (specks, hairs and scratches, seeded, drawn on pixels).
6
- *
7
- * @module effects/film
8
- */
9
-
10
- import { attrs, num } from '../svg.js';
11
- import { len } from '../geometry.js';
12
- import { encodePng, pngUri } from '../png.js';
13
- import { createRng } from '../rng.js';
14
- import { mat, seedOf } from './util.js';
15
- import { px, seedFor } from './buffer.js';
16
-
17
- /**
18
- * `grain`: film grain, `amount` (0–1), `size` (a length: the grain), and
19
- * `color` (true: each channel its own grain, as colour film; default
20
- * monochrome). Overlaid, so it's strongest in the midtones.
21
- *
22
- * @param {import('./index.js').Effect & { kind: 'grain' }} fx
23
- * @param {import('../render/context.js').RenderContext} ctx
24
- * @returns {string}
25
- */
26
- export function grain(fx, ctx) {
27
- const size = Math.max(0.05, len(fx.size, ctx.canvas, '0.09vmin'));
28
- const a = (fx.amount ?? 0.35) * 1.5;
29
- const mono = !fx.color;
30
- // Noise centred on mid-grey with its spread set by amount.
31
- const row = (/** @type {number} */ c) => (mono ? [a, 0, 0, 0, 0.5 - a / 2] : [c === 0 ? a : 0, c === 1 ? a : 0, c === 2 ? a : 0, 0, 0.5 - a / 2]);
32
- return `<feTurbulence type="fractalNoise" baseFrequency="${num(0.5 / size, 4)}" numOctaves="2" seed="${seedOf(ctx, fx)}" result="gn"/>`
33
- + `<feColorMatrix in="gn" type="matrix" values="${mat([...row(0), ...row(1), ...row(2), 0, 0, 0, 0, 1])}" result="gc"/>`
34
- + '<feBlend in="gc" in2="SourceGraphic" mode="overlay"/><feComposite in2="SourceAlpha" operator="in"/>';
35
- }
36
-
37
- /** @type {Map<string, string>} */
38
- const WEAVES = new Map();
39
-
40
- /**
41
- * A tile of canvas weave as heights: over-under threads, 2×2 to the tile.
42
- *
43
- * @returns {string} A data URI.
44
- */
45
- function weave() {
46
- const hit = WEAVES.get('w');
47
- if (hit) return hit;
48
- const n = 32;
49
- const out = new Uint8Array(n * n);
50
- for (let y = 0; y < n; y++) {
51
- for (let x = 0; x < n; x++) {
52
- const u = (x + 0.5) / n;
53
- const v = (y + 0.5) / n;
54
- // Warp and weft threads, each a rounded ridge; which is on top alternates.
55
- const warp = Math.cos(Math.PI * ((u * 2) % 1 - 0.5)) ** 0.6;
56
- const weft = Math.cos(Math.PI * ((v * 2) % 1 - 0.5)) ** 0.6;
57
- const top = (Math.floor(u * 2) + Math.floor(v * 2)) % 2 === 0;
58
- out[y * n + x] = Math.round(255 * (top ? 0.25 + 0.75 * warp * (0.6 + 0.4 * weft) : 0.25 + 0.75 * weft * (0.6 + 0.4 * warp)));
59
- }
60
- }
61
- const uri = pngUri(encodePng(out, n, n, 1));
62
- WEAVES.set('w', uri);
63
- return uri;
64
- }
65
-
66
- /**
67
- * `paper`: printed on a surface. `texture` `'paper'` (fibrous, uneven) or
68
- * `'canvas'` (a woven tooth), `scale` (a length: the fibres, or a thread
69
- * pair), `amount` (0–1), lit from `angle`; the lit relief is overlaid.
70
- *
71
- * @param {import('./index.js').Effect & { kind: 'paper' }} fx
72
- * @param {import('../render/context.js').RenderContext} ctx
73
- * @returns {string}
74
- */
75
- export function paper(fx, ctx) {
76
- const texture = fx.texture ?? 'paper';
77
- if (texture !== 'paper' && texture !== 'canvas') throw new TypeError(`Unknown paper texture "${texture}" (paper, canvas)`);
78
- const amount = fx.amount ?? 0.5;
79
- const az = fx.angle ?? 225;
80
- let relief;
81
- if (texture === 'canvas') {
82
- const s = len(fx.scale, ctx.canvas, '0.9vmin');
83
- relief = `<feImage${attrs({ href: weave(), x: 0, y: 0, width: s, height: s, preserveAspectRatio: 'none', result: 'pt' })}/><feTile in="pt"/>`
84
- + '<feColorMatrix type="luminanceToAlpha" color-interpolation-filters="sRGB" result="ph"/>';
85
- } else {
86
- const s = len(fx.scale, ctx.canvas, '0.45vmin');
87
- relief = `<feTurbulence type="fractalNoise" baseFrequency="${num(1 / s, 4)} ${num(0.6 / s, 4)}" numOctaves="4" seed="${seedOf(ctx, fx)}"/>`
88
- + '<feColorMatrix type="luminanceToAlpha" color-interpolation-filters="sRGB" result="ph"/>';
89
- }
90
- const depth = texture === 'canvas' ? 3 : 2.5;
91
- // Lit flat, the surface is sin(elevation) grey; that maps to 0.5 (no change under overlay).
92
- const elev = 50;
93
- const flat = Math.sin((elev * Math.PI) / 180);
94
- const k = (texture === 'canvas' ? 0.8 : 0.7) * amount;
95
- const row = (/** @type {number} */ c) => [c === 0 ? k / flat : 0, c === 1 ? k / flat : 0, c === 2 ? k / flat : 0, 0, 0.5 - k];
96
- return relief
97
- + `<feDiffuseLighting in="ph" surfaceScale="${num(depth, 3)}" diffuseConstant="1" lighting-color="#fff" color-interpolation-filters="sRGB" result="pl"><feDistantLight azimuth="${num(az)}" elevation="${elev}"/></feDiffuseLighting>`
98
- // Lit ridges lighten, shaded ones darken: the relief overlaid.
99
- + `<feColorMatrix in="pl" type="matrix" values="${mat([...row(0), ...row(1), ...row(2), 0, 0, 0, 0, 1])}" result="pm"/>`
100
- + '<feBlend in="pm" in2="SourceGraphic" mode="overlay"/><feComposite in2="SourceAlpha" operator="in"/>';
101
- }
102
-
103
- /**
104
- * Stamp an anti-aliased stroke (a polyline of `[x, y]` points) or speck
105
- * into a coverage buffer.
106
- *
107
- * @param {Float32Array} cov
108
- * @param {number} w
109
- * @param {number} h
110
- * @param {[number, number][]} pts
111
- * @param {number} r - Half-width, px.
112
- * @param {number} o - Opacity.
113
- * @returns {void}
114
- */
115
- function stroke(cov, w, h, pts, r, o) {
116
- for (let s = 0; s < pts.length - (pts.length > 1 ? 1 : 0); s++) {
117
- const [x0, y0] = pts[s];
118
- const [x1, y1] = pts.length > 1 ? pts[s + 1] : pts[s];
119
- const dx = x1 - x0;
120
- const dy = y1 - y0;
121
- const l2 = dx * dx + dy * dy || 1e-9;
122
- const bx0 = Math.max(0, Math.floor(Math.min(x0, x1) - r - 1));
123
- const bx1 = Math.min(w - 1, Math.ceil(Math.max(x0, x1) + r + 1));
124
- const by0 = Math.max(0, Math.floor(Math.min(y0, y1) - r - 1));
125
- const by1 = Math.min(h - 1, Math.ceil(Math.max(y0, y1) + r + 1));
126
- for (let y = by0; y <= by1; y++) {
127
- for (let x = bx0; x <= bx1; x++) {
128
- const t = Math.max(0, Math.min(1, ((x + 0.5 - x0) * dx + (y + 0.5 - y0) * dy) / l2));
129
- const d = Math.hypot(x + 0.5 - x0 - t * dx, y + 0.5 - y0 - t * dy);
130
- const c = Math.max(0, Math.min(1, r + 0.5 - d)) * o;
131
- const i = y * w + x;
132
- if (c > cov[i]) cov[i] = c;
133
- }
134
- }
135
- }
136
- }
137
-
138
- /**
139
- * `dust`: dust and scratches on film, seeded: specks (`amount`, 0–1),
140
- * curling hairs, and `scratches` running down the frame; `color`
141
- * `'light'` (dust on a negative prints white), `'dark'`, or `'mixed'`.
142
- *
143
- * @param {import('./buffer.js').Pixels} img
144
- * @param {import('./index.js').Effect & { kind: 'dust' }} fx
145
- * @param {any} ctx
146
- * @returns {Uint8ClampedArray}
147
- */
148
- export function dust(img, fx, ctx) {
149
- const { data, width: w, height: h } = img;
150
- const rng = createRng(`dust:${seedFor(fx, ctx)}`);
151
- const unit = px('0.1vmin', ctx, img, 0);
152
- const amount = fx.amount ?? 0.5;
153
- const mode = fx.color ?? 'mixed';
154
- if (!['light', 'dark', 'mixed'].includes(mode)) throw new TypeError(`Unknown dust color "${mode}" (light, dark, mixed)`);
155
- const light = new Float32Array(w * h);
156
- const dark = new Float32Array(w * h);
157
- const pick = () => (mode === 'light' ? light : mode === 'dark' ? dark : rng.chance(0.7) ? light : dark);
158
- const specks = Math.round(amount * 260);
159
- for (let i = 0; i < specks; i++) {
160
- const x = rng.range(0, w);
161
- const y = rng.range(0, h);
162
- const r = unit * rng.range(0.3, 1) * (rng.chance(0.08) ? 3.5 : 1);
163
- // A clump of two or three dots, so specks aren't perfect circles.
164
- const buf = pick();
165
- const o = rng.range(0.35, 0.95);
166
- for (let k = 0, n = rng.int(1, 3); k < n; k++) stroke(buf, w, h, [[x + rng.range(-r, r), y + rng.range(-r, r)]], r * rng.range(0.5, 1), o);
167
- }
168
- const hairs = Math.round(amount * 9);
169
- for (let i = 0; i < hairs; i++) {
170
- /** @type {[number, number][]} */
171
- const pts = [[rng.range(0, w), rng.range(0, h)]];
172
- let a = rng.range(0, Math.PI * 2);
173
- const seg = unit * rng.range(3, 7);
174
- for (let k = 0, n = rng.int(12, 40); k < n; k++) {
175
- a += rng.range(-0.35, 0.35);
176
- const [px0, py0] = pts[pts.length - 1];
177
- pts.push([px0 + Math.cos(a) * seg, py0 + Math.sin(a) * seg]);
178
- }
179
- stroke(pick(), w, h, pts, unit * rng.range(0.25, 0.5), rng.range(0.5, 0.9));
180
- }
181
- const scratches = fx.scratches ?? Math.round(amount * 4);
182
- for (let i = 0; i < scratches; i++) {
183
- let x = rng.range(0, w);
184
- const y0 = rng.chance(0.5) ? 0 : rng.range(0, h * 0.6);
185
- const y1 = rng.chance(0.5) ? h : rng.range(y0 + h * 0.2, h);
186
- /** @type {[number, number][]} */
187
- const pts = [];
188
- for (let y = y0; y <= y1; y += h / 24) { pts.push([x, y]); x += rng.range(-0.6, 0.6) * unit; }
189
- stroke(rng.chance(0.75) ? light : dark, w, h, pts, unit * rng.range(0.15, 0.4), rng.range(0.3, 0.7));
190
- }
191
- const out = new Uint8ClampedArray(data);
192
- for (let p = 0, i = 0; p < light.length; p++, i += 4) {
193
- const l = light[p];
194
- const d = dark[p];
195
- if (!l && !d) continue;
196
- for (let c = 0; c < 3; c++) {
197
- let v = out[i + c];
198
- v += (250 - v) * l;
199
- v -= (v - 12) * d;
200
- out[i + c] = v;
201
- }
202
- }
203
- return out;
204
- }
@@ -1,8 +0,0 @@
1
- // Light spreading from the bright parts.
2
- import type { Effect } from './index.js';
3
- import type { RenderContext } from '../render/context.js';
4
-
5
- /** The `bloom` effect's filter primitives. */
6
- export function bloom(fx: Extract<Effect, { kind: 'bloom' }>, ctx: RenderContext): string;
7
- /** The `halation` effect's filter primitives. */
8
- export function halation(fx: Extract<Effect, { kind: 'halation' }>, ctx: RenderContext): string;
@@ -1,56 +0,0 @@
1
- /**
2
- * Light that spreads from the bright parts, as SVG filters: `bloom` (the
3
- * highlights glowing, at two scales, screened over the layer) and
4
- * `halation` (film's red-orange halo round bright edges, where light
5
- * bounced off the film base back through the red layer).
6
- *
7
- * @module effects/glow
8
- */
9
-
10
- import { num } from '../svg.js';
11
- import { len } from '../geometry.js';
12
- import { LUMA, rgb, mat } from './util.js';
13
-
14
- /**
15
- * `bloom`: what's brighter than `threshold` (0–1), blurred to `radius`
16
- * and to three times that, screened over the layer at `intensity`, tinted
17
- * by `color` when given. It spreads past the layer's edges (neon type).
18
- *
19
- * @param {import('./index.js').Effect & { kind: 'bloom' }} fx
20
- * @param {import('../render/context.js').RenderContext} ctx
21
- * @returns {string}
22
- */
23
- export function bloom(fx, ctx) {
24
- const t = Math.min(0.99, fx.threshold ?? 0.65);
25
- const r = len(fx.radius, ctx.canvas, '1.6vmin');
26
- const k = fx.intensity ?? 0.9;
27
- const slope = 1 / (1 - t);
28
- const tint = fx.color ? rgb(fx.color) : [1, 1, 1];
29
- return `<feComponentTransfer in="SourceGraphic">${['R', 'G', 'B'].map((ch, i) => `<feFunc${ch} type="linear" slope="${num(slope * tint[i], 4)}" intercept="${num(-t * slope * tint[i], 4)}"/>`).join('')}</feComponentTransfer>`
30
- + '<feComposite in2="SourceAlpha" operator="in" result="bh"/>'
31
- + `<feGaussianBlur in="bh" stdDeviation="${num(r)}" result="b1"/><feGaussianBlur in="bh" stdDeviation="${num(r * 3)}" result="b2"/>`
32
- + `<feComposite in="b1" in2="b2" operator="arithmetic" k2="${num(0.6 * k, 3)}" k3="${num(0.7 * k, 3)}" result="bg"/>`
33
- + '<feBlend in="bg" in2="SourceGraphic" mode="screen"/>';
34
- }
35
-
36
- /**
37
- * `halation`: the highlights above `threshold`, flooded with `color`
38
- * (default film's red-orange), blurred to `radius` and screened on at
39
- * `intensity`.
40
- *
41
- * @param {import('./index.js').Effect & { kind: 'halation' }} fx
42
- * @param {import('../render/context.js').RenderContext} ctx
43
- * @returns {string}
44
- */
45
- export function halation(fx, ctx) {
46
- const t = Math.min(0.99, fx.threshold ?? 0.72);
47
- const [r, g, b] = rgb(fx.color ?? '#ff3d14');
48
- const k = 1 / (1 - t);
49
- const i = fx.intensity ?? 1;
50
- return `<feColorMatrix in="SourceGraphic" type="matrix" values="${mat([0, 0, 0, 0, r, 0, 0, 0, 0, g, 0, 0, 0, 0, b, LUMA[0] * k, LUMA[1] * k, LUMA[2] * k, 0, -t * k])}"/>`
51
- + '<feComposite in2="SourceAlpha" operator="in"/>'
52
- + `<feGaussianBlur stdDeviation="${num(len(fx.radius, ctx.canvas, '0.9vmin'))}"/>`
53
- + `<feComponentTransfer><feFuncA type="linear" slope="${num(i, 3)}"/></feComponentTransfer>`
54
- + '<feComposite in2="SourceAlpha" operator="in" result="hl"/>'
55
- + '<feBlend in="hl" in2="SourceGraphic" mode="screen"/>';
56
- }
@@ -1,30 +0,0 @@
1
- // Colour grading.
2
- import type { Effect } from './index.js';
3
- import type { Pixels } from './buffer.js';
4
-
5
- /** A monotone cubic through points, sampled as a table. */
6
- export function curveTable(points: [number, number][], n?: number): number[];
7
- /** A component transfer from three tables (R, G, B). */
8
- export function transfer(tables: number[][]): string;
9
- /** A gradient's colour at `t`, 0–1 channels. */
10
- export function sampleGradient(colors: string[], stops: number[], t: number, space: 'srgb' | 'oklab'): [number, number, number];
11
- /** A colour balance's tables, per channel. */
12
- export function balanceTables(fx: { shadows?: number[]; midtones?: number[]; highlights?: number[] }): number[][];
13
- /** The `curves` effect's filter primitives. */
14
- export function curves(fx: Extract<Effect, { kind: 'curves' }>): string;
15
- /** The `gradientMap` effect's filter primitives. */
16
- export function gradientMap(fx: Extract<Effect, { kind: 'gradientMap' }>): string;
17
- /** The `posterize` effect's filter primitives. */
18
- export function posterize(fx: Extract<Effect, { kind: 'posterize' }>): string;
19
- /** The `invert` effect's filter primitives. */
20
- export function invert(fx: Extract<Effect, { kind: 'invert' }>): string;
21
- /** The `hue` effect's filter primitives. */
22
- export function hue(fx: Extract<Effect, { kind: 'hue' }>): string;
23
- /** The `colorBalance` effect's filter primitives. */
24
- export function colorBalance(fx: Extract<Effect, { kind: 'colorBalance' }>): string;
25
- /** The `channelMixer` effect's filter primitives. */
26
- export function channelMixer(fx: Extract<Effect, { kind: 'channelMixer' }>): string;
27
- /** The `sepia` effect's filter primitives. */
28
- export function sepia(fx: Extract<Effect, { kind: 'sepia' }>): string;
29
- /** `vibrance`, on pixels. */
30
- export function vibrance(img: Pixels, fx: Extract<Effect, { kind: 'vibrance' }>): Uint8ClampedArray;