├── .gitignore
├── .prettierignore
├── LICENSE.md
├── README.md
├── index.html
├── jsconfig.json
├── package-lock.json
├── package.json
├── src
├── App.svelte
├── Generator.svelte
├── Progress.svelte
├── dither.js
├── main.js
├── prelude.js
├── query.js
├── recording.js
├── render.js
├── templates.js
└── vendor
│ ├── regl.2.1.0.js
│ ├── three.r124.js
│ └── three.r124.min.js
└── vite.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components
2 | node_modules
3 | *.log
4 | .DS_Store
5 | dist/
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | src/vendor/
2 | src/vendor/*.js
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2017 Matt DesLauriers
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
20 | OR OTHER DEALINGS IN THE SOFTWARE.
21 |
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # artblocks-renderer
2 |
3 | video renderer
4 |
5 | ## Usage
6 |
7 | [](https://www.npmjs.com/package/artblocks-renderer)
8 |
9 | ## License
10 |
11 | MIT, see [LICENSE.md](http://github.com/mattdesl/artblocks-renderer/blob/master/LICENSE.md) for details.
12 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | artblocks-renderer
7 |
8 |
9 |
13 |
19 |
28 |
29 |
30 |
35 |
50 |
51 |
57 |
62 |
63 |
64 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "node",
4 | "target": "esnext",
5 | "module": "esnext",
6 | /**
7 | * svelte-preprocess cannot figure out whether you have
8 | * a value or a type, so tell TypeScript to enforce using
9 | * `import type` instead of `import` for Types.
10 | */
11 | "importsNotUsedAsValues": "error",
12 | "isolatedModules": true,
13 | "resolveJsonModule": true,
14 | /**
15 | * To have warnings / errors of the Svelte compiler at the
16 | * correct position, enable source maps by default.
17 | */
18 | "sourceMap": true,
19 | "esModuleInterop": true,
20 | "skipLibCheck": true,
21 | "forceConsistentCasingInFileNames": true,
22 | "baseUrl": ".",
23 | /**
24 | * Typecheck JS in `.svelte` and `.js` files by default.
25 | * Disable this if you'd like to use dynamic types.
26 | */
27 | "checkJs": true
28 | },
29 | /**
30 | * Use global.d.ts instead of compilerOptions.types
31 | * to avoid limiting type declarations.
32 | */
33 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
34 | }
35 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "artblocks-renderer",
3 | "version": "1.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "artblocks-renderer",
9 | "version": "1.0.0",
10 | "license": "MIT",
11 | "dependencies": {
12 | "esbuild": "^0.12.9",
13 | "escape-html": "^1.0.3",
14 | "gifenc": "^1.0.3",
15 | "maxstache": "^1.0.7",
16 | "mp4-wasm": "^1.0.3",
17 | "node-fetch": "^2.6.1",
18 | "svelte-hmr": "^0.14.4"
19 | },
20 | "devDependencies": {
21 | "@sveltejs/vite-plugin-svelte": "^1.0.0-next.11",
22 | "svelte": "^3.37.0",
23 | "vite": "^2.3.8"
24 | }
25 | },
26 | "node_modules/@rollup/pluginutils": {
27 | "version": "4.1.0",
28 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.0.tgz",
29 | "integrity": "sha512-TrBhfJkFxA+ER+ew2U2/fHbebhLT/l/2pRk0hfj9KusXUuRXd2v0R58AfaZK9VXDQ4TogOSEmICVrQAA3zFnHQ==",
30 | "dev": true,
31 | "dependencies": {
32 | "estree-walker": "^2.0.1",
33 | "picomatch": "^2.2.2"
34 | },
35 | "engines": {
36 | "node": ">= 8.0.0"
37 | },
38 | "peerDependencies": {
39 | "rollup": "^1.20.0||^2.0.0"
40 | }
41 | },
42 | "node_modules/@sveltejs/vite-plugin-svelte": {
43 | "version": "1.0.0-next.11",
44 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.11.tgz",
45 | "integrity": "sha512-EYR1I145k5rflVqhPwk3442m3bkYimTKSHM9uO5KdomXzt+GS9ZSBJQE3/wy1Di9V8OnGa3oKpckI3OZsHkTIA==",
46 | "dev": true,
47 | "dependencies": {
48 | "@rollup/pluginutils": "^4.1.0",
49 | "chalk": "^4.1.1",
50 | "debug": "^4.3.2",
51 | "require-relative": "^0.8.7",
52 | "svelte-hmr": "^0.14.4"
53 | },
54 | "engines": {
55 | "node": "^12.20 || ^14.13.1 || >= 16"
56 | },
57 | "peerDependencies": {
58 | "svelte": "^3.38.2",
59 | "vite": "^2.3.7"
60 | }
61 | },
62 | "node_modules/ansi-styles": {
63 | "version": "4.3.0",
64 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
65 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
66 | "dev": true,
67 | "dependencies": {
68 | "color-convert": "^2.0.1"
69 | },
70 | "engines": {
71 | "node": ">=8"
72 | },
73 | "funding": {
74 | "url": "https://github.com/chalk/ansi-styles?sponsor=1"
75 | }
76 | },
77 | "node_modules/chalk": {
78 | "version": "4.1.1",
79 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
80 | "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
81 | "dev": true,
82 | "dependencies": {
83 | "ansi-styles": "^4.1.0",
84 | "supports-color": "^7.1.0"
85 | },
86 | "engines": {
87 | "node": ">=10"
88 | },
89 | "funding": {
90 | "url": "https://github.com/chalk/chalk?sponsor=1"
91 | }
92 | },
93 | "node_modules/color-convert": {
94 | "version": "2.0.1",
95 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
96 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
97 | "dev": true,
98 | "dependencies": {
99 | "color-name": "~1.1.4"
100 | },
101 | "engines": {
102 | "node": ">=7.0.0"
103 | }
104 | },
105 | "node_modules/color-name": {
106 | "version": "1.1.4",
107 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
108 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
109 | "dev": true
110 | },
111 | "node_modules/colorette": {
112 | "version": "1.2.2",
113 | "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
114 | "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
115 | "dev": true
116 | },
117 | "node_modules/debug": {
118 | "version": "4.3.2",
119 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
120 | "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
121 | "dev": true,
122 | "dependencies": {
123 | "ms": "2.1.2"
124 | },
125 | "engines": {
126 | "node": ">=6.0"
127 | },
128 | "peerDependenciesMeta": {
129 | "supports-color": {
130 | "optional": true
131 | }
132 | }
133 | },
134 | "node_modules/esbuild": {
135 | "version": "0.12.9",
136 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.9.tgz",
137 | "integrity": "sha512-MWRhAbMOJ9RJygCrt778rz/qNYgA4ZVj6aXnNPxFjs7PmIpb0fuB9Gmg5uWrr6n++XKwwm/RmSz6RR5JL2Ocsw==",
138 | "hasInstallScript": true,
139 | "bin": {
140 | "esbuild": "bin/esbuild"
141 | }
142 | },
143 | "node_modules/escape-html": {
144 | "version": "1.0.3",
145 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
146 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
147 | },
148 | "node_modules/estree-walker": {
149 | "version": "2.0.2",
150 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
151 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
152 | "dev": true
153 | },
154 | "node_modules/fsevents": {
155 | "version": "2.3.2",
156 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
157 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
158 | "dev": true,
159 | "hasInstallScript": true,
160 | "optional": true,
161 | "os": [
162 | "darwin"
163 | ],
164 | "engines": {
165 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
166 | }
167 | },
168 | "node_modules/function-bind": {
169 | "version": "1.1.1",
170 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
171 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
172 | "dev": true
173 | },
174 | "node_modules/gifenc": {
175 | "version": "1.0.3",
176 | "resolved": "https://registry.npmjs.org/gifenc/-/gifenc-1.0.3.tgz",
177 | "integrity": "sha512-xdr6AdrfGBcfzncONUOlXMBuc5wJDtOueE3c5rdG0oNgtINLD+f2iFZltrBRZYzACRbKr+mSVU/x98zv2u3jmw=="
178 | },
179 | "node_modules/has": {
180 | "version": "1.0.3",
181 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
182 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
183 | "dev": true,
184 | "dependencies": {
185 | "function-bind": "^1.1.1"
186 | },
187 | "engines": {
188 | "node": ">= 0.4.0"
189 | }
190 | },
191 | "node_modules/has-flag": {
192 | "version": "4.0.0",
193 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
194 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
195 | "dev": true,
196 | "engines": {
197 | "node": ">=8"
198 | }
199 | },
200 | "node_modules/is-core-module": {
201 | "version": "2.4.0",
202 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
203 | "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
204 | "dev": true,
205 | "dependencies": {
206 | "has": "^1.0.3"
207 | },
208 | "funding": {
209 | "url": "https://github.com/sponsors/ljharb"
210 | }
211 | },
212 | "node_modules/maxstache": {
213 | "version": "1.0.7",
214 | "resolved": "https://registry.npmjs.org/maxstache/-/maxstache-1.0.7.tgz",
215 | "integrity": "sha1-IjHVGAung9Xs/DHEX+2seuQnaYQ="
216 | },
217 | "node_modules/mp4-wasm": {
218 | "version": "1.0.3",
219 | "resolved": "https://registry.npmjs.org/mp4-wasm/-/mp4-wasm-1.0.3.tgz",
220 | "integrity": "sha512-2h3iX7xmVch9j71YrbtSFRXr3EZtCSSpb/DNp3dQ3/X8lpnBVz4VIIKCc5KD+hF0EFGquFcAR4WeiuKGwcGGtg=="
221 | },
222 | "node_modules/ms": {
223 | "version": "2.1.2",
224 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
225 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
226 | "dev": true
227 | },
228 | "node_modules/nanoid": {
229 | "version": "3.1.23",
230 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
231 | "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
232 | "dev": true,
233 | "bin": {
234 | "nanoid": "bin/nanoid.cjs"
235 | },
236 | "engines": {
237 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
238 | }
239 | },
240 | "node_modules/node-fetch": {
241 | "version": "2.6.1",
242 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
243 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
244 | "engines": {
245 | "node": "4.x || >=6.0.0"
246 | }
247 | },
248 | "node_modules/path-parse": {
249 | "version": "1.0.7",
250 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
251 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
252 | "dev": true
253 | },
254 | "node_modules/picomatch": {
255 | "version": "2.3.0",
256 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
257 | "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
258 | "dev": true,
259 | "engines": {
260 | "node": ">=8.6"
261 | },
262 | "funding": {
263 | "url": "https://github.com/sponsors/jonschlinkert"
264 | }
265 | },
266 | "node_modules/postcss": {
267 | "version": "8.3.5",
268 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz",
269 | "integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==",
270 | "dev": true,
271 | "dependencies": {
272 | "colorette": "^1.2.2",
273 | "nanoid": "^3.1.23",
274 | "source-map-js": "^0.6.2"
275 | },
276 | "engines": {
277 | "node": "^10 || ^12 || >=14"
278 | },
279 | "funding": {
280 | "type": "opencollective",
281 | "url": "https://opencollective.com/postcss/"
282 | }
283 | },
284 | "node_modules/require-relative": {
285 | "version": "0.8.7",
286 | "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
287 | "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
288 | "dev": true
289 | },
290 | "node_modules/resolve": {
291 | "version": "1.20.0",
292 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
293 | "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
294 | "dev": true,
295 | "dependencies": {
296 | "is-core-module": "^2.2.0",
297 | "path-parse": "^1.0.6"
298 | },
299 | "funding": {
300 | "url": "https://github.com/sponsors/ljharb"
301 | }
302 | },
303 | "node_modules/rollup": {
304 | "version": "2.52.2",
305 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz",
306 | "integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==",
307 | "dev": true,
308 | "bin": {
309 | "rollup": "dist/bin/rollup"
310 | },
311 | "engines": {
312 | "node": ">=10.0.0"
313 | },
314 | "optionalDependencies": {
315 | "fsevents": "~2.3.2"
316 | }
317 | },
318 | "node_modules/source-map-js": {
319 | "version": "0.6.2",
320 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz",
321 | "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==",
322 | "dev": true,
323 | "engines": {
324 | "node": ">=0.10.0"
325 | }
326 | },
327 | "node_modules/supports-color": {
328 | "version": "7.2.0",
329 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
330 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
331 | "dev": true,
332 | "dependencies": {
333 | "has-flag": "^4.0.0"
334 | },
335 | "engines": {
336 | "node": ">=8"
337 | }
338 | },
339 | "node_modules/svelte": {
340 | "version": "3.38.3",
341 | "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.38.3.tgz",
342 | "integrity": "sha512-N7bBZJH0iF24wsalFZF+fVYMUOigaAUQMIcEKHO3jstK/iL8VmP9xE+P0/a76+FkNcWt+TDv2Gx1taUoUscrvw==",
343 | "engines": {
344 | "node": ">= 8"
345 | }
346 | },
347 | "node_modules/svelte-hmr": {
348 | "version": "0.14.4",
349 | "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.4.tgz",
350 | "integrity": "sha512-kItFF7vqzStckSigoFmMnxJpTOdB9TWnQAW6Js+yAB4277tLbJIIE5KBlGHNmJNpA7MguqidsPB27Uw5UzQPCA==",
351 | "peerDependencies": {
352 | "svelte": ">=3.19.0"
353 | }
354 | },
355 | "node_modules/vite": {
356 | "version": "2.3.8",
357 | "resolved": "https://registry.npmjs.org/vite/-/vite-2.3.8.tgz",
358 | "integrity": "sha512-QiEx+iqNnJntSgSF2fWRQvRey9pORIrtNJzNyBJXwc+BdzWs83FQolX84cTBo393cfhObrtWa6180dAa4NLDiQ==",
359 | "dev": true,
360 | "dependencies": {
361 | "esbuild": "^0.12.8",
362 | "postcss": "^8.3.4",
363 | "resolve": "^1.20.0",
364 | "rollup": "^2.38.5"
365 | },
366 | "bin": {
367 | "vite": "bin/vite.js"
368 | },
369 | "engines": {
370 | "node": ">=12.0.0"
371 | },
372 | "optionalDependencies": {
373 | "fsevents": "~2.3.2"
374 | }
375 | }
376 | },
377 | "dependencies": {
378 | "@rollup/pluginutils": {
379 | "version": "4.1.0",
380 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.0.tgz",
381 | "integrity": "sha512-TrBhfJkFxA+ER+ew2U2/fHbebhLT/l/2pRk0hfj9KusXUuRXd2v0R58AfaZK9VXDQ4TogOSEmICVrQAA3zFnHQ==",
382 | "dev": true,
383 | "requires": {
384 | "estree-walker": "^2.0.1",
385 | "picomatch": "^2.2.2"
386 | }
387 | },
388 | "@sveltejs/vite-plugin-svelte": {
389 | "version": "1.0.0-next.11",
390 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.11.tgz",
391 | "integrity": "sha512-EYR1I145k5rflVqhPwk3442m3bkYimTKSHM9uO5KdomXzt+GS9ZSBJQE3/wy1Di9V8OnGa3oKpckI3OZsHkTIA==",
392 | "dev": true,
393 | "requires": {
394 | "@rollup/pluginutils": "^4.1.0",
395 | "chalk": "^4.1.1",
396 | "debug": "^4.3.2",
397 | "require-relative": "^0.8.7",
398 | "svelte-hmr": "^0.14.4"
399 | }
400 | },
401 | "ansi-styles": {
402 | "version": "4.3.0",
403 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
404 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
405 | "dev": true,
406 | "requires": {
407 | "color-convert": "^2.0.1"
408 | }
409 | },
410 | "chalk": {
411 | "version": "4.1.1",
412 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
413 | "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
414 | "dev": true,
415 | "requires": {
416 | "ansi-styles": "^4.1.0",
417 | "supports-color": "^7.1.0"
418 | }
419 | },
420 | "color-convert": {
421 | "version": "2.0.1",
422 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
423 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
424 | "dev": true,
425 | "requires": {
426 | "color-name": "~1.1.4"
427 | }
428 | },
429 | "color-name": {
430 | "version": "1.1.4",
431 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
432 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
433 | "dev": true
434 | },
435 | "colorette": {
436 | "version": "1.2.2",
437 | "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
438 | "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
439 | "dev": true
440 | },
441 | "debug": {
442 | "version": "4.3.2",
443 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
444 | "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
445 | "dev": true,
446 | "requires": {
447 | "ms": "2.1.2"
448 | }
449 | },
450 | "esbuild": {
451 | "version": "0.12.9",
452 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.9.tgz",
453 | "integrity": "sha512-MWRhAbMOJ9RJygCrt778rz/qNYgA4ZVj6aXnNPxFjs7PmIpb0fuB9Gmg5uWrr6n++XKwwm/RmSz6RR5JL2Ocsw=="
454 | },
455 | "escape-html": {
456 | "version": "1.0.3",
457 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
458 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
459 | },
460 | "estree-walker": {
461 | "version": "2.0.2",
462 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
463 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
464 | "dev": true
465 | },
466 | "fsevents": {
467 | "version": "2.3.2",
468 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
469 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
470 | "dev": true,
471 | "optional": true
472 | },
473 | "function-bind": {
474 | "version": "1.1.1",
475 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
476 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
477 | "dev": true
478 | },
479 | "gifenc": {
480 | "version": "1.0.3",
481 | "resolved": "https://registry.npmjs.org/gifenc/-/gifenc-1.0.3.tgz",
482 | "integrity": "sha512-xdr6AdrfGBcfzncONUOlXMBuc5wJDtOueE3c5rdG0oNgtINLD+f2iFZltrBRZYzACRbKr+mSVU/x98zv2u3jmw=="
483 | },
484 | "has": {
485 | "version": "1.0.3",
486 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
487 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
488 | "dev": true,
489 | "requires": {
490 | "function-bind": "^1.1.1"
491 | }
492 | },
493 | "has-flag": {
494 | "version": "4.0.0",
495 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
496 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
497 | "dev": true
498 | },
499 | "is-core-module": {
500 | "version": "2.4.0",
501 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
502 | "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
503 | "dev": true,
504 | "requires": {
505 | "has": "^1.0.3"
506 | }
507 | },
508 | "maxstache": {
509 | "version": "1.0.7",
510 | "resolved": "https://registry.npmjs.org/maxstache/-/maxstache-1.0.7.tgz",
511 | "integrity": "sha1-IjHVGAung9Xs/DHEX+2seuQnaYQ="
512 | },
513 | "mp4-wasm": {
514 | "version": "1.0.3",
515 | "resolved": "https://registry.npmjs.org/mp4-wasm/-/mp4-wasm-1.0.3.tgz",
516 | "integrity": "sha512-2h3iX7xmVch9j71YrbtSFRXr3EZtCSSpb/DNp3dQ3/X8lpnBVz4VIIKCc5KD+hF0EFGquFcAR4WeiuKGwcGGtg=="
517 | },
518 | "ms": {
519 | "version": "2.1.2",
520 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
521 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
522 | "dev": true
523 | },
524 | "nanoid": {
525 | "version": "3.1.23",
526 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
527 | "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
528 | "dev": true
529 | },
530 | "node-fetch": {
531 | "version": "2.6.1",
532 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
533 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
534 | },
535 | "path-parse": {
536 | "version": "1.0.7",
537 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
538 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
539 | "dev": true
540 | },
541 | "picomatch": {
542 | "version": "2.3.0",
543 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
544 | "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
545 | "dev": true
546 | },
547 | "postcss": {
548 | "version": "8.3.5",
549 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz",
550 | "integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==",
551 | "dev": true,
552 | "requires": {
553 | "colorette": "^1.2.2",
554 | "nanoid": "^3.1.23",
555 | "source-map-js": "^0.6.2"
556 | }
557 | },
558 | "require-relative": {
559 | "version": "0.8.7",
560 | "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
561 | "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
562 | "dev": true
563 | },
564 | "resolve": {
565 | "version": "1.20.0",
566 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
567 | "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
568 | "dev": true,
569 | "requires": {
570 | "is-core-module": "^2.2.0",
571 | "path-parse": "^1.0.6"
572 | }
573 | },
574 | "rollup": {
575 | "version": "2.52.2",
576 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz",
577 | "integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==",
578 | "dev": true,
579 | "requires": {
580 | "fsevents": "~2.3.2"
581 | }
582 | },
583 | "source-map-js": {
584 | "version": "0.6.2",
585 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz",
586 | "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==",
587 | "dev": true
588 | },
589 | "supports-color": {
590 | "version": "7.2.0",
591 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
592 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
593 | "dev": true,
594 | "requires": {
595 | "has-flag": "^4.0.0"
596 | }
597 | },
598 | "svelte": {
599 | "version": "3.38.3",
600 | "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.38.3.tgz",
601 | "integrity": "sha512-N7bBZJH0iF24wsalFZF+fVYMUOigaAUQMIcEKHO3jstK/iL8VmP9xE+P0/a76+FkNcWt+TDv2Gx1taUoUscrvw=="
602 | },
603 | "svelte-hmr": {
604 | "version": "0.14.4",
605 | "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.4.tgz",
606 | "integrity": "sha512-kItFF7vqzStckSigoFmMnxJpTOdB9TWnQAW6Js+yAB4277tLbJIIE5KBlGHNmJNpA7MguqidsPB27Uw5UzQPCA==",
607 | "requires": {}
608 | },
609 | "vite": {
610 | "version": "2.3.8",
611 | "resolved": "https://registry.npmjs.org/vite/-/vite-2.3.8.tgz",
612 | "integrity": "sha512-QiEx+iqNnJntSgSF2fWRQvRey9pORIrtNJzNyBJXwc+BdzWs83FQolX84cTBo393cfhObrtWa6180dAa4NLDiQ==",
613 | "dev": true,
614 | "requires": {
615 | "esbuild": "^0.12.8",
616 | "fsevents": "~2.3.2",
617 | "postcss": "^8.3.4",
618 | "resolve": "^1.20.0",
619 | "rollup": "^2.38.5"
620 | }
621 | }
622 | }
623 | }
624 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "artblocks-renderer",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "license": "MIT",
7 | "author": {
8 | "name": "Matt DesLauriers",
9 | "email": "dave.des@gmail.com",
10 | "url": "https://github.com/mattdesl"
11 | },
12 | "dependencies": {
13 | "esbuild": "^0.12.9",
14 | "escape-html": "^1.0.3",
15 | "gifenc": "^1.0.3",
16 | "maxstache": "^1.0.7",
17 | "mp4-wasm": "^1.0.3",
18 | "node-fetch": "^2.6.1",
19 | "svelte-hmr": "^0.14.4"
20 | },
21 | "type": "module",
22 | "scripts": {
23 | "dev": "vite",
24 | "build": "vite build",
25 | "serve": "vite preview"
26 | },
27 | "devDependencies": {
28 | "@sveltejs/vite-plugin-svelte": "^1.0.0-next.11",
29 | "svelte": "^3.37.0",
30 | "vite": "^2.3.8"
31 | },
32 | "keywords": [],
33 | "repository": {
34 | "type": "git",
35 | "url": "git://github.com/mattdesl/artblocks-renderer.git"
36 | },
37 | "homepage": "https://github.com/mattdesl/artblocks-renderer",
38 | "bugs": {
39 | "url": "https://github.com/mattdesl/artblocks-renderer/issues"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/App.svelte:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
ArtBlocks Recorder
38 |
39 | Enter your configuration and click the Render button to export
40 | the high quality media.
41 |
42 |
43 | Made by
44 | @mattdesl .
45 |
46 |
47 | {#if rendering}
48 |
49 |
Cancel
50 | {#if format !== "png" && format !== 'inline'}
51 | {:else}
52 |
Rendering...
53 | {/if}
54 |
55 | {
57 | progress = detail;
58 | }}
59 | on:finish={stopRender}
60 | {fps}
61 | {dithering}
62 | totalFrames={format === "png" ? 1 : totalFrames}
63 | {width}
64 | {height}
65 | {format}
66 | {id}
67 | />
68 | {:else}
69 |
162 | {/if}
163 |
164 |
165 |
305 |
--------------------------------------------------------------------------------
/src/Generator.svelte:
--------------------------------------------------------------------------------
1 |
219 |
220 |
225 | {#await promise}
226 |
loading...
227 | {:then html}
228 | {#if html}
229 | {#if format !== 'inline'}
230 |
231 | {/if}
232 |
240 | {/if}
241 | {/await}
242 |
243 |
244 |
265 |
--------------------------------------------------------------------------------
/src/Progress.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
12 |
13 |
31 |
--------------------------------------------------------------------------------
/src/dither.js:
--------------------------------------------------------------------------------
1 | const clamp = (value, min, max) => Math.max(Math.min(value, max), min);
2 |
3 | export default function floydSteinberg(pixels, width, height, palette) {
4 | const data = new Uint8ClampedArray(pixels);
5 | const format = "rgb565";
6 | const bincount = format === "rgb444" ? 4096 : 65536;
7 | const cache = new Array(bincount);
8 | const error = [0, 0, 0];
9 | step(true);
10 | step(false);
11 | return data;
12 |
13 | function step(randomly) {
14 | for (let y = 0; y < height; y++) {
15 | let leftToRight = y % 2 === 0;
16 | for (let ox = 0; ox < width; ox++) {
17 | // const leftToRight = true;
18 | // const leftToRight = Math.random() > 0.5;
19 | if (randomly) leftToRight = Math.random() > 0.5;
20 | const x = leftToRight ? ox : width - ox - 1;
21 |
22 | const index = toIndex(x, y, width, height);
23 |
24 | // get the old color
25 | const oldR = data[index + 0];
26 | const oldG = data[index + 1];
27 | const oldB = data[index + 2];
28 | // reduce to our palette
29 | const key = rgb888_to_rgb565(oldR, oldG, oldB);
30 | const idx =
31 | key in cache
32 | ? cache[key]
33 | : (cache[key] = nearestColorIndexRGB(oldR, oldG, oldB, palette));
34 | const newRGB = palette[idx];
35 | // now we have a new RGB color for this pixel
36 | const newR = newRGB[0];
37 | const newG = newRGB[1];
38 | const newB = newRGB[2];
39 | error[0] = oldR - newR;
40 | error[1] = oldG - newG;
41 | error[2] = oldB - newB;
42 |
43 | const down = toIndex(x, y + 1, width, height);
44 | const rightDown = toIndex(x + 1, y + 1, width, height);
45 | const right = toIndex(x + 1, y, width, height);
46 | const left = toIndex(x - 1, y, width, height);
47 | const rightUp = toIndex(x + 1, y - 1, width, height);
48 | const leftDown = toIndex(x - 1, y + 1, width, height);
49 |
50 | data[index] = newR;
51 | data[index + 1] = newG;
52 | data[index + 2] = newB;
53 | data[index + 3] = 0xff;
54 |
55 | for (let c = 0; c < 3; c++) {
56 | if (leftToRight) {
57 | if (x >= 0 && y < height - 1)
58 | data[down + c] = data[down + c] + (error[c] * 5) / 16;
59 | if (x < width - 1 && y < height - 1)
60 | data[rightDown + c] = data[rightDown + c] + (error[c] * 1) / 16;
61 | if (y >= 0 && x < width - 1)
62 | data[right + c] = data[right + c] + (error[c] * 7) / 16;
63 | if (x > 0 && y < height - 1)
64 | data[leftDown + c] = data[leftDown + c] + (error[c] * 3) / 16;
65 | } else {
66 | if (x >= 0 && y < height - 1)
67 | data[down + c] = data[down + c] + (error[c] * 5) / 16;
68 | if (x > 0 && y < height - 1)
69 | data[leftDown + c] = data[leftDown + c] + (error[c] * 1) / 16;
70 | if (y >= 0 && x > 0)
71 | data[left + c] = data[left + c] + (error[c] * 7) / 16;
72 | if (x < width - 1 && y < height - 1)
73 | data[rightDown + c] = data[rightDown + c] + (error[c] * 3) / 16;
74 | }
75 | }
76 | }
77 | }
78 | }
79 | }
80 |
81 | function rgb888_to_rgb565(r, g, b) {
82 | return ((r << 8) & 0xf800) | ((g << 2) & 0x03e0) | (b >> 3);
83 | }
84 |
85 | function nearestColorIndexRGB(r, g, b, palette) {
86 | let k = 0;
87 | let mindist = 1e100;
88 | for (let i = 0; i < palette.length; i++) {
89 | const px2 = palette[i];
90 | const r2 = px2[0];
91 | let curdist = sqr(r2 - r);
92 | if (curdist > mindist) continue;
93 | const g2 = px2[1];
94 | curdist += sqr(g2 - g);
95 | if (curdist > mindist) continue;
96 | const b2 = px2[2];
97 | curdist += sqr(b2 - b);
98 | if (curdist > mindist) continue;
99 | mindist = curdist;
100 | k = i;
101 | }
102 | return k;
103 | }
104 |
105 | function toIndex(x, y, width, height) {
106 | x = Math.floor(x);
107 | y = Math.floor(y);
108 | x = clamp(x, 0, width - 1);
109 | y = clamp(y, 0, height - 1);
110 | return (x + y * width) * 4;
111 | }
112 |
113 | function sqr(a) {
114 | return a * a;
115 | }
116 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import App from "./App.svelte";
2 |
3 | const app = new App({
4 | target: document.body,
5 | });
6 |
7 | export default app;
8 |
--------------------------------------------------------------------------------
/src/prelude.js:
--------------------------------------------------------------------------------
1 | export default function getPrelude(opts = {}) {
2 | const {
3 | inline = false,
4 | width = 512,
5 | height = 512,
6 | fps = 30,
7 | totalFrames = 30,
8 | clickOnStart = false,
9 | } = opts;
10 | const config = {
11 | width,
12 | height,
13 | fps,
14 | totalFrames,
15 | inline,
16 | clickOnStart,
17 | };
18 | return /*js*/ `
19 |
148 | `;
149 | }
150 |
151 | function getTimeLib() {
152 | return /*js*/ `;window.__timelib__ = (function () {
153 | if (!("performance" in window)) {
154 | window.performance = {};
155 | }
156 | Date.now =
157 | Date.now ||
158 | function () {
159 | // thanks IE8
160 | return new Date().getTime();
161 | };
162 | if (!("now" in window.performance)) {
163 | var nowOffset = Date.now();
164 | if (performance.timing && performance.timing.navigationStart) {
165 | nowOffset = performance.timing.navigationStart;
166 | }
167 | window.performance.now = function now() {
168 | return Date.now() - nowOffset;
169 | };
170 | }
171 |
172 | let sessionStartTime = null;
173 | const startTimeOffset = 0;
174 |
175 | const _oldSetTimeout = window.setTimeout,
176 | _oldSetInterval = window.setInterval,
177 | _oldClearInterval = window.clearInterval,
178 | _oldClearTimeout = window.clearTimeout,
179 | _oldRequestAnimationFrame = window.requestAnimationFrame,
180 | _oldCancelAnimationFrame = window.cancelAnimationFrame,
181 | _oldNow = window.Date.now,
182 | _oldPerformanceNow = window.performance.now,
183 | _oldGetTime = window.Date.prototype.getTime;
184 |
185 | let startTime, time, performanceStartTime, performanceTime;
186 |
187 | let timeouts = [];
188 | let intervals = [];
189 | let requestAnimationFrameCallbacks = [];
190 | let elapsed = 0;
191 | let _attached = false;
192 |
193 | function attach() {
194 | if (_attached) return;
195 | timeouts.length = 0;
196 | elapsed = 0;
197 | _attached = true;
198 | if (sessionStartTime == null) {
199 | sessionStartTime = window.Date.now();
200 | }
201 | startTime = window.Date.now();
202 | time = startTime + startTimeOffset;
203 | performanceStartTime = window.performance.now();
204 | performanceTime = performanceStartTime + startTimeOffset;
205 | requestAnimationFrameCallbacks = [];
206 | timeouts = [];
207 | intervals = [];
208 |
209 | window.Date.prototype.getTime = function () {
210 | return time;
211 | };
212 | window.Date.now = function () {
213 | return time;
214 | };
215 |
216 | window.setTimeout = function (callback, delay) {
217 | var t = {
218 | callback: callback,
219 | triggerTime: time + delay,
220 | };
221 | timeouts.push(t);
222 | return t;
223 | };
224 | window.clearTimeout = function (id) {
225 | for (var j = 0; j < timeouts.length; j++) {
226 | if (timeouts[j] == id) {
227 | timeouts.splice(j, 1);
228 | }
229 | }
230 | };
231 | window.setInterval = function (callback, delay) {
232 | var t = {
233 | callback: callback,
234 | delay,
235 | triggerTime: time + delay,
236 | };
237 | intervals.push(t);
238 | return t;
239 | };
240 | window.clearInterval = function (id) {
241 | for (var j = 0; j < intervals.length; j++) {
242 | if (intervals[j] == id) {
243 | intervals.splice(j, 1);
244 | }
245 | }
246 | };
247 | window.requestAnimationFrame = function (callback) {
248 | const cb = { callback };
249 | requestAnimationFrameCallbacks.push(cb);
250 | return cb;
251 | };
252 | window.cancelAnimationFrame = function (id) {
253 | for (var j = 0; j < requestAnimationFrameCallbacks.length; j++) {
254 | if (requestAnimationFrameCallbacks[j] == id) {
255 | requestAnimationFrameCallbacks.splice(j, 1);
256 | }
257 | }
258 | };
259 | window.performance.now = function () {
260 | return performanceTime;
261 | };
262 | }
263 |
264 | function step(dt = 0) {
265 | time += dt;
266 | performanceTime += dt;
267 | elapsed += dt;
268 |
269 | for (let j = 0; j < timeouts.length; j++) {
270 | if (time >= timeouts[j].triggerTime) {
271 | timeouts[j].callback();
272 | timeouts.splice(j, 1);
273 | }
274 | }
275 |
276 | for (let j = 0; j < intervals.length; j++) {
277 | if (time >= intervals[j].triggerTime) {
278 | intervals[j].callback();
279 | intervals[j].triggerTime += intervals[j].delay;
280 | }
281 | }
282 |
283 | const rafs = [...requestAnimationFrameCallbacks];
284 | requestAnimationFrameCallbacks = [];
285 | rafs.forEach(({ callback }) => {
286 | callback(time - sessionStartTime);
287 | });
288 | }
289 |
290 | function sleep(delay = 0) {
291 | return new Promise((resolve) => _oldSetTimeout.call(window, resolve, delay));
292 | }
293 |
294 | function detach() {
295 | if (!_attached) return;
296 | timeouts.length = 0;
297 | _attached = false;
298 | window.setTimeout = _oldSetTimeout;
299 | window.setInterval = _oldSetInterval;
300 | window.clearInterval = _oldClearInterval;
301 | window.clearTimeout = _oldClearTimeout;
302 | window.requestAnimationFrame = _oldRequestAnimationFrame;
303 | window.cancelAnimationFrame = _oldCancelAnimationFrame;
304 | window.Date.prototype.getTime = _oldGetTime;
305 | window.Date.now = _oldNow;
306 | window.performance.now = _oldPerformanceNow;
307 | }
308 |
309 | function getElapsedTime() {
310 | return elapsed;
311 | }
312 |
313 | return { getElapsedTime, attach, detach, step, sleep };
314 | })();`;
315 | }
316 |
--------------------------------------------------------------------------------
/src/query.js:
--------------------------------------------------------------------------------
1 | // Thanks to @r4v3n and artblocks-gallery
2 | // https://github.com/r4v3n-art/art-blocks-gallery
3 |
4 | const PROJECT_EXPLORER =
5 | "https://api.thegraph.com/subgraphs/name/artblocks/art-blocks";
6 |
7 | const contracts = [
8 | "0x99a9b7c1116f9ceeb1652de04d5969cce509b069",
9 | "0x059edd72cd353df5106d2b9cc5ab83a52287ac3a",
10 | "0xa7d8d9ef8d8ce8992df33d8b8cf4aebabd5bd270",
11 | ];
12 |
13 | const contract_in = JSON.stringify(contracts);
14 |
15 | // Utility to query from subgraph
16 | async function query(url, query) {
17 | const response = await fetch(url, {
18 | method: "post",
19 | // mode: "cors", // no-cors, *cors, same-origin
20 | // cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
21 | // credentials: "same-origin", // include, *same-origin, omit
22 | headers: {
23 | "Content-Type": "application/json",
24 | },
25 | body: JSON.stringify({ query }),
26 | });
27 | const result = await response.json();
28 | if (result.errors) throw new Error(result.errors[0].message);
29 | return result.data;
30 | }
31 |
32 | // Gets all AB tokens held by owner address
33 | async function fetchTokensByOwner(ownerAddress, opt = {}) {
34 | const { limit = 100 } = opt;
35 | const lastId = opt.lastId || opt.lastId === 0 ? opt.lastId : "-1";
36 | ownerAddress = ownerAddress.toLowerCase();
37 | const { tokens } = await query(
38 | PROJECT_EXPLORER,
39 | `{
40 | tokens(first: ${limit}, orderBy: tokenId, orderDirection: asc, where: {id_gt: "${lastId}", owner: "${ownerAddress}", contract_in: ${contract_in} }) {
41 | tokenId
42 | project {
43 | tokenId
44 | }
45 | }
46 | }`
47 | );
48 | return tokens;
49 | }
50 |
51 | async function fetchTokensByProject(projectId, opt = {}) {
52 | const { limit = 100 } = opt;
53 | const lastId = opt.lastId || opt.lastId === 0 ? opt.lastId : "-1";
54 | const { tokens } = await query(
55 | PROJECT_EXPLORER,
56 | `{
57 | tokens(first: ${limit}, orderBy: tokenId, orderDirection: asc, where: {id_gt: "${lastId}", project: "${projectId}", contract_in: ${contract_in} }) {
58 | tokenId
59 | project {
60 | tokenId
61 | name
62 | }
63 | }
64 | }`
65 | );
66 | return tokens;
67 | }
68 |
69 | // Gets AB contracts currently in use and the nextProjectId
70 | async function fetchPlatform() {
71 | const { contracts } = await query(
72 | PROJECT_EXPLORER,
73 | `{
74 | contracts {
75 | id
76 | nextProjectId
77 | }
78 | }`
79 | );
80 | const nextProjectId = contracts.reduce(
81 | (max, c) => Math.max(max, parseInt(c.nextProjectId, 10)),
82 | 0
83 | );
84 | return {
85 | nextProjectId: String(nextProjectId),
86 | addresses: contracts.map((c) => c.id),
87 | };
88 | }
89 |
90 | // Gets details about a specific project ID
91 | async function fetchProject(id) {
92 | const { projects } = await query(
93 | PROJECT_EXPLORER,
94 | `{
95 | projects(where: { projectId: "${id}", contract_in: ${contract_in} }) {
96 | projectId
97 | name
98 | aspectRatio
99 | script
100 | scriptTypeAndVersion
101 | scriptJSON
102 | website
103 | artistName
104 | description
105 | license
106 | invocations
107 | maxInvocations
108 | paused
109 | currencySymbol
110 | pricePerTokenInWei
111 | script
112 | }
113 | }
114 | `
115 | );
116 | if (!projects || projects.length === 0) return null;
117 | const result = projects[0];
118 | if (result) {
119 | result.id = result.projectId;
120 | if (result.scriptJSON != null) {
121 | if (typeof result.scriptJSON === "string") {
122 | result.scriptJSON = JSON.parse(result.scriptJSON);
123 | }
124 | if ("aspectRatio" in result.scriptJSON) {
125 | result.aspectRatio = parseFloat(result.scriptJSON.aspectRatio);
126 | }
127 | result.scriptType = result.scriptJSON.type;
128 | result.scriptVersion = result.scriptJSON.version;
129 | }
130 |
131 | result.aspectRatio = parseFloat(result.aspectRatio) || 0;
132 | if (result.scriptTypeAndVersion) {
133 | const parts = result.scriptTypeAndVersion.split("@");
134 | const type = parts[0];
135 | let version = "n/a";
136 | if (parts.length > 1) version = parts[1];
137 | result.scriptType = type;
138 | result.scriptVersion = version;
139 | }
140 |
141 | if (!result.scriptJSON) result.scriptJSON = {};
142 | if (!result.scriptJSON.type) result.scriptJSON.type = result.type;
143 | if (!result.scriptJSON.aspectRatio)
144 | result.scriptJSON.aspectRatio = result.aspectRatio;
145 |
146 | result.maxInvocations = parseInt(result.maxInvocations, 10);
147 | result.pricePerTokenInWei = parseInt(result.pricePerTokenInWei, 10);
148 | result.invocations = parseInt(result.invocations, 10);
149 | }
150 | return result;
151 | }
152 |
153 | async function fetchToken(id) {
154 | const { tokens } = await query(
155 | PROJECT_EXPLORER,
156 | `{
157 | tokens(where: { tokenId: "${id}", contract_in: ${contract_in} }) {
158 | tokenId
159 | hash
160 | }
161 | }
162 | `
163 | );
164 | if (!tokens || tokens.length === 0) return null;
165 | if (tokens.length > 1)
166 | throw new Error("Received multiple tokens for id: " + id);
167 | const t = tokens[0];
168 | return {
169 | id: t.tokenId,
170 | // owner: t.owner.id,
171 | hash: t.hash,
172 | };
173 | }
174 |
175 | export {
176 | fetchProject,
177 | fetchPlatform,
178 | fetchTokensByProject,
179 | fetchTokensByOwner,
180 | fetchToken,
181 | query,
182 | };
183 |
--------------------------------------------------------------------------------
/src/recording.js:
--------------------------------------------------------------------------------
1 | import { GIFEncoder, quantize, applyPalette } from "gifenc";
2 | import floydSteinberg from "./dither.js";
3 |
4 | let tmpCanvas = document.createElement("canvas");
5 | let tmpContext = tmpCanvas.getContext("2d");
6 |
7 | function detectWebM() {
8 | var elem = document.createElement("canvas");
9 | if (!!(elem.getContext && elem.getContext("2d"))) {
10 | // was able or not to get WebP representation
11 | return elem.toDataURL("image/webp").indexOf("data:image/webp") == 0;
12 | } else {
13 | // very old browser like IE 8, canvas not supported
14 | return false;
15 | }
16 | }
17 |
18 | let _isWebM = detectWebM();
19 |
20 | export function isWebMSupported() {
21 | return _isWebM;
22 | }
23 |
24 | export function isFrameSequenceSupported() {
25 | return typeof window.showDirectoryPicker === "function";
26 | }
27 |
28 | export function downloadBlob(buf, filename, type) {
29 | const blob = buf instanceof Blob ? buf : new Blob([buf], { type });
30 | const url = URL.createObjectURL(blob);
31 | const anchor = document.createElement("a");
32 | anchor.href = url;
33 | anchor.download = filename;
34 | anchor.click();
35 | }
36 |
37 | export async function createMP4Encoder(opts = {}) {
38 | const { width, height, mp4, fps = 30 } = opts;
39 | const { loadMP4Module } = await window.MP4Encoder;
40 | const MP4 = await loadMP4Module();
41 | const encoder = MP4.createWebCodecsEncoder({
42 | width,
43 | height,
44 | fps,
45 | codec: "avc1.420034",
46 | encoderOptions: {
47 | // bitrate: 180_000_000,
48 | // bitrateMode: "constant",
49 | // framerate: fps,
50 | // displayWidth: width,
51 | // displayHeight: height,
52 | // hardwareAcceleration: "prefer-software",
53 | // framerate: fps,
54 | },
55 | });
56 | return {
57 | type: "video/mp4",
58 | extension: ".mp4",
59 | async encode(bitmap) {
60 | await encoder.addFrame(bitmap);
61 | },
62 | async finish() {
63 | const buf = await encoder.end();
64 | return buf;
65 | },
66 | };
67 | }
68 |
69 | function getBitmapRGBA(bitmap, width = bitmap.width, height = bitmap.height) {
70 | tmpCanvas.width = width;
71 | tmpCanvas.height = height;
72 | tmpContext.clearRect(0, 0, width, height);
73 | tmpContext.drawImage(bitmap, 0, 0, width, height);
74 | return tmpContext.getImageData(0, 0, width, height).data;
75 | }
76 |
77 | function getBitmapBlob(
78 | bitmap,
79 | width = bitmap.width,
80 | height = bitmap.height,
81 | type = "image/png",
82 | encoderOptions = 0.9
83 | ) {
84 | tmpCanvas.width = width;
85 | tmpCanvas.height = height;
86 | tmpContext.clearRect(0, 0, width, height);
87 | tmpContext.drawImage(bitmap, 0, 0, width, height);
88 | const url = tmpCanvas.toDataURL(type, encoderOptions);
89 | return createBlobFromDataURL(url);
90 | }
91 |
92 | export async function createFrameSequenceEncoder(opts = {}) {
93 | const {
94 | width,
95 | height,
96 | type = "image/png",
97 | prefix = "",
98 | encoderOptions = 0.9,
99 | totalFrames = 1000,
100 | } = opts;
101 |
102 | const extension = {
103 | "image/png": ".png",
104 | "image/webp": ".webp",
105 | "image/jpeg": ".jpg",
106 | }[type];
107 | if (!extension) throw new Error(`Invalid image mime type ${type}`);
108 |
109 | let dir;
110 | try {
111 | dir = await window.showDirectoryPicker();
112 | } catch (err) {
113 | if (err.code === 20 || err.name === "AbortError") {
114 | // don't warn on abort
115 | return null;
116 | } else {
117 | throw err;
118 | }
119 | }
120 |
121 | return {
122 | type: "image/png",
123 | extension: ".png",
124 | async encode(bitmap, frameIndex) {
125 | const frameDigitCount = Math.max(3, String(totalFrames).length);
126 | const curFrameName = String(frameIndex).padStart(frameDigitCount, "0");
127 | const curFrameFile = `${prefix}${curFrameName}${extension}`;
128 |
129 | const fh = await dir.getFileHandle(curFrameFile, { create: true });
130 | const fw = await fh.createWritable();
131 |
132 | const blob = getBitmapBlob(bitmap, width, height, type, encoderOptions);
133 | await fw.write(blob);
134 | await fw.close();
135 | },
136 | async finish() {},
137 | };
138 | }
139 |
140 | export async function createGIFEncoder(opts = {}) {
141 | const { fps = 30, width, height, dithering = false } = opts;
142 | const gif = GIFEncoder();
143 | let firstFrame = true;
144 | let initialPalette = null;
145 | const useInitialPalette = false;
146 | return {
147 | type: "image/gif",
148 | extension: ".gif",
149 | async encode(bitmap) {
150 | const pixels = getBitmapRGBA(bitmap, width, height);
151 | const palette =
152 | useInitialPalette && initialPalette
153 | ? initialPalette
154 | : quantize(pixels, 256);
155 | if (!initialPalette) initialPalette = palette;
156 | const dithered = dithering
157 | ? floydSteinberg(pixels, width, height, palette)
158 | : pixels;
159 | const index = applyPalette(dithered, palette);
160 | const fpsInterval = 1 / fps;
161 | const delay = fpsInterval * 1000;
162 | let curPalette;
163 | if (firstFrame || !useInitialPalette) curPalette = palette;
164 | gif.writeFrame(index, width, height, {
165 | palette: curPalette,
166 | delay,
167 | });
168 | firstFrame = false;
169 | },
170 | async finish() {
171 | gif.finish();
172 | return gif.bytes();
173 | },
174 | };
175 | }
176 |
177 | export async function createPNGEncoder(opts = {}) {
178 | const { width, height } = opts;
179 | const gif = GIFEncoder();
180 | let blob;
181 | return {
182 | type: "image/png",
183 | extension: ".png",
184 | async encode(bitmap) {
185 | blob = getBitmapBlob(bitmap, width, height, "image/png");
186 | },
187 | async finish() {
188 | return blob;
189 | },
190 | };
191 | }
192 |
193 | function createBlobFromDataURL(dataURL) {
194 | const splitIndex = dataURL.indexOf(",");
195 | if (splitIndex === -1) {
196 | return new Blob();
197 | }
198 | const base64 = dataURL.slice(splitIndex + 1);
199 | const byteString = atob(base64);
200 | const type = dataURL.slice(0, splitIndex);
201 | const mimeMatch = /data:([^;]+)/.exec(type);
202 | const mime = (mimeMatch ? mimeMatch[1] : "") || undefined;
203 | const ab = new ArrayBuffer(byteString.length);
204 | const ia = new Uint8Array(ab);
205 | for (var i = 0; i < byteString.length; i++) {
206 | ia[i] = byteString.charCodeAt(i);
207 | }
208 | return new Blob([ab], { type: mime });
209 | }
210 |
--------------------------------------------------------------------------------
/src/render.js:
--------------------------------------------------------------------------------
1 | import templates from "./templates";
2 | import getPrelude from "./prelude";
3 |
4 | const acceptedTemplates = ["p5js", "js", "threejs", "regl", "paper"];
5 |
6 | export default (data, opts = {}) => {
7 | let type = "js";
8 | let version;
9 | if (data.project.scriptType) {
10 | type = data.project.scriptType;
11 | }
12 |
13 | type = (type || "").toLowerCase();
14 | if (!type || type === "vanilla" || type === "n/a") type = "js";
15 | if (type === "p5") type = "p5js";
16 | if (type === "three") type = "threejs";
17 |
18 | if (!acceptedTemplates.includes(type)) {
19 | return alert(`Error: Project type "${type}" not yet supported.`);
20 | }
21 |
22 | if (data.project.scriptVersion) {
23 | if (!/n[\\/]?a/i.test(data.project.scriptVersion)) {
24 | version = data.project.scriptVersion;
25 | }
26 | }
27 |
28 | const projectNum = parseInt(data.project.id, 10);
29 | const tokenId = String(data.token.id);
30 | const tokenData =
31 | projectNum <= 2
32 | ? {
33 | hashes: [data.token.hash],
34 | tokenId,
35 | }
36 | : {
37 | hash: data.token.hash,
38 | tokenId,
39 | };
40 |
41 | const base = templates[type] || templates.js;
42 | version = resolveVersion(type, version);
43 |
44 | let vendorUrl;
45 | if (type === "regl") {
46 | vendorUrl = "https://cdnjs.cloudflare.com/ajax/libs/regl/2.1.0/regl.min.js";
47 | } else if (type === "p5js") {
48 | vendorUrl = `https://cdnjs.cloudflare.com/ajax/libs/p5.js/${version}/p5.min.js`;
49 | } else if (type === "paper") {
50 | vendorUrl = `https://cdnjs.cloudflare.com/ajax/libs/paper.js/${type}/paper-full.min.js`;
51 | }
52 | let vendor;
53 | if (vendorUrl) vendor = ``;
54 |
55 | const prelude = getPrelude(opts);
56 | const doc = maxstache(base, {
57 | version,
58 | prelude,
59 | vendor,
60 | tokenData: ``,
61 | script: ``,
62 | });
63 | return doc;
64 | };
65 |
66 | function resolveVersion(type, version) {
67 | if (type === "p5js") return version || "1.0.0";
68 | return version;
69 | }
70 |
71 | // Minimalist mustache template replacement
72 | // (str, obj) -> null
73 | function maxstache(str, ctx) {
74 | ctx = ctx || {};
75 | const tokens = str.split(/\{\{|\}\}/);
76 | const res = tokens.map(parse(ctx));
77 | return res.join("");
78 | }
79 |
80 | // parse a token
81 | // obj -> (str, num) -> str
82 | function parse(ctx) {
83 | return function parse(token, i) {
84 | if (i % 2 === 0) return token;
85 | return ctx[token];
86 | };
87 | }
88 |
--------------------------------------------------------------------------------
/src/templates.js:
--------------------------------------------------------------------------------
1 | import srcThree from "./vendor/three.r124.js?raw";
2 | import srcRegl from "./vendor/regl.2.1.0.js?raw";
3 |
4 | export default {
5 | js: `
6 |
7 | {{prelude}}
8 | {{tokenData}}
9 |
25 |
26 |
27 |
28 | {{script}}
29 |
30 | `,
31 | p5js: `
32 |
33 |
34 | {{prelude}}
35 | {{tokenData}}
36 |
37 | {{script}}
38 |
54 |
55 | `,
56 | threejs: `
57 |
58 | {{prelude}}
59 |
62 | {{tokenData}}
63 |
79 |
80 |
81 | {{script}}
82 |
83 | `,
84 | regl: `{{prelude}}
85 |
86 | {{tokenData}}{{script}}`,
100 | };
101 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import { svelte } from '@sveltejs/vite-plugin-svelte'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [svelte()]
7 | })
8 |
--------------------------------------------------------------------------------