├── .gitignore ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── dist ├── colorcube.js ├── colorcube.js.map ├── colorcube.min.js └── colorcube.min.js.map ├── images ├── andrew-monks.png └── cocacola.jpg ├── index.html ├── package.json └── src └── colorcube.js /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | node_modules 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | 4 | 'clean': { 5 | src: ["dist"] 6 | }, 7 | 8 | 'babel': { 9 | options: { 10 | sourceMap: true 11 | }, 12 | dist: { 13 | files: { 14 | 'dist/colorcube.js': 'src/colorcube.js' 15 | } 16 | } 17 | }, 18 | 19 | 'uglify': { 20 | js: { 21 | options: { 22 | sourceMap: true, 23 | sourceMapIncludeSources: true, 24 | sourceMapIn: 'dist/colorcube.js.map', // input sourcemap from a previous compilation 25 | banner: '/* ' + grunt.file.read('LICENSE.md') + ' */' 26 | }, 27 | files: { 28 | 'dist/colorcube.min.js': 'dist/colorcube.js', 29 | // 'dist/colorcube.es6.min.js': 'dist/colorcube.es6.js' 30 | } 31 | } 32 | }, 33 | 34 | }); 35 | 36 | 37 | grunt.loadNpmTasks('grunt-contrib-clean'); 38 | grunt.loadNpmTasks('grunt-contrib-uglify'); 39 | grunt.loadNpmTasks('grunt-babel'); 40 | 41 | 42 | grunt.registerTask('cleanup', [ 43 | 'clean', 44 | ]); 45 | 46 | grunt.registerTask('build', [ 47 | 'cleanup', 48 | 'babel', 49 | 'uglify:js', 50 | ]); 51 | grunt.registerTask('default', [ 52 | 'build' 53 | ]); 54 | }; 55 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Ole Krause-Sparmann, 2 | Andrew Monks 3 | 4 | Permission to use, copy, modify, and/or distribute this software for 5 | any purpose with or without fee is hereby granted, provided that the 6 | above copyright notice and this permission notice appear in all 7 | copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 10 | WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 11 | WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 12 | AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 13 | DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 14 | PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 15 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 | PERFORMANCE OF THIS SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # colorcube.js 2 | 3 | ## [demo](http://amonks.github.io/colorcube-js/) 4 | 5 | This is a JavaScript port of [ColorCube](https://github.com/pixelogik/ColorCube), by Ole Krause-Sparmann. You can find an excellent description of how it works at [that repo](https://github.com/pixelogik/ColorCube) 6 | 7 | ColorCube is for dominant color extraction from RGB images. Given an image element, it returns a sorted array of hex colors. 8 | 9 | ## usage 10 | 11 | ```js 12 | var cc = new ColorCube( // all arguments are optional; these are the defaults: 13 | 20, // color-space resolution 14 | 0.2, // brightness threshold 15 | 0.4 // distinctness threshold 16 | ); 17 | var image = document.getElementById("image"); 18 | var colors = cc.get_colors(image); 19 | ``` 20 | 21 | ## info/caveat 22 | 23 | colorcube-js has no dependencies. Not even jQuery! However... 24 | 25 | colorcube-js's source uses several ES6 features (default arguments, for/of, let, arrow functions ((for lexical `this`)) ). The files in `dist` have been automagically run through [babel](http://babeljs.io/), which makes it run in current versions of Chrome and Firefox. (Not even Chrome Canary can handle the un-babelfied code). 26 | 27 | Even with Babel, colorcube still uses [Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol), which is [supported](https://kangax.github.io/compat-table/es6/#Symbol) by current versions of Chrome and Firefox but not Safari. 28 | 29 | There are multiple polyfills available to add Symbol to browsers that don't support it natively. [es6-symbol](https://github.com/medikoo/es6-symbol) is likely the smallest. 30 | 31 | In the demo page, I use [core-js](https://github.com/zloirock/core-js) because it was easy to CDN in. 32 | 33 | Babel maintains their own [polyfill](http://babeljs.io/docs/advanced/caveats/), which would work also. 34 | 35 | ## alternatives 36 | 37 | There are several other options for in-browser color extraction. I like this one best because it is small and easy to understand. (Props again to Ole Krause-Sparmann for the excellent algorithm). 38 | 39 | * [vibrant.js](http://jariz.github.io/vibrant.js/) is based on Android's support library 40 | * [color thief](http://lokeshdhakar.com/projects/color-thief/) works by color quantizing 41 | * [jquery.adaptive-backgrounds.js](https://github.com/briangonzalez/jquery.adaptive-backgrounds.js) is even smaller than colorcube-js but requires jQuery 42 | -------------------------------------------------------------------------------- /dist/colorcube.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Ole Krause-Sparmann, 3 | Andrew Monks 4 | Permission to use, copy, modify, and/or distribute this software for 5 | any purpose with or without fee is hereby granted, provided that the 6 | above copyright notice and this permission notice appear in all 7 | copies. 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 9 | WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 10 | WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 11 | AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 12 | DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 13 | PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 14 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | /* jshint esnext: true */ 19 | 20 | /* 21 | ColorCube Class 22 | 23 | Uses a 3d RGB histogram to find local maximas in the density distribution 24 | in order to retrieve dominant colors of pixel images 25 | */ 26 | "use strict"; 27 | 28 | function ColorCube() { 29 | "use strict"; 30 | 31 | // subclasses // // // // // // // // // // // // // // // // // // // // 32 | // // // // // // // // // // // // // // // // // // // // // // // // // 33 | 34 | /* 35 | CanvasImage Class 36 | Class that wraps the html image element and canvas. 37 | It also simplifies some of the canvas context manipulation 38 | with a set of helper functions. 39 | modified from Color Thief v2.0 40 | by Lokesh Dhakar - http://www.lokeshdhakar.com 41 | */ 42 | var resolution = arguments.length <= 0 || arguments[0] === undefined ? 20 : arguments[0]; 43 | 44 | var _this = this; 45 | 46 | var bright_threshold = arguments.length <= 1 || arguments[1] === undefined ? 0.2 : arguments[1]; 47 | var distinct_threshold = arguments.length <= 2 || arguments[2] === undefined ? 0.4 : arguments[2]; 48 | var CanvasImage = function CanvasImage(image) { 49 | 50 | if (!image instanceof HTMLElement) { 51 | throw "You've gotta use an html image element as ur input!!"; 52 | } 53 | 54 | var API = {}; 55 | 56 | var canvas = document.createElement('canvas'); 57 | var context = canvas.getContext('2d'); 58 | 59 | // document.body.appendChild(canvas); 60 | 61 | canvas.width = image.width; 62 | canvas.height = image.height; 63 | 64 | context.drawImage(image, 0, 0, image.width, image.height); 65 | 66 | API.getImageData = function () { 67 | return context.getImageData(0, 0, image.width, image.height); 68 | }; 69 | 70 | return API; 71 | }; 72 | 73 | /* 74 | CubeCell Class 75 | class that represents one voxel within rgb colorspace 76 | */ 77 | function CubeCell() { 78 | var API = {}; 79 | 80 | // Count of hits 81 | // (dividing the accumulators by this value gives the average color) 82 | API.hit_count = 0; 83 | 84 | // accumulators for color components 85 | API.r_acc = 0.0; 86 | API.g_acc = 0.0; 87 | API.b_acc = 0.0; 88 | 89 | return API; 90 | } 91 | 92 | /* 93 | LocalMaximum Class 94 | Local maxima as found during the image analysis. 95 | We need this class for ordering by cell hit count. 96 | */ 97 | function LocalMaximum(hit_count, cell_index, r, g, b) { 98 | var API = {}; 99 | 100 | // hit count of the cell 101 | API.hit_count = hit_count; 102 | 103 | // linear index of the cell 104 | API.cell_index = cell_index; 105 | 106 | // average color of the cell 107 | API.r = r; 108 | API.g = g; 109 | API.b = b; 110 | 111 | return API; 112 | } 113 | 114 | // ColorCube // // // // // // // // // // // // // // // // // // // // 115 | // // // // // // // // // // // // // // // // // // // // // // // // // 116 | 117 | var API = {}; 118 | 119 | // helper variable to have cell count handy 120 | var cell_count = resolution * resolution * resolution; 121 | 122 | // create cells 123 | var cells = []; 124 | for (var i = 0; i <= cell_count; i++) { 125 | cells.push(new CubeCell()); 126 | } 127 | 128 | // indices for neighbor cells in three dimensional grid 129 | var neighbour_indices = [[0, 0, 0], [0, 0, 1], [0, 0, -1], [0, 1, 0], [0, 1, 1], [0, 1, -1], [0, -1, 0], [0, -1, 1], [0, -1, -1], [1, 0, 0], [1, 0, 1], [1, 0, -1], [1, 1, 0], [1, 1, 1], [1, 1, -1], [1, -1, 0], [1, -1, 1], [1, -1, -1], [-1, 0, 0], [-1, 0, 1], [-1, 0, -1], [-1, 1, 0], [-1, 1, 1], [-1, 1, -1], [-1, -1, 0], [-1, -1, 1], [-1, -1, -1]]; 130 | 131 | // returns linear index for cell with given 3d index 132 | var cell_index = function cell_index(r, g, b) { 133 | return r + g * resolution + b * resolution * resolution; 134 | }; 135 | 136 | var clear_cells = function clear_cells() { 137 | var _iteratorNormalCompletion = true; 138 | var _didIteratorError = false; 139 | var _iteratorError = undefined; 140 | 141 | try { 142 | for (var _iterator = cells[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 143 | var cell = _step.value; 144 | 145 | cell.hit_count = 0; 146 | cell.r_acc = 0; 147 | cell.g_acc = 0; 148 | cell.b_acc = 0; 149 | } 150 | } catch (err) { 151 | _didIteratorError = true; 152 | _iteratorError = err; 153 | } finally { 154 | try { 155 | if (!_iteratorNormalCompletion && _iterator["return"]) { 156 | _iterator["return"](); 157 | } 158 | } finally { 159 | if (_didIteratorError) { 160 | throw _iteratorError; 161 | } 162 | } 163 | } 164 | }; 165 | 166 | API.get_colors = function (image) { 167 | var canvasimage = new CanvasImage(image); 168 | 169 | var m = find_local_maxima(canvasimage); 170 | 171 | m = filter_distinct_maxima(m); 172 | 173 | var colors = []; 174 | var _iteratorNormalCompletion2 = true; 175 | var _didIteratorError2 = false; 176 | var _iteratorError2 = undefined; 177 | 178 | try { 179 | for (var _iterator2 = m[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 180 | var n = _step2.value; 181 | 182 | var r = Math.round(n.r * 255.0); 183 | var g = Math.round(n.g * 255.0); 184 | var b = Math.round(n.b * 255.0); 185 | var color = rgbToHex(r, g, b); 186 | if (color === "#NaNNaNNaN") { 187 | continue; 188 | } 189 | colors.push(color); 190 | } 191 | } catch (err) { 192 | _didIteratorError2 = true; 193 | _iteratorError2 = err; 194 | } finally { 195 | try { 196 | if (!_iteratorNormalCompletion2 && _iterator2["return"]) { 197 | _iterator2["return"](); 198 | } 199 | } finally { 200 | if (_didIteratorError2) { 201 | throw _iteratorError2; 202 | } 203 | } 204 | } 205 | 206 | return colors; 207 | }; 208 | 209 | var componentToHex = function componentToHex(c) { 210 | var hex = c.toString(16); 211 | return hex.length == 1 ? "0" + hex : hex; 212 | }; 213 | 214 | var rgbToHex = function rgbToHex(r, g, b) { 215 | return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); 216 | }; 217 | 218 | // finds and returns local maxima in 3d histogram, sorted by hit count 219 | var find_local_maxima = function find_local_maxima(image) { 220 | // reset all cells 221 | clear_cells(); 222 | 223 | // get the image pixels 224 | var data = image.getImageData().data; 225 | 226 | // iterate over all pixels of the image 227 | for (var i = 0; i < data.length; i += 4) { 228 | // get color components 229 | var red = data[i] / 255.0; 230 | var green = data[i + 1] / 255.0; 231 | var blue = data[i + 2] / 255.0; 232 | var alpha = data[i + 3] / 255.0; 233 | 234 | // stop if brightnesses are all below threshold 235 | if (red < bright_threshold && green < bright_threshold && blue < bright_threshold) {} 236 | // continue; 237 | 238 | // weigh colors by alpha channel 239 | red *= alpha; 240 | green *= alpha; 241 | blue *= alpha; 242 | 243 | // map color components to cell indicies in each color dimension 244 | // TODO maybe this should round down? OG colorcube uses python's int() 245 | var _r_index = Math.round(red * (resolution - 1.0)); 246 | var _g_index = Math.round(green * (resolution - 1.0)); 247 | var _b_index = Math.round(blue * (resolution - 1.0)); 248 | 249 | // compute linear cell index 250 | var index = cell_index(_r_index, _g_index, _b_index); 251 | 252 | // increase hit count of cell 253 | cells[index].hit_count += 1; 254 | 255 | // add pixel colors to cell color accumulators 256 | cells[index].r_acc += red; 257 | cells[index].g_acc += green; 258 | cells[index].b_acc += blue; 259 | } 260 | 261 | // we collect local maxima in here 262 | var local_maxima = []; 263 | 264 | // find local maxima in the grid 265 | for (var r = 0; r < resolution; r++) { 266 | for (var g = 0; g < resolution; g++) { 267 | for (var b = 0; b < resolution; b++) { 268 | 269 | var local_index = cell_index(r, g, b); 270 | 271 | // get hit count of this cell 272 | var local_hit_count = cells[local_index].hit_count; 273 | 274 | // if this cell has no hits, ignore it 275 | if (local_hit_count === 0) { 276 | continue; 277 | } 278 | 279 | // it's a local maxima until we find a neighbor with a higher hit count 280 | var is_local_maximum = true; 281 | 282 | // check if any neighbor has a higher hit count, if so, no local maxima 283 | for (var n in new Array(27)) { 284 | r_index = r + _this.neighbor_indices[n][0]; 285 | g_index = g + _this.neighbor_indices[n][1]; 286 | b_index = b + _this.neighbor_indices[n][2]; 287 | 288 | // only check valid cell indices 289 | if (r_index >= 0 && g_index >= 0 && b_index >= 0) { 290 | if (r_index < _this.resolution && g_index < _this.resolution && b_index < _this.resolution) { 291 | if (_this.cells[_this.cell_index(r_index, g_index, b_index)].hit_count > local_hit_count) { 292 | // this is not a local maximum 293 | is_local_maximum = false; 294 | break; 295 | } 296 | } 297 | } 298 | } 299 | 300 | // if this is not a local maximum, continue with loop 301 | if (is_local_maximum === false) { 302 | continue; 303 | } 304 | 305 | // otherwise add this cell as a local maximum 306 | var avg_r = cells[local_index].r_acc / cells[local_index].hit_count; 307 | var avg_g = cells[local_index].g_acc / cells[local_index].hit_count; 308 | var avg_b = cells[local_index].b_acc / cells[local_index].hit_count; 309 | var localmaximum = new LocalMaximum(local_hit_count, local_index, avg_r, avg_g, avg_b); 310 | 311 | local_maxima.push(localmaximum); 312 | } 313 | } 314 | } 315 | 316 | // return local maxima sorted with respect to hit count 317 | local_maxima = local_maxima.sort(function (a, b) { 318 | return b.hit_count - a.hit_count; 319 | }); 320 | 321 | return local_maxima; 322 | }; 323 | 324 | // Returns a filtered version of the specified array of maxima, 325 | // in which all entries have a minimum distance of distinct_threshold 326 | var filter_distinct_maxima = function filter_distinct_maxima(maxima) { 327 | 328 | var result = []; 329 | 330 | // check for each maximum 331 | var _iteratorNormalCompletion3 = true; 332 | var _didIteratorError3 = false; 333 | var _iteratorError3 = undefined; 334 | 335 | try { 336 | for (var _iterator3 = maxima[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { 337 | var m = _step3.value; 338 | 339 | // this color is distinct until an earlier color is too close 340 | var is_distinct = true; 341 | 342 | var _iteratorNormalCompletion4 = true; 343 | 344 | // add to filtered array if is distinct 345 | var _didIteratorError4 = false; 346 | var _iteratorError4 = undefined; 347 | 348 | try { 349 | for (var _iterator4 = result[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { 350 | var n = _step4.value; 351 | 352 | // compute delta components 353 | var r_delta = m.r - n.r; 354 | var g_delta = m.g - n.g; 355 | var b_delta = m.b - n.b; 356 | 357 | // compute delta in color space distance 358 | var delta = Math.sqrt(r_delta * r_delta + g_delta * g_delta + b_delta * b_delta); 359 | 360 | // if too close, mark as non distinct and break inner loop 361 | if (delta < distinct_threshold) { 362 | is_distinct = false; 363 | break; 364 | } 365 | } 366 | } catch (err) { 367 | _didIteratorError4 = true; 368 | _iteratorError4 = err; 369 | } finally { 370 | try { 371 | if (!_iteratorNormalCompletion4 && _iterator4["return"]) { 372 | _iterator4["return"](); 373 | } 374 | } finally { 375 | if (_didIteratorError4) { 376 | throw _iteratorError4; 377 | } 378 | } 379 | } 380 | 381 | if (is_distinct === true) { 382 | result.push(m); 383 | } 384 | } 385 | } catch (err) { 386 | _didIteratorError3 = true; 387 | _iteratorError3 = err; 388 | } finally { 389 | try { 390 | if (!_iteratorNormalCompletion3 && _iterator3["return"]) { 391 | _iterator3["return"](); 392 | } 393 | } finally { 394 | if (_didIteratorError3) { 395 | throw _iteratorError3; 396 | } 397 | } 398 | } 399 | 400 | return result; 401 | }; 402 | 403 | return API; 404 | } 405 | //# sourceMappingURL=colorcube.js.map 406 | -------------------------------------------------------------------------------- /dist/colorcube.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["src/colorcube.js"],"names":[],"mappings":"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","file":"src/colorcube.js","sourcesContent":["/*\nCopyright (c) 2015, Ole Krause-Sparmann,\n Andrew Monks \nPermission to use, copy, modify, and/or distribute this software for\nany purpose with or without fee is hereby granted, provided that the\nabove copyright notice and this permission notice appear in all\ncopies.\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL\nWARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE\nAUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL\nDAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR\nPROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\nTORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n*/\n\n/* jshint esnext: true */\n\n/*\nColorCube Class\n\n Uses a 3d RGB histogram to find local maximas in the density distribution\n in order to retrieve dominant colors of pixel images\n*/\nfunction ColorCube( resolution = 20,\n bright_threshold = 0.2,\n distinct_threshold = 0.4 ) {\n \"use strict\";\n\n\n\n // subclasses // // // // // // // // // // // // // // // // // // // //\n // // // // // // // // // // // // // // // // // // // // // // // // //\n\n\n\n /*\n CanvasImage Class\n\n Class that wraps the html image element and canvas.\n It also simplifies some of the canvas context manipulation\n with a set of helper functions.\n\n modified from Color Thief v2.0\n by Lokesh Dhakar - http://www.lokeshdhakar.com\n */\n let CanvasImage = function (image) {\n\n if (! image instanceof HTMLElement) {\n throw \"You've gotta use an html image element as ur input!!\";\n }\n\n let API = {};\n\n let canvas = document.createElement('canvas');\n let context = canvas.getContext('2d');\n\n // document.body.appendChild(canvas);\n\n canvas.width = image.width;\n canvas.height = image.height;\n\n context.drawImage(image, 0, 0, image.width, image.height);\n\n API.getImageData = () => {\n return context.getImageData(0, 0, image.width, image.height);\n };\n\n return API;\n };\n\n\n\n\n /*\n CubeCell Class\n\n class that represents one voxel within rgb colorspace\n */\n function CubeCell() {\n let API = {};\n\n // Count of hits\n // (dividing the accumulators by this value gives the average color)\n API.hit_count = 0;\n\n // accumulators for color components\n API.r_acc = 0.0;\n API.g_acc = 0.0;\n API.b_acc = 0.0;\n\n return API;\n }\n\n\n\n\n /*\n LocalMaximum Class\n\n Local maxima as found during the image analysis.\n We need this class for ordering by cell hit count.\n */\n function LocalMaximum(hit_count, cell_index, r, g, b) {\n let API = {};\n\n // hit count of the cell\n API.hit_count = hit_count;\n\n // linear index of the cell\n API.cell_index = cell_index;\n\n // average color of the cell\n API.r = r;\n API.g = g;\n API.b = b;\n\n return API;\n }\n\n\n\n\n\n // ColorCube // // // // // // // // // // // // // // // // // // // //\n // // // // // // // // // // // // // // // // // // // // // // // // //\n\n\n\n\n let API = {};\n\n // helper variable to have cell count handy\n let cell_count = resolution * resolution * resolution;\n\n // create cells\n let cells = [];\n for (let i = 0; i <= cell_count; i++) {\n cells.push( new CubeCell() );\n }\n\n // indices for neighbor cells in three dimensional grid\n let neighbour_indices = [\n [ 0, 0, 0],\n [ 0, 0, 1],\n [ 0, 0,-1],\n\n [ 0, 1, 0],\n [ 0, 1, 1],\n [ 0, 1,-1],\n\n [ 0,-1, 0],\n [ 0,-1, 1],\n [ 0,-1,-1],\n\n [ 1, 0, 0],\n [ 1, 0, 1],\n [ 1, 0,-1],\n\n [ 1, 1, 0],\n [ 1, 1, 1],\n [ 1, 1,-1],\n\n [ 1,-1, 0],\n [ 1,-1, 1],\n [ 1,-1,-1],\n\n [-1, 0, 0],\n [-1, 0, 1],\n [-1, 0,-1],\n\n [-1, 1, 0],\n [-1, 1, 1],\n [-1, 1,-1],\n\n [-1,-1, 0],\n [-1,-1, 1],\n [-1,-1,-1]\n ];\n\n // returns linear index for cell with given 3d index\n let cell_index = (r, g, b) => {\n return (r + g * resolution + b * resolution * resolution);\n };\n\n let clear_cells = () => {\n for (let cell of cells) {\n cell.hit_count = 0;\n cell.r_acc = 0;\n cell.g_acc = 0;\n cell.b_acc = 0;\n }\n };\n\n API.get_colors = (image) => {\n let canvasimage = new CanvasImage(image);\n\n let m = find_local_maxima(canvasimage);\n\n m = filter_distinct_maxima(m);\n\n let colors = [];\n for (let n of m) {\n let r = Math.round(n.r * 255.0);\n let g = Math.round(n.g * 255.0);\n let b = Math.round(n.b * 255.0);\n let color = rgbToHex(r, g, b);\n if (color === \"#NaNNaNNaN\") {continue;}\n colors.push(color);\n }\n\n return colors;\n };\n\n let componentToHex = (c) => {\n let hex = c.toString(16);\n return hex.length == 1 ? \"0\" + hex : hex;\n };\n\n let rgbToHex = (r, g, b) => {\n return \"#\" + componentToHex(r) + componentToHex(g) + componentToHex(b);\n };\n\n // finds and returns local maxima in 3d histogram, sorted by hit count\n let find_local_maxima = (image) => {\n // reset all cells\n clear_cells();\n\n // get the image pixels\n let data = image.getImageData().data;\n\n // iterate over all pixels of the image\n for(let i = 0; i < data.length; i += 4) {\n // get color components\n let red = data[i] / 255.0;\n let green = data[i+1] / 255.0;\n let blue = data[i+2] / 255.0;\n let alpha = data[i+3] / 255.0;\n\n // stop if brightnesses are all below threshold\n if (red < bright_threshold &&\n green < bright_threshold &&\n blue < bright_threshold) {\n // continue;\n }\n\n // weigh colors by alpha channel\n red *= alpha;\n green *= alpha;\n blue *= alpha;\n\n // map color components to cell indicies in each color dimension\n // TODO maybe this should round down? OG colorcube uses python's int()\n let r_index = Math.round( red * ( resolution - 1.0 ) );\n let g_index = Math.round( green * ( resolution - 1.0 ) );\n let b_index = Math.round( blue * ( resolution - 1.0 ) );\n\n // compute linear cell index\n let index = cell_index(r_index, g_index, b_index);\n\n // increase hit count of cell\n cells[index].hit_count += 1;\n\n // add pixel colors to cell color accumulators\n cells[index].r_acc += red;\n cells[index].g_acc += green;\n cells[index].b_acc += blue;\n }\n\n // we collect local maxima in here\n let local_maxima = [];\n\n // find local maxima in the grid\n for (let r = 0; r < resolution; r++) {\n for (let g = 0; g < resolution; g++) {\n for (let b = 0; b < resolution; b++) {\n\n let local_index = cell_index(r, g, b);\n\n // get hit count of this cell\n let local_hit_count = cells[local_index].hit_count;\n\n // if this cell has no hits, ignore it\n if (local_hit_count === 0) {\n continue;\n }\n\n // it's a local maxima until we find a neighbor with a higher hit count\n let is_local_maximum = true;\n\n // check if any neighbor has a higher hit count, if so, no local maxima\n for (let n in new Array(27)) {\n r_index = r + this.neighbor_indices[n][0];\n g_index = g + this.neighbor_indices[n][1];\n b_index = b + this.neighbor_indices[n][2];\n\n // only check valid cell indices\n if (r_index >= 0 && g_index >= 0 && b_index >= 0) {\n if (r_index < this.resolution && g_index < this.resolution && b_index < this.resolution) {\n if (this.cells[this.cell_index(r_index, g_index, b_index)].hit_count > local_hit_count) {\n // this is not a local maximum\n is_local_maximum = false;\n break;\n }\n }\n }\n }\n\n // if this is not a local maximum, continue with loop\n if (is_local_maximum === false) {\n continue;\n }\n\n // otherwise add this cell as a local maximum\n let avg_r = cells[local_index].r_acc / cells[local_index].hit_count;\n let avg_g = cells[local_index].g_acc / cells[local_index].hit_count;\n let avg_b = cells[local_index].b_acc / cells[local_index].hit_count;\n let localmaximum = new LocalMaximum(local_hit_count, local_index, avg_r, avg_g, avg_b);\n\n local_maxima.push( localmaximum );\n }\n }\n }\n\n // return local maxima sorted with respect to hit count\n local_maxima = local_maxima.sort(function(a, b) { return b.hit_count - a.hit_count; });\n\n return local_maxima;\n };\n\n // Returns a filtered version of the specified array of maxima,\n // in which all entries have a minimum distance of distinct_threshold\n let filter_distinct_maxima = (maxima) => {\n\n let result = [];\n\n // check for each maximum\n for (let m of maxima) {\n // this color is distinct until an earlier color is too close\n let is_distinct = true;\n\n for (let n of result) {\n // compute delta components\n let r_delta = m.r - n.r;\n let g_delta = m.g - n.g;\n let b_delta = m.b - n.b;\n\n // compute delta in color space distance\n let delta = Math.sqrt(r_delta * r_delta + g_delta * g_delta + b_delta * b_delta);\n\n // if too close, mark as non distinct and break inner loop\n if (delta < distinct_threshold) {\n is_distinct = false;\n break;\n }\n }\n\n // add to filtered array if is distinct\n if (is_distinct === true) {\n result.push(m);\n }\n }\n\n\n return result;\n };\n\n return API;\n}\n"]} -------------------------------------------------------------------------------- /dist/colorcube.min.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015, Ole Krause-Sparmann, 2 | Andrew Monks 3 | 4 | Permission to use, copy, modify, and/or distribute this software for 5 | any purpose with or without fee is hereby granted, provided that the 6 | above copyright notice and this permission notice appear in all 7 | copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 10 | WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 11 | WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 12 | AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 13 | DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 14 | PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 15 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 | PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | "use strict";function ColorCube(){function a(){var a={};return a.hit_count=0,a.r_acc=0,a.g_acc=0,a.b_acc=0,a}function b(a,b,c,d,e){var f={};return f.hit_count=a,f.cell_index=b,f.r=c,f.g=d,f.b=e,f}for(var c=arguments.length<=0||void 0===arguments[0]?20:arguments[0],d=this,e=(arguments.length<=1||void 0===arguments[1]?.2:arguments[1],arguments.length<=2||void 0===arguments[2]?.4:arguments[2]),f=function(a){if(!a instanceof HTMLElement)throw"You've gotta use an html image element as ur input!!";var b={},c=document.createElement("canvas"),d=c.getContext("2d");return c.width=a.width,c.height=a.height,d.drawImage(a,0,0,a.width,a.height),b.getImageData=function(){return d.getImageData(0,0,a.width,a.height)},b},g={},h=c*c*c,i=[],j=0;h>=j;j++)i.push(new a);var k=function(a,b,d){return a+b*c+d*c*c},l=function(){var a=!0,b=!1,c=void 0;try{for(var d,e=i[Symbol.iterator]();!(a=(d=e.next()).done);a=!0){var f=d.value;f.hit_count=0,f.r_acc=0,f.g_acc=0,f.b_acc=0}}catch(g){b=!0,c=g}finally{try{!a&&e["return"]&&e["return"]()}finally{if(b)throw c}}};g.get_colors=function(a){var b=new f(a),c=o(b);c=p(c);var d=[],e=!0,g=!1,h=void 0;try{for(var i,j=c[Symbol.iterator]();!(e=(i=j.next()).done);e=!0){var k=i.value,l=Math.round(255*k.r),m=Math.round(255*k.g),q=Math.round(255*k.b),r=n(l,m,q);"#NaNNaNNaN"!==r&&d.push(r)}}catch(s){g=!0,h=s}finally{try{!e&&j["return"]&&j["return"]()}finally{if(g)throw h}}return d};var m=function(a){var b=a.toString(16);return 1==b.length?"0"+b:b},n=function(a,b,c){return"#"+m(a)+m(b)+m(c)},o=function(a){l();for(var e=a.getImageData().data,f=0;fs;s++)for(var t=0;c>t;t++)for(var u=0;c>u;u++){var v=k(s,t,u),w=i[v].hit_count;if(0!==w){var x=!0;for(var y in new Array(27))if(r_index=s+d.neighbor_indices[y][0],g_index=t+d.neighbor_indices[y][1],b_index=u+d.neighbor_indices[y][2],r_index>=0&&g_index>=0&&b_index>=0&&r_indexw){x=!1;break}if(x!==!1){var z=i[v].r_acc/i[v].hit_count,A=i[v].g_acc/i[v].hit_count,B=i[v].b_acc/i[v].hit_count,C=new b(w,v,z,A,B);r.push(C)}}}return r=r.sort(function(a,b){return b.hit_count-a.hit_count})},p=function(a){var b=[],c=!0,d=!1,f=void 0;try{for(var g,h=a[Symbol.iterator]();!(c=(g=h.next()).done);c=!0){var i=g.value,j=!0,k=!0,l=!1,m=void 0;try{for(var n,o=b[Symbol.iterator]();!(k=(n=o.next()).done);k=!0){var p=n.value,q=i.r-p.r,r=i.g-p.g,s=i.b-p.b,t=Math.sqrt(q*q+r*r+s*s);if(e>t){j=!1;break}}}catch(u){l=!0,m=u}finally{try{!k&&o["return"]&&o["return"]()}finally{if(l)throw m}}j===!0&&b.push(i)}}catch(u){d=!0,f=u}finally{try{!c&&h["return"]&&h["return"]()}finally{if(d)throw f}}return b};return g} 19 | //# sourceMappingURL=colorcube.min.js.map -------------------------------------------------------------------------------- /dist/colorcube.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"colorcube.min.js","sources":["src/colorcube.js"],"names":["ColorCube","CubeCell","API","hit_count","r_acc","g_acc","b_acc","LocalMaximum","cell_index","r","g","b","resolution","arguments","length","undefined","distinct_threshold","CanvasImage","image","HTMLElement","canvas","document","createElement","context","getContext","width","height","drawImage","getImageData","cell_count","cells","i","push","clear_cells","_step","_iterator","Symbol","iterator","_iteratorNormalCompletion","next","done","cell","value","get_colors","canvasimage","m","find_local_maxima","filter_distinct_maxima","colors","_step2","_iterator2","_iteratorNormalCompletion2","n","Math","round","color","rgbToHex","componentToHex","c","hex","toString","data","red","green","blue","alpha","_r_index","_g_index","_b_index","index","local_maxima","local_index","local_hit_count","is_local_maximum","Array","r_index","_this","neighbor_indices","g_index","b_index","avg_r","avg_g","avg_b","localmaximum","sort","a","maxima","result","_step3","_iterator3","_iteratorNormalCompletion3","is_distinct","_step4","_iterator4","_iteratorNormalCompletion4","r_delta","g_delta","b_delta","delta","sqrt"],"mappings":";;;;;;;;;;;;;;;;;YAyBA,SAASA,aAuDP,QAASC,KACP,GAAIC,KAWJ,OAPAA,GAAIC,UAAY,EAGhBD,EAAIE,MAAQ,EACZF,EAAIG,MAAQ,EACZH,EAAII,MAAQ,EAELJ,EAYT,QAASK,GAAaJ,EAAWK,EAAYC,EAAGC,EAAGC,GACjD,GAAIT,KAaJ,OAVAA,GAAIC,UAAYA,EAGhBD,EAAIM,WAAaA,EAGjBN,EAAIO,EAAIA,EACRP,EAAIQ,EAAIA,EACRR,EAAIS,EAAIA,EAEDT,EAoBT,IAAK,GAjHaU,GAAUC,UAAAC,QAAA,GAAAC,SAAAF,UAAA,GAAG,GAAEA,UAAA,UAEfG,GADgBH,UAAAC,QAAA,GAAAC,SAAAF,UAAA,GAAG,GAAGA,UAAA,GACJA,UAAAC,QAAA,GAAAC,SAAAF,UAAA,GAAG,GAAGA,UAAA,IAoBtCI,EAAc,SAAUC,GAE1B,IAAMA,YAAiBC,aACrB,KAAM,sDAGR,IAAIjB,MAEAkB,EAAUC,SAASC,cAAc,UACjCC,EAAUH,EAAOI,WAAW,KAahC,OATAJ,GAAOK,MAASP,EAAMO,MACtBL,EAAOM,OAASR,EAAMQ,OAEtBH,EAAQI,UAAUT,EAAO,EAAG,EAAGA,EAAMO,MAAOP,EAAMQ,QAElDxB,EAAI0B,aAAe,WACjB,MAAOL,GAAQK,aAAa,EAAG,EAAGV,EAAMO,MAAOP,EAAMQ,SAGhDxB,GA8DLA,KAGA2B,EAAajB,EAAaA,EAAaA,EAGvCkB,KACKC,EAAI,EAASF,GAANE,EAAkBA,IAChCD,EAAME,KAAM,GAAI/B,GAIlB,IAuCIO,GAAa,SAACC,EAAGC,EAAGC,GACtB,MAAQF,GAAIC,EAAIE,EAAaD,EAAIC,EAAaA,GAG5CqB,EAAc,sCAChB,IAAA,GAAsBC,GAAtBC,EAAiBL,EAAKM,OAAAC,cAAAC,GAAAJ,EAAAC,EAAAI,QAAAC,MAAAF,GAAA,EAAE,IAAfG,GAAIP,EAAAQ,KACXD,GAAKtC,UAAY,EACjBsC,EAAKrC,MAAQ,EACbqC,EAAKpC,MAAQ,EACboC,EAAKnC,MAAQ,uFAIjBJ,GAAIyC,WAAa,SAACzB,GAChB,GAAI0B,GAAc,GAAI3B,GAAYC,GAE9B2B,EAAIC,EAAkBF,EAE1BC,GAAIE,EAAuBF,EAE3B,IAAIG,6BACJ,IAAA,GAAeC,GAAfC,EAAcL,EAACT,OAAAC,cAAAc,GAAAF,EAAAC,EAAAX,QAAAC,MAAAW,GAAA,EAAE,IAARC,GAACH,EAAAP,MACJjC,EAAI4C,KAAKC,MAAY,IAANF,EAAE3C,GACjBC,EAAI2C,KAAKC,MAAY,IAANF,EAAE1C,GACjBC,EAAI0C,KAAKC,MAAY,IAANF,EAAEzC,GACjB4C,EAAQC,EAAS/C,EAAGC,EAAGC,EACb,gBAAV4C,GACJP,EAAOhB,KAAKuB,uFAGd,MAAOP,GAGT,IAAIS,GAAiB,SAACC,GACpB,GAAIC,GAAMD,EAAEE,SAAS,GACrB,OAAqB,IAAdD,EAAI7C,OAAc,IAAM6C,EAAMA,GAGnCH,EAAW,SAAC/C,EAAGC,EAAGC,GACpB,MAAO,IAAM8C,EAAehD,GAAKgD,EAAe/C,GAAK+C,EAAe9C,IAIlEmC,EAAoB,SAAC5B,GAEvBe,GAMA,KAAI,GAHA4B,GAAO3C,EAAMU,eAAeiC,KAGxB9B,EAAI,EAAGA,EAAI8B,EAAK/C,OAAQiB,GAAK,EAAG,CAEtC,GAAI+B,GAAMD,EAAK9B,GAAK,IAChBgC,EAAQF,EAAK9B,EAAE,GAAK,IACpBiC,EAAOH,EAAK9B,EAAE,GAAK,IACnBkC,EAAQJ,EAAK9B,EAAE,GAAK,GAUxB+B,IAAOG,EACPF,GAASE,EACTD,GAAQC,CAIR,IAAIC,GAAUb,KAAKC,MAAOQ,GAAQlD,EAAa,IAC3CuD,EAAUd,KAAKC,MAAOS,GAAUnD,EAAa,IAC7CwD,EAAUf,KAAKC,MAAOU,GAASpD,EAAa,IAG5CyD,EAAQ7D,EAAW0D,EAASC,EAASC,EAGzCtC,GAAMuC,GAAOlE,WAAa,EAG1B2B,EAAMuC,GAAOjE,OAAS0D,EACtBhC,EAAMuC,GAAOhE,OAAS0D,EACtBjC,EAAMuC,GAAO/D,OAAS0D,EAOxB,IAAK,GAHDM,MAGK7D,EAAI,EAAOG,EAAJH,EAAgBA,IAC9B,IAAK,GAAIC,GAAI,EAAOE,EAAJF,EAAgBA,IAC9B,IAAK,GAAIC,GAAI,EAAOC,EAAJD,EAAgBA,IAAK,CAEnC,GAAI4D,GAAc/D,EAAWC,EAAGC,EAAGC,GAG/B6D,EAAkB1C,EAAMyC,GAAapE,SAGzC,IAAwB,IAApBqE,EAAJ,CAKA,GAAIC,IAAmB,CAGvB,KAAK,GAAIrB,KAAK,IAAIsB,OAAM,IAMtB,GALAC,QAAUlE,EAAImE,EAAKC,iBAAiBzB,GAAG,GACvC0B,QAAUpE,EAAIkE,EAAKC,iBAAiBzB,GAAG,GACvC2B,QAAUpE,EAAIiE,EAAKC,iBAAiBzB,GAAG,GAGnCuB,SAAW,GAAKG,SAAW,GAAKC,SAAW,GACzCJ,QAAUC,EAAKhE,YAAckE,QAAUF,EAAKhE,YAAcmE,QAAUH,EAAKhE,YACvEgE,EAAK9C,MAAM8C,EAAKpE,WAAWmE,QAASG,QAASC,UAAU5E,UAAYqE,EAAiB,CAEtFC,GAAmB,CACnB,OAOR,GAAIA,KAAqB,EAAzB,CAKA,GAAIO,GAAQlD,EAAMyC,GAAanE,MAAQ0B,EAAMyC,GAAapE,UACtD8E,EAAQnD,EAAMyC,GAAalE,MAAQyB,EAAMyC,GAAapE,UACtD+E,EAAQpD,EAAMyC,GAAajE,MAAQwB,EAAMyC,GAAapE,UACtDgF,EAAe,GAAI5E,GAAaiE,EAAiBD,EAAaS,EAAOC,EAAOC,EAEhFZ,GAAatC,KAAMmD,KAQzB,MAFAb,GAAeA,EAAac,KAAK,SAASC,EAAG1E,GAAK,MAAOA,GAAER,UAAYkF,EAAElF,aAOvE4C,EAAyB,SAACuC,GAE5B,GAAIC,6BAGJ,IAAA,GAAoBC,GAApBC,EAAcH,EAAMlD,OAAAC,cAAAqD,GAAAF,EAAAC,EAAAlD,QAAAC,MAAAkD,GAAA,EAAE,IAAb7C,GAAC2C,EAAA9C,MAEJiD,GAAc,yBAElB,IAAA,GAAoBC,GAApBC,EAAcN,EAAMnD,OAAAC,cAAAyD,GAAAF,EAAAC,EAAAtD,QAAAC,MAAAsD,GAAA,EAAE,IAAb1C,GAACwC,EAAAlD,MAEJqD,EAAUlD,EAAEpC,EAAI2C,EAAE3C,EAClBuF,EAAUnD,EAAEnC,EAAI0C,EAAE1C,EAClBuF,EAAUpD,EAAElC,EAAIyC,EAAEzC,EAGlBuF,EAAQ7C,KAAK8C,KAAKJ,EAAUA,EAAUC,EAAUA,EAAUC,EAAUA,EAGxE,IAAYjF,EAARkF,EAA4B,CAC9BP,GAAc,CACd,4FAKAA,KAAgB,GAClBJ,EAAOvD,KAAKa,uFAKhB,MAAO0C,GAGT,OAAOrF;;;;;;;;;;AAvVT,SAAS,SAAS,GAE6B;AAC7C,cAAY,CAAC;;;;;;;;;;;;;MAHK,UAAU,yDAAG,EAAE;;;;MACf,gBAAgB,yDAAG,GAAG;MACtB,kBAAkB,yDAAG,GAAG;AAoB1C,MAAI,WAAW,GAAG,SAAd,WAAW,CAAa,KAAK,EAAE;;AAEjC,QAAI,CAAE,KAAK,YAAY,WAAW,EAAE;AAClC,YAAM,sDAAsD,CAAC;KAC9D;;AAED,QAAI,GAAG,GAAG,EAAE,CAAC;;AAEb,QAAI,MAAM,GAAI,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;AAC/C,QAAI,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;;;;AAItC,UAAM,CAAC,KAAK,GAAI,KAAK,CAAC,KAAK,CAAC;AAC5B,UAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;;AAE7B,WAAO,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;;AAE1D,OAAG,CAAC,YAAY,GAAG,YAAM;AACvB,aAAO,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;KAC9D,CAAC;;AAEF,WAAO,GAAG,CAAC;GACZ,CAAC;;;;;;AAUF,WAAS,QAAQ,GAAG;AAClB,QAAI,GAAG,GAAG,EAAE,CAAC;;;;AAIb,OAAG,CAAC,SAAS,GAAG,CAAC,CAAC;;;AAGlB,OAAG,CAAC,KAAK,GAAG,GAAG,CAAC;AAChB,OAAG,CAAC,KAAK,GAAG,GAAG,CAAC;AAChB,OAAG,CAAC,KAAK,GAAG,GAAG,CAAC;;AAEhB,WAAO,GAAG,CAAC;GACZ;;;;;;;AAWD,WAAS,YAAY,CAAC,SAAS,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACpD,QAAI,GAAG,GAAG,EAAE,CAAC;;;AAGb,OAAG,CAAC,SAAS,GAAG,SAAS,CAAC;;;AAG1B,OAAG,CAAC,UAAU,GAAG,UAAU,CAAC;;;AAG5B,OAAG,CAAC,CAAC,GAAG,CAAC,CAAC;AACV,OAAG,CAAC,CAAC,GAAG,CAAC,CAAC;AACV,OAAG,CAAC,CAAC,GAAG,CAAC,CAAC;;AAEV,WAAO,GAAG,CAAC;GACZ;;;;;AAYD,MAAI,GAAG,GAAG,EAAE,CAAC;;;AAGb,MAAI,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;;;AAGtD,MAAI,KAAK,GAAG,EAAE,CAAC;AACf,OAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAK,UAAU,EAAE,CAAC,EAAE,EAAE;AACrC,SAAK,CAAC,IAAI,CAAE,IAAI,QAAQ,EAAE,CAAE,CAAC;GAC9B;;;AAGD,MAAI,iBAAiB,GAAG,CACtB,CAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACV,CAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACV,CAAE,CAAC,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC,EAEV,CAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACV,CAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACV,CAAE,CAAC,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC,EAEV,CAAE,CAAC,EAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EACV,CAAE,CAAC,EAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EACV,CAAE,CAAC,EAAC,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,EAEV,CAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACV,CAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACV,CAAE,CAAC,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC,EAEV,CAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACV,CAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACV,CAAE,CAAC,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC,EAEV,CAAE,CAAC,EAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EACV,CAAE,CAAC,EAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EACV,CAAE,CAAC,EAAC,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,EAEV,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACV,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACV,CAAC,CAAC,CAAC,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC,EAEV,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACV,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACV,CAAC,CAAC,CAAC,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC,EAEV,CAAC,CAAC,CAAC,EAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EACV,CAAC,CAAC,CAAC,EAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EACV,CAAC,CAAC,CAAC,EAAC,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,CACX,CAAC;;;AAGF,MAAI,UAAU,GAAG,SAAb,UAAU,CAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAK;AAC5B,WAAQ,CAAC,GAAG,CAAC,GAAG,UAAU,GAAG,CAAC,GAAG,UAAU,GAAG,UAAU,CAAE;GAC3D,CAAC;;AAEF,MAAI,WAAW,GAAG,SAAd,WAAW,GAAS;;;;;;AACtB,2BAAiB,KAAK,8HAAE;YAAf,IAAI;;AACX,YAAI,CAAC,SAAS,GAAG,CAAC,CAAC;AACnB,YAAI,CAAC,KAAK,GAAG,CAAC,CAAC;AACf,YAAI,CAAC,KAAK,GAAG,CAAC,CAAC;AACf,YAAI,CAAC,KAAK,GAAG,CAAC,CAAC;OAChB;;;;;;;;;;;;;;;GACF,CAAC;;AAEF,KAAG,CAAC,UAAU,GAAG,UAAC,KAAK,EAAK;AAC1B,QAAI,WAAW,GAAG,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC;;AAEzC,QAAI,CAAC,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;;AAEvC,KAAC,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAAC;;AAE9B,QAAI,MAAM,GAAG,EAAE,CAAC;;;;;;AAChB,4BAAc,CAAC,mIAAE;YAAR,CAAC;;AACR,YAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;AAChC,YAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;AAChC,YAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;AAChC,YAAI,KAAK,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC9B,YAAI,KAAK,KAAK,YAAY,EAAE;AAAC,mBAAS;SAAC;AACvC,cAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;OACpB;;;;;;;;;;;;;;;;AAED,WAAO,MAAM,CAAC;GACf,CAAC;;AAEF,MAAI,cAAc,GAAG,SAAjB,cAAc,CAAI,CAAC,EAAK;AAC1B,QAAI,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AACzB,WAAO,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;GAC1C,CAAC;;AAEF,MAAI,QAAQ,GAAG,SAAX,QAAQ,CAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAK;AAC1B,WAAO,GAAG,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;GACxE,CAAC;;;AAGF,MAAI,iBAAiB,GAAG,SAApB,iBAAiB,CAAI,KAAK,EAAK;;AAEjC,eAAW,EAAE,CAAC;;;AAGd,QAAI,IAAI,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC;;;AAGrC,SAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE;;AAEtC,UAAI,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;AAC1B,UAAI,KAAK,GAAG,IAAI,CAAC,CAAC,GAAC,CAAC,CAAC,GAAG,KAAK,CAAC;AAC9B,UAAI,IAAI,GAAG,IAAI,CAAC,CAAC,GAAC,CAAC,CAAC,GAAG,KAAK,CAAC;AAC7B,UAAI,KAAK,GAAG,IAAI,CAAC,CAAC,GAAC,CAAC,CAAC,GAAG,KAAK,CAAC;;;AAG9B,UAAI,GAAG,GAAG,gBAAgB,IACtB,KAAK,GAAG,gBAAgB,IACxB,IAAI,GAAG,gBAAgB,EAAE,EAE5B;;;;AAAA,AAGD,SAAG,IAAI,KAAK,CAAC;AACb,WAAK,IAAI,KAAK,CAAC;AACf,UAAI,IAAI,KAAK,CAAC;;;;AAId,UAAI,QAAO,GAAG,IAAI,CAAC,KAAK,CAAE,GAAG,IAAK,UAAU,GAAG,GAAG,CAAA,AAAE,CAAE,CAAC;AACvD,UAAI,QAAO,GAAG,IAAI,CAAC,KAAK,CAAE,KAAK,IAAK,UAAU,GAAG,GAAG,CAAA,AAAE,CAAE,CAAC;AACzD,UAAI,QAAO,GAAG,IAAI,CAAC,KAAK,CAAE,IAAI,IAAK,UAAU,GAAG,GAAG,CAAA,AAAE,CAAE,CAAC;;;AAGxD,UAAI,KAAK,GAAG,UAAU,CAAC,QAAO,EAAE,QAAO,EAAE,QAAO,CAAC,CAAC;;;AAGlD,WAAK,CAAC,KAAK,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC;;;AAG5B,WAAK,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC;AAC1B,WAAK,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC;AAC5B,WAAK,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC;KAC5B;;;AAGD,QAAI,YAAY,GAAG,EAAE,CAAC;;;AAGtB,SAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;AACnC,WAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;AACnC,aAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;;AAEnC,cAAI,WAAW,GAAG,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;;;AAGtC,cAAI,eAAe,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC;;;AAGnD,cAAI,eAAe,KAAK,CAAC,EAAE;AACzB,qBAAS;WACV;;;AAGD,cAAI,gBAAgB,GAAG,IAAI,CAAC;;;AAG5B,eAAK,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE;AAC3B,mBAAO,GAAG,CAAC,GAAG,MAAK,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1C,mBAAO,GAAG,CAAC,GAAG,MAAK,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1C,mBAAO,GAAG,CAAC,GAAG,MAAK,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;;;AAG1C,gBAAI,OAAO,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,EAAE;AAChD,kBAAI,OAAO,GAAG,MAAK,UAAU,IAAI,OAAO,GAAG,MAAK,UAAU,IAAI,OAAO,GAAG,MAAK,UAAU,EAAE;AACvF,oBAAI,MAAK,KAAK,CAAC,MAAK,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,eAAe,EAAE;;AAEtF,kCAAgB,GAAG,KAAK,CAAC;AACzB,wBAAM;iBACP;eACF;aACF;WACF;;;AAGD,cAAI,gBAAgB,KAAK,KAAK,EAAE;AAC9B,qBAAS;WACV;;;AAGD,cAAI,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC;AACpE,cAAI,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC;AACpE,cAAI,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC;AACpE,cAAI,YAAY,GAAG,IAAI,YAAY,CAAC,eAAe,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;;AAEvF,sBAAY,CAAC,IAAI,CAAE,YAAY,CAAE,CAAC;SACnC;OACF;KACF;;;AAGD,gBAAY,GAAG,YAAY,CAAC,IAAI,CAAC,UAAS,CAAC,EAAE,CAAC,EAAE;AAAE,aAAO,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC;KAAE,CAAC,CAAC;;AAEvF,WAAO,YAAY,CAAC;GACrB,CAAC;;;;AAIF,MAAI,sBAAsB,GAAG,SAAzB,sBAAsB,CAAI,MAAM,EAAK;;AAEvC,QAAI,MAAM,GAAG,EAAE,CAAC;;;;;;;;AAGhB,4BAAc,MAAM,mIAAE;YAAb,CAAC;;;AAER,YAAI,WAAW,GAAG,IAAI,CAAC;;;;;;;;;AAEvB,gCAAc,MAAM,mIAAE;gBAAb,CAAC;;;AAER,gBAAI,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACxB,gBAAI,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACxB,gBAAI,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;;;AAGxB,gBAAI,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,CAAC;;;AAGjF,gBAAI,KAAK,GAAG,kBAAkB,EAAE;AAC9B,yBAAW,GAAG,KAAK,CAAC;AACpB,oBAAM;aACP;WACF;;;;;;;;;;;;;;;;AAGD,YAAI,WAAW,KAAK,IAAI,EAAE;AACxB,gBAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SAChB;OACF;;;;;;;;;;;;;;;;AAGD,WAAO,MAAM,CAAC;GACf,CAAC;;AAEF,SAAO,GAAG,CAAC;CACZ","sourcesContent":["/*\nCopyright (c) 2015, Ole Krause-Sparmann,\n Andrew Monks \nPermission to use, copy, modify, and/or distribute this software for\nany purpose with or without fee is hereby granted, provided that the\nabove copyright notice and this permission notice appear in all\ncopies.\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL\nWARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE\nAUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL\nDAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR\nPROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\nTORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n*/\n\n/* jshint esnext: true */\n\n/*\nColorCube Class\n\n Uses a 3d RGB histogram to find local maximas in the density distribution\n in order to retrieve dominant colors of pixel images\n*/\nfunction ColorCube( resolution = 20,\n bright_threshold = 0.2,\n distinct_threshold = 0.4 ) {\n \"use strict\";\n\n\n\n // subclasses // // // // // // // // // // // // // // // // // // // //\n // // // // // // // // // // // // // // // // // // // // // // // // //\n\n\n\n /*\n CanvasImage Class\n\n Class that wraps the html image element and canvas.\n It also simplifies some of the canvas context manipulation\n with a set of helper functions.\n\n modified from Color Thief v2.0\n by Lokesh Dhakar - http://www.lokeshdhakar.com\n */\n let CanvasImage = function (image) {\n\n if (! image instanceof HTMLElement) {\n throw \"You've gotta use an html image element as ur input!!\";\n }\n\n let API = {};\n\n let canvas = document.createElement('canvas');\n let context = canvas.getContext('2d');\n\n // document.body.appendChild(canvas);\n\n canvas.width = image.width;\n canvas.height = image.height;\n\n context.drawImage(image, 0, 0, image.width, image.height);\n\n API.getImageData = () => {\n return context.getImageData(0, 0, image.width, image.height);\n };\n\n return API;\n };\n\n\n\n\n /*\n CubeCell Class\n\n class that represents one voxel within rgb colorspace\n */\n function CubeCell() {\n let API = {};\n\n // Count of hits\n // (dividing the accumulators by this value gives the average color)\n API.hit_count = 0;\n\n // accumulators for color components\n API.r_acc = 0.0;\n API.g_acc = 0.0;\n API.b_acc = 0.0;\n\n return API;\n }\n\n\n\n\n /*\n LocalMaximum Class\n\n Local maxima as found during the image analysis.\n We need this class for ordering by cell hit count.\n */\n function LocalMaximum(hit_count, cell_index, r, g, b) {\n let API = {};\n\n // hit count of the cell\n API.hit_count = hit_count;\n\n // linear index of the cell\n API.cell_index = cell_index;\n\n // average color of the cell\n API.r = r;\n API.g = g;\n API.b = b;\n\n return API;\n }\n\n\n\n\n\n // ColorCube // // // // // // // // // // // // // // // // // // // //\n // // // // // // // // // // // // // // // // // // // // // // // // //\n\n\n\n\n let API = {};\n\n // helper variable to have cell count handy\n let cell_count = resolution * resolution * resolution;\n\n // create cells\n let cells = [];\n for (let i = 0; i <= cell_count; i++) {\n cells.push( new CubeCell() );\n }\n\n // indices for neighbor cells in three dimensional grid\n let neighbour_indices = [\n [ 0, 0, 0],\n [ 0, 0, 1],\n [ 0, 0,-1],\n\n [ 0, 1, 0],\n [ 0, 1, 1],\n [ 0, 1,-1],\n\n [ 0,-1, 0],\n [ 0,-1, 1],\n [ 0,-1,-1],\n\n [ 1, 0, 0],\n [ 1, 0, 1],\n [ 1, 0,-1],\n\n [ 1, 1, 0],\n [ 1, 1, 1],\n [ 1, 1,-1],\n\n [ 1,-1, 0],\n [ 1,-1, 1],\n [ 1,-1,-1],\n\n [-1, 0, 0],\n [-1, 0, 1],\n [-1, 0,-1],\n\n [-1, 1, 0],\n [-1, 1, 1],\n [-1, 1,-1],\n\n [-1,-1, 0],\n [-1,-1, 1],\n [-1,-1,-1]\n ];\n\n // returns linear index for cell with given 3d index\n let cell_index = (r, g, b) => {\n return (r + g * resolution + b * resolution * resolution);\n };\n\n let clear_cells = () => {\n for (let cell of cells) {\n cell.hit_count = 0;\n cell.r_acc = 0;\n cell.g_acc = 0;\n cell.b_acc = 0;\n }\n };\n\n API.get_colors = (image) => {\n let canvasimage = new CanvasImage(image);\n\n let m = find_local_maxima(canvasimage);\n\n m = filter_distinct_maxima(m);\n\n let colors = [];\n for (let n of m) {\n let r = Math.round(n.r * 255.0);\n let g = Math.round(n.g * 255.0);\n let b = Math.round(n.b * 255.0);\n let color = rgbToHex(r, g, b);\n if (color === \"#NaNNaNNaN\") {continue;}\n colors.push(color);\n }\n\n return colors;\n };\n\n let componentToHex = (c) => {\n let hex = c.toString(16);\n return hex.length == 1 ? \"0\" + hex : hex;\n };\n\n let rgbToHex = (r, g, b) => {\n return \"#\" + componentToHex(r) + componentToHex(g) + componentToHex(b);\n };\n\n // finds and returns local maxima in 3d histogram, sorted by hit count\n let find_local_maxima = (image) => {\n // reset all cells\n clear_cells();\n\n // get the image pixels\n let data = image.getImageData().data;\n\n // iterate over all pixels of the image\n for(let i = 0; i < data.length; i += 4) {\n // get color components\n let red = data[i] / 255.0;\n let green = data[i+1] / 255.0;\n let blue = data[i+2] / 255.0;\n let alpha = data[i+3] / 255.0;\n\n // stop if brightnesses are all below threshold\n if (red < bright_threshold &&\n green < bright_threshold &&\n blue < bright_threshold) {\n // continue;\n }\n\n // weigh colors by alpha channel\n red *= alpha;\n green *= alpha;\n blue *= alpha;\n\n // map color components to cell indicies in each color dimension\n // TODO maybe this should round down? OG colorcube uses python's int()\n let r_index = Math.round( red * ( resolution - 1.0 ) );\n let g_index = Math.round( green * ( resolution - 1.0 ) );\n let b_index = Math.round( blue * ( resolution - 1.0 ) );\n\n // compute linear cell index\n let index = cell_index(r_index, g_index, b_index);\n\n // increase hit count of cell\n cells[index].hit_count += 1;\n\n // add pixel colors to cell color accumulators\n cells[index].r_acc += red;\n cells[index].g_acc += green;\n cells[index].b_acc += blue;\n }\n\n // we collect local maxima in here\n let local_maxima = [];\n\n // find local maxima in the grid\n for (let r = 0; r < resolution; r++) {\n for (let g = 0; g < resolution; g++) {\n for (let b = 0; b < resolution; b++) {\n\n let local_index = cell_index(r, g, b);\n\n // get hit count of this cell\n let local_hit_count = cells[local_index].hit_count;\n\n // if this cell has no hits, ignore it\n if (local_hit_count === 0) {\n continue;\n }\n\n // it's a local maxima until we find a neighbor with a higher hit count\n let is_local_maximum = true;\n\n // check if any neighbor has a higher hit count, if so, no local maxima\n for (let n in new Array(27)) {\n r_index = r + this.neighbor_indices[n][0];\n g_index = g + this.neighbor_indices[n][1];\n b_index = b + this.neighbor_indices[n][2];\n\n // only check valid cell indices\n if (r_index >= 0 && g_index >= 0 && b_index >= 0) {\n if (r_index < this.resolution && g_index < this.resolution && b_index < this.resolution) {\n if (this.cells[this.cell_index(r_index, g_index, b_index)].hit_count > local_hit_count) {\n // this is not a local maximum\n is_local_maximum = false;\n break;\n }\n }\n }\n }\n\n // if this is not a local maximum, continue with loop\n if (is_local_maximum === false) {\n continue;\n }\n\n // otherwise add this cell as a local maximum\n let avg_r = cells[local_index].r_acc / cells[local_index].hit_count;\n let avg_g = cells[local_index].g_acc / cells[local_index].hit_count;\n let avg_b = cells[local_index].b_acc / cells[local_index].hit_count;\n let localmaximum = new LocalMaximum(local_hit_count, local_index, avg_r, avg_g, avg_b);\n\n local_maxima.push( localmaximum );\n }\n }\n }\n\n // return local maxima sorted with respect to hit count\n local_maxima = local_maxima.sort(function(a, b) { return b.hit_count - a.hit_count; });\n\n return local_maxima;\n };\n\n // Returns a filtered version of the specified array of maxima,\n // in which all entries have a minimum distance of distinct_threshold\n let filter_distinct_maxima = (maxima) => {\n\n let result = [];\n\n // check for each maximum\n for (let m of maxima) {\n // this color is distinct until an earlier color is too close\n let is_distinct = true;\n\n for (let n of result) {\n // compute delta components\n let r_delta = m.r - n.r;\n let g_delta = m.g - n.g;\n let b_delta = m.b - n.b;\n\n // compute delta in color space distance\n let delta = Math.sqrt(r_delta * r_delta + g_delta * g_delta + b_delta * b_delta);\n\n // if too close, mark as non distinct and break inner loop\n if (delta < distinct_threshold) {\n is_distinct = false;\n break;\n }\n }\n\n // add to filtered array if is distinct\n if (is_distinct === true) {\n result.push(m);\n }\n }\n\n\n return result;\n };\n\n return API;\n}\n"]} -------------------------------------------------------------------------------- /images/andrew-monks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amonks/colorcube-js/14117eea778ec997132ab5e35ef3fe6e3b151d89/images/andrew-monks.png -------------------------------------------------------------------------------- /images/cocacola.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amonks/colorcube-js/14117eea778ec997132ab5e35ef3fe6e3b151d89/images/cocacola.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | colorcube-js 5 | 10 | 11 | 12 | 13 |

colorcube-js

14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colorcube-js", 3 | "title": "colorcube-js", 4 | "version": "0.0.1", 5 | "devDependencies": { 6 | "grunt": "~0.4.5", 7 | "grunt-contrib-clean": "~0.6.0", 8 | "grunt-contrib-uglify": "~0.9.1", 9 | "grunt-babel": "~5.0.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/colorcube.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Ole Krause-Sparmann, 3 | Andrew Monks 4 | Permission to use, copy, modify, and/or distribute this software for 5 | any purpose with or without fee is hereby granted, provided that the 6 | above copyright notice and this permission notice appear in all 7 | copies. 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 9 | WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 10 | WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 11 | AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 12 | DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 13 | PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 14 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | /* jshint esnext: true */ 19 | 20 | /* 21 | ColorCube Class 22 | 23 | Uses a 3d RGB histogram to find local maximas in the density distribution 24 | in order to retrieve dominant colors of pixel images 25 | */ 26 | function ColorCube( resolution = 20, 27 | bright_threshold = 0.2, 28 | distinct_threshold = 0.4 ) { 29 | "use strict"; 30 | 31 | 32 | 33 | // subclasses // // // // // // // // // // // // // // // // // // // // 34 | // // // // // // // // // // // // // // // // // // // // // // // // // 35 | 36 | 37 | 38 | /* 39 | CanvasImage Class 40 | 41 | Class that wraps the html image element and canvas. 42 | It also simplifies some of the canvas context manipulation 43 | with a set of helper functions. 44 | 45 | modified from Color Thief v2.0 46 | by Lokesh Dhakar - http://www.lokeshdhakar.com 47 | */ 48 | let CanvasImage = function (image) { 49 | 50 | if (! image instanceof HTMLElement) { 51 | throw "You've gotta use an html image element as ur input!!"; 52 | } 53 | 54 | let API = {}; 55 | 56 | let canvas = document.createElement('canvas'); 57 | let context = canvas.getContext('2d'); 58 | 59 | // document.body.appendChild(canvas); 60 | 61 | canvas.width = image.width; 62 | canvas.height = image.height; 63 | 64 | context.drawImage(image, 0, 0, image.width, image.height); 65 | 66 | API.getImageData = () => { 67 | return context.getImageData(0, 0, image.width, image.height); 68 | }; 69 | 70 | return API; 71 | }; 72 | 73 | 74 | 75 | 76 | /* 77 | CubeCell Class 78 | 79 | class that represents one voxel within rgb colorspace 80 | */ 81 | function CubeCell() { 82 | let API = {}; 83 | 84 | // Count of hits 85 | // (dividing the accumulators by this value gives the average color) 86 | API.hit_count = 0; 87 | 88 | // accumulators for color components 89 | API.r_acc = 0.0; 90 | API.g_acc = 0.0; 91 | API.b_acc = 0.0; 92 | 93 | return API; 94 | } 95 | 96 | 97 | 98 | 99 | /* 100 | LocalMaximum Class 101 | 102 | Local maxima as found during the image analysis. 103 | We need this class for ordering by cell hit count. 104 | */ 105 | function LocalMaximum(hit_count, cell_index, r, g, b) { 106 | let API = {}; 107 | 108 | // hit count of the cell 109 | API.hit_count = hit_count; 110 | 111 | // linear index of the cell 112 | API.cell_index = cell_index; 113 | 114 | // average color of the cell 115 | API.r = r; 116 | API.g = g; 117 | API.b = b; 118 | 119 | return API; 120 | } 121 | 122 | 123 | 124 | 125 | 126 | // ColorCube // // // // // // // // // // // // // // // // // // // // 127 | // // // // // // // // // // // // // // // // // // // // // // // // // 128 | 129 | 130 | 131 | 132 | let API = {}; 133 | 134 | // helper variable to have cell count handy 135 | let cell_count = resolution * resolution * resolution; 136 | 137 | // create cells 138 | let cells = []; 139 | for (let i = 0; i <= cell_count; i++) { 140 | cells.push( new CubeCell() ); 141 | } 142 | 143 | // indices for neighbor cells in three dimensional grid 144 | let neighbour_indices = [ 145 | [ 0, 0, 0], 146 | [ 0, 0, 1], 147 | [ 0, 0,-1], 148 | 149 | [ 0, 1, 0], 150 | [ 0, 1, 1], 151 | [ 0, 1,-1], 152 | 153 | [ 0,-1, 0], 154 | [ 0,-1, 1], 155 | [ 0,-1,-1], 156 | 157 | [ 1, 0, 0], 158 | [ 1, 0, 1], 159 | [ 1, 0,-1], 160 | 161 | [ 1, 1, 0], 162 | [ 1, 1, 1], 163 | [ 1, 1,-1], 164 | 165 | [ 1,-1, 0], 166 | [ 1,-1, 1], 167 | [ 1,-1,-1], 168 | 169 | [-1, 0, 0], 170 | [-1, 0, 1], 171 | [-1, 0,-1], 172 | 173 | [-1, 1, 0], 174 | [-1, 1, 1], 175 | [-1, 1,-1], 176 | 177 | [-1,-1, 0], 178 | [-1,-1, 1], 179 | [-1,-1,-1] 180 | ]; 181 | 182 | // returns linear index for cell with given 3d index 183 | let cell_index = (r, g, b) => { 184 | return (r + g * resolution + b * resolution * resolution); 185 | }; 186 | 187 | let clear_cells = () => { 188 | for (let cell of cells) { 189 | cell.hit_count = 0; 190 | cell.r_acc = 0; 191 | cell.g_acc = 0; 192 | cell.b_acc = 0; 193 | } 194 | }; 195 | 196 | API.get_colors = (image) => { 197 | let canvasimage = new CanvasImage(image); 198 | 199 | let m = find_local_maxima(canvasimage); 200 | 201 | m = filter_distinct_maxima(m); 202 | 203 | let colors = []; 204 | for (let n of m) { 205 | let r = Math.round(n.r * 255.0); 206 | let g = Math.round(n.g * 255.0); 207 | let b = Math.round(n.b * 255.0); 208 | let color = rgbToHex(r, g, b); 209 | if (color === "#NaNNaNNaN") {continue;} 210 | colors.push(color); 211 | } 212 | 213 | return colors; 214 | }; 215 | 216 | let componentToHex = (c) => { 217 | let hex = c.toString(16); 218 | return hex.length == 1 ? "0" + hex : hex; 219 | }; 220 | 221 | let rgbToHex = (r, g, b) => { 222 | return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); 223 | }; 224 | 225 | // finds and returns local maxima in 3d histogram, sorted by hit count 226 | let find_local_maxima = (image) => { 227 | // reset all cells 228 | clear_cells(); 229 | 230 | // get the image pixels 231 | let data = image.getImageData().data; 232 | 233 | // iterate over all pixels of the image 234 | for(let i = 0; i < data.length; i += 4) { 235 | // get color components 236 | let red = data[i] / 255.0; 237 | let green = data[i+1] / 255.0; 238 | let blue = data[i+2] / 255.0; 239 | let alpha = data[i+3] / 255.0; 240 | 241 | // stop if brightnesses are all below threshold 242 | if (red < bright_threshold && 243 | green < bright_threshold && 244 | blue < bright_threshold) { 245 | // continue; 246 | } 247 | 248 | // weigh colors by alpha channel 249 | red *= alpha; 250 | green *= alpha; 251 | blue *= alpha; 252 | 253 | // map color components to cell indicies in each color dimension 254 | // TODO maybe this should round down? OG colorcube uses python's int() 255 | let r_index = Math.round( red * ( resolution - 1.0 ) ); 256 | let g_index = Math.round( green * ( resolution - 1.0 ) ); 257 | let b_index = Math.round( blue * ( resolution - 1.0 ) ); 258 | 259 | // compute linear cell index 260 | let index = cell_index(r_index, g_index, b_index); 261 | 262 | // increase hit count of cell 263 | cells[index].hit_count += 1; 264 | 265 | // add pixel colors to cell color accumulators 266 | cells[index].r_acc += red; 267 | cells[index].g_acc += green; 268 | cells[index].b_acc += blue; 269 | } 270 | 271 | // we collect local maxima in here 272 | let local_maxima = []; 273 | 274 | // find local maxima in the grid 275 | for (let r = 0; r < resolution; r++) { 276 | for (let g = 0; g < resolution; g++) { 277 | for (let b = 0; b < resolution; b++) { 278 | 279 | let local_index = cell_index(r, g, b); 280 | 281 | // get hit count of this cell 282 | let local_hit_count = cells[local_index].hit_count; 283 | 284 | // if this cell has no hits, ignore it 285 | if (local_hit_count === 0) { 286 | continue; 287 | } 288 | 289 | // it's a local maxima until we find a neighbor with a higher hit count 290 | let is_local_maximum = true; 291 | 292 | // check if any neighbor has a higher hit count, if so, no local maxima 293 | for (let n in new Array(27)) { 294 | r_index = r + this.neighbor_indices[n][0]; 295 | g_index = g + this.neighbor_indices[n][1]; 296 | b_index = b + this.neighbor_indices[n][2]; 297 | 298 | // only check valid cell indices 299 | if (r_index >= 0 && g_index >= 0 && b_index >= 0) { 300 | if (r_index < this.resolution && g_index < this.resolution && b_index < this.resolution) { 301 | if (this.cells[this.cell_index(r_index, g_index, b_index)].hit_count > local_hit_count) { 302 | // this is not a local maximum 303 | is_local_maximum = false; 304 | break; 305 | } 306 | } 307 | } 308 | } 309 | 310 | // if this is not a local maximum, continue with loop 311 | if (is_local_maximum === false) { 312 | continue; 313 | } 314 | 315 | // otherwise add this cell as a local maximum 316 | let avg_r = cells[local_index].r_acc / cells[local_index].hit_count; 317 | let avg_g = cells[local_index].g_acc / cells[local_index].hit_count; 318 | let avg_b = cells[local_index].b_acc / cells[local_index].hit_count; 319 | let localmaximum = new LocalMaximum(local_hit_count, local_index, avg_r, avg_g, avg_b); 320 | 321 | local_maxima.push( localmaximum ); 322 | } 323 | } 324 | } 325 | 326 | // return local maxima sorted with respect to hit count 327 | local_maxima = local_maxima.sort(function(a, b) { return b.hit_count - a.hit_count; }); 328 | 329 | return local_maxima; 330 | }; 331 | 332 | // Returns a filtered version of the specified array of maxima, 333 | // in which all entries have a minimum distance of distinct_threshold 334 | let filter_distinct_maxima = (maxima) => { 335 | 336 | let result = []; 337 | 338 | // check for each maximum 339 | for (let m of maxima) { 340 | // this color is distinct until an earlier color is too close 341 | let is_distinct = true; 342 | 343 | for (let n of result) { 344 | // compute delta components 345 | let r_delta = m.r - n.r; 346 | let g_delta = m.g - n.g; 347 | let b_delta = m.b - n.b; 348 | 349 | // compute delta in color space distance 350 | let delta = Math.sqrt(r_delta * r_delta + g_delta * g_delta + b_delta * b_delta); 351 | 352 | // if too close, mark as non distinct and break inner loop 353 | if (delta < distinct_threshold) { 354 | is_distinct = false; 355 | break; 356 | } 357 | } 358 | 359 | // add to filtered array if is distinct 360 | if (is_distinct === true) { 361 | result.push(m); 362 | } 363 | } 364 | 365 | 366 | return result; 367 | }; 368 | 369 | return API; 370 | } 371 | --------------------------------------------------------------------------------