Skip to content

Instantly share code, notes, and snippets.

@harrislapiroff
Created December 18, 2012 21:49
Show Gist options
  • Save harrislapiroff/4332363 to your computer and use it in GitHub Desktop.
Save harrislapiroff/4332363 to your computer and use it in GitHub Desktop.
Selects colors from an image.
/*
* ColorExtractor
*
* Usage:
*
* extractor = new ColorExtractor("/path/to/image.jpg");
* extractor.ready(function (data) {
* document.body.styles.backgroundColor = data['edge_color'].hex_for_css();
* });
*
*/
(function () {
"use strict";
var ColorExtractor, Color,
average_color, mode_color;
// ColorExtractor
ColorExtractor = window.ColorExtractor = function () {
// ColorExtractor takes a single image argument in the form of
// either a string URL or an image element.
return this._init.apply(this, arguments);
};
ColorExtractor.prototype = {
// Some Attributes
_canvas: null,
_image: null,
_edge_tolerance: 5,
_ready_fns: [],
_complete: false,
_payload: {},
// Some Methods
_init: function (image_) {
var that = this,
image, canvas;
// instantiate the canvas for use later
canvas = this._canvas = document.createElement('canvas');
// instantiate the image
if (typeof image_ === "string") {
// if image_ is a url, create the image element/
image = this._image = new Image();
image.src = image_;
// I'm fairly sure the image does *not* need to be added
// to the page in order to load.
} else {
// otherwise, assume image_ is already an image element.
image = this._image = image_;
}
if (image.complete) {
// if the image is ready, calculate the colors immediately
this.calculate_colors();
} else {
// otherwise, bind to the image's ready event
image.addEventListener("load", function () { that.calculate_colors.call(that); });
}
},
calculate_colors: function () {
var canvas = this._canvas,
image = this._image,
context = canvas.getContext('2d'),
return_data = {};
// make the canvas dimensions the same as the image
canvas.width = image.width;
canvas.height = image.height;
// draw the image onto the canvas
context.drawImage(this._image, 0, 0, this._image.width, this._image.height);
return_data['edge_color'] = this.find_edge_color();
// fire the ready functions
this._ready(return_data);
// mark complete
this._complete = true;
// cache the payload
this._payload = return_data
return return_data;
},
find_edge_color: function (tolerance) {
var tolerance = tolerance || this._edge_tolerance,
canvas = this._canvas,
context = canvas.getContext('2d'),
colors = [],
n, w, s, e;
// get west side
w = context.getImageData(0, 0, tolerance, canvas.height - tolerance).data;
// get south side
s = context.getImageData(0, canvas.height - tolerance, canvas.width - tolerance, tolerance).data;
// get east side
e = context.getImageData(canvas.width - tolerance, tolerance, tolerance, canvas.height - tolerance).data;
// get north side
n = context.getImageData(tolerance, 0, canvas.width - tolerance, tolerance).data;
// flatten all the pixel color values into a single array
for (var i in [w, s, e, n]) {
var arr = [w, s, e, n][i]
for (var j=0; j<arr.length; j+=4) {
colors.push(new Color(arr[j], arr[j+1], arr[j+2], arr[j+3]))
}
}
// run that array through the color averager
return mode_color(colors);
},
_ready: function (payload) {
var fns = this._ready_fns;
// fire the ready functions
for (var i=0; i<fns.length; i++) {
fns[i](payload);
}
console.log(payload)
},
ready: function (fn) {
// register a function to be fired after the colors have been calculated
this._ready_fns.push(fn);
// if calculation has already occurred, fire the function
if (this._complete) fn(this._payload);
return fn;
},
};
// Color
Color = function () {
// Color takes four arguments in the form of
// r, g, b, a values out of 255.
return this._init.apply(this, arguments);
};
Color.prototype = {
_r: 0,
_g: 0,
_b: 0,
_a: 0,
_init: function (r, g, b, a) {
this._r = r || this._r;
this._g = g || this._g;
this._b = b || this._b;
this._a = a || this._a;
},
rgba_for_css: function () {
return "rgba(" + [this._r, this._g, this._b, this._a].join(",") + ")";
},
hex_for_css: function () {
return "#" + [
this._r > 15 ? Math.floor(this._r).toString(16) : "0" + Math.floor(this._r).toString(16),
this._g > 15 ? Math.floor(this._g).toString(16) : "0" + Math.floor(this._g).toString(16),
this._b > 15 ? Math.floor(this._b).toString(16) : "0" + Math.floor(this._b).toString(16)
].join("");
}
};
// Utility Functions
average_color = function (array) {
var _r = 0,
_g = 0,
_b = 0,
_a = 0;
for (var i=0; i<array.length; i++){
_r += array[i]._r;
_g += array[i]._g;
_b += array[i]._b;
_a += array[i]._a;
}
return new Color(_r/array.length, _g/array.length, _b/array.length, _a/array.length);
};
mode_color = function (array) {
var _r = 0,
_g = 0,
_b = 0,
_a = 0,
color_count = {},
sorted_color_count = [],
index, new_color;
// count the # of pixels of each distinct color
for (var i=0; i<array.length; i++){
index = array[i]._r + "," + array[i]._g + "," + array[i]._b;
if (!color_count[index]) color_count[index] = 1;
color_count[index]++;
}
// add each count > 2 to sorted_color_count
for (var key in color_count){
if (color_count.hasOwnProperty(key)) {
if (color_count[key] > 2) {
sorted_color_count.push([key, color_count[key]]);
}
}
}
// sort the sorted_color_count array
sorted_color_count.sort(function (a, b) { return b[1] - a[1]; })
new_color = sorted_color_count[0][0].split(',');
return new Color(parseInt(new_color[0]), parseInt(new_color[1]), parseInt(new_color[2]), 255);
};
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment