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