@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,265 +0,0 @@
1
- /**
2
- * Blurs about a centre, on pixels: `zoom` (radial, every point smeared
3
- * toward the centre), `spin` (rotational, along circles round it) and
4
- * `rays` (god rays: the highlights streaked away from a light).
5
- *
6
- * The layer is resampled into polar coordinates (one row per radius or
7
- * per angle, a pixel apart at the rim), blurred along one axis with
8
- * running sums (O(1) a sample, whatever the length), and resampled back.
9
- * The centre pixel never moves.
10
- *
11
- * @module effects/polar
12
- */
13
-
14
- import { premultiplied, unpremultiply, bilinear, pointPx, luma, smoothstep, toSrgb, TO_LINEAR, shrink, enlarge } from './buffer.js';
15
- import { rgb } from './util.js';
16
-
17
- /** The most polar samples (angles × radii) a blur works on. */
18
- const BUDGET = 30e6;
19
-
20
- /**
21
- * A polar grid round a centre, sized to the farthest corner: a sample a
22
- * pixel apart along each ray, and round the rim a pixel apart (two, when
23
- * `blur` px of smear there hides it).
24
- *
25
- * @param {number} w @param {number} h @param {number} cx @param {number} cy
26
- * @param {(rmax: number) => number} [blur] - How long the blur is at the rim, px.
27
- * @returns {{ nr: number, nt: number }}
28
- */
29
- function gridFor(w, h, cx, cy, blur) {
30
- const rmax = Math.max(Math.hypot(cx, cy), Math.hypot(w - 1 - cx, cy), Math.hypot(cx, h - 1 - cy), Math.hypot(w - 1 - cx, h - 1 - cy)) + 2;
31
- const nr = Math.ceil(rmax) + 1;
32
- const step = blur && blur(rmax) >= 32 ? 2 : 1;
33
- const nt = Math.max(16, Math.min(Math.ceil((2 * Math.PI * rmax) / step), Math.floor(BUDGET / nr)));
34
- return { nr, nt };
35
- }
36
-
37
- /**
38
- * Resample into polar coordinates: row-major by angle (`byAngle`, each row
39
- * a ray from the centre) or by radius (each row a circle).
40
- *
41
- * @param {Uint8ClampedArray} src - Premultiplied RGBA.
42
- * @param {number} w @param {number} h @param {number} cx @param {number} cy
43
- * @param {number} nt @param {number} nr
44
- * @param {boolean} byAngle
45
- * @returns {Uint8ClampedArray}
46
- */
47
- function toPolar(src, w, h, cx, cy, nt, nr, byAngle) {
48
- const out = new Uint8ClampedArray(nt * nr * 4);
49
- for (let j = 0; j < nt; j++) {
50
- const t = (2 * Math.PI * j) / nt;
51
- const c = Math.cos(t);
52
- const s = Math.sin(t);
53
- for (let i = 0; i < nr; i++) bilinear(src, w, h, cx + i * c, cy + i * s, out, (byAngle ? j * nr + i : i * nt + j) * 4);
54
- }
55
- return out;
56
- }
57
-
58
- /**
59
- * Resample polar samples back onto the layer's pixels.
60
- *
61
- * @param {Uint8ClampedArray} p
62
- * @param {number} w @param {number} h @param {number} cx @param {number} cy
63
- * @param {number} nt @param {number} nr
64
- * @param {boolean} byAngle
65
- * @returns {Uint8ClampedArray}
66
- */
67
- function fromPolar(p, w, h, cx, cy, nt, nr, byAngle) {
68
- const out = new Uint8ClampedArray(w * h * 4);
69
- const kt = nt / (2 * Math.PI);
70
- for (let y = 0, o = 0; y < h; y++) {
71
- const dy = y - cy;
72
- for (let x = 0; x < w; x++, o += 4) {
73
- const dx = x - cx;
74
- let r = Math.sqrt(dx * dx + dy * dy);
75
- let t = Math.atan2(dy, dx);
76
- if (t < 0) t += 2 * Math.PI;
77
- if (r > nr - 1) r = nr - 1;
78
- const ft = t * kt;
79
- let j0 = ft | 0;
80
- const wt = ft - j0;
81
- if (j0 >= nt) j0 -= nt;
82
- const j1 = j0 + 1 === nt ? 0 : j0 + 1;
83
- const i0 = r | 0;
84
- const wr = r - i0;
85
- const i1 = i0 + 1 < nr ? i0 + 1 : i0;
86
- const a = (byAngle ? j0 * nr + i0 : i0 * nt + j0) * 4;
87
- const b = (byAngle ? j0 * nr + i1 : i1 * nt + j0) * 4;
88
- const c = (byAngle ? j1 * nr + i0 : i0 * nt + j1) * 4;
89
- const d = (byAngle ? j1 * nr + i1 : i1 * nt + j1) * 4;
90
- const w00 = (1 - wr) * (1 - wt);
91
- const w10 = wr * (1 - wt);
92
- const w01 = (1 - wr) * wt;
93
- const w11 = wr * wt;
94
- for (let k = 0; k < 4; k++) out[o + k] = p[a + k] * w00 + p[b + k] * w10 + p[c + k] * w01 + p[d + k] * w11;
95
- }
96
- }
97
- return out;
98
- }
99
-
100
- /** The centre in pixel coordinates (pixel x's centre is x). @param {any} fx @param {import('./buffer.js').Pixels} img @returns {[number, number]} */
101
- function centreOf(fx, img) {
102
- const [x, y] = pointPx(fx.center, img);
103
- return [x - 0.5, y - 0.5];
104
- }
105
-
106
- /**
107
- * `spin`: a rotational blur, `angle` degrees along circles round `center`
108
- * (a camera turned during the exposure).
109
- *
110
- * @param {import('./buffer.js').Pixels} img
111
- * @param {import('./index.js').Effect & { kind: 'spin' }} fx
112
- * @returns {Uint8ClampedArray}
113
- */
114
- export function spin(img, fx) {
115
- const { width: w, height: h } = img;
116
- const [cx, cy] = centreOf(fx, img);
117
- const turn = Math.min(360, Math.abs(fx.angle ?? 12));
118
- const { nr, nt } = gridFor(w, h, cx, cy, (r) => (turn / 180) * Math.PI * r);
119
- const span = Math.round((turn / 360) * nt);
120
- if (span < 2) return img.data;
121
- const p = toPolar(premultiplied(img), w, h, cx, cy, nt, nr, false);
122
- // Each row is a circle: a circular box of `span` samples, centred.
123
- const row = new Uint8ClampedArray(nt * 4);
124
- const acc = new Float64Array(4);
125
- const half = span >> 1;
126
- const inv = 1 / (span + 1);
127
- for (let i = 0; i < nr; i++) {
128
- const base = i * nt * 4;
129
- row.set(p.subarray(base, base + nt * 4));
130
- acc.fill(0);
131
- for (let k = -half; k <= span - half; k++) {
132
- const j = ((k % nt) + nt) % nt;
133
- for (let c = 0; c < 4; c++) acc[c] += row[j * 4 + c];
134
- }
135
- for (let j = 0; j < nt; j++) {
136
- const o = base + j * 4;
137
- for (let c = 0; c < 4; c++) p[o + c] = acc[c] * inv;
138
- let add = j + span - half + 1;
139
- if (add >= nt) add -= nt;
140
- let sub = j - half;
141
- if (sub < 0) sub += nt;
142
- for (let c = 0; c < 4; c++) acc[c] += row[add * 4 + c] - row[sub * 4 + c];
143
- }
144
- }
145
- const out = fromPolar(p, w, h, cx, cy, nt, nr, false);
146
- return img.opaque ? out : unpremultiply(out);
147
- }
148
-
149
- /**
150
- * `zoom`: a radial blur, each point averaged with what lies between it and
151
- * `center` over `amount` of the way (a zoom during the exposure).
152
- *
153
- * @param {import('./buffer.js').Pixels} img
154
- * @param {import('./index.js').Effect & { kind: 'zoom' }} fx
155
- * @returns {Uint8ClampedArray}
156
- */
157
- export function zoom(img, fx) {
158
- const { width: w, height: h } = img;
159
- const [cx, cy] = centreOf(fx, img);
160
- const amount = Math.min(0.95, Math.max(0, fx.amount ?? 0.2));
161
- const { nr, nt } = gridFor(w, h, cx, cy, (r) => amount * r);
162
- if (!amount) return img.data;
163
- const p = toPolar(premultiplied(img), w, h, cx, cy, nt, nr, true);
164
- const sum = new Float64Array((nr + 1) * 4);
165
- for (let j = 0; j < nt; j++) {
166
- const base = j * nr * 4;
167
- for (let i = 0; i < nr; i++) for (let c = 0; c < 4; c++) sum[(i + 1) * 4 + c] = sum[i * 4 + c] + p[base + i * 4 + c];
168
- for (let i = 1; i < nr; i++) {
169
- const from = Math.floor(i * (1 - amount));
170
- const k = 1 / (i + 1 - from);
171
- for (let c = 0; c < 4; c++) p[base + i * 4 + c] = (sum[(i + 1) * 4 + c] - sum[from * 4 + c]) * k;
172
- }
173
- }
174
- const out = fromPolar(p, w, h, cx, cy, nt, nr, true);
175
- return img.opaque ? out : unpremultiply(out);
176
- }
177
-
178
- /**
179
- * Where the light is, when `rays` isn't given a `center`: the brightest
180
- * patch, from a coarse grid.
181
- *
182
- * @param {import('./buffer.js').Pixels} img
183
- * @returns {[number, number]} Pixel coordinates.
184
- */
185
- function brightest(img) {
186
- const { data, width: w, height: h } = img;
187
- const g = 48;
188
- const cw = w / g;
189
- const ch = h / g;
190
- let best = -1;
191
- let at = [w / 2, h / 2];
192
- for (let gy = 0; gy < g; gy++) {
193
- for (let gx = 0; gx < g; gx++) {
194
- let s = 0;
195
- let n = 0;
196
- for (let y = Math.floor(gy * ch); y < Math.floor((gy + 1) * ch); y += 2) {
197
- for (let x = Math.floor(gx * cw); x < Math.floor((gx + 1) * cw); x += 2) { const i = (y * w + x) * 4; s += luma(data, i) * data[i + 3]; n++; }
198
- }
199
- if (n && s / n > best) { best = s / n; at = [(gx + 0.5) * cw, (gy + 0.5) * ch]; }
200
- }
201
- }
202
- return /** @type {[number, number]} */ (at);
203
- }
204
-
205
- /**
206
- * `rays`: god rays. The highlights above `threshold` are streaked away
207
- * from the light (`center`, default the brightest patch): each point gets
208
- * the light between it and the source, so anything in the way casts a
209
- * shaft, fading over `length` (a fraction of the frame), tinted with
210
- * `color` and added on top (`intensity`).
211
- *
212
- * @param {import('./buffer.js').Pixels} img
213
- * @param {import('./index.js').Effect & { kind: 'rays' }} fx
214
- * @returns {Uint8ClampedArray}
215
- */
216
- export function rays(img, fx) {
217
- const { data, width: w, height: h } = img;
218
- const [cx0, cy0] = fx.center ? centreOf(fx, img) : brightest(img);
219
- const t = fx.threshold ?? 0.8;
220
- const tint = rgb(fx.color ?? '#fff3d6');
221
- // Bright pass, premultiplied.
222
- const bright = new Uint8ClampedArray(w * h * 4);
223
- for (let i = 0; i < data.length; i += 4) {
224
- const k = smoothstep(t, 1, luma(data, i) / 255) * (data[i + 3] / 255);
225
- if (!k) continue;
226
- bright[i] = data[i] * k * tint[0];
227
- bright[i + 1] = data[i + 1] * k * tint[1];
228
- bright[i + 2] = data[i + 2] * k * tint[2];
229
- bright[i + 3] = 255 * k;
230
- }
231
- // Rays are soft: past a couple of megapixels they're worked at half size.
232
- const f = w * h > 2.5e6 ? 2 : 1;
233
- const small = f > 1 ? shrink(bright, w, h, f) : { data: bright, width: w, height: h };
234
- const sw = small.width;
235
- const sh = small.height;
236
- const cx = (cx0 + 0.5) / f - 0.5;
237
- const cy = (cy0 + 0.5) / f - 0.5;
238
- const { nr, nt } = gridFor(sw, sh, cx, cy);
239
- const p = toPolar(small.data, sw, sh, cx, cy, nt, nr, true);
240
- // Each point gets the mean of the light between it and the source (an
241
- // occluder on the way leaves a shadow shaft), fading with distance.
242
- const reach = Math.max(1, (fx.length ?? 0.5) * nr);
243
- const acc = new Float64Array(4);
244
- for (let j = 0; j < nt; j++) {
245
- const base = j * nr * 4;
246
- acc.fill(0);
247
- for (let i = 0; i < nr; i++) {
248
- const fade = Math.exp(-i / reach) / (i + 1);
249
- for (let c = 0; c < 4; c++) {
250
- acc[c] += p[base + i * 4 + c];
251
- p[base + i * 4 + c] = Math.min(255, acc[c] * fade * 1.6);
252
- }
253
- }
254
- }
255
- const back = fromPolar(p, sw, sh, cx, cy, nt, nr, true);
256
- const glow = f > 1 ? enlarge(back, sw, sh, w, h) : back;
257
- // Added in linear light, so the rays brighten like light does.
258
- const k = fx.intensity ?? 1;
259
- const out = new Uint8ClampedArray(data.length);
260
- for (let i = 0; i < data.length; i += 4) {
261
- out[i + 3] = Math.max(data[i + 3], glow[i + 3]);
262
- for (let c = 0; c < 3; c++) out[i + c] = toSrgb(TO_LINEAR[data[i + c]] * (data[i + 3] / Math.max(1, out[i + 3])) + TO_LINEAR[glow[i + c]] * k);
263
- }
264
- return out;
265
- }
@@ -1,8 +0,0 @@
1
- // Making filters cheap for librsvg without changing what they draw.
2
-
3
- /** Split each `feMorphology` into an x pass and a y pass (identical output, O(r) not O(r²)). */
4
- export function separableMorphology(markup: string): string;
5
- /** Whether a chain of primitives only paints where its source does. */
6
- export function bounded(markup: string): boolean;
7
- /** How far filter primitives reach from what they read, in their units (an overestimate). */
8
- export function filterReach(markup: string): number;
@@ -1,136 +0,0 @@
1
- /**
2
- * Making filters cheap for librsvg (`renderImage`), without changing what
3
- * they draw:
4
- *
5
- * - **Separable morphology.** `feMorphology` is a max/min over a
6
- * rectangle, which is the same as a pass along x then one along y;
7
- * librsvg does the full rectangle per pixel (O(r²)), the two passes are
8
- * O(r). Identical output.
9
- * - **Content-sized regions.** A filter's region defaults to the canvas
10
- * (padded); librsvg works every primitive over all of it. When the
11
- * chain's result can only paint where its source does (worked out from
12
- * the primitives: floods, turbulence, images and lighting paint
13
- * everywhere; `in` composites, blurs and offsets stay with their input),
14
- * the region shrinks to the layer's painted box plus the same padding.
15
- *
16
- * @module effects/region
17
- */
18
-
19
- import { attrs } from '../svg.js';
20
-
21
- /**
22
- * Split each `feMorphology` into an x pass and a y pass.
23
- *
24
- * @param {string} markup
25
- * @returns {string}
26
- */
27
- export function separableMorphology(markup) {
28
- return markup.replace(/<feMorphology\b([^>]*?)\/>/g, (whole, rest) => {
29
- const r = /\sradius\s*=\s*"([^"]*)"/.exec(rest);
30
- if (!r) return whole;
31
- const [rx, ry = rx] = r[1].trim().split(/[\s,]+/).map(Number);
32
- if (!(rx > 0 && ry > 0)) return whole;
33
- const result = /\sresult\s*=\s*"([^"]*)"/.exec(rest)?.[1];
34
- const base = rest.replace(/\s(radius|result)\s*=\s*"[^"]*"/g, '');
35
- const mid = `${result ?? 'm'}${Math.round(rx * 1000)}x`;
36
- const second = base.replace(/\sin\s*=\s*"[^"]*"/, '');
37
- return `<feMorphology${base}${attrs({ radius: `${rx} 0`, result: mid })}/><feMorphology${second}${attrs({ in: mid, radius: `0 ${ry}`, result })}/>`;
38
- });
39
- }
40
-
41
- /** @param {string} svg @param {RegExp} re @returns {number} The largest number a pattern's first group holds. */
42
- function most(svg, re) {
43
- let m = 0;
44
- for (const x of svg.matchAll(re)) for (const v of x[1].trim().split(/[\s,]+/)) m = Math.max(m, Math.abs(Number(v)) || 0);
45
- return m;
46
- }
47
-
48
- /**
49
- * How far filter primitives reach from what they read, in their units:
50
- * blur (3σ), displacement (half its scale), morphology radius and offsets,
51
- * added up (an overestimate).
52
- *
53
- * @param {string} markup
54
- * @returns {number}
55
- */
56
- export function filterReach(markup) {
57
- return 3 * most(markup, /stdDeviation="([^"]*)"/g)
58
- + most(markup, /<feDisplacementMap\b[^>]*\sscale="([^"]*)"/g) / 2
59
- + most(markup, /<feMorphology\b[^>]*\sradius="([^"]*)"/g)
60
- + most(markup, /<(?:feOffset|feDropShadow)\b[^>]*\sd[xy]="([^"]*)"/g);
61
- }
62
-
63
- /** @param {string} s @returns {Record<string, string>} */
64
- const attributesOf = (s) => Object.fromEntries([...s.matchAll(/([\w:-]+)\s*=\s*"([^"]*)"/g)].map((m) => [m[1], m[2]]));
65
-
66
- /**
67
- * Whether a chain of primitives only paints where its source does (so its
68
- * region can shrink to the source's box plus padding).
69
- *
70
- * @param {string} markup - Filter primitives.
71
- * @returns {boolean}
72
- */
73
- export function bounded(markup) {
74
- /** @type {Map<string, boolean>} */
75
- const results = new Map([['SourceGraphic', true], ['SourceAlpha', true]]);
76
- let last = true;
77
- const input = (/** @type {string | undefined} */ name) => (name == null ? last : results.get(name) ?? false);
78
- for (const m of markup.matchAll(/<(fe\w+)\b([^>]*?)(\/>|>([\s\S]*?)<\/\1>)/g)) {
79
- const [, tag, rest, , inner = ''] = m;
80
- const a = attributesOf(rest);
81
- let b;
82
- switch (tag) {
83
- case 'feFlood': case 'feTurbulence': case 'feImage': case 'feDiffuseLighting': case 'feSpecularLighting': case 'feTile':
84
- b = false; break;
85
- case 'feComposite': {
86
- const i1 = input(a.in);
87
- const i2 = input(a.in2);
88
- const op = a.operator ?? 'over';
89
- if (op === 'in') b = i1 || i2;
90
- else if (op === 'atop') b = i2;
91
- else if (op === 'out') b = i1;
92
- else if (op === 'arithmetic') {
93
- const k = (/** @type {string} */ n) => Number(a[n] ?? 0);
94
- b = k('k4') === 0 && (k('k2') === 0 || i1) && (k('k3') === 0 || i2) && (k('k1') === 0 || i1 || i2);
95
- } else b = i1 && i2;
96
- break;
97
- }
98
- case 'feMerge':
99
- b = [...inner.matchAll(/<feMergeNode\b([^>]*)\/?>/g)].every((n) => input(attributesOf(n[1]).in));
100
- break;
101
- case 'feBlend':
102
- b = input(a.in) && input(a.in2);
103
- break;
104
- case 'feColorMatrix': {
105
- const v = (a.values ?? '').trim().split(/[\s,]+/).map(Number);
106
- // A matrix whose alpha row adds a constant can paint where the source is clear.
107
- b = input(a.in) && !((a.type ?? 'matrix') === 'matrix' && v.length === 20 && v[19] > 0);
108
- break;
109
- }
110
- case 'feComponentTransfer': {
111
- const fa = /<feFuncA\b([^>]*)\/?>/.exec(inner);
112
- b = input(a.in) && (!fa || zeroStays(attributesOf(fa[1])));
113
- break;
114
- }
115
- case 'feDisplacementMap':
116
- b = input(a.in);
117
- break;
118
- default:
119
- // Blur, offset, morphology, convolve: they spread their input (the padding covers it).
120
- b = input(a.in);
121
- }
122
- last = b;
123
- if (a.result) results.set(a.result, b);
124
- }
125
- return last;
126
- }
127
-
128
- /** @param {Record<string, string>} f - A feFuncA's attributes. @returns {boolean} Whether alpha 0 stays 0. */
129
- function zeroStays(f) {
130
- const type = f.type ?? 'identity';
131
- if (type === 'identity') return true;
132
- if (type === 'linear') return Number(f.intercept ?? 0) <= 0;
133
- if (type === 'gamma') return Number(f.offset ?? 0) <= 0;
134
- const first = Number((f.tableValues ?? '0').trim().split(/[\s,]+/)[0]);
135
- return first <= 0;
136
- }
@@ -1,5 +0,0 @@
1
- // A round sticker border.
2
- import type { RenderContext, Markup } from '../render/context.js';
3
-
4
- /** A disc under the layer, round what it paints (undefined where nothing can rasterise). */
5
- export function roundBorder(fx: { kind: 'border'; shape: 'circle'; width?: import('../index.js').Length; color?: string; line?: boolean | string; lineWidth?: import('../index.js').Length; cx?: import('../index.js').Length; cy?: import('../index.js').Length }, m: Markup, ctx: RenderContext): Promise<Markup | undefined>;
@@ -1,62 +0,0 @@
1
- /**
2
- * `border` with `shape: 'circle'`: a round sticker backing. The layer is
3
- * drawn small to find the circle that holds it (centred on what it paints,
4
- * out to its farthest pixel), then a disc `width` beyond that goes under it
5
- * (and a cut line outside, with `line`).
6
- *
7
- * @module effects/round-border
8
- */
9
-
10
- import { attrs, num } from '../svg.js';
11
- import { len } from '../geometry.js';
12
- import { toHex } from '../color.js';
13
- import { pixelsOf } from '../raster/index.js';
14
-
15
- /** The probe's longer side, px. */
16
- const PROBE = 320;
17
-
18
- /**
19
- * The round border, or undefined where nothing can rasterise (the
20
- * die-cut `border` filter is used instead).
21
- *
22
- * @param {any} fx - `{ kind: 'border', shape: 'circle', width, color, line, lineWidth, cx, cy }`.
23
- * @param {{ defs: string, body: string }} m
24
- * @param {import('../layers.js').RenderContext} ctx
25
- * @returns {Promise<{ defs: string, body: string } | undefined>}
26
- */
27
- export async function roundBorder(fx, m, ctx) {
28
- const ox = -ctx.width / 2;
29
- const oy = -ctx.height / 2;
30
- const uw = ctx.width * 2;
31
- const uh = ctx.height * 2;
32
- const s = PROBE / Math.max(uw, uh);
33
- const w = Math.max(1, Math.round(uw * s));
34
- const h = Math.max(1, Math.round(uh * s));
35
- const doc = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="${num(ox, 4)} ${num(oy, 4)} ${num(uw, 4)} ${num(uh, 4)}" preserveAspectRatio="none">${m.defs ? `<defs>${m.defs}</defs>` : ''}${m.body}</svg>`;
36
- let px;
37
- try {
38
- px = await pixelsOf(doc, w, h, /** @type {any} */ (ctx).sharp);
39
- } catch {
40
- px = undefined;
41
- }
42
- if (!px) return undefined;
43
- /** @type {[number, number][]} */
44
- const painted = [];
45
- let x0 = Infinity; let y0 = Infinity; let x1 = -Infinity; let y1 = -Infinity;
46
- for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
47
- if (px[(y * w + x) * 4 + 3] < 8) continue;
48
- painted.push([x, y]);
49
- x0 = Math.min(x0, x); x1 = Math.max(x1, x); y0 = Math.min(y0, y); y1 = Math.max(y1, y);
50
- }
51
- if (!painted.length) return m;
52
- const cx = fx.cx != null ? len(fx.cx, ctx.canvas) : ox + ((x0 + x1 + 1) / 2) / s;
53
- const cy = fx.cy != null ? len(fx.cy, ctx.canvas) : oy + ((y0 + y1 + 1) / 2) / s;
54
- // The farthest painted pixel's far corner.
55
- let reach = 0;
56
- for (const [x, y] of painted) reach = Math.max(reach, Math.hypot(ox + (x + 0.5) / s - cx, oy + (y + 0.5) / s - cy) + 0.71 / s);
57
- const r = reach + len(fx.width, ctx.canvas, '1.2vmin');
58
- let under = '';
59
- if (fx.line) under += `<circle${attrs({ cx, cy, r: r + len(fx.lineWidth, ctx.canvas, '0.25vmin'), fill: fx.line === true ? '#c9c6c0' : toHex(fx.line) })}/>`;
60
- under += `<circle${attrs({ cx, cy, r, fill: toHex(fx.color ?? '#ffffff') })}/>`;
61
- return { defs: m.defs, body: `${under}${m.body}` };
62
- }
@@ -1,14 +0,0 @@
1
- // Screens and signals.
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 `scanlines` effect's filter primitives. */
8
- export function scanlines(fx: Extract<Effect, { kind: 'scanlines' }>, ctx: RenderContext): string;
9
- /** `crt`, on pixels. */
10
- export function crt(img: Pixels, fx: Extract<Effect, { kind: 'crt' }>, ctx: object): Uint8ClampedArray;
11
- /** `vhs`, on pixels. */
12
- export function vhs(img: Pixels, fx: Extract<Effect, { kind: 'vhs' }>, ctx: object): Uint8ClampedArray;
13
- /** `jpeg`: through a real JPEG encoder, on pixels. */
14
- export function jpeg(img: Pixels, fx: Extract<Effect, { kind: 'jpeg' }>, ctx: object): Promise<PixelResult | Uint8ClampedArray>;