├── .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":"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","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 |
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 |
--------------------------------------------------------------------------------