├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── docs ├── 072.png ├── 075.png ├── 103.png ├── 220.png ├── 335-r100.png ├── 335-r42.png ├── 363.png └── 378.png ├── package-lock.json ├── package.json ├── src ├── README.md ├── core │ ├── color.js │ ├── math.js │ ├── random.js │ └── vec.js └── test.js └── tools ├── cli-es5.js ├── cli.js ├── core.js ├── export-all.sh ├── get-hashes.sh ├── hashes.js ├── index.html ├── render.js └── serialize.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | outputs/ 7 | tmp/ 8 | tools/print.js -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ 9 | .npmignore 10 | LICENSE.md 11 | outputs 12 | tools/print.js 13 | docs/ 14 | tmp/ -------------------------------------------------------------------------------- /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 | # Subscapes 2 | 3 | :wrench: Tooling for the [Subscapes](https://artblocks.io/project/53) generative art project by Matt DesLauriers. 4 | 5 | 6 | 7 | *Pictured above: [#378](https://artblocks.io/token/53000378), [#72](https://artblocks.io/token/53000072), [#75](https://artblocks.io/token/53000075), [#220](https://artblocks.io/token/53000220), [#363](https://artblocks.io/token/53000363), [#103](https://artblocks.io/token/53000103)* 8 | 9 | ## About Subscapes 10 | 11 | Subscapes is a generative art project where the core rendering code is immutably secured & hosted on the Ethereum mainnet blockchain. The program accepts a unique *hash* as a seed, and outputs the corresponding artwork using a determinstic generative algorithm. 12 | 13 | A total of 650 unique iterations of this project were minted by collectors on [ArtBlocks.io](https://artblocks.io/), and will continue to be available for re-sale in the secondary market on [OpenSea](https://opensea.io/assets/art-blocks?search[resultModel]=ASSETS&search[stringTraits][0][name]=Subscapes&search[stringTraits][0][values][0]=All%20Subscapes). These 650 editions make up the entire collection available for tokenized ownership, and no more editions will be minted or sold in the future. 14 | 15 | The ethos of ArtBlocks is *1/1 of X*, as each minted output is limited and unique among the infinite array of possibilities. 16 | 17 | ## Hacking & Experimentation 18 | 19 | I'm releasing some of the code & tools here to encourage printing, hacking, and experimentation with the Subscapes program, for non-commercial explorations within the community and collectors. The code here allows users to programmatically run the software to generate high resolution outputs, as well as render new iterations beyond the 650 ArtBlocks-minted editions. 20 | 21 | For example: since the Subscapes code is renderer-agnostic (it can run in browser or Node.js), it could be adapted to different display mediums, galleries, and engines (web frontends, e-ink screens, WebGL, mechanical pen plotters, 3D printing, etc). 22 | 23 | ## CLI Tool 24 | 25 | The CLI tool for subscapes allows users to render a specific iteration of Susbcapes from a hash or ArtBlocks mint number. These can be output as PNG, JPG, or SVG format with a specific width and height size. 26 | 27 | Installation with [Node.js and npm](https://nodejs.org/en/download/): 28 | 29 | ```sh 30 | npm install subscapes --global 31 | ``` 32 | 33 | On first run, the tool will fetch the code from Ethereum. Subsequent runs are cached for convenience. 34 | 35 | Examples: 36 | 37 | ```sh 38 | # render Subscapes #32 (minted) as 512x512 39 | subscapes -i 32 -w 512 40 | 41 | # render an output from a specific hash 42 | subscapes -i 0xd6a58b3f39ac40bc4160bb7153eb1d27f32d0588c29004e19d0a69cfa0d491d8 43 | 44 | # render #300 as SVG with a specific filename 45 | subscapes -i 300 --format svg --name 300.svg 46 | 47 | # render a purely random Subscapes, beyond the 650 minted set 48 | subscapes 49 | ``` 50 | 51 | Details: 52 | 53 | ```txt 54 | Usage: 55 | subscapes [options] 56 | 57 | Options: 58 | --id, -i input hash as 0xXXXX, or mint id number 0..649 (optional) 59 | --dir, -d output directory (defaults to cwd) 60 | --name, -n file name (defaults to hash) 61 | --format, -f file format: png, svg, jpg (default png) 62 | --resolution, -r sets the raycast resolution (default 42) 63 | --width, -w output width in pixels (default 2048) 64 | --height, -h output height in pixels (default 2048) 65 | --no-log disables logging 66 | --no-cache forces a new fetch from blockchain and disables cache 67 | ``` 68 | 69 | If only one dimension (width or height) is set, the other dimension will use the same, to produce a square ratio. 70 | 71 | #### Resolution 72 | 73 | The algorithm raycasts against the topology to remove lines that are behind peaks/mountain ranges, but to improve this user experience for the online (Live) artwork, I have kept the number of subdivisions low (42). You can increase the resolution (`-r` flag) to 60-100 during rendering to get higher quality outputs, but the rendering time will also increase exponentially. 74 | 75 | ```sh 76 | subscapes -i 335 -r 80 77 | ``` 78 | 79 | See #335 before and after, for example: 80 | 81 | 82 | 83 | ## Programmatic API (Node.js/JavaScript) 84 | 85 | Work in progress. 86 | 87 | ## Web/Frontend Tools 88 | 89 | Work in progress. 90 | 91 | ## Archival Limited-Edition Signed Prints 92 | 93 | I am still considering how best to approach bespoke, archival, and signed prints of Subscapes. This may be suitable for collectors who plan to archive and display/exhibit the minted works long-term, and wish to own an artist-certified physical artefact. Keep an eye on my Twitter or the [#subscapes discord channel](https://discord.com/channels/411959613370400778/833713318107545670) for details. 94 | 95 | ## Source Code & License 96 | 97 | The code in this repository is Open Source MIT, see [LICENSE.md](http://github.com/mattdesl/subscapes/blob/master/LICENSE.md) for details. This includes a small set of generic math/vector/color/etc utilities used by the Subscapes algorithm in [src/](./src). 98 | 99 | The resulting PNG/JPG/SVG files produced from the Subscapes algorithm (such as the images that can be downloaded from ArtBlocks) are licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/). 100 | 101 | Neither of these licenses covers the code for the Subscapes generative algorithm (which is hosted on the blockchain and not present in this repository). I've chosen not to release this yet code yet, to help maintain my artistic IP over the project and ensure that no third-parties can claim distribution rights over newly tokenized iterations. However, I may decide to publish all of the Subscapes code at a later point, granting a non-commercial license for educational use. -------------------------------------------------------------------------------- /docs/072.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/subscapes/917f969746c056d743b8b406f9a0076b1aee039e/docs/072.png -------------------------------------------------------------------------------- /docs/075.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/subscapes/917f969746c056d743b8b406f9a0076b1aee039e/docs/075.png -------------------------------------------------------------------------------- /docs/103.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/subscapes/917f969746c056d743b8b406f9a0076b1aee039e/docs/103.png -------------------------------------------------------------------------------- /docs/220.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/subscapes/917f969746c056d743b8b406f9a0076b1aee039e/docs/220.png -------------------------------------------------------------------------------- /docs/335-r100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/subscapes/917f969746c056d743b8b406f9a0076b1aee039e/docs/335-r100.png -------------------------------------------------------------------------------- /docs/335-r42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/subscapes/917f969746c056d743b8b406f9a0076b1aee039e/docs/335-r42.png -------------------------------------------------------------------------------- /docs/363.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/subscapes/917f969746c056d743b8b406f9a0076b1aee039e/docs/363.png -------------------------------------------------------------------------------- /docs/378.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/subscapes/917f969746c056d743b8b406f9a0076b1aee039e/docs/378.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "subscapes", 3 | "version": "1.0.3", 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 | "canvas": "^2.7.0", 14 | "chalk": "^4.1.1", 15 | "conf": "^10.0.1", 16 | "esm": "^3.2.25", 17 | "jimp": "^0.16.1", 18 | "minimist": "^1.2.5", 19 | "mkdirp": "^1.0.4", 20 | "ora": "^5.4.0", 21 | "web3": "^1.3.5" 22 | }, 23 | "scripts": { 24 | "test": "node -r esm tools/cli.js" 25 | }, 26 | "bin": { 27 | "subscapes": "tools/cli-es5.js" 28 | }, 29 | "keywords": [], 30 | "repository": { 31 | "type": "git", 32 | "url": "git://github.com/mattdesl/subscapes.git" 33 | }, 34 | "homepage": "https://github.com/mattdesl/subscapes", 35 | "bugs": { 36 | "url": "https://github.com/mattdesl/subscapes/issues" 37 | }, 38 | "devDependencies": {} 39 | } 40 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # src 2 | 3 | The un-minified source code for Subscapes is not currently published, to maintain artistic IP, until a suitable license can be sorted out. 4 | 5 | However, I've published here a few of the un-minified utility modules used within the Subscapes algorithm, to help others build similar compact & on-chain JavaScript artworks. 6 | 7 | ## bundling 8 | 9 | Bundling was done with [rollup](https://rollupjs.org/) to produce a very compact JavaScript closure: 10 | 11 | ```sh 12 | rollup --format=iife --file=dist/index.js src/main.js 13 | ``` 14 | 15 | ## minification 16 | 17 | Minification was done by combining `babel-minify` with `terser` on the bundled JavaScript, see here: 18 | 19 | https://gist.github.com/mattdesl/38058dafb94d6518fc18e76e414ca5ef 20 | 21 | ## testing 22 | 23 | You can do a simple test of these utilities by git cloning this `subscapes` repo, and from within that cloned folder, installing dependencies: 24 | 25 | ```sh 26 | npm install 27 | ``` 28 | 29 | Then: 30 | 31 | ```sh 32 | node -r esm src/test.js 33 | ``` -------------------------------------------------------------------------------- /src/core/color.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A set of color utilities: contrast, OKLAB, hex, RGB, etc. 3 | * 4 | * Reference: 5 | * https://github.com/mattdesl/canvas-sketch-util/blob/master/math.js 6 | * https://github.com/nschloe/colorio 7 | */ 8 | 9 | import { clamp } from "./math.js"; 10 | 11 | // red, green, and blue coefficients 12 | var rc = 0.2126; 13 | var gc = 0.7152; 14 | var bc = 0.0722; 15 | // low-gamma adjust coefficient 16 | var lowc = 1 / 12.92; 17 | 18 | const adjustGamma = (a) => Math.pow((a + 0.055) / 1.055, 2.4); 19 | 20 | export const luma = (n, a = ~~((n / 100) * 255)) => rgbToHex([a, a, a]); 21 | 22 | export const blend = (c0, c1, opacity) => { 23 | c0 = hexToRGB(c0); 24 | c1 = hexToRGB(c1); 25 | for (var i = 0; i < 3; i++) { 26 | c1[i] = c1[i] * opacity + c0[i] * (1 - opacity); 27 | } 28 | return rgbToHex(c1); 29 | }; 30 | 31 | export const relativeLuminance = (rgb) => { 32 | if (typeof rgb == "string") rgb = hexToRGB(rgb); 33 | var rsrgb = rgb[0] / 255; 34 | var gsrgb = rgb[1] / 255; 35 | var bsrgb = rgb[2] / 255; 36 | var r = rsrgb <= 0.03928 ? rsrgb * lowc : adjustGamma(rsrgb); 37 | var g = gsrgb <= 0.03928 ? gsrgb * lowc : adjustGamma(gsrgb); 38 | var b = bsrgb <= 0.03928 ? bsrgb * lowc : adjustGamma(bsrgb); 39 | return r * rc + g * gc + b * bc; 40 | }; 41 | 42 | // // Extracted from @tmcw / wcag-contrast 43 | // // https://github.com/tmcw/wcag-contrast 44 | export const contrastRatio = (colorA, colorB) => { 45 | var a = relativeLuminance(colorA); 46 | var b = relativeLuminance(colorB); 47 | var l1 = Math.max(a, b); 48 | var l2 = Math.min(a, b); 49 | return (l1 + 0.05) / (l2 + 0.05); 50 | }; 51 | 52 | export const hexToRGB = (str) => { 53 | var hex = str.replace("#", ""); 54 | // NOTE: This can be removed for brevity if you stick with 6-character codes 55 | if (hex.length === 3) { 56 | hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; 57 | } 58 | var num = parseInt(hex, 16); 59 | return [num >> 16, (num >> 8) & 255, num & 255]; 60 | }; 61 | 62 | export function rgbToHex(rgb) { 63 | var r = clamp(~~rgb[0], 0, 255); 64 | var g = clamp(~~rgb[1], 0, 255); 65 | var b = clamp(~~rgb[2], 0, 255); 66 | return "#" + (b | (g << 8) | (r << 16) | (1 << 24)).toString(16).slice(1); 67 | } 68 | 69 | const gammaToLinear = (c) => 70 | c < 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); 71 | const linearToGamma = (c) => 72 | c > 0.0031308 ? 1.055 * Math.pow(c, 1 / 2.4) - 0.055 : 12.92 * c; 73 | 74 | export function oklabToRGB([l, a, b]) { 75 | let L = Math.pow(l + 0.3963377774 * a + 0.2158037573 * b, 3); 76 | let M = Math.pow(l - 0.1055613458 * a - 0.0638541728 * b, 3); 77 | let S = Math.pow(l - 0.0894841775 * a - 1.291485548 * b, 3); 78 | return [ 79 | clamp( 80 | ~~( 81 | 255 * 82 | linearToGamma(+4.0767245293 * L - 3.3072168827 * M + 0.2307590544 * S) 83 | ), 84 | 0, 85 | 255 86 | ), 87 | clamp( 88 | ~~( 89 | 255 * 90 | linearToGamma(-1.2681437731 * L + 2.6093323231 * M - 0.341134429 * S) 91 | ), 92 | 0, 93 | 255 94 | ), 95 | clamp( 96 | ~~( 97 | 255 * 98 | linearToGamma(-0.0041119885 * L - 0.7034763098 * M + 1.7068625689 * S) 99 | ), 100 | 0, 101 | 255 102 | ), 103 | ]; 104 | } 105 | 106 | export const rgbToOklab = (rgb) => { 107 | if (typeof rgb === "string") rgb = hexToRGB(rgb); 108 | let [r, g, b] = rgb; 109 | r = gammaToLinear(r / 255); 110 | g = gammaToLinear(g / 255); 111 | b = gammaToLinear(b / 255); 112 | let L = Math.cbrt(0.412165612 * r + 0.536275208 * g + 0.0514575653 * b); 113 | let M = Math.cbrt(0.211859107 * r + 0.6807189584 * g + 0.107406579 * b); 114 | let S = Math.cbrt(0.0883097947 * r + 0.2818474174 * g + 0.6302613616 * b); 115 | return [ 116 | 0.2104542553 * L + 0.793617785 * M - 0.0040720468 * S, 117 | 1.9779984951 * L - 2.428592205 * M + 0.4505937099 * S, 118 | 0.0259040371 * L + 0.7827717662 * M - 0.808675766 * S, 119 | ]; 120 | }; 121 | 122 | export function Lch(L, c, h) { 123 | const a = (h / 180) * Math.PI; 124 | c /= 100; 125 | L /= 100; 126 | return rgbToHex( 127 | oklabToRGB([L, c ? c * Math.cos(a) : 0, c ? c * Math.sin(a) : 0]) 128 | ); 129 | } 130 | 131 | const normalizeHue = (hue) => ((hue = hue % 360) < 0 ? hue + 360 : hue); 132 | 133 | export const LabToLch = ([l, a, b]) => { 134 | let c = Math.sqrt(a * a + b * b); 135 | let res = [l, c, c ? normalizeHue((Math.atan2(b, a) * 180) / Math.PI) : 0]; 136 | res[0] *= 100; 137 | res[1] *= 100; 138 | return res; 139 | }; 140 | 141 | export const getBestColors = ( 142 | colors, 143 | others, 144 | min = 1, 145 | thresholds = [3, 2.5, 2] 146 | ) => { 147 | for (let i = 0; i < thresholds.length; i++) { 148 | const best = colors.filter((c) => 149 | others.every((b) => contrastRatio(c, b) >= thresholds[i]) 150 | ); 151 | if (best.length >= min) return best; 152 | } 153 | }; 154 | -------------------------------------------------------------------------------- /src/core/math.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A set of math utilities. Mostly taken from canvas-sketch-util. 3 | * 4 | * Reference: 5 | * https://github.com/mattdesl/canvas-sketch-util/blob/master/math.js 6 | */ 7 | 8 | export const clamp = (value, min, max) => Math.max(Math.min(value, max), min); 9 | export const clamp01 = (v) => clamp(v, 0, 1); 10 | 11 | export const lerp = (min, max, t) => min * (1 - t) + max * t; 12 | 13 | export const inverseLerp = (min, max, t) => (t - min) / (max - min); 14 | 15 | export const smoothstep = (min, max, t) => { 16 | t = clamp01((t - min) / (max - min)); 17 | return t * t * (3 - 2 * t); 18 | }; 19 | 20 | export function lerpArray(min, max, t, out = []) { 21 | for (var i = 0; i < min.length; i++) { 22 | out[i] = lerp(min[i], max[i], t); 23 | } 24 | return out; 25 | } 26 | 27 | export function lerpFrames(values, t) { 28 | t = clamp(t, 0, 1); 29 | var len = values.length - 1; 30 | var whole = t * len; 31 | var frame = ~~whole; 32 | var fract = whole - frame; 33 | var nextFrame = Math.min(frame + 1, len); 34 | var a = values[frame % values.length]; 35 | var b = values[nextFrame % values.length]; 36 | return lerpArray(a, b, fract); 37 | } 38 | 39 | export const mod = (a, b) => ((a % b) + b) % b; 40 | 41 | export const stepped = (t, list) => 42 | list[clamp(~~(t * list.length), 0, list.length - 1)]; 43 | 44 | export const mapRange = (value, inputMin, inputMax, outputMin, outputMax) => 45 | lerp(outputMin, outputMax, inverseLerp(inputMin, inputMax, value)); 46 | -------------------------------------------------------------------------------- /src/core/random.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A set of random utilities. 3 | * 4 | * The randomizer is built in two parts: 5 | * 6 | * - Uses two murmur2 32-bit hashes with different seeds 7 | * on the set of input bytes (i.e. the token hash) to get a 64-bit value 8 | * - Uses PCG to get a random 0..1 value, extracted from Jacob Rus' notebook 9 | * 10 | * See here for more: 11 | * https://gist.github.com/mattdesl/779daf4c9fa72e21733f9db928f993aa 12 | * https://github.com/mattdesl/canvas-sketch-util/blob/master/random.js 13 | */ 14 | 15 | // Note that the index order [0, 1, 2, 3] is little-endian 16 | const eps = Math.pow(2, -32), 17 | m0 = 0x7f2d, 18 | m1 = 0x4c95, 19 | m2 = 0xf42d, 20 | m3 = 0x5851, // 6364136223846793005 21 | a0 = 0x814f, 22 | a1 = 0xf767, 23 | a2 = 0x7b7e, 24 | a3 = 0x1405; // 1442695040888963407 25 | 26 | const state = new Uint16Array(4); 27 | const dv = new DataView(state.buffer); 28 | 29 | let _nextGaussian = null; 30 | let _hasNextGaussian = false; 31 | 32 | // random value between 0..1 33 | export const value = () => { 34 | // Advance internal state 35 | const s0 = state[0], 36 | s1 = state[1], 37 | s2 = state[2], 38 | s3 = state[3], 39 | new0 = (a0 + m0 * s0) | 0, 40 | new1 = (a1 + m0 * s1 + (m1 * s0 + (new0 >>> 16))) | 0, 41 | new2 = (a2 + m0 * s2 + m1 * s1 + (m2 * s0 + (new1 >>> 16))) | 0, 42 | new3 = a3 + m0 * s3 + (m1 * s2 + m2 * s1) + (m3 * s0 + (new2 >>> 16)); 43 | (state[0] = new0), (state[1] = new1), (state[2] = new2); 44 | state[3] = new3; 45 | 46 | // Calculate output function (XSH RR), uses old state 47 | const xorshifted = 48 | (s3 << 21) + (((s3 >> 2) ^ s2) << 5) + (((s2 >> 2) ^ s1) >> 11), 49 | out_int32 = 50 | (xorshifted >>> (s3 >> 11)) | (xorshifted << (-(s3 >> 11) & 31)); 51 | return eps * (out_int32 >>> 0); 52 | }; 53 | 54 | // internally gets a 32-bit from tokenData hash bytes 55 | const hash32 = (bytes, seed = 0) => { 56 | // murmur2 32bit 57 | // https://github.com/garycourt/murmurhash-js/blob/master/murmurhash2_gc.js 58 | const K = 16; 59 | const mask = 65535; 60 | const maskByte = 0xff; 61 | var m = 0x5bd1e995; 62 | var l = bytes.length, 63 | h = seed ^ l, 64 | i = 0, 65 | k; 66 | while (l >= 4) { 67 | k = 68 | (bytes[i] & maskByte) | 69 | ((bytes[++i] & maskByte) << 8) | 70 | ((bytes[++i] & maskByte) << 16) | 71 | ((bytes[++i] & maskByte) << 24); 72 | k = (k & mask) * m + ((((k >>> K) * m) & mask) << K); 73 | k ^= k >>> 24; 74 | k = (k & mask) * m + ((((k >>> K) * m) & mask) << K); 75 | h = ((h & mask) * m + ((((h >>> K) * m) & mask) << K)) ^ k; 76 | l -= 4; 77 | ++i; 78 | } 79 | switch (l) { 80 | case 3: 81 | h ^= (bytes[i + 2] & maskByte) << K; 82 | case 2: 83 | h ^= (bytes[i + 1] & maskByte) << 8; 84 | case 1: 85 | h ^= bytes[i] & maskByte; 86 | h = (h & mask) * m + ((((h >>> K) * m) & mask) << K); 87 | } 88 | h ^= h >>> 13; 89 | h = (h & mask) * m + ((((h >>> K) * m) & mask) << K); 90 | h ^= h >>> 15; 91 | return h >>> 0; 92 | }; 93 | 94 | // sets the seed to a tokenData hash string "0x..." 95 | export const set_seed = (hash) => { 96 | _hasNextGaussian = false; 97 | _nextGaussian = null; 98 | const nBytes = ~~((hash.length - 2) / 2); 99 | const bytes = []; 100 | for (let j = 0; j < nBytes; j++) { 101 | const e0 = 2 + 2 * j; 102 | bytes.push(parseInt(hash.slice(e0, e0 + 2), 16)); 103 | } 104 | 105 | // to keep it simple, we just use 32bit murmur2 with two different seeds 106 | const seed_a = 1690382925; 107 | const seed_b = 72970470; 108 | const lower = hash32(bytes, seed_a); 109 | const upper = hash32(bytes, seed_b); 110 | dv.setUint32(0, lower); 111 | dv.setUint32(4, upper); 112 | }; 113 | 114 | // random boolean with 50% uniform chance 115 | export const boolean = () => value() > 0.5; 116 | 117 | // random chance 118 | export const chance = (n = 0.5) => value() < n; 119 | 120 | // random value between min (inclusive) and max (exclusive) 121 | export const range = (min, max) => { 122 | if (max === undefined) { 123 | max = min; 124 | min = 0; 125 | } 126 | return value() * (max - min) + min; 127 | }; 128 | 129 | // random value between min (inclusive) and max (exclusive), then floored 130 | export const rangeFloor = (min, max) => Math.floor(range(min, max)); 131 | 132 | // pick a random element in the given array 133 | export const pick = (array) => 134 | array.length ? array[rangeFloor(array.length)] : undefined; 135 | 136 | // shuffle an array 137 | export const shuffle = (arr) => { 138 | var rand; 139 | var tmp; 140 | var len = arr.length; 141 | var ret = [...arr]; 142 | while (len) { 143 | rand = ~~(value() * len--); 144 | tmp = ret[len]; 145 | ret[len] = ret[rand]; 146 | ret[rand] = tmp; 147 | } 148 | return ret; 149 | }; 150 | 151 | // random point in a uniform 2D disc with given radius 152 | export function insideCircle(radius = 1, out = []) { 153 | var theta = value() * 2.0 * Math.PI; 154 | var r = radius * Math.sqrt(value()); 155 | out[0] = r * Math.cos(theta); 156 | out[1] = r * Math.sin(theta); 157 | return out; 158 | } 159 | 160 | // weighted randomness, specify weights array and the return value is an index 161 | export const weighted = (weights) => { 162 | var totalWeight = 0; 163 | var i; 164 | 165 | for (i = 0; i < weights.length; i++) { 166 | totalWeight += weights[i]; 167 | } 168 | 169 | var random = value() * totalWeight; 170 | for (i = 0; i < weights.length; i++) { 171 | if (random < weights[i]) { 172 | return i; 173 | } 174 | random -= weights[i]; 175 | } 176 | return 0; 177 | }; 178 | 179 | // random gaussian distribution 180 | export const gaussian = (mean = 0, standardDerivation = 1) => { 181 | // https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/util/Random.java#L496 182 | if (_hasNextGaussian) { 183 | _hasNextGaussian = false; 184 | var result = _nextGaussian; 185 | _nextGaussian = null; 186 | return mean + standardDerivation * result; 187 | } else { 188 | var v1 = 0; 189 | var v2 = 0; 190 | var s = 0; 191 | do { 192 | v1 = value() * 2 - 1; // between -1 and 1 193 | v2 = value() * 2 - 1; // between -1 and 1 194 | s = v1 * v1 + v2 * v2; 195 | } while (s >= 1 || s === 0); 196 | var multiplier = Math.sqrt((-2 * Math.log(s)) / s); 197 | _nextGaussian = v2 * multiplier; 198 | _hasNextGaussian = true; 199 | return mean + standardDerivation * (v1 * multiplier); 200 | } 201 | }; 202 | 203 | // Generates a pure random hash, useful for testing 204 | // i.e. not deterministic! 205 | export function getRandomHash() { 206 | let result = "0x"; 207 | for (let i = 64; i > 0; --i) 208 | result += "0123456789abcdef"[~~(Math.random() * 16)]; 209 | return result; 210 | } 211 | -------------------------------------------------------------------------------- /src/core/vec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A set of Vector math utilities. Mostly taken from gl-matrix and other places. 3 | */ 4 | 5 | export const cross = (out, a, b) => { 6 | var ax = a[0], 7 | ay = a[1], 8 | az = a[2], 9 | bx = b[0], 10 | by = b[1], 11 | bz = b[2]; 12 | out[0] = ay * bz - az * by; 13 | out[1] = az * bx - ax * bz; 14 | out[2] = ax * by - ay * bx; 15 | return out; 16 | }; 17 | 18 | export const length = (a) => Math.hypot(a[0], a[1], a[2]); 19 | 20 | export const copy = (out, a) => set(out, a[0], a[1], a[2]); 21 | 22 | export const set = (out, x, y, z) => { 23 | out[0] = x; 24 | out[1] = y; 25 | out[2] = z; 26 | return out; 27 | }; 28 | 29 | export const sub = (out, a, b) => 30 | set(out, a[0] - b[0], a[1] - b[1], a[2] - b[2]); 31 | 32 | export const scale = (out, a, b) => set(out, a[0] * b, a[1] * b, a[2] * b); 33 | 34 | export const add = (out, a, b) => 35 | set(out, a[0] + b[0], a[1] + b[1], a[2] + b[2]); 36 | 37 | export const scaleAndAdd = (out, a, b, scale) => 38 | set(out, a[0] + b[0] * scale, a[1] + b[1] * scale, a[2] + b[2] * scale); 39 | 40 | export const normalize = (out, a) => { 41 | let len = dot(a, a); 42 | if (len > 0) len = 1 / Math.sqrt(len); 43 | return scale(out, a, len); 44 | }; 45 | 46 | export const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; 47 | -------------------------------------------------------------------------------- /src/test.js: -------------------------------------------------------------------------------- 1 | import * as random from "./core/random.js"; 2 | 3 | const hash = random.getRandomHash(); 4 | random.set_seed(hash); 5 | 6 | console.log(hash); 7 | console.log(random.value()); 8 | console.log(random.pick([25, 10])); 9 | console.log(random.weighted([100, 50, 25, 10])); 10 | -------------------------------------------------------------------------------- /tools/cli-es5.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require = require("esm")(module); 3 | require("./cli.js"); 4 | -------------------------------------------------------------------------------- /tools/cli.js: -------------------------------------------------------------------------------- 1 | import { fetch, compile, idToHash } from "./core.js"; 2 | import render from "./render.js"; 3 | import Web3 from "web3"; 4 | import ora from "ora"; 5 | import chalk from "chalk"; 6 | import { promisify } from "util"; 7 | import fs from "fs"; 8 | import path from "path"; 9 | import minimist from "minimist"; 10 | import mkdirp from "mkdirp"; 11 | import Conf from "conf"; 12 | import serialize from "./serialize"; 13 | 14 | const DEFAULT_RESOLUTION = 42; 15 | const DEFAULT_SIZE = 2048; 16 | const INFURA_PROJECT_ID = "569220dd1f86497882844120d441afa9"; 17 | const local = false; 18 | 19 | const writeFile = promisify(fs.writeFile); 20 | const readFile = promisify(fs.readFile); 21 | 22 | async function fetchFromBlockchain() { 23 | const web3 = new Web3( 24 | new Web3.providers.HttpProvider( 25 | `https://mainnet.infura.io/v3/${INFURA_PROJECT_ID}` 26 | ) 27 | ); 28 | return fetch(web3); 29 | } 30 | 31 | async function run(argv = {}) { 32 | const cwd = process.cwd(); 33 | let { 34 | cache = true, 35 | log = true, 36 | dir = cwd, 37 | format = "png", 38 | name, 39 | width, 40 | height, 41 | margin = 0, 42 | resolution, 43 | id, 44 | } = argv; 45 | 46 | const hash = idToHash(id); 47 | 48 | if (width != null) { 49 | width = parseInt(width, 10); 50 | } 51 | if (height != null) { 52 | height = parseInt(height, 10); 53 | } 54 | if (width == null && height == null) { 55 | width = height = DEFAULT_SIZE; 56 | } else if (width == null || height == null) { 57 | if (width == null) width = height; 58 | else height = width; 59 | } 60 | 61 | let canvasWidth = width; 62 | let canvasHeight = height; 63 | if (margin) { 64 | margin = parseFloat(margin); 65 | canvasWidth += margin * 2; 66 | canvasHeight += margin * 2; 67 | } 68 | 69 | if (!format) format = "png"; 70 | format = format.toLowerCase(); 71 | const formats = ["png", "jpg", "svg"]; 72 | if (!formats.includes(format)) { 73 | return bail(new Error(`Invalid format ${format}, must be png, jpg, svg`)); 74 | } 75 | 76 | const mimeType = { 77 | png: "image/png", 78 | jpg: "image/jpeg", 79 | svg: "image/svg+xml", 80 | }[format]; 81 | 82 | const ext = `.${format}`; 83 | if (!name) name = `${hash}${ext}`; 84 | else if (!path.extname(name)) name = `${name}${ext}`; 85 | 86 | const outFile = path.resolve(dir, name); 87 | await mkdirp(path.dirname(outFile)); 88 | 89 | const subdivisions = 90 | resolution != null ? parseInt(resolution, 10) : DEFAULT_RESOLUTION; 91 | 92 | let spinner; 93 | 94 | if (log) spinner = ora().start(); 95 | 96 | let code, Subscapes; 97 | 98 | let config; 99 | if (cache) { 100 | config = new Conf(); 101 | code = config.get("code"); 102 | } 103 | 104 | if (!code) { 105 | await info("Fetching"); 106 | try { 107 | code = await fetchFromBlockchain(); 108 | if (cache) config.set("code", code); 109 | } catch (err) { 110 | bail(err); 111 | } 112 | } 113 | 114 | await info("Rendering"); 115 | try { 116 | Subscapes = compile(code); 117 | } catch (err) { 118 | bail(err); 119 | } 120 | 121 | const subscapes = Subscapes(hash, true, subdivisions); 122 | 123 | let buf; 124 | if (format === "svg") { 125 | buf = serialize(subscapes, { width, height, margin }); 126 | } else { 127 | const { createCanvas } = require("canvas"); 128 | const canvas = createCanvas(canvasWidth, canvasHeight); 129 | const context = canvas.getContext("2d"); 130 | context.save(); 131 | context.fillStyle = "white"; 132 | context.fillRect(0, 0, canvasWidth, canvasHeight); 133 | context.translate(margin, margin); 134 | render(subscapes, { context, width, height }); 135 | context.restore(); 136 | buf = canvas.toBuffer(mimeType); 137 | } 138 | 139 | try { 140 | await writeFile(outFile, buf); 141 | } catch (err) { 142 | bail(err); 143 | } 144 | 145 | if (spinner) { 146 | spinner.succeed(`Saved ${chalk.bold(path.relative(cwd, outFile))}`); 147 | } 148 | 149 | function bail(error) { 150 | if (spinner) spinner.fail(error.message); 151 | throw error; 152 | } 153 | 154 | async function info(text) { 155 | if (spinner) { 156 | spinner.text = text; 157 | spinner.render(); 158 | } 159 | return new Promise((resolve) => process.nextTick(resolve)); 160 | } 161 | } 162 | 163 | const argv = minimist(process.argv.slice(2), { 164 | string: ["dir", "name", "id", "format"], 165 | boolean: ["log"], 166 | default: { 167 | log: true, 168 | }, 169 | alias: { 170 | id: "i", 171 | width: "w", 172 | height: "h", 173 | dir: "d", 174 | name: "n", 175 | format: "f", 176 | margin: "m", 177 | resolution: "r", 178 | }, 179 | }); 180 | 181 | run(argv); 182 | -------------------------------------------------------------------------------- /tools/core.js: -------------------------------------------------------------------------------- 1 | import hashes from "./hashes"; 2 | 3 | // Script on blockchain transaction 4 | const TRANSACTION_HASH = 5 | "0x0d16bc2905299da0695af1edab2368c2cef2a2a44b1ff090a9cea17dbc0f573a"; 6 | // ArtBlocks method ID for script 7 | const AB_METHODID = "0xacad0124"; 8 | 9 | export async function fetch(web3) { 10 | const result = await web3.eth.getTransaction(TRANSACTION_HASH); 11 | const decoded = web3.eth.abi.decodeParameters( 12 | ["uint256", "string"], 13 | result.input.slice(AB_METHODID.length) 14 | ); 15 | return decoded["1"]; 16 | } 17 | 18 | export function compile(src) { 19 | const script = ` 20 | (() => { 21 | const RUN = false; 22 | return ${src} 23 | })()`; 24 | return eval(script); 25 | } 26 | 27 | export function generate(src, hash) { 28 | const script = ` 29 | (() => { 30 | const tokenData = ${JSON.stringify(hash)}; 31 | return ${src} 32 | })()`; 33 | return eval(script); 34 | } 35 | 36 | export function getRandomHash() { 37 | let result = "0x"; 38 | for (let i = 64; i > 0; --i) 39 | result += "0123456789abcdef"[~~(Math.random() * 16)]; 40 | return result; 41 | } 42 | 43 | export function idToHash(id) { 44 | if (id == null) return getRandomHash(); 45 | id = String(id); 46 | id = id.replace(/^\#/, ""); 47 | if (id === "?") id = String(Math.floor(Math.random() * 650)); 48 | if (id.startsWith("0x")) return id; 49 | let num = parseInt(id, 10); 50 | if (!isNaN(num)) { 51 | if (num < 650) { 52 | num += 53000000; 53 | } 54 | if (num < 53000000 || num > 53000649) { 55 | throw new Error(`id ${id} out of range 0-649`); 56 | } 57 | let index = num - 53000000; 58 | const h = hashes[index]; 59 | if (!h) throw new Error(`invalid id ${id}`); 60 | return h; 61 | } else { 62 | throw new Error(`id ${id} is not a number`); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tools/export-all.sh: -------------------------------------------------------------------------------- 1 | # for i in {0..649} 2 | # do 3 | 4 | # node tools/cli-es5.js -d outputs -i $i -n $i.png; 5 | # done 6 | 7 | for i in $(seq -f "%03g" 0 649) 8 | do 9 | node tools/cli-es5.js -d outputs -i $i -n $i.png 10 | done -------------------------------------------------------------------------------- /tools/get-hashes.sh: -------------------------------------------------------------------------------- 1 | for i in {53000000..53000649}; do curl -s "https://api.artblocks.io/generator/"$i | egrep -o '\{\"hash.*?\}'; done -------------------------------------------------------------------------------- /tools/hashes.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | "0x8b10b071a17bb4e717c3a6b1e67ee3997b492cc2aba20c0af7b1074f49ddfd22", 3 | "0xca4ba3dab70466eb2b8ec1503ea9b8d766e7a30c5a1230341a2b74103994bb94", 4 | "0x0aada90907e1c4d81b8d1c941b4edcb5b923cd0e6b7542cc03cddba7c6ade4f1", 5 | "0x9233956331acf3aac61d6df69175160d3ec56ce78ddfabd56049fab2f746f71c", 6 | "0x960b00d64f6ce096f67aaeff8cd13cfb9d4cac58235e38eaf4dc6287dd07d954", 7 | "0xd188bf3811022164314b1becb567e18a869b32d914a2263f27114678722e40a8", 8 | "0x07b2dc5c78648519685843cf5c90b525056629c3f8a36be1a29906ef78c6a402", 9 | "0x055beea3ca6b986be3d61f112a3f70d97abd89f2b1c2ea97ac62ae442fec4545", 10 | "0x419bae61b85829d02c72e1879074dbbfb8084cee702dfb4478afb07b067bbdd1", 11 | "0x0c4c2956b677a954f6cbe4f3e0361fc183e7b02022eaea84a1d13f2d4add5b1b", 12 | "0xe8a13acd028d86412e57da5862a20045260618f790018df4fe31df5e9fb960c1", 13 | "0x927e57ab350c42d710efceb3adbfb24f972bba033b35a72a3ddfb998326e980b", 14 | "0x856e8ccd7e357ebee582c62011341498b97897bcdd341bb0b466eb8e50d990c5", 15 | "0x024a2a6be5f9e49948bb80c936d914aa8d81bccc67e09189bb5590d6d87101c2", 16 | "0x8df9305a6de7ce93e5ce9df601e329c58cca6d53c0269003b0ebd594c097ad1a", 17 | "0x88aa5c3c0df4acc0f269e661400413f0dcff4d9b617db0db8f8b99048100b285", 18 | "0xc3357f17e8406550a4b0f605b79c77b49e89aba1d35a259fcb7a786a8f8fc2de", 19 | "0x722e35cd260529992da95882cd603ea26c8e02369563f85d99189ca1b61d1f6c", 20 | "0xe39e4dd8f49a482fe3134b33630468574558dd6ca73c09b16ea7ff0122a2976a", 21 | "0x0af59b1fa885732b56506a6160b784db6ffa9e11ff74550254fdc1e278140aa7", 22 | "0xa15a5ec15a5ccbe0e9fd7fd3a913a007172740d7de86ae60ee3bd3afba16a028", 23 | "0x5a79f1c1ef75f994c27c315eb4fd89606944b0738d2304d6ccc7182bcdbbdf73", 24 | "0xf22233c22c22528c16e3063cf97bed8d851b91fb8b0d98e27567e85fca626628", 25 | "0x03a29dc9695524cfcfb2644a7de1d731a56257add9f51ff02f6721cb0732c37c", 26 | "0xbc7637b016d8d5a2c44e9accd4fb700b8558a208c2c59f191ae7eb567a9e9143", 27 | "0x6486ac8338600d55d43800d8d12f13bb41aa56c7aa7432edf21dfe9266607365", 28 | "0x16637118de6fc8937a1116cccfdca3fb916b56f752d3206b431fc6e11cff88b6", 29 | "0x3e62de45b63067ee07c7afc06d2602cd8685d1d30b04515564b1320ddb621704", 30 | "0x22e05ecd77ca4a588cd6b0f46f48e2e350b4c08cdc730c63e7488a8d07f44452", 31 | "0x23caa34710816a4d23f9065c79a6bb00e70d9661ca92477e2a349948832ae900", 32 | "0xd6648267f065f54d29e15ae8444dacf31b9782117f0bf102b73c8cb51f5053f2", 33 | "0xab130479fa2479c397c396bba728a8b0b328fab64d0f3316aa43668b7d7529f7", 34 | "0xd0836e02991e8f4d146290635e4071a17bab4c6fdf5768ec872625cd83e4a8e8", 35 | "0x73ce9ef4228b319c712f92c0738fa653666861fe90bd0f0b6bc01965e40493b9", 36 | "0xc89fc15e7dee3129da9289d9e570be68e7947da93ae3f569e3af4a10ebefe96c", 37 | "0x638b85684e4eb95dc64ab5b89d686e7b80965a1baae1cf815b806410cc49d6f5", 38 | "0xbbeab36add412481af771c3310caa05f00a130831f533468de549b137e98acc0", 39 | "0x36bca5fefd138991f0214ed21a719840399001472a6d8a1368a7279197e4d613", 40 | "0x9b3b46843ca29abc15d620a1f3f56dbe62b46a27488e9fd7a1dacb036c23b42d", 41 | "0x87bd095c8cdb27ad7a46ce084ffcbdaefec1bb8aa3eb354ed530cbd1adebd6c4", 42 | "0x56ef3f821a488512a499c8ddaa1b7e2110407fe4e1c35fd66520b7c5e3e7db59", 43 | "0xa087dac3d6c9dc02b678bba3c41ac96a301a451393ed87be6973a52bffa171ce", 44 | "0xb99fffe9cbdd38694c71a69010099f36922622366dd153efe0b9b2bed0267d08", 45 | "0xd68dca3197371b10c5c9546c0a99d213903155afe4d3478745557dfb96175777", 46 | "0xda2c93ddbaea965a2f45e29f834e1f090f1f5e612874c6668008827c4786eb0a", 47 | "0x0772edeb8ca87417431ac180b0999e279655c7c73dc6dc029bc9a6315617231c", 48 | "0x9f92c68ec3d7897ee63d76b525b2f49cf51aedcae502777d027a8485f8e5ccfd", 49 | "0x1ffdddce4445d5d2d6ebd4d2ccc3e62a19b9fc8458540febad02d280f0afcaea", 50 | "0x765ea479ab304e868627c78cb730761bb003698ab0d7ff84f4c97567cbc96a6d", 51 | "0x7ae9df6145726c74d71619c6249981f9c28331b9bc871177fce8b0e1b8ece859", 52 | "0xc93fbdecc8ec31a06c2ee8500fe35eabdb33553f648d4eac4be047695e68d831", 53 | "0x317d37b7e81a64a6f3020dd462c7a51953c4239412731beca5a6a90928e5a29f", 54 | "0x11f688e952a5e3e8eb962eea209672284b43104c779e64911a54808738704a3b", 55 | "0x884ad01c2b5eebf577270061f0b1256178f5dc022dda83e94b40d2baa63211c5", 56 | "0x0b11206e55413d173f740392cfbc4d9426b743a0515ae62e4a39243c9224de95", 57 | "0x2068d932f2b3f5f5e56e18d6515ab1b21ba731fc7d4607aa59c2961f2c552505", 58 | "0xb0e13c5eb6a59eff3851c71a47ae2179a5f5bac2913ad2b1317d2186ce42cbfd", 59 | "0xb8deb5d5c48bcf38fc66f68ab7303f6101d30e65e439fc632a5d58a9a2dba7f8", 60 | "0x6840ce024a21c3a6fae524bf00380ca210170ccf43aa43af812b27cbfe3711e7", 61 | "0x9b25f7a72498c4fce627c4f993ff8c3bc86e77af236bb2281372d41f1c42ab1b", 62 | "0xef06c930f3662fc9f1df29e7db9e3987f110e08d1fd1fa89a06b68f8fe52c585", 63 | "0xcc5b5d72932a319fbc7b3a4b8d7101eefb0a83f2b5f79d392430e74e4a2166b7", 64 | "0x32668f675214e260ecaef280852c10427ba137f6543c5ae140662d4ec4ceef48", 65 | "0xaa38c183b5ad3d27d9c6cf38ec8a354441f3edf25a6f54a540bac095647c5003", 66 | "0xcd7936f6b201b031230afe0f809513b92bb9c641a0286f141269ea67dd799a65", 67 | "0x0de807135b6f53fa5fec5bc1b0c66cf4b62022c2a7f4649ae4f5c4ef921d3c24", 68 | "0x661a9c2c7dbcd8c9083c117a349a055e0ad4ff93ab440873c323ea3685b30599", 69 | "0x2420c4c3bbdd6a9eb65d759da9e3adf1f480b697b8b13223bb227d7bdcc1f0b9", 70 | "0xca6211ef44139da3466097dd992badf47e8dde06bd1c59b2297989752484f9bd", 71 | "0x15d16bfc4933aef3c5c9e3550c9821a31d54c1366bde18fab66cb5aba5e1f2ef", 72 | "0x06ca85a1ff8255c36deb0c0ebcfc1f08dbb6a1818c2d2eb49e66c690756aecc1", 73 | "0xf0a60bf4828c91215c5ede16965051f9dd0686c283cfd98162c49e454a8104ff", 74 | "0xd1d7f478f15c4c22acafc94cf0e053ce2e395e563ad0a46ffa1f97023acaf225", 75 | "0xb8197c8269bc6204092715bb565bb8bbd3599802f6400d4187b52ab20ae1ee36", 76 | "0x0b94a30826e471ee938e2e6e1a28f9a0b52786cc3fa8d1cfaa96d4ef02c8d437", 77 | "0x220bf663931a44eaeded5b3d0cd903a34c5c8afc88a3f1693a7118a9853e0fc6", 78 | "0xde687a2333cbe77f46be28ad5f0aea51d1908b6624b608254d602b654dbe6bfd", 79 | "0x83b8e3c4a09bce7d95d4f68c23617f1e65e9ec7d2e30176f6c34c3257064765e", 80 | "0x15f4d18c517e66159c3b54254be3b354ab8b575cb498876f104c00baa78a3959", 81 | "0x11b09a699c5e4c131cda4d29aeaa642171e7bc2d957bdae32a1ef94917842fb8", 82 | "0xfc2539e52de640d7c1613ce65e2459041ac3dc00a8ec5b792ac405e45bc80a89", 83 | "0xa82f3aefa6a8546e87f64f6b70ec86de83c602d2e570679225c7ffbb16c6765b", 84 | "0xa1e6c1e31afe10393f1933011cefe2d27b827a1b74d51f49057391ec11806441", 85 | "0xd01f44afffe571efe8c23004500bebec8b40a31450546924520bb28f5b774a79", 86 | "0x6cdc16ba5d89ae4262cc705788d0e1fafec161700a44edecb0775921f4139d52", 87 | "0x43d571ddc9d7228350132e9feb6bc366b5f4825453eecf903a361c82b9d4b088", 88 | "0x0038352d34b17a49ecd196b8ee4fffea9095b7d7b63f5ba2342587947a4e9da1", 89 | "0x53dad5acc6bdf12be3b1ea543503ce208e8d03cd13f21beb6a53a4b3f456038e", 90 | "0x2fe534b4b13c6a80db28608c44041559f9d013f3278c247f6578b40d80312cae", 91 | "0xd99a24f21d6ad44e0c77e9f93a78e0884089bcc5e28d2050232e9434b797c729", 92 | "0xe84b3326de02085a6c5af020811c0e16b220a30dd98cdf8b4cb5d8617c2c8030", 93 | "0x7164fd6f1db5a0267b684623fede3db13e15b98c07d69d32454f62bdd07b5734", 94 | "0x5aea554aa64a5486ac69d697e553e54c0a4cac325a1d63e0a25652f76e9df71e", 95 | "0xaba78ea8436ce7fa708422b6cc83b6486122da25b4cf12326ed2d80cb471611e", 96 | "0x59c9b56816b112b9e195e434c555d0bae63822956360a6f8df4d6ddde36f3bd9", 97 | "0x04f7d62be0322753889e352686bedb75bceccc5f431b0f3b3f8071c53b897a7b", 98 | "0x8a57637cca1dd3488ef8211f9823e78b990a82bbced014259322118b4c04a82a", 99 | "0x9d3df012370e650655a35419610a43c6aca8adf85c3875ac856c40b4fcbe4ed7", 100 | "0x24ec8a0fda33bde58c69dca8a812780809e7fbdc3865862b3ce20906d2e492e8", 101 | "0x20baa3e163ca6d4a19d7cbd76398589413f445c8366af1ccb0935367887d369f", 102 | "0xc89cdebe26415d840597ce43086c32a37f666fb674cdfa1d501520674fbc7a93", 103 | "0x9fe62ae8823bd79e41fd519240e3df4be43536263f962f7b75679ef171fc8d32", 104 | "0x210db3537f733bbddeff512f6f6d6c2b3016f482421d2190f879483a70f97adb", 105 | "0xd41e1f87e86704cac5ed20680231878ceb5cbebca01b40eaab702fb2d590228c", 106 | "0x7a88b3b8ae1fe1aa1915dc1c1744b435a13566d015ddf821679f88b148d3948b", 107 | "0x496e85738e02fbd096e7029bad2914743d0dd8d96d2e5a77c7d245deb4ad02fa", 108 | "0x1755e3372d0acb4ffffccb30aa4616ed09b6724e31869e9e9ac7de56b9976102", 109 | "0x5dee265dce1439281c01abb0f9cad7dc35680e06be4bebe7f1e33f21e38787a8", 110 | "0x9e40a3a21f4d7b27b0e0b621295699badc9e92a82b97bddc2a6a568a892dd406", 111 | "0xf916448a0f0d02809706356852fff3aeeab701aff2ec6e03f663dc9459685482", 112 | "0xf57e98a81b6e14c96d708c79744409574653eee87dbe2b06ecd777b76cf73448", 113 | "0xefbdc570ca31dc7c873ec3c98d13a8406bb9c3d0809c14742836e71b41809981", 114 | "0x4df35065bb167992e5566d585aa57d82ec2535f56aaebf60c492d4f42b2db55a", 115 | "0x191e75d13b6f64b57ed23fee31ede977bede9326b10c357b76f051bacd6b93e4", 116 | "0x804569bb1d5869bfb20e364282f2f090fcb05d911d9120bec28572032df9cfb0", 117 | "0xd3a1d7b5c32a65ac6e41a266d823aab75b88149f19fe768265747591de7fafc9", 118 | "0x73bec19433edd9b275d3ca62f784da87d49e284f9a79b66994785437f86c4241", 119 | "0xc4d2642121877c2055cc6d1c72f3254f051da1b6f32459979e2701cfd0169080", 120 | "0xe6a97ea1f39b6b4fc4eb3e26cd20f684267730d802f8f1c7e00d80d532e7aa62", 121 | "0xb4118d4b1832b2f95544dec40c67d071bb06ba97d2421625faa5aa0e71123ed0", 122 | "0x46d55b2df4adb3964c742849d9e74cc1f3442eabb2754a08f1a174a94c3957d0", 123 | "0x0fdb840409da6c47b785e1eb22b5e2646d09561850637a456ea86775b40c0fb3", 124 | "0x1a0150bffa272258c22a4755490f4a4e5047eeb50e866a06668bbc9f12ae06c4", 125 | "0x1e70f95e18150e96fa9599b174c831a4983f026a3aff308a87576994219e5f96", 126 | "0xef4bab06608e3439c1e76b9140a196c2057122176e950cada1aea75fb7ddf8e6", 127 | "0x5dcfa51e72831c9c9f418c0f9af084e9ae29c186b299b5e184124e8a57b8ef75", 128 | "0x0a6dad816c02c8bf2d807675b9dd2455541ddbcd73bae83f0b0cedba77c22eac", 129 | "0xadf8ab2d5afbbdc822a831a8a6bf2cd40815a0face74c87bcaa5072f4c5fc801", 130 | "0x934465bff48210a69a2a8472e23bcede87d493c260edf995c5d152d1dad67fab", 131 | "0x047f1e21c4ff82bf8cb6ac295e7eef04c077168c0b0b23be42d1dfe2c3ebafb1", 132 | "0x8f34d4d82b463255c84d529623833c4bc10b545f362800a50c4f5701e61cb352", 133 | "0x6a70d7789364223e79da75b7852d9d3b7da938c663e989071f3eae6f145736fc", 134 | "0xef3264a8cf68824aaff646fbe90512515be03936a10a635726622ec57544f8bd", 135 | "0x4bc0dc03dc0ff3ff63b7c19df2eab573a5268cf77f67ddafb604bc6e9d9c3dd8", 136 | "0x602e1c4cd2d3bf9ffa9706f8bf607791b9a2021a552aac072fcd498ffc2af59a", 137 | "0x562c8ae7e17f6b72d2076d571316367e3f7f1ce0c93aa85cb4aae34dc000066b", 138 | "0x411023f209b19ac2eff32bd2c7ae74c36decfc6caba500dc8d2d958a1eeb0986", 139 | "0xd57ceb9e0c2e5ba22e4c96cfae833cb5e115bfd2e894909741f2eb09b11e02e7", 140 | "0x00bd3ec431dc44a6fdaa26b0d76dfbf56ab9a3de7dda8a7ea1c9efd0a58cf413", 141 | "0x86e21129aba6dd78b7adab4abac7e32c8060e752fbd3058d1e3202af4dc6cae2", 142 | "0xdee421d4d58a6c9d4a03be491f63b66a0de57586e5a89f95eb54d103673fef66", 143 | "0x55ef42a581370a33973d95efeccaafb3daa21c3fffdd3ed8a3b12f9379ba05aa", 144 | "0x050ea20b41663384bd2fda525b1c436d4edf06f8a41f20255c348b4cc9461aa2", 145 | "0x936d21891945b99639b0b59f41c9ba96b1f35e285d7b78228a09e1bfdf1a7f42", 146 | "0x8f518e7e0baa472a37ab12433637e4844baec17c59b2546e005bc1188281906c", 147 | "0x37f33006e1ad16b96b3be613e7bdc5df54947730d764acfc0b8190b97e11d2b0", 148 | "0x1154f97986a25aa62f4d1ae8a38ee18ffb258fd0f236f75c07b3c7850dd5949e", 149 | "0xf6a6cc78a224e7ebeee6b65fd768ba42c586deda4958106cb78a1b0f01666071", 150 | "0x12e2f3c0ae6526a61d087b3e9b85413e8c789166847b554e34d531048201ad19", 151 | "0x0e2c8a0ecdf17e96f9c2ec41083dd5d4dc4907e811566a67c47c873afa27b572", 152 | "0x09107de18a165032975333af6001023c27efe13d910f5d335ea917f0dcbca4bb", 153 | "0x6d7fdad53bdd58788bc5bfe33548c6b76fb194934099ab16c53ea513d32eea90", 154 | "0x85577d3761179f4218c499f8bb8cefaf822dd40f057c041c7510985db81b2c23", 155 | "0x3269d345cef69b1750e00c99f0ab2fa590f55a190eac127574f323c39aee91dd", 156 | "0x2446223fb6efa40a55ea71439ea59420f1fdd1363b349379d190ade3ce9f2cc6", 157 | "0x6e3e29ac5b403d7114b3a3618d4b425e0f4c0c193a68a11d7d13f4138168732d", 158 | "0x43c5df09d629c696beb836461b01f10736898c50f257a7690490f34deb0c655f", 159 | "0xbc723a2fa3f6520162c132193a6eeca14cec5f91464634b4acde3e178de02dc5", 160 | "0x12c60dd23b459309131d4e69d0607918ddd90d649dab7e700045bc4e9b0a2612", 161 | "0x8edc016c11838385495283149a2f936052a77a503938d7c8b618a895daff56e1", 162 | "0x57704294050bc8a88e94986d3baf897ffe85f0e32ed3217b9cb59478693d211c", 163 | "0xf63aabcae10bf4a7b23c8187f8b83e2f56e0fef4c41d2fe7e034f83bd0141da7", 164 | "0x394f616b5d873bc1053c65242058b0070ce7ea03474de251b8911251f271f8ec", 165 | "0xce233294efd7a00618d6e314483f02317f0ef1262f00d82afcd4d499e7eebf90", 166 | "0x704dcf722b5b47e11b4bff1466ed717d1ce6192eef81b2d141c695673fd80558", 167 | "0x326f89fb5cbcb7e183a1b9ae67ae54da7dac1d629df6dcbedfa60d888bb01cb3", 168 | "0xb4f3bb8058e445ecda87c5bf6339be399f8cdb394afffc2f763d8a2cc5ce9e41", 169 | "0xd6a58b3f39ac40bc4160bb7153eb1d27f32d0588c29004e19d0a69cfa0d491d8", 170 | "0xf5b4451cc1430268b55061ff082dcc88889fab80d488337b26ff5c37530534cc", 171 | "0x5f2bbc98e582e164fe2cf0b23649537f252bd6a468b3eb181d80e3a635b68632", 172 | "0xd0456032ff40f1d7646760f9f0191dc57d1fd090972b7ce4293536410c28fcdd", 173 | "0xa0a7d3f1ae02a8f94d642f218c2cc81428b966e26098ca22dd56bd59d2c0788d", 174 | "0xd9d965a9ca9bf4395328d03a54db2e3a74b713b79e01bdffcdaa65cd862cb5b5", 175 | "0x746fe3484b1c158b052eab0e4550083b9028fec0d49aa58294ec2d8aea2af8a1", 176 | "0xe13503fcc03baefc40cefce073d04559c457a8684e904e92a94b7ab83ed9bdf7", 177 | "0xbe1f59befb13e895da32d2ef3f003472ccae527924b8ca3df8044a62da8a9ea1", 178 | "0x2d4ecc84a5b34b53895ec10d42daeebc7d056e3a4476c7791d9e9b44d640bebb", 179 | "0x60dcd7112887a2996f8c677e85be0550b67fc754a251547a059420e4453b364b", 180 | "0x5ae9280798b993d7381ebdc58b2da0ef82c4729926da7d6ab5337ed421eaa838", 181 | "0xdca7f6a0ef18ee26dfc2c75c6d1c7ef4a134c106ad72278962a106b234c273a3", 182 | "0x821fa384d863f3bc585a38cf126d4647601189b1645e7223cdc16a82ec29808b", 183 | "0xe9def9954ed0e3fcf0ea0ab4159a5e8c3cb12ab15c58905a0d4cb16ff45d1dee", 184 | "0x08ad1911d0a1093cfe134bd051f4f2edc02246e9c328b0f7ca08dbeeb1956596", 185 | "0x7ffc5789f49b5ecc68aecc0e8fba171990a2b68befd5098d753d325c7e629dd6", 186 | "0x5166d7c807c5b036e24526c49e93790100b58f96a646b1996ccf5a6ab08834ad", 187 | "0x1dd6e2ed2a6b5d9a77370a2a0e54f07e0bbc8ae9c9ce0440b1ff7fd427db52ad", 188 | "0xfb6aa685466e9f1e0d32b572e0d2f8801f4eb6848c2090b3494dc1bf6c94c8bf", 189 | "0xa875d4b589698a2d4b324c5b8c457802a92facf44528c18d3297a39762d7843e", 190 | "0x8f26251f97a92111704ce17cd84b08ca7a544c87b992e7f63e2a8cb7c1eb715d", 191 | "0x5fbf25c0c28615fab94b622227027b5263592521f19a46c8d46a12ed64382a21", 192 | "0xa7f9ffc162e3d9d18e4513dc75eb2d2a72e69b98e3345be80c69f03060884d6b", 193 | "0x24a7481bb58f5b98128a1fd0768ebb3d8a540a7a5f6514884aca8bc6d52781a5", 194 | "0xcf17b33d937f2b7aa5213a3941bfc69ec6d6ea46ce606a977b12986e9b2a50a4", 195 | "0x91dfc5e7e2d8d1dfcc3cd47daad6a8a3a37ad28db2174e45e761011a3adf5ca4", 196 | "0x12162793a3e2f540ad22bb663c9308983ff6242c45653f90430f52db2fd15766", 197 | "0x53f9b53eac7c8eb9fe9494d4c0c37add6f8071684855dd8b374128833d301608", 198 | "0x7929d25e56e2119ee3119d4cbd41ecfd3df7993ad81823729cd3672998205537", 199 | "0xfdd27315369277ebbc8bae83b4c6a255d7308949f5d5608a6b7242d38b18307d", 200 | "0x6b1a17c2f6b6574de52bd1b2c8c8d96885e931ac8600847d747e2aa2c2255154", 201 | "0x32ef284d9ae69e097d24076649c62766db049e7b5ded9bf1e28c2442b5b49172", 202 | "0x6aac4212093355fca1a49a061f43137452f5c1e6b4accb959c178329747ab185", 203 | "0xb1dbd17041c8eb400f103aab080d829759e537a666a96fa7e37c5ec2ee24bea4", 204 | "0x84eded1fb29bf3ef3c36424a131b9ac1dcb4ca63a081ac3ff40f4e9b39771c29", 205 | "0x3a37df094eeedfaa08e79d68b00272ce329ff994afed82a7d3edd88bce8d7459", 206 | "0xc2716d5a09346904e41c34bab9c4a71d0a0628fc3f4c1cdcc47da73c2b091237", 207 | "0x50c03cc7e51ee799b59e608fa1f50199dc9867dbe223fde518675c388b0d497c", 208 | "0xaa4622350ea749cfe7447b138dab3a57517d5e5fee23760d2a3027ecc05d9ad3", 209 | "0x2a1846136e5a8f8639073ef2aed669b9752ba58f74d39dc8e02c838f8936a411", 210 | "0x46abe7dc83309eaae715677483868aeae24258ff5983fb005c88afbde145aff2", 211 | "0x861380e86db39ef8dc38ce7226f0281bd059b6f1a58fe77e80927f51a8574dd8", 212 | "0x304bf1f326d1219ecd2540c4cdeef6520943a4535666f0520cb72984833fe44f", 213 | "0xeabc494c5021c654b6c99eb0cd04b29a7dd39bf5f3df518321c2fd348a7fb16b", 214 | "0xc0066e9dfc06baa4aa799a79f99d0fa6842c5b8204c76b9392a898a0ef77d73c", 215 | "0x701e5024df5e06637b20cc5e038adae7832efda0ec61cbc8ebfb424f95f13878", 216 | "0x1cfea6dcaed02101603afdcab18ebbf11e8d1cb68be32fc2aabffb6d99634f53", 217 | "0x5d50d07bf5a893bc9cce9c56edc9ece01074d53ca82279118f19dc9b4efa733f", 218 | "0xbca69c7f6799c0d6120a5bde977f4a35463509417d5fcaf46a2da9e752224345", 219 | "0xf389c2a350af6d26d10aab6a3010bb79b213b64555718994d45101a2cc8d7bb3", 220 | "0xa71d19c79c1883f02acb4b7a1e04a7d403f4e1341b4ab4ffd5385373af95492e", 221 | "0x4f10f9e057eec4586bd5be23344fad25c0b39e26a98207af3e8ac3fbbeeffaad", 222 | "0xd5f78ca25764e126d98bc159b61854e59f094bfe0309671ff496e228e5d00931", 223 | "0x3636ac09e69e5eddbfd4082399aa8b13fa4829d6b01b247f5476792b6d46f606", 224 | "0x1da87ac6022848c7f8d88bcfb9aa2dc8e210fcf662ddd818cf788705ff51c186", 225 | "0xdade40ea38ad2763a7ee6637f2ab79cc715ca71f5a29cfe8f8e23e3199700df6", 226 | "0xa98047ec5744f5e89ab9d351b429678b215e554ffe7fdc98fbfe7eb896540103", 227 | "0x6da2ab051d3ecc2c7bf7847505ddc531423061e5a71e4fd75b8fa2bacbfe72c5", 228 | "0x108418d3eb2f7d50de54f2a9cfb7df459246bdd2edccf9e588443f184a7929af", 229 | "0x28c253726ecd1226f5e4dd2f532f8228ccf6ddca7f915f0ced6e0e50e6b9fd86", 230 | "0xa5eee2996fb09ed919bdcb588b0dd031f3aea0c7f48deaf11d884bef161a8587", 231 | "0xdc2738f050f4a8980ed9e1da045d13a95bb62c730811e7b59ea8638ac6962087", 232 | "0x0914689ad96f3f4e88202e0ded8fbea368074e1f4f3de0ac2b2b3507e1dc431b", 233 | "0xfbe40171e5272c53163592d117a7154b76f6f0b15915da9d35f9f15ce92f3f67", 234 | "0xca18e751fd9352ca1206847f6e23da4af279c7e13940bf63c13695cb19ff6d5b", 235 | "0x641f94302df734c6f1744c4a347c3e2e2f26afa6aa75af6d615779877da16dd5", 236 | "0x8a5c0b1ed9f129e7393ac36aee7b7c8a9b64f6e6cd0e8191aba2bc30cd0fdaea", 237 | "0x3f23a974c1bb6fb0056afbe77dda02c5c17db9cc201f371fe9d6721cceeece55", 238 | "0x96e15b108afe7b127bfd97e98491b6a93a312127f351a3ebf0839c241685b2e6", 239 | "0x0bff0bf70a9fc164635f3326f4651d48e9db5ee93db8c8b2031a52743d50f480", 240 | "0x9c17495bb8b5afacf031473a9f0299f57cfa1f4fae510a1be0e723e38177e295", 241 | "0x7bcc0c68363293f1c46029e842ae21243e863904e45f7d138139f16c216d80ff", 242 | "0x751ed22ff36154b66a87a93e4c69299964982400a33a157904fdeafed42e6908", 243 | "0x2a1808a4cf2fd856e40e1505a66230e32b1bc42ce771e88c25a1fe38fe67e5cc", 244 | "0x36e85d108886c26cc94f4f568afe6874ef4ae64cd656e9188d88b876a0009f04", 245 | "0x4f0521df4d79b0ca6d22af562078cd9e66ad8d2944405b18a0fdeca62a39f9c4", 246 | "0x50bb200cce9af693e957d811a04ea9c27d0e529349c1b0e5bd2c4948e4449474", 247 | "0xccc6ba68430c48aa178c19051d87ee27a3b66477cdf3d836f7ef9454979f2d4e", 248 | "0x91eaec38d6b57c9a0f7fb30f6554eaae03364efe6ca259c35f296aec0a1a5734", 249 | "0xbce03460936ef47f55b70024db4a155d5ee11e71073fb708a1b4211c37819eea", 250 | "0xc5e87a28a922ace6422e5d780de57c1b1d94dd84170726849dc5dd59fbe5f2f9", 251 | "0xd48943c2bf342e3e547b28bac24410cf8c8431ed0703d84befb9efb9ed190dc3", 252 | "0xb04d222570ba2b7d1a03b6b0f156daebae1f9dd2332f5025b88cbe143b11e374", 253 | "0xcaccfbffd913889023f5f321288e3f383bbaeeb07d5189d55590233a4185a279", 254 | "0xf5081839b1931ddebce40cad0dca23e608a136ae7fec1e6c3966b6e86f31fda8", 255 | "0xae3abd3a6eeb7e8647e371d6de77877117d166bff5572acfe919cd7fed4b8a55", 256 | "0x0a442d20ea6c57ff1e48b20882094d7a7be65e7d4c30b4670f43e8e8482fb203", 257 | "0x3100f76b04705d7121b595da28e80d22bedb40e33456b3391793f86971252d11", 258 | "0xd439a220704b868187ea550e80fdc90a7b72423c8b0854a361120f0492db74f7", 259 | "0x84c9134e411655818724955fce89dbc864030ca681926952e986e625751f4ad9", 260 | "0xc9b0ad88019027d9bcde78b8da226a95a1168fdcdb0dee395b2e86afb4ec6ee6", 261 | "0x0da8d4aac909f3e99eb43c3b234b4452c5156d8f6492197e690613f8a2f9c9c5", 262 | "0x7f77d1a3898ff41ea0b64125b2fcfcf61b1b868ca4b0c54d693fc601408da43b", 263 | "0xbdc89f6a756e133acf5af35fac75eb5382846255d0b7adec38189df795dbee0d", 264 | "0x7bbd70b299d535e8a18ec396f1a73bbff0f1ec4cd39e4d39fd8367f3ae46d030", 265 | "0x9c6bcdd10d256a647b245ba7b2e768fe160d5d1c34e7022c2d64bbe9b034f49c", 266 | "0xf3c655805260b000109c4ac3036c7a9d5e72b2554c171814fad6087277dfd4f9", 267 | "0xdb4ff63f7e49380f6a6e4e1ed6d926268d2ca88a2e9d50a86622d1731b071a53", 268 | "0x8e25c69887e6530be94672d31150ae80766ee6f5b70cc5f0f65ee1c72f02c310", 269 | "0xd1f72ae6d6e7e1df6c3eeb22532a3584d596a705c73d706a7169d5916f41a33d", 270 | "0x897d86c4404cf99b9e7ebf006a9d961feefe762549f5fe1e15c8c8da213a5e05", 271 | "0xa731d1ffcc4346ea34193a5671f6540b309a0578747114255e08d471ed78253c", 272 | "0xf8b40b0a4adc02e36288f5f5a89d4ff753ccf3f770e24561de57867300eef334", 273 | "0x934e73afe5b9d7b5a14580505253020f8e16cecee6843864f10b5e43bb5659f7", 274 | "0x86b579b24c486d3d67f386ccc8fd6aa2578217c8de9cbdcd6f4807dfbb719ede", 275 | "0xc021263cba85c4cb71b27eb5beb1488e2f3edbce22378ce7a73951e06d3c64d2", 276 | "0x45300d489a3a6a3779466b38e3d155f38a81d663be7f65c73e0acfc270d5b909", 277 | "0x36c2dd38e6641135fe6065a203b52b5d16c3d72b0d9ca24db811b799b0527235", 278 | "0x9153b2425b863789fed4912784bf8eaba44444f7c1792e1661af49367cfe3753", 279 | "0x0c69f2ded6f308e455cd231a77b587c3d66d88a548d6cf0a42a3365010b5f0c1", 280 | "0x7743a4e01233edb21e10ca493ac70c70c43fd36d5d99f1d6e5c912c05ae96988", 281 | "0x43ac7b110945aefe39ee3b580745218c4e2dd84086ed1293f09aa001944bb87e", 282 | "0xbf640be2489791a4eea1e189c25f752808a713a6309c54d10fd933a8df6c5150", 283 | "0xaefb7759c201af3edf1cc8741de64e75cebad817dfca51e51e5a8f03ef2e532a", 284 | "0x51b14aa5c28c7c4e5651470458711cad33e422b0e7e7af1bafb248aa91580c52", 285 | "0xbfc3c44c07f45d51240a36df48966dba9284218202d017969cbe2272a9a3ac58", 286 | "0xfdeebd6bc2d942a168d2d53e39cc864e09506c068b88f5418264a579b093e9b6", 287 | "0x9052900b1983b5fc0606208e9b5cd32a7af0a0afaee1e080a85769555222622e", 288 | "0x16bc2387db4eeb6104d8c0d293b0c829bc78244e61eb5c868e77e6a3a9422fc5", 289 | "0x7720e9a23a8d2cd8217d6ff273bb5d13948ae37878a20c490271db9001494cb0", 290 | "0xb0d827cdf499cc315da19a5761373af29d3c3882ffb0b371bb2d489e541dc827", 291 | "0x23c244b9d76a3d687b9a5e78878b8d2152c532c5d07cabcb0aa7b82165abda39", 292 | "0xb5ebf26b45597d9a814761793b7e42c9d13c1668f0c2cbfc977ad0ba700588fd", 293 | "0x96faf4eed90bbccdbd6a22651a85988e9d6c2f3558d59c64ac64648c056c415d", 294 | "0x7a47748fc3d1fe570d3a9c113a906a5ae8742255344d0d5e85d2c2ae6c879e90", 295 | "0x82dd1ea9138234c92d39de24025a8b1637022030bcabb30562dd73cbf1fb514b", 296 | "0xf57ba43b37922c699280a7529154fbba53a20ad36d1cb491e6cc697a29f7b080", 297 | "0x086d55c4ed12d55c2b07abc4a87215aea4e5ad317f44d4d99eae7ecab7c4dc31", 298 | "0xa0c22ddbbf5b5185b3d5b957d93df92245e5e56fdb143ac2b36ddbd87f08871d", 299 | "0x1ff7326254e07555d1a506457d5feddeb4a185366c175ac404a82a6d8a4d0897", 300 | "0xbc6ba4669adbebb7430318d9f9d55f5170c755de9f9daf8cd914b51e2d9ebf3d", 301 | "0x30957c33f0840fadaa4aafb5f91959f2e5635ba22cf3e9276528affc18fc92e0", 302 | "0x8cf2c7399060a94b6b9a791d3f50b95eaafb4b2cb60dbc3eee8e2d1449405f21", 303 | "0xe76c686c10600a09fa21b374ed9c9d7d40284e67253c8a3db3b47d1ca2120345", 304 | "0x906ae04749ff594f9ba074d05bb9382b2451ee9906ab400d3c543f112c625e26", 305 | "0x76bdff8463030b4c175d1c62c281100f4c68d8d5329549aa48e334631559a18e", 306 | "0x86d2948a69f92050c2fbd2a9313b6b88f63941f74547f57a1c496fc9fd3dd3f0", 307 | "0xfdad008d6ddd146ed8e946af133826ad1a3a9070036f811212e3421ed2daba2d", 308 | "0xeadfcd0a534616462dc6814bae71fdfd06b5cc4b304898fd337e2fdd1fd0648f", 309 | "0x11f786c17bc542e7782b4ba5346a6d575fa2200e71945ccd195db93f89200cfd", 310 | "0x4fbd530a2775bc99784165fc661f138711359ab00412ad44c76bb178cabc402f", 311 | "0x33a8f460ec0fc37b78793c14433703abfa1046a1d0ba6e37c3a88f761e6ebf6c", 312 | "0xf048d8bb0320520d9de437d80172152776b1eb46308d89a2d0b0eb47b3116200", 313 | "0xde5410aa0652fabdd64a70f9967a6c320020a6ca03979aa4f58d461fcbf930f9", 314 | "0xfa9586094f58bea4239528aaee91cca8349dd4baff775e065359b70259e24b1b", 315 | "0x35f872473f781573e604f93b4f1bd659d4d84ca11efdaac691e738f1efc055a2", 316 | "0xfce793580851458bf6d8bc0967c8ce9e4c374e6837de0791a12c8f5cb4e0bc64", 317 | "0x6eab30ccdc0ef141da3e374c34e8484503ed950332e439dd5177827b5c86db2b", 318 | "0x395e3b68eebd14a258547f64a212cc7b947165cdf4f1fb5569186f84a300d865", 319 | "0x3ffbbd2cf6e66b03f679503c94072a93f6a6904153c4202694f7c37b1fbccfef", 320 | "0xda13a4f5c628bb36da5f2604d6544bc5e829946c5b7e7100c9ab2c30c3be31e8", 321 | "0xce36caa3a7bd398b0636c2ddcd188231c9ce4b02d99144a766bec0abec69a0d5", 322 | "0xe8294c4319dadb8cce7810e24c6190e80bbeb05c8eea9bd4112d6d72c56d3f67", 323 | "0xb7b633e38e35f69192858943cc347c0f4a24db4a4e45351f6d180109c48e4b6b", 324 | "0x8baacc67ea15ca9a5d8b8d1e533f6b0c91dd0634dbad6d4f12a97b903e9f7946", 325 | "0x4562ce76c280d8ae6720dd6494bfab066d157a2b40cbcf9e81c5bf8809b1d83d", 326 | "0xc6b7bc649a101f43e3744571620a8fc19ea3742943ef974c727369124a103956", 327 | "0xccd4b518beaf102132aa9fefc75543f633d8b3dad8f9d3cbd6e0b19ccc7bbd7e", 328 | "0x53b7bdf8687ce1ce67cf4c9e4ce7a2696af7dad7b79fc9be338b0ac497dbefdd", 329 | "0x8f1e852020a54d6a68f9945c00b7a43e1c26677b533937d13781f3857e2e7ac6", 330 | "0xeea784bf652fa329dafdd2941ccc63fcfa1118df61f5e9c4b20c67c3393cdec8", 331 | "0xcb281ba2e32d69fb6aa79a1dcddcac37cd143751ec83a72e73e02047d1a6971d", 332 | "0x4d17923093983c672f20e180f16f5ab8b74ab4a8cc3227940295d50cc0e134fe", 333 | "0x874a921f135814470afb180554358be09f4eaddc7059f5a329d09ffb215ccb10", 334 | "0x0f71a1e1209634641b921e00c7f7d6a4d7e693993a015a108f2bf4f13a61bb66", 335 | "0x2b5b002ad562a130c5ef3066fd8ad51b951af7d72fad0400bf0323b663a10fce", 336 | "0xea0d8a34fcecb7dc4043fbf95bd631bbe305c77e5fda25a4689ef14d3313a98d", 337 | "0xd8224514ce5ed833c4efe3e9dd11fb930162dec659b872845bc4b63f4d254f19", 338 | "0x3bd6d883400c6280e1bbc9a76d2435ec6b4d4f2c3258d3d9aa73b7b2c086defa", 339 | "0x21952e66a61c8f109a2054541858881a5d5f3efca194a9dc9dc56459cd96c2f5", 340 | "0x9b1a8edb4672fd9ee1837a0321a0ae57278bfbd9a654a99cb0208c731673f0ef", 341 | "0x134547bcfa19b641125b28cc562dcbb61eb2f83339e9b9ce0b8c79bbf89a3df7", 342 | "0x660187a100eb6681fc0ac9b6eaaa399eaf93b0ea6518226b3a43ee2858497493", 343 | "0x550b17cfca2e681765e31199bb10fc85520a330ab9a7acd3c2a638714fc49fc9", 344 | "0x603ecd919113ad75c6a9a65fca4bc2befc2021e3541cc3dd8928fe2c408519cd", 345 | "0x7ec6681f23e62db2cb28299a8777061d032904d2c6145346f5db6d07777e02ed", 346 | "0x4e0a524bf41d42666ed57613362efbeb35c4df4d45abe01c6e2e4f37431910b4", 347 | "0x92b307c3be91be0019bbc02ad802719c9f0fc988f0bb5dc137bff5e0814f71e6", 348 | "0x5fc6592faa680200ddf78d44308ebaa670465596d2dd2a7a60c428f0d7ed8de9", 349 | "0x220dcc2ec3d65d5be171cad98f45f71e575443121e42842d7f9ccafaf65d43ee", 350 | "0xe3a234a641f57b1220d0c25c4d26ed9b6f00213baec8d2b3eec99192da5b1eaf", 351 | "0x3b86168102c788c1fad468a00fe5d6913c009e787ce52368d78f6333263789d7", 352 | "0x4afc6c659804d983e0d269727c37cc696a506f1d246a0b2550dd71b8e336c7fd", 353 | "0xfe303df0fb494bad2baf3967e622e3dde5ed3cfb38944c2fca362341b7996102", 354 | "0x7d047ff9a519bad80a96e617fe6aba85077d377884bad4d2b92fabf5d85157f7", 355 | "0xd86bca30e7e7333f4bf07ce1e6da4a6e11ab3a593ad6a00a3e6ad8c815babca3", 356 | "0x2b30d1447621f567b07a8212acac972e14b9d2e33aaaf39014bb6a4b9672615d", 357 | "0x10fcf1248f25f293e09108d9283cc5f151a08aea145b8689f33f926f69a6dede", 358 | "0xd5810bad618b5e68ec1387e5619e97de807c56303f27743b29c120cd43379964", 359 | "0x5a23a25eb3c5ca147f4d72d6632921ee84cb4a848feffb68df507ab4e4594d14", 360 | "0x755a82723b2a28a9e394331bb25016332313709d8d2a91c6dd7a74f41c0bd4e7", 361 | "0xd9a0b4e3de8c34e9dfe21a3e9e873c089f4deb41c928c638aa6b5299a7157e21", 362 | "0x4515e8bc181420cda8fc3852a9bb4fba12f0b1d9eba5bfe9e4f30ef657ec7475", 363 | "0x958a4579b7914841dbb4fd0931c52986bf4a8bbcbc8f972b9f322bfea4cb361d", 364 | "0x19fab65e3118f30abdd0af4d160586e6e0ab887fd57389b168d520004b0ad688", 365 | "0x592d9bc593bf434a4580c180f43682e215188119d53395555e6a8affd8c7a99c", 366 | "0x1947db9e43b4ba827d6f0e83c0ed132ac1cdcfa4ad2391158335290818996927", 367 | "0xa1a8556db620640a67c381f54862353894b10e3b03666ab0454a5d872d1ff542", 368 | "0x20d3736716038a2e719245bae5a5c2c73a8c234cfb5505548681fa9194988e41", 369 | "0x3ec9fe7c595a3f329828a88a7948789b448b2b43f8688fbc991d7a7d4c4c051c", 370 | "0x4037ef86167e7b7b87043045ef2d6ad3a0a9da7412e975c7b38d10c27b2028bf", 371 | "0x229a55d7216af386e15ebf830f8d75c9b60794d5e95d4362810d6ef9f610d988", 372 | "0xd6b9a3576e1c001bfcaa4a13bb58a5429b733902fe8ef4ad8c7d880aa75aa845", 373 | "0x238e2ac21ebae4da653663fc5bc0e87960c0fa396d9d6a8ffdf57a7c2abc7118", 374 | "0x2b26ddffcbe7ca7687a0b84cacb90e5a4b42d151cd1023a299f93893e176b457", 375 | "0x947397d72b21b087bdf1dfa99548d17a86de5238a29b3ea71a06d770e9752b84", 376 | "0xec7bda7e01e22ca9f0dfa8a461fe6efd2de038f6c39f1f6247bebd569dea6a21", 377 | "0xf0ea03073ba8079a2747420db8b69760fb12793b4dc676cfeb046cc6e5bb7bf6", 378 | "0xe6d3b1d3360c328411ac8a4239bcf3dc33a9183903ac17b4497818ce39aae24a", 379 | "0x56a088adce27d3a59767da338a747d1de2d4d66a6276674e99c5f52468b68b11", 380 | "0x0038bb927f17e389da54bc41a7fd4ccf2f1249690dae3b51dc65c610a556a6d3", 381 | "0x42b4cc085fc6571f1fa277c0f12979821e3575fb489e63443a2c4e1b4de53be3", 382 | "0xb97436ce68c61a395a0dded6bff2b47794c4b5f6d0821458c66b80fd0d6d1bf2", 383 | "0x9880e34f47ad3874611b7d32c9d612946f0d14ea6aae1f29b57cedbfc450a847", 384 | "0x68ce856a25267f4dc3925d2292196f4b2c69432022d82dad89b7cc6e7189d0c0", 385 | "0xbb42265fc28450df19f4b88543d8ae8f6d81ee9e9acc7b7a2edbacbde305edd4", 386 | "0xa54aa20762ad2bbe5222bbadadea70bf6bbf6abb0ec0be81b0addc40d3c94c1c", 387 | "0x0935faa83ecf171c27f1338bfb4be5a914b8ad1c3a1de8b4242ab4858e9fc04b", 388 | "0x3ffda8757474f60d64bf2a0d8c1cb8f04f5cca2aa73e4033948bd39a62729a02", 389 | "0x02477e20f7eb730a95c29abf8d24c8f936d6d39e0eb6e86a4c9c532d6ef0babc", 390 | "0x6b3a0601aed0fa56a8605ba543db078c5871a924ba0c54b8b76e5c7d3ff838f2", 391 | "0xea7a9deac1ddb8e75b8edc7fead0f3772189694a537ff86db94f94c19eca918c", 392 | "0x02c5a3d8e341453c6f8c1e115df6bade6410949a95953653aefcc15dd30ed5d4", 393 | "0xc2e8eddd8ed2d609be08f7cb1492a8a2b26a319c0d7271485b62ff6f23618534", 394 | "0xaf5abcf2592e2128d5b8bc5b5b28c725c3f5083a8a27735f6ed2d8373564a296", 395 | "0x1f52a106ce74b224ea9d7cb0471cc13105101ba5686289aee63fe306ac44d2bc", 396 | "0x8e5b05e81d47bd5054e23e39ff92df8ee70fc42fa5f611223d143cb9b5e56ecc", 397 | "0xc93068e37c664a1231793a3bb1e1d42c4f7dd25f77b4ad3e5933ff021412fe72", 398 | "0x64fb2c86c279814156f3c8500a76bad46b16d482b7f03f880489cead00335ac1", 399 | "0x1c6231399cc485cdc32c0125d4a17f8c7f4a069c2588db525f87def305e5c480", 400 | "0x6fde69cac723607aeb84f4367a5f6f4d62316af09582fa44e4caf1057cd26a76", 401 | "0xc94810813e3e7ae6b8175f9cef040975b2fb91f3c3f54509ba8e77c481731537", 402 | "0xd2e3f1c529635a6b6bcb1ff5029ef70cc2c0d8bde3e34d7f88bf17f67cf6bdc6", 403 | "0xe843c411d8ee34239af02fb958fc91971a19c6f299b1c4822458076ff94cb380", 404 | "0xe9d046d7a07df601f29609e8005971c88704fdc5700227a31db05594c2c07adc", 405 | "0x416ac2599f70076e12458788d4b307405ed41afa5a8bd24a00df9af0cc02b8af", 406 | "0x21a18d301fc0694cdcc06a4ab053a979b438187ea320f251583b6b15cd746a15", 407 | "0xb7857f99c58681d06233666380a982c52f3c881994567c739b392920abd528bd", 408 | "0x0880ec2562b2b7690fd35ce101d022df32f56a34efcc58fb22baa030cfd5a293", 409 | "0x409177f8bbbcdfc1963179bcd83282b99349e7403fba3f89879ba7ee56fe7697", 410 | "0x47f642b5957a11b936b0fc1918cdc0be412efa880520e04c470e1a48d7b54675", 411 | "0x1c9eee338d249d9ff29fce771a95f70390c460ef8f62a3f27999a21ef9f34ca1", 412 | "0xc3c0fec659c31f7edd875ef7f63859cc7536da4828d95a06fe0c15bc89397f2f", 413 | "0x6d43fb3eff9057a4539a68502dbe66224aad2f047e5e11469f7076a3d2fe5cbf", 414 | "0xeb8e7d2a9cc3a9429038e5817fbe89893a5195d58d9efadf701688576644a2cf", 415 | "0x7e9ba9681259846d791e6be12ed388c769ba9b569066d82cd8956888e1a3cd99", 416 | "0x63903acb12c3a615980f1823ada148678e9c1c71bc4db947f0e5d37b3e26901d", 417 | "0x1fb58300535a2bca69265c52328c7fb117fc4150dc0d2cc13c5d76c0d35bb749", 418 | "0x8fce08975ecf1ae841003aadccf3813e72cd081752756e3a8a5156530de1b1d9", 419 | "0x74c53624c6a189c1d8af670fe60f99e91e3d89b591470083d1cab8a9b5134a22", 420 | "0xf27cbdce8d42b22c78b6a1f96bbc92614a6676745fc23c5e8418b7b84e26c77c", 421 | "0xe58488958bae0aba6d1de565e0a1ac9392a5ff6274e8315e4acaa4f62abe9ba4", 422 | "0xed1f3211a735cdbed94ccb20bacd49d06280c587d5ba5bbc1afe87f9a8f653d9", 423 | "0x0aff53ba9052751e181d0566a569097a00a86675eca46e9c6f86497f820b2519", 424 | "0xeadd2d99a7f79e450547e4512b5f33ae386cf802033fcd93b8af4378c9a8db98", 425 | "0x68f9e7c0595230dd1e5739b3f9b7af56ebb3bceb6814c9b7db86b4ec385689d3", 426 | "0x91230f142df72beca41365eed53ef5c1679f7518380b12f193ef8e9549daf31c", 427 | "0x47c0c6a57a2916b0fe44fff0ce63552ffb348fffc828a651aae41be804bf7ece", 428 | "0xc97535c60e57a62a99d0ef36ca5440d8f04ee8ff33bea8dbe2e3361c38d02ae4", 429 | "0x7d62c3b8f5c6623e4bbe99ba11867357ede60df81ae3bab0fcae1176aa1192ba", 430 | "0xcf8fb691088e78d51d6e1452a26d04846c75385ce49a5bb951cc7f40b086fb5a", 431 | "0xb1618ea13fb478c8d28ada82e53f613a08ca949e802cc888b55222511caa8224", 432 | "0xf9fe09e627df25b949aa90d0217635e794fed223ef84285cfb243253be9413e9", 433 | "0xfe1a109b8c1468d1cd96f84336a541fb9986cfa6552c3ab9a9fb1b24b8c4571e", 434 | "0x4168eff754cf0c5a7e1b1c93fc5799d81157d3baf0a939f350ef64577554e693", 435 | "0xdc4154dbfa94cb0db2046bd612df09c8852d7a46d29ec4ee06f88e37a11bc338", 436 | "0xd9d02f4e86604bf5458f042862e095adfd98dbd295b19b414bb7f91d0cbdd6da", 437 | "0x875e3a0a1310c1c6ae360db91ddcf3bf726c8091f949c1cc49b114b4ddf6583d", 438 | "0x08394f2a180b8c070f1336e7c3b738c357186b81d79d3376e1552cd6f4af3c2a", 439 | "0xff3183b0515ac2b937e57aa5efe88b73f0a99a17f17d81bc3a706294c1573855", 440 | "0x7279c81515517c195afdcc326ce44089de361697cdbc99d21275ed9db637f356", 441 | "0xe39b50f0a7dbbf9f923e2ee7ac2869299a999d823bb3edcc7cd7468d88d3d21b", 442 | "0xc6dc429d727a1b5c7175d16e523dfb4c7a1759b63fbb56996e5d5286428e1627", 443 | "0x5ce167c8f6d6c2c63559ae8fdcca085e1cff8033e989633567aa18d971c661b0", 444 | "0xc44ade8e75d395e22069c54e4d2fdc8de9fb58051c8168645e4f608678ff93a8", 445 | "0xdf730cad0e218b27b875972858a07cd80de34b4bc7d3af3246f4e5843e21ebe2", 446 | "0x9403ad848ddc06f3d7358401636937d76b2f61d2b08237d8c11ff3283af5095f", 447 | "0xad6408d8c1c65473e0a3f0405c1cb2a1706ad676a29f0f49ef61dcf77f9e1208", 448 | "0x61a3f01b7410d2e10381dd8b85188be2e15e4323e3adc939870c9e677e65ce40", 449 | "0x4923c0af8ea0b7822410422f6e3c83a82e5365ff5f8fcf64f31df52d0050a51c", 450 | "0xfb40807cc5b11b23ff821b4256eaa75f3a0703370be517fcf4566988920a1bb2", 451 | "0x06ff64170ceeef2f0ee38fd7c5f3905e065b825f806c98f6b9e04ad22e8e4def", 452 | "0x0777259e23a09d4a2f0da130481d11f12ea9a52ef10766c2148a7a5fd6e5f97b", 453 | "0x78ddd26d8d7af9b1e0094ee8b0fbf1e4bbbd10d2a6273b936fa201b933df4fae", 454 | "0xcac755e51554abc66dc6a3e10221b8573a2b752f54a64d79820d37fa061f6282", 455 | "0xf331b267fda3af9ef7450230227b848e08d10619c901b5759b07908c327ef057", 456 | "0xae5b542e66f624f3a4a2b371f7a17a89a074c9d6f8905cd9f2763ea7d649e54d", 457 | "0x0d2d38080e2a462b4aed70d04256702a1b164435e24cd879c0990a56c4a3dc7e", 458 | "0xf85aa1308b89d36949e2cb59391f1d94f687d67ae51822eb6a8eee15188a6741", 459 | "0x2feb127c2a6861afae3c098c2a05de84ed826e2a0a41071d8939659bbf634534", 460 | "0xf842e6728aab3225f3f203a79ffeb063a6a282552337e6d410e471273baf54b4", 461 | "0xb9fb6a9cbf8f113bde4db0c2713abc8954b905c28b051372bcfa5be50c0efb91", 462 | "0x82a362b939cfd5419a662e08e9d91ec751dc0919f46c442dba89f559b4241a98", 463 | "0xa1d35b1f73a9554387301a1bf0ea6c2a8b809f57a0a2b125cbc959a4a2b6930f", 464 | "0xa497872c3adf6588a4c9b699c82b2cc799d1a65a75988da06b75d831adc526ce", 465 | "0xfd5ee017ee1643fed654b1dc925ef62b3b1593c27eb9beaaf681cf8084bf0977", 466 | "0xf025994690c94a53c13d2d86fe0cc4f65ccd73dec58a619fc807090c9c058c00", 467 | "0x84c39ee449f6d47d6cd88b7c2d8de534dc9106b2ff63794bb53903885b0f5076", 468 | "0x690504f4ed45ccd3b208f2f0ac845aa904219aea2bd0e16eec677e3eb930e4f3", 469 | "0xe69df20033f9fc9a1042ae9f585062529f75e2f4857ab4145fff01017d1d2621", 470 | "0x2564a00b5179931569dac036db26d331d90b3bee87251b8d809789f0d1faf328", 471 | "0xa895d856eb23ad2558669eefe81f863ec1adf75e49451dbff6937ce209478314", 472 | "0xb944b4bd0eba0b3cec29f93ab758dd75f6d813c516f5e246be231e593ecc9a17", 473 | "0xbaf6ec49d8126b927841a9e94e1c6aaef86da80e14eccca49f520e18312b2a8d", 474 | "0xcfb7971c01fb5b02842e96a9c6aee87fad80508896eb7ae24bd58f99e3e4ff0d", 475 | "0xdb7f24bf80b279586147f6cbc05eabdf34d78e46f1bcd6138e4eb9a6c157f27e", 476 | "0x9eb92cf1daa9940884e4e2d3a963b6af09af73dfea0d7f191519c8bc94ea6a50", 477 | "0x298dbc7accc218b0e3c856adf3c692f901e771e29183d1831fa8a1a60e3ca484", 478 | "0x30d4da7632466da207243e15904595c4dfb9c31415786699dc066917c5cdeb54", 479 | "0x7ae6965e4057e14d43e6165f547416e06244167f1fc55a0de566856f26cc72ac", 480 | "0x301d0d03ae04c0d4e523130015fd0dfb6eb91cad471c0618d1c041682b921571", 481 | "0x926158ac11b123a141218cee3352449858e193424aa28f23b6f4d2e75418ba1d", 482 | "0x0d394b43e93d3a7ac4c3d21a282664641a754009c8aaf178438a13f9b03bad87", 483 | "0x3c9dfc3ff6a63d55ade5f0202035408d703c178c89bf89b87adcdbc65164e3ef", 484 | "0xfaaa41e5a65595c7bf2448fe54c47d84610fe8ccb30fcf6f48de6b340f4408e7", 485 | "0xb4ea6242dab8d885ebddbad6d73cf2db9f525e8bd3f3714838263cc49acb0ce7", 486 | "0x9684e42b4e6a4290ec258e6101c89cf16b45c2fc959525a92742ef89da76d955", 487 | "0xc8d7339c114a73d1c653ade011e421bbaa92fb9222fb8bc82a22a3a500e9bb70", 488 | "0x084db4da3ee556962f0a72eb1f72eb8e563c300820d6a2c7281cdee587a2d6ff", 489 | "0x130d82a76f045e581bad62d60e9e6506cfae68e9da755dd546337c6dabb1e8be", 490 | "0x079f41fee78283d5384357d254e584961195f181199147a94c6d4d9620072b4a", 491 | "0x824dc1fc4c1a5d90322d031a2176fc781c7691ad61769d907aa9113b6b872c80", 492 | "0x182500937e49e5623652fa4083b9725f671b46a941849dbc0220900c8af2a61f", 493 | "0x119e25d5f08cd3b774d9ceb770f6a4994febb173f23c6ca0146e5407d4af899f", 494 | "0x945060044a1c2da1ae6055deb6ccf03f46c5a8815ec8eeeea3bf23e23f4bfbd2", 495 | "0x311d8225afce812219b3fc9660dcd8927f07f0d5c49dacf1aef98bb21426ff46", 496 | "0x422ebc1f53d6aff90883d9ff296420ca73f60bc924710a7c7ad08786c5bd67da", 497 | "0xd903fc6c2ae061b098017e59fe26f0ab947e0ef76d6a25aaf9d4e413ea6fe61f", 498 | "0x607f346fbe0a951d11f32257780a0584d6d04168ffde7f3fbde3a7c336b00b9b", 499 | "0xa039e5506bdc0872e628809ebebe41b9ac2507d208522cd0f9d71896c7e2a022", 500 | "0x943269f0634e198a900b6009e78ca1bbc689e038fdb4d02611b6a096ce67fce6", 501 | "0xc706f775414dbac01e5fcfc6f4a0e6e2755d1001b629823e2f245b5a2e1345d4", 502 | "0xc9e96c5b20ba047bd01ae251d9228031a343258fe2447dc2e2291deffdce50b0", 503 | "0x1e97fe935cd2f0046481dda63e167c9e52f08e46c547f32319a334fac5ab5a37", 504 | "0xa129b1ce9b6e81e7f2ca3c50094e2e711c9d6e8704831dba29ba60934a90050d", 505 | "0xf43ec8a187461c3664f8104f7e0ecc0f28ddb0328ee77ca6f987ca20956eab24", 506 | "0x20fa349971b445b47198be80f92f1660ac2b958e065820264cdf1ec95c49be10", 507 | "0x666be34b09568176acb1a2e9c3e8b1042c0eefcbb599f9e52da7cb71cf3099fe", 508 | "0x15deda88d94bd3f532acce07aff436a6d32b09b1239562d6f83d5991996b8ce5", 509 | "0x765ba2eb9757edcdc4f224754d17e36adc8c9e5aa7d6e984427e8fd460baff8b", 510 | "0x3ea1c990c4e32f03dbc3ef382b29c51d1886874c7d52b231cf670d5484d67689", 511 | "0xd36b56e5d2be59975f1ca60635d751fd61a63c9a63cfdfb5afa18a6022e3adde", 512 | "0xc04b987a270d6fff51c90fbe8263b8f84afba0f3fdf06858de53da3559f2697a", 513 | "0x20f0b93bf506101d099f6d3fb701fca22de1054fb41c3929ed19aae91d379b2f", 514 | "0xf2e87434322d99e1ef2174b1947f755a2ffaa579adf1a5c548b681334c3b5ae1", 515 | "0xd8dfc51842578a2eb2d4981eafddc8091b6d5bcce17d330f333c0981449b371b", 516 | "0xc5c243361f548f7f7670ebf18f8c4bd41b70bfab1f8be2f571ae73cf6608b3ab", 517 | "0xe5d6db8fce2ecd45de47b8078f26726d1235058b29720ecd30f646f3631e4a4d", 518 | "0x3ce418dedc0f3f144a024efbcc471223389b66e5ca35ee8a58f9cdc9c47e435b", 519 | "0x2766e240a6d6d72140d6784561f487af6fca94184a5de1b20a8f54bb9247a325", 520 | "0xb74739841433b26a60f602c5cdc55b4e565529663edb78d91ff41dceb6fe1e89", 521 | "0xd9613d47f30746fc071136ba7555b14f1b8ad2945962fd0406ea100bd35c6eeb", 522 | "0x6950a465bdfe00bd3fe9cbc4270f7c6cc42b749538ede4e596ec5c691268fe26", 523 | "0x95b2f5c8d60de7b4564700000a2fd0546a556b437a54aaa44ce091338b6dd8f3", 524 | "0x6f9b2ae67f63f621654925c9ca45ec7899229ad67898538f3ce20e398b4ed223", 525 | "0x3813a8431dcac290298a1b38146000dfc2456c48d8b356fb8c3c4cbd1dd25c10", 526 | "0x963ef78ec2ec90df82b048573eaeaf5f5c885060e7ddca43c28581e8db8aa773", 527 | "0xc6a12fcd4683eaa2d5b1087b9b567bb60b7b8ac7c87dc4523acab9873dcd3c0e", 528 | "0x03f83ce980a149e27e2dae3569b417958df1c339f468ba1605212d8136444eb0", 529 | "0xcebae75f2c6978dedc733d9d5e4ac1362a5b5dc5682a479033d2947474cf9d5d", 530 | "0x4cac25f96f66d93f7af7f905f3a5065d4c790f5aa3c3fc91279a279c14631195", 531 | "0xd2560d1eb4c2be059ff094ff010383817a4702226cb98a957571a0e0a87bd229", 532 | "0xae6e0fbedef5fe75c99a8708db56cb499fb16d614647a507cfb53aaf7e4f296c", 533 | "0xc1b6d15d30aa60f78e4ca16abd2cb2c00cb9f81144d78440028a0f517a907c83", 534 | "0x613f968840c9ddde8e4ab9c2a755db90b24890a90306f56d460905cc0dfa07cf", 535 | "0x45986014f62be4c35bba4953bc2865a83f40173de0265fbc4e9504f69a6c43dc", 536 | "0xbcb10deadd3b22e0b2fb80304ed6f42400535068ed2ebac9f406008f3867f50f", 537 | "0x10684720fb078ada2e6b82009f293cc80edd85e1a7aa58746828a2bd1cd1d48b", 538 | "0x4399232a35da0ea90b428128e3ca4b73b380d10c2981811b5a3f4b9d21751605", 539 | "0x7c2b605d5de781d9590bf0281b1abdbe7af1e4d5a05ccf9672e80b6e9a3dc9a0", 540 | "0xa350ba0cc0073ba447c4e48c816687abcb5cab98b5bc7520303f8100f8bb7173", 541 | "0x3dd7dd69b94aefe44db435db5559a578ae5236b86871b5cb8183ee3e04e444de", 542 | "0xa7558bb9e40b59c1d775a5f4abbee09d51a60aacecbcf15f9eb334b0edc7902f", 543 | "0x1cefcdb9bf3744b08da883927fc23e5d021da16928b56e7a2cdf36e6c89c600c", 544 | "0xc5d9b045602f0ad7084b348f6953a06ba5d23456383d265eb550c046fe080a47", 545 | "0x1afbea08014d80cbd3f96d28d87a4d85208cb78174c42faf51129166d0dc01c7", 546 | "0xdd0bc8f7ff734fb63b83bbd91626cad4e9c59c4473af37ab672c807b3a3312f5", 547 | "0x95e1f8bd9228a2a724e057c3588245b2526ec69af43518e64152d157dde5ac1f", 548 | "0xb07d92e56c803482be648249305dda6d38b1a3d333afb1bcf58ca271906cf82d", 549 | "0xab9ced1688789b7ff4cff97bede447c4dc06a340fac9c2c3ea90d0014518ec93", 550 | "0xee7fa576a04cd9d253fce00f1bc72e535080a6e5de4430dc2ceaea5bc0248b5b", 551 | "0x5e6384fa2bd8e62955152892c37c6e28f91b3919862a1eb269a47cec9e5d0b63", 552 | "0x8e6f02b1dceb75f1704f3144d4015299b1a7b07ba8a97073c3970c653cd69db8", 553 | "0x810716e76817e543a7ee83b4920420f7cb47f706846926794cf5af4765cc1cae", 554 | "0x54e282707365b4298747b2994e32c4bdde74cfffe77c710928ba6009d74f461e", 555 | "0xf60d5c10a922a53e08fc91dd88c0fdbc375ff6bccb30911c867ab6fde83429dd", 556 | "0x79f0e5163a299a3b8417fcc8d195a618828c369cf395f245ca6e4be1f2e15eae", 557 | "0x00eb04c0299229f5c3ed42f25217517b56203339d827f16f58e8cdee92dd2068", 558 | "0xa98bce7d60f10d78130b931ec33895f43b1b2299c6bdb681e0fff38f2d05c70c", 559 | "0xbde31fc55cb7ff238ea0221f0f8968431b824ba978296d1efe5f4e2bc8cbc15d", 560 | "0xa6e65662e90c27abf8401d385c7cc6e55bf16ae8d0d6fc9851c92148f3f3d087", 561 | "0x12e4b1a327d98e737afa97c3651698a8ec13f9aae917f693b4ed2f043afb9df8", 562 | "0x48920365aebbf719669b537bced27bfc1ad2edb80f665061b575862629b59b59", 563 | "0x480972f9943c1673d32cf0441cb8e5042cae050f2ad0c31a20c464921de72a2e", 564 | "0x626e6695b70c991e902575d522d33f0127ec164fec4faed217cfd0ee145fb429", 565 | "0x865ca776fd89fdb92c504ebd15ff28d46434e96fc12b8c7b2b1c391dafd3cbf7", 566 | "0x11b062b8ad23fffe35aa3de34386feb6f755b5c6b668c1ad05a101c4bd2f7b0f", 567 | "0x548807a8663642f927f420d517b36b314e6159998492c213cd52319bd533ddef", 568 | "0xd687892e2be102ab7c4fee8b64bba4ef699b7b9a5d5d7de18f8b8c13c71d7397", 569 | "0x3984569d57e4e6c0b6b95261c69af1e833968723ccc0ca9b1b78f93b7269fb89", 570 | "0xa7aeb1590006087fa1cb9359f15803beb8ac7e26e88a0cd81b4ac7909863b242", 571 | "0x5e8cb88b86e73555cdf16432eb620d16554cdef3a720bb9fdaecf82f669abf33", 572 | "0xa333b8eb53ba0e953e270c2ac1a13253e0050674bc9d6d374cfaf769daa19533", 573 | "0x746ed0eae47e90806a5097a0e1708fe9ea0255d30dfea5dcc2987149ecfe757c", 574 | "0x901af594c4de8ec6b10007c918024d28286f2d7680841c5d8120f341e5aa7822", 575 | "0xab3500061c0578c43dae2a19c457db71206b01f4c20962bee12770803c061b8d", 576 | "0x9810ca1bcd8ca8f7fab6c3a9889ba174b0ee17aca98fb205a1e68529f55c1890", 577 | "0xd5cf27a5d10c29945dbb7751dc9a4cb12c25497eb64800f73ecb5b047e8724dc", 578 | "0x44119dfdacfeead06e554ef155bea8d403090da39861cc4f19987dcbadde0057", 579 | "0xd006611b15278ad95a57a1c999207e98809fdedd59bf4401f4978d3e59d1aa05", 580 | "0x5fd4b9f5f049b630c88adf21c6735a57786e7242289232ab13a2fef42383c9cd", 581 | "0x52384c93f1759efdcd40069971449dae86c0e0bbc8d900ebb1a4c4cfb3d2d3e0", 582 | "0x81a34c29ffaff3ff198902a7e57230c9707b92f22d9428d2bec0e5b6cbfdc751", 583 | "0x2f09c3be303864860849ee9b2794ea924dc945cab089eb461f4a40442a682dfd", 584 | "0x530a1aacc82ced6123d3a84e733316cd29bf5f93a66d769f3adb76dcd95e3a03", 585 | "0x69f61701b302b122a40c314ed1e9369c0ab040f440467fc0e8253c452bf3b4ca", 586 | "0xec95e78c22b1e4e9ee344ceb3ed4ceb8b3947de2a8089452dbd9e5e5ebfe4662", 587 | "0x2a3f946af6011bac2c4a05a6d2da22e86ffea3ff0a797c4963b9384cdd9b559b", 588 | "0x8cdf56e40e7344878e2d854c4ee2daf23037d6d2db0d0567008c5a581737c45e", 589 | "0xc33b9c27933689a9df85c2c3ca67e76ca619a4076bdcf95fe1350c0cc7311f66", 590 | "0xf4f2bcc0f365fd711b3b827fa20e65d89af57fc21be3344a21badc065077ce72", 591 | "0x63f8b41e77a8ee1492c1bf9d5f62f4e1a8c2ded3a93f9fe8e31a9c5fe848a474", 592 | "0x8702eaa1c95a138aa084c3a3aa3c1cdd06c4bee7a133299542772c85fe348f4e", 593 | "0xff68ff9634602f8ae183a8f0976c98df479a0d966c44f78202951d38f1af61a3", 594 | "0x5468fa5287c9fb4235402b22ac27d5e316b3f4e6ef9b97a9888049a90de56026", 595 | "0xb54e72c5a8c5c5d5145e480a102ce8476de05ebf8ab03fd2c5b6bdd57f6be47a", 596 | "0xab14b3512dd11c5248932990c7e565f2b7697b1a2e29432e67ad0662bbf9daa8", 597 | "0x4e5c4dc556f39a20e93d8bcfdd019ba0b38948127c003abdb4ab3a194ec8b540", 598 | "0xd3bcf8c776e17089450bc5e069c485f11ab8dc7b35d29410e1c4a37b1f522729", 599 | "0xddd618ead71048ed6446a21550a7584f7995c86f466210a4a2f13dca8311b1fc", 600 | "0xe72a578e30c0a7ea95062a1b9e81d8fcb65524179a8422e01b47123c03008905", 601 | "0x7d660a3923db1628ba8b98ed3ba9be5eeef1332cf851b055beafffcb8000d2d2", 602 | "0xe5c7c96314843c442bab72de4c30660afb2e895308f635debc57b754d34c2968", 603 | "0x55bc7c009394bfb15027de0f8a2e452194df53b80a3ff8f7a0ed76503f31360d", 604 | "0x2be54fbc44129afe896718f0d4a3c2ea90a8bc6bdba5e7bfa4b1ca991be70710", 605 | "0xd6c4d85d462612abac488b9dd3b5466a07953c86b8f6cf5eaaabab8043dc0b54", 606 | "0x33124601ce1464ba83b8c4043a178eb5e6f5cc5720cd44420fbbe7b93454b730", 607 | "0xac71f9033653e4a61b6c56dc81d411d2dd8a65d371547e31f95bf62193fe953c", 608 | "0x94f77072d926dc4b27e590058f84cb42b0019be9d8c2c9372d6f216fc3bbb621", 609 | "0xa7e2c4acc101ec260c56837988064508de4408a93fb431ba7184705005cbc990", 610 | "0xedcd086aa372a64e1ae5089c7d22d56fa9a35e88cd6ae0f427264d69f1cf2e51", 611 | "0x1274827a32de34273102e4194b16e00b762baf010e7f545acdee4c57c02c4df5", 612 | "0x2320379d5f6068fd5439b1e881e6417984402368563763bb566fe99033031c15", 613 | "0x01c332b3683b1a0d9c7009a356e11619c1ec4b09f3d68c670a415824d9b5c672", 614 | "0xe129e7df04d563c85648d5e1a96787df650e051a2d7dc05a838b112ba4abba37", 615 | "0x857265fe20191d3ed78271dedabdd85cd11449ac15981d970dd56004548d6198", 616 | "0x8dcfa60a314b8af771529478c5adfc36226158bbe29fbb84e65e8099f4889a40", 617 | "0x4bc0cdb8b8315c872978240cb05f851c2b0bc85421c2174a51e6349a2efd9ebe", 618 | "0x6636fe6a5fcc8061fa8846b5387caf268431875f792b662f0421a34dc8379f5b", 619 | "0xc91355a51606881be58e5a9b355ba48499ce489a5eac8cbe9635b32f9311d613", 620 | "0x0f911aa38e79790b5b830621846992b4e097775c5f823ce4c1b9cead0396abb9", 621 | "0x5c710e17061f4ba0dbbe0de78244e20c4baf4c96ead6e757ee86beb841a7e10d", 622 | "0x309258faade955e07423ac9aa910f33d47ba3c6b2f144abb0b4388724906c037", 623 | "0x5b457f9b64e1059a9b676772cafce4c8fe40059b5d2f6489fcb27b4dec3e112a", 624 | "0x973e61bcd86e17f4e674ec1f4cb30439ae469bb040358ae0e5b342dfcff4510e", 625 | "0xa3aafe83549adfa55ba1e1af5de6432479da1f7e16c39136e9bcdcf147489433", 626 | "0xa0d53b30c0acb8a9dc0912d03a997bd467394d72643afb5050a4a000e43e5f6b", 627 | "0xdf66c0a9c224d996a0d77bc5f286f779b45db2753170e5a894d5c5b690736f3d", 628 | "0xb67d4365b01082077181e381a263536625feb938f6c43c1252c382657ad9777a", 629 | "0x6e0460cfe83d74e8ab82cb2ae2efd95872774d69686291e647743077ca6fd008", 630 | "0xe5b4fe8ec51302af7f61837e73cc30da8048fa4549d90b8adc3ea09b3e287215", 631 | "0xa77813ad613f9407d83c4c9dcc15cc56be55ff9fdaa8b186de636836cc2a5628", 632 | "0xbc2d0e086b96c440fb917b1aff504673ff45345267503b379155b2e8c0cb46da", 633 | "0xe54db9180d72e19be62c0218ec15287cc3335daa8711e23bf0e3b0869d7cc365", 634 | "0xd5a7348d7b1d56ef3ed65630385ee52f304de6228d6c6dbda8b2c1e4cb70f02b", 635 | "0x04f5e64f0b658444cd6b785379052a81096f5de874bf64aaf197fe8c21c20f42", 636 | "0x2a08415e107492a15c8f33a10778be76462c6425d28304849f3188332cfadb33", 637 | "0x0277fddf1e170b4e73999d150a8fb493278b1325bc82c515637255f24f4bd715", 638 | "0x29306029ed9388a739549910a924215ade9f7b53d4ccd99dbcdc2d0e10226f3e", 639 | "0x6db09fd8aa2581d0b26a01b01807d9db73e8085e7083d870a74c99bd05e68944", 640 | "0x486499fee03175ace8f2ba1173e2969a59ce70341a13284540c88cadc82d5135", 641 | "0x6446caf92f769d49d0459b0f6fa09919dae844f5c0855256e38eb96adfd253a4", 642 | "0x19110a6e490e7fa01a8b9078ead2c3c6fd373dcd8d2a28010c42dc389b0404e4", 643 | "0xf57c1d112cd7527140523d6e0f08be067d927e52452b6aa8facb1056bac1d9a3", 644 | "0x9630a31334779d06fdee8452263837009fe9f3434bd9d533fde0759e4ce4940b", 645 | "0x980928d12e60f9709b8115a2d210567837b4b083c31252ab836397ab45c3aa56", 646 | "0xa88f2a9e73c8d1a7d3cda3838df3feff2df0bed32b0368c3c1c23309bc9f5a75", 647 | "0xc4b79f822aadb3020612142052b9dd96897abb51b444fef26ec6d9ca5e4d8f8a", 648 | "0x6cacd7a4ad8b5c76ed828532fb3f7ce6ead2b3fa2e8bfd59673a98d72705ce8b", 649 | "0xa4a399a2b02eb65f15270d7ea5b954bf8a11dbe89dbd8484360f66692346d0aa", 650 | "0xf0727f3b3e0d4f53dc06e28f72b7d003eb7cc8ae264898d308b1f66e02c78a78", 651 | "0xa701e4ee9f77cd4f9985280be81319d8dfd61f03f3c3db7c23fa320f779c3736" 652 | ] -------------------------------------------------------------------------------- /tools/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | subscapes 8 | 13 | 14 | 15 | 16 | 33 | 34 | -------------------------------------------------------------------------------- /tools/render.js: -------------------------------------------------------------------------------- 1 | // helper functions 2 | const setctx = (ctx, composite, color, alpha) => { 3 | ctx.globalCompositeOperation = composite; 4 | ctx.globalAlpha = alpha; 5 | ctx.fillStyle = ctx.strokeStyle = color; 6 | }; 7 | 8 | export const toScreenPoint = (p, tx, ty, sx, sy) => [ 9 | tx + p[0] * sx, 10 | ty + p[1] * sy, 11 | ]; 12 | 13 | function getMinMaxY(projectedPaths, projectedBasePolygon) { 14 | // Figure out min/max Y for recentering 15 | let minScreenY = Infinity; 16 | let maxScreenY = -Infinity; 17 | projectedPaths.forEach((p) => { 18 | p[0].forEach((line) => { 19 | line.forEach((pt) => { 20 | minScreenY = Math.min(minScreenY, pt[1]); 21 | maxScreenY = Math.max(maxScreenY, pt[1]); 22 | }); 23 | }); 24 | }); 25 | projectedBasePolygon.forEach((pt) => { 26 | minScreenY = Math.min(minScreenY, pt[1]); 27 | maxScreenY = Math.max(maxScreenY, pt[1]); 28 | }); 29 | return [minScreenY, maxScreenY]; 30 | } 31 | 32 | export function fitter(projectedPaths, projectedBasePolygon, width, height) { 33 | const [minScreenY, maxScreenY] = getMinMaxY( 34 | projectedPaths, 35 | projectedBasePolygon 36 | ); 37 | 38 | // aspect ratio fitter 39 | let tx, 40 | ty, 41 | sx = width, 42 | sy = height; 43 | if (1 > width / height) { 44 | sy = sx; 45 | } else { 46 | sx = sy; 47 | } 48 | tx = (width - sx) * 0.5; 49 | ty = (height - sy) * 0.5; 50 | 51 | // recenter based on projected paths 52 | const ky0 = minScreenY * sy; 53 | const ky1 = maxScreenY * sy; 54 | ty += (sy - ky1 - ky0) / 2; 55 | 56 | return [tx, ty, sx, sy]; 57 | } 58 | 59 | export default function render(subscapes, opts = {}) { 60 | let [ 61 | background, 62 | colors, 63 | baseColor, 64 | mode, 65 | alpha, 66 | lineWidthFactor, 67 | projectedPaths, 68 | projectedBasePolygon, 69 | projectedBasePaths, 70 | ] = subscapes; 71 | 72 | const SRC_OVER = "source-over"; 73 | 74 | const { 75 | context, 76 | width, 77 | height, 78 | lineJoin = "round", 79 | lineCap = "round", 80 | clear = true, 81 | } = opts; 82 | 83 | if (!context) throw new Error("Must specify { context } attribute"); 84 | if (!width || !height) { 85 | throw new Error("Must specify { width, height } attributes and != 0"); 86 | } 87 | 88 | const dim = Math.min(width, height); 89 | const lineWidth = dim * lineWidthFactor; 90 | 91 | const [tx, ty, sx, sy] = fitter( 92 | projectedPaths, 93 | projectedBasePolygon, 94 | width, 95 | height 96 | ); 97 | 98 | context.lineJoin = lineJoin; 99 | context.lineCap = lineCap; 100 | 101 | // clear canvas 102 | if (clear) { 103 | context.clearRect(0, 0, width, height); 104 | } 105 | 106 | // draw background color 107 | setctx(context, SRC_OVER, background, 1); 108 | context.fillRect(0, 0, width, height); 109 | 110 | // draw core subscapes terrain 111 | draw(projectedPaths); 112 | 113 | // fill the base polygon so that we 'clip' the lines that might be visible 114 | setctx(context, SRC_OVER, background, 1); 115 | context.beginPath(); 116 | projectedBasePolygon.forEach((p) => 117 | context.lineTo(...toScreenPoint(p, tx, ty, sx, sy)) 118 | ); 119 | context.closePath(); 120 | context.fill(); 121 | 122 | // some subscapes have a base fill color, so fill if needed 123 | if (baseColor) { 124 | setctx(context, mode, baseColor, alpha); 125 | context.fill(); 126 | } 127 | 128 | // finally draw the ridge lines within the base 129 | draw(projectedBasePaths); 130 | 131 | function draw(curPaths) { 132 | curPaths.forEach(([paths, color, thickness]) => { 133 | const curLineWidth = thickness * lineWidth; 134 | context.lineWidth = curLineWidth; 135 | context.beginPath(); 136 | paths.forEach((path) => { 137 | path.forEach((p, i) => { 138 | const x = tx + p[0] * sx; 139 | const y = ty + p[1] * sy; 140 | if (i === 0) context.moveTo(x, y); 141 | else context.lineTo(x, y); 142 | // Special case especially for iOS is to create 143 | // a path that is 'long enough' to form a visible point 144 | if (path.length === 1) { 145 | const jitter = curLineWidth / 20; 146 | context.lineTo(x - jitter, y - jitter); 147 | context.lineTo(x + jitter, y + jitter); 148 | } 149 | }); 150 | }); 151 | setctx(context, mode, color, alpha); 152 | context.stroke(); 153 | }); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /tools/serialize.js: -------------------------------------------------------------------------------- 1 | // import convert from "convert-length"; 2 | import { fitter, toScreenPoint } from "./render"; 3 | 4 | export default function serialize(subscapes, opts = {}) { 5 | let { width = 512, height = 512, margin = 0 } = opts; 6 | 7 | let [ 8 | background, 9 | colors, 10 | baseColor, 11 | mode, 12 | alpha, 13 | lineWidthFactor, 14 | projectedPaths, 15 | projectedBasePolygon, 16 | projectedBasePaths, 17 | ] = subscapes; 18 | 19 | const dim = Math.min(width, height); 20 | const lineWidth = dim * lineWidthFactor; 21 | 22 | const [tx, ty, sx, sy] = fitter( 23 | projectedPaths, 24 | projectedBasePolygon, 25 | width, 26 | height 27 | ); 28 | 29 | const { lineJoin = "round", lineCap = "round" } = opts; 30 | 31 | // width += margin * 2; 32 | // height += margin * 2; 33 | 34 | const outerWidth = margin * 2 + width; 35 | const outerHeight = margin * 2 + height; 36 | 37 | const units = "px"; 38 | 39 | const corePaths = []; 40 | 41 | let basePolyData = projectedBasePolygon 42 | .map((p, i) => { 43 | const xy = toScreenPoint(p, tx, ty, sx, sy); 44 | return [i === 0 ? "M" : "L", ...xy].join(" "); 45 | }) 46 | .join(""); 47 | 48 | // close the path 49 | basePolyData = basePolyData + "Z"; 50 | 51 | const elements = [ 52 | // background 53 | margin 54 | ? `` 55 | : "", 56 | ``, 57 | // main lines 58 | ` 59 | ${draw(projectedPaths)} 60 | `, 61 | // base polygon to fill out background 62 | ``, 63 | // another one to handle any blending 64 | baseColor 65 | ? `` 66 | : "", 67 | // ridge lines 68 | ` 69 | ${draw(projectedBasePaths)} 70 | `, 71 | ] 72 | .filter(Boolean) 73 | .map((s) => ` ${s}`) 74 | .join("\n"); 75 | 76 | const graphics = ` 79 | ${elements} 80 | `; 81 | 82 | return ` 83 | 85 | 87 | 91 | 92 | ${graphics} 93 | 94 | `; 95 | 96 | function draw(curPaths) { 97 | return curPaths 98 | .map(([paths, color, thickness]) => { 99 | const curLineWidth = thickness * lineWidth; 100 | const svgPath = []; 101 | paths.forEach((path) => { 102 | path.forEach((p, i) => { 103 | const x = tx + p[0] * sx; 104 | const y = ty + p[1] * sy; 105 | if (i === 0) svgPath.push(["M", x, y]); 106 | else svgPath.push(["L", x, y]); 107 | // Special case especially for iOS is to create 108 | // a path that is 'long enough' to form a visible point 109 | if (path.length === 1) { 110 | const jitter = curLineWidth / 20; 111 | svgPath.push( 112 | ["L", x - jitter, y - jitter], 113 | ["L", x + jitter, y + jitter] 114 | ); 115 | } 116 | }); 117 | }); 118 | const d = svgPath.map((s) => s.join(" ")).join(""); 119 | return ``; 122 | }) 123 | .map((s) => ` ${s}`) 124 | .join("\n"); 125 | } 126 | } 127 | --------------------------------------------------------------------------------