├── .gitignore ├── LICENSE ├── README.md ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | 40 | benchmarks/ 41 | .idea 42 | test.js 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kevin Chapelier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # convchain 2 | 3 | Vanilla javascript port of [ConvChain](https://github.com/mxgmn/ConvChain). 4 | 5 | [Interactive demo](http://www.kchapelier.com/convchain-demo/) 6 | 7 | ## Installing and testing 8 | 9 | With [npm](http://npmjs.org) do: 10 | 11 | ``` 12 | npm install convchain 13 | ``` 14 | 15 | ## Basic example 16 | 17 | ```js 18 | var ConvChain = require('convchain'); 19 | 20 | var samplePattern = Uint8Array.from([ 21 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24 | 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 25 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27 | 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 28 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 29 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 31 | ]); 32 | 33 | var width = 45, 34 | height = 20; 35 | 36 | var convChain = new ConvChain(samplePattern); 37 | 38 | var generatedPattern = convChain.generate([width, height], 3, 0.5, 4); // a flat Uint8Array 39 | 40 | // some code to display the result 41 | for (var y = 0; y < height; y++) { 42 | var s = ''; 43 | for (var x = 0; x < width; x++) { 44 | s += ' ' + generatedPattern[x + y * width]; 45 | } 46 | console.log(s); 47 | } 48 | ``` 49 | 50 | ## Public API 51 | 52 | ### Constructor 53 | 54 | **new ConvChain(sample[, sampleSize])** 55 | 56 | - *sample :* Sample pattern as a flat array or a 2D array. 57 | - *sampleSize :* Indicate the width and height of the sample when used with a flat array, if omitted the sample pattern is assumed to be a square. 58 | 59 | ```js 60 | var testSample = Uint8Array.from([ 61 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 62 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 63 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64 | 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 65 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67 | 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 68 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 69 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 70 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 71 | ]); //flat array 72 | 73 | var convChain = new ConvChain(testSample, [14, 10]); 74 | ``` 75 | 76 | ```js 77 | var testSample = [ 78 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 79 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 80 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 81 | [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1], 82 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 83 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 84 | [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1], 85 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 86 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 87 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 88 | ]; //2D array 89 | 90 | var convChain = new ConvChain(testSample); 91 | ``` 92 | 93 | ### Methods 94 | 95 | **convChain.setSample(sample[, sampleSize])** 96 | 97 | Same arguments as the constructor. 98 | 99 | **convChain.generate(resultSize, n, temperature, iterations[, rng])** 100 | 101 | Generate a new pattern based on the sample pattern. The generated pattern is returned as a flat Uin8Array. 102 | 103 | - *resultSize :* Width and height of the generated pattern. 104 | - *n :* Receptor size, an integer greater than 0. 105 | - *temperature :* Temperature, a float. 106 | - *iterations :* Number of iterations. 107 | - *rng :* A function to use as random number generator, defaults to Math.random. 108 | 109 | ```js 110 | var result = convChain.generate([100, 50], 3, 0.5, 4); 111 | ``` 112 | 113 | **convChain.iterate(field, resultSize, n, temperature[, tries[, rng]])** 114 | 115 | Execute a specific number of operations on a given pattern. 116 | 117 | - *field :* An existing pattern given as a flat Uint8Array. If *null* is given, a noisy pattern will be used instead. 118 | - *resultSize :* Width and height of the generated pattern. 119 | - *n :* Receptor size, an integer greater than 0. 120 | - *temperature :* Temperature, a float. 121 | - *tries :* Number of operations to execute, default to the result's width multiplied by the result's height 122 | - *rng :* A function to use as random number generator, defaults to Math.random. 123 | 124 | ```js 125 | var field = null; 126 | 127 | for (var i = 0; i < 32; i++) { 128 | field = convChain.iterate(field, [64, 64], 3, 0.2, 128); 129 | 130 | // ... do something with the return pattern here 131 | } 132 | ``` 133 | 134 | ## Changelog 135 | 136 | ### [1.1.0](https://github.com/kchapelier/convchain/tree/1.1.0) (2016-08-25) 137 | 138 | * Implement the iterate method. 139 | 140 | ### [1.0.0](https://github.com/kchapelier/convchain/tree/1.0.0) (2016-08-21) 141 | 142 | * First implementation. 143 | 144 | ### License 145 | 146 | MIT 147 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * ConvChain constructor 5 | * @param {Array|Uint8Array} sample Sample pattern as a flat array or a 2D array 6 | * @param {int|Array} [sampleSize] Indicate the width and height of the sample when used with a flat array, if omitted assume the sample is a square 7 | * @constructor 8 | */ 9 | var ConvChain = function ConvChain (sample, sampleSize) { 10 | this.setSample(sample, sampleSize); 11 | }; 12 | 13 | /** 14 | * Set the sample pattern 15 | * @param {Array|Uint8Array} sample Sample pattern as a flat array or a 2D array 16 | * @param {int|Array} [sampleSize] When used with a flat array indicate the width and height of the sample, if omitted assume the sample is a square 17 | */ 18 | ConvChain.prototype.setSample = function (sample, sampleSize) { 19 | if (typeof sample[0] === 'number') { 20 | // assume flat array 21 | this.sample = sample; 22 | 23 | if (!sampleSize) { 24 | // assume square sample 25 | 26 | this.sampleWidth = this.sampleHeight = Math.sqrt(sample.length) | 0; 27 | } else { 28 | this.sampleWidth = typeof sampleSize === 'number' ? sampleSize : sampleSize[0]; 29 | this.sampleHeight = typeof sampleSize === 'number' ? sampleSize : sampleSize[1]; 30 | } 31 | } else { 32 | // assume 2D array 33 | this.sampleWidth = sample[0].length; 34 | this.sampleHeight = sample.length; 35 | 36 | var flatArray = new Uint8Array(this.sampleWidth * this.sampleHeight), 37 | x, 38 | y; 39 | 40 | for (y = 0; y < this.sampleHeight; y++) { 41 | for (x = 0; x < this.sampleWidth; x++) { 42 | flatArray[x + y * this.sampleWidth] = sample[y][x]; 43 | } 44 | } 45 | 46 | this.sample = flatArray; 47 | } 48 | 49 | // invalidate cached weights 50 | this.cachedN = null; 51 | this.cachedWeights = null; 52 | }; 53 | 54 | var processWeights = function processWeights (sample, sampleWidth, sampleHeight, n) { 55 | var weights = new Float32Array(1 << (n * n)), 56 | k, 57 | x, 58 | y; 59 | 60 | var pattern = function pattern (fn) { 61 | var result = new Array(n * n), 62 | x, 63 | y; 64 | 65 | for (y = 0; y < n; y++) { 66 | for (x = 0; x < n; x++) { 67 | result[x + y * n] = fn(x, y); 68 | } 69 | } 70 | 71 | return result; 72 | }; 73 | 74 | var rotate = function rotate (p) { 75 | return pattern(function (x, y) { return p[n - 1 - y + x * n]; }); 76 | }; 77 | 78 | var reflect = function reflect (p) { 79 | return pattern(function (x, y) { return p[n - 1 - x + y * n]; }); 80 | }; 81 | 82 | var index = function index (p) { 83 | var result = 0, 84 | power = 1, 85 | i; 86 | 87 | for (i = 0; i < p.length; i++) { 88 | result += p[p.length - 1 - i] ? power : 0; 89 | power *= 2; 90 | } 91 | 92 | return result; 93 | }; 94 | 95 | for (y = 0; y < sampleHeight; y++) { 96 | for (x = 0; x < sampleWidth; x++) { 97 | var p0 = pattern(function (dx, dy) { return sample[((x + dx) % sampleWidth) + ((y + dy) % sampleHeight) * sampleWidth]; }), 98 | p1 = rotate(p0), 99 | p2 = rotate(p1), 100 | p3 = rotate(p2), 101 | p4 = reflect(p0), 102 | p5 = reflect(p1), 103 | p6 = reflect(p2), 104 | p7 = reflect(p3); 105 | 106 | weights[index(p0)] += 1; 107 | weights[index(p1)] += 1; 108 | weights[index(p2)] += 1; 109 | weights[index(p3)] += 1; 110 | weights[index(p4)] += 1; 111 | weights[index(p5)] += 1; 112 | weights[index(p6)] += 1; 113 | weights[index(p7)] += 1; 114 | } 115 | } 116 | 117 | for (k = 0; k < weights.length; k++) { 118 | if (weights[k] <= 0) { 119 | weights[k] = 0.1; 120 | } 121 | } 122 | 123 | return weights; 124 | }; 125 | 126 | /** 127 | * Get the weights for the sample pattern and the given receptor size 128 | * @param {int} n Receptor size, an integer greater than 0 129 | * @returns {Float32Array} 130 | * @private 131 | */ 132 | ConvChain.prototype.getWeights = function (n) { 133 | // check if we have to generate new weights, otherwise return cached result 134 | if (this.cachedN !== n) { 135 | this.cachedN = n; 136 | this.cachedWeights = processWeights(this.sample, this.sampleWidth, this.sampleHeight, n); 137 | } 138 | 139 | return this.cachedWeights; 140 | }; 141 | 142 | var generateBaseField = function generateBaseField (resultWidth, resultHeight, rng) { 143 | var field = new Uint8Array(resultWidth * resultHeight), 144 | i; 145 | 146 | for (i = 0; i < field.length; i++) { 147 | field[i] = rng() < 0.5; 148 | } 149 | 150 | return field; 151 | }; 152 | 153 | var applyChanges = function applyChanges (field, weights, resultWidth, resultHeight, n, temperature, changes, rng) { 154 | var r, 155 | q, 156 | i, 157 | x, 158 | y, 159 | sy, 160 | sx, 161 | dy, 162 | dx, 163 | ind, 164 | difference; 165 | 166 | for (i = 0; i < changes; i++) { 167 | q = 1; 168 | r = (rng() * resultWidth * resultHeight) | 0; 169 | x = (r % resultWidth) | 0; 170 | y = (r / resultWidth) | 0; 171 | 172 | for (sy = y - n + 1; sy <= y + n - 1; sy++) { 173 | for (sx = x - n + 1; sx <= x + n - 1; sx++) { 174 | ind = 0; 175 | difference = 0; 176 | 177 | for (dy = 0; dy < n; dy++) { 178 | for (dx = 0; dx < n; dx++) { 179 | var power = 1 << (dy * n + dx), 180 | X = sx + dx, 181 | Y = sy + dy, 182 | value; 183 | 184 | if (X < 0) { 185 | X += resultWidth; 186 | } else if (X >= resultWidth) { 187 | X -= resultWidth; 188 | } 189 | 190 | if (Y < 0) { 191 | Y += resultHeight; 192 | } else if (Y >= resultHeight) { 193 | Y -= resultHeight; 194 | } 195 | 196 | value = field[X + Y * resultWidth]; 197 | 198 | ind += value ? power : 0; 199 | 200 | if (X === x && Y === y) { 201 | difference = value ? power : -power; 202 | } 203 | } 204 | } 205 | 206 | q *= weights[ind - difference] / weights[ind]; 207 | } 208 | } 209 | 210 | if (q >= 1) { 211 | field[x + y * resultWidth] = !field[x + y * resultWidth]; 212 | } else { 213 | if (temperature != 1) { 214 | q = Math.pow(q, 1.0 / temperature); 215 | } 216 | 217 | if (q > rng()) { 218 | field[x + y * resultWidth] = !field[x + y * resultWidth]; 219 | } 220 | } 221 | } 222 | }; 223 | 224 | /** 225 | * Generate a pattern based on the sample pattern 226 | * @param {int|Array} resultSize Width and height of the generated pattern 227 | * @param {int} n Receptor size, an integer greater than 0 228 | * @param {float} temperature Temperature 229 | * @param {int} iterations Number of iterations 230 | * @param {function} [rng] A random number generator, default to Math.random 231 | * @returns {Uint8Array} Generated pattern, returned as a flat Uint8Array 232 | */ 233 | ConvChain.prototype.generate = function (resultSize, n, temperature, iterations, rng) { 234 | rng = rng || Math.random; 235 | 236 | var resultWidth = typeof resultSize === 'number' ? resultSize : resultSize[0], 237 | resultHeight = typeof resultSize === 'number' ? resultSize : resultSize[1], 238 | changesPerIterations = resultWidth * resultHeight, 239 | field = generateBaseField(resultWidth, resultHeight, rng), 240 | weights = this.getWeights(n), 241 | i; 242 | 243 | for (i = 0; i < iterations; i++) { 244 | applyChanges(field, weights, resultWidth, resultHeight, n, temperature, changesPerIterations, rng); 245 | } 246 | 247 | return field; 248 | }; 249 | 250 | /** 251 | * Execute a specific number of operations on a given pattern 252 | * @param {Uint8Array|null} field Pattern on which to iterate, default to a noisy pattern if null is given 253 | * @param {int|Array} resultSize Width and height of the pattern on which to iterate 254 | * @param {int} n Receptor size, an integer greater than 0 255 | * @param {float} temperature Temperature 256 | * @param {int} [tries] Number of operations to execute, default to the result's width multiplied by the result's height 257 | * @param {function} [rng] A random number generator, default to Math.random 258 | * @returns {Uint8Array} Pattern iterated upon, returned as a flat Uint8Array 259 | */ 260 | ConvChain.prototype.iterate = function (field, resultSize, n, temperature, tries, rng) { 261 | var resultWidth = typeof resultSize === 'number' ? resultSize : resultSize[0], 262 | resultHeight = typeof resultSize === 'number' ? resultSize : resultSize[1], 263 | weights = this.getWeights(n), 264 | i; 265 | 266 | tries = tries || resultWidth * resultHeight; 267 | 268 | rng = rng || Math.random; 269 | field = field || generateBaseField(resultWidth, resultHeight, rng); 270 | 271 | applyChanges(field, weights, resultWidth, resultHeight, n, temperature, tries, rng); 272 | 273 | return field; 274 | }; 275 | 276 | module.exports = ConvChain; 277 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "convchain", 3 | "version": "1.1.0", 4 | "description": "Vanilla javascript port of https://github.com/mxgmn/ConvChain", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/kchapelier/convchain.git" 12 | }, 13 | "keywords": [ 14 | "pattern", 15 | "generation", 16 | "markov" 17 | ], 18 | "author": "Kevin Chapelier", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/kchapelier/convchain/issues" 22 | }, 23 | "homepage": "https://github.com/kchapelier/convchain" 24 | } 25 | --------------------------------------------------------------------------------