color-thief
color-thief copied to clipboard
Use ColorThief with XMLHttpRequest and without canvas
I'm trying to use Color-Thief without making use of the <canvas/>
element.
So, given a image url, width and height I have tried this
function getColorsNoCanvas(imageURL, imageHeight, imageWidth, done, error) {
console.log(imageURL,imageHeight,imageWidth)
var xhr = new XMLHttpRequest();
xhr.open('GET', imageURL, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
if (this.status == 200) {
var uInt8Array = new Uint8Array(this.response);
var i = uInt8Array.length;
var biStr = new Array(i);
while (i--)
{ biStr[i] = String.fromCharCode(uInt8Array[i]);
}
//var data = biStr.join('');
//var base64 = window.btoa(data);
//$("#myImage").attr("src", "data:image/jpeg;base64,"+base64);
// Store the RGB values in an array format suitable for quantize function
var threshold = 0.15;
var pixels=uInt8Array
var pixelCount=uInt8Array.length;
var pixelArray = [];
var bgPixelArray = [];
for (var i = 0, offset, r, g, b, a; i < pixelCount; i++) {
offset = i * 4;
r = pixels[offset + 0];
g = pixels[offset + 1];
b = pixels[offset + 2];
a = pixels[offset + 3];
// If pixel is mostly opaque and not white
if (a >= 125) {
if (!(r > 250 && g > 250 && b > 250)) {
pixelArray.push([r, g, b]);
if ((i < pixelCount * threshold) || (i % imageHeight < imageWidth * threshold / 2)) {
bgPixelArray.push([r, g, b]);
}
}
}
}
var cmap = MMCQ.quantize(pixelArray, 5);
var palette = cmap.palette();
var bgCmap = MMCQ.quantize(bgPixelArray, 5);
var bgPalette = bgCmap.palette();
done.apply(this,[ [palette, bgPalette[0]] ])
} // 200
};
xhr.send();
}
The result is anyways not the same as the getColors function. The reason I think it that I'm wrong when converting the uInt8Array
:
var uInt8Array = new Uint8Array(this.response);
var i = uInt8Array.length;
to the rgba
format:
// Store the RGB values in an array format suitable for quantize function
var threshold = 0.15;
var pixels=uInt8Array
var pixelCount=uInt8Array.length;
var pixelArray = [];
var bgPixelArray = [];
for (var i = 0, offset, r, g, b, a; i < pixelCount; i++) {
offset = i * 4;
r = pixels[offset + 0];
g = pixels[offset + 1];
b = pixels[offset + 2];
a = pixels[offset + 3];
// If pixel is mostly opaque and not white
if (a >= 125) {
if (!(r > 250 && g > 250 && b > 250)) {
pixelArray.push([r, g, b]);
if ((i < pixelCount * threshold) || (i % imageHeight < imageWidth * threshold / 2)) {
bgPixelArray.push([r, g, b]);
}
}
}
}
Any hint?
I have adapted the code to the latest version of Color-Thief:
ColorThief.prototype.getColorNoCanvas = function(sourceImage, quality, done) {
this.getPaletteNoCanvas(sourceImage, 5, quality, function(palette) {
done.apply(this, [palette[0]])
});
};
where
ColorThief.prototype.getPaletteNoCanvas = function(sourceImageURL, colorCount, quality, done) {
var xhr = new XMLHttpRequest();
xhr.open('GET', sourceImageURL, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
if (this.status == 200) {
var uInt8Array = new Uint8Array(this.response);
var i = uInt8Array.length;
var biStr = new Array(i);
while (i--)
{ biStr[i] = String.fromCharCode(uInt8Array[i]);
}
if (typeof colorCount === 'undefined') {
colorCount = 10;
}
if (typeof quality === 'undefined' || quality < 1) {
quality = 10;
}
var pixels = uInt8Array;
var pixelCount = 152 * 152 * 4 // this should be width*height*4
// Store the RGB values in an array format suitable for quantize function
var pixelArray = [];
for (var i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) {
offset = i * 4;
r = pixels[offset + 0];
g = pixels[offset + 1];
b = pixels[offset + 2];
a = pixels[offset + 3];
// If pixel is mostly opaque and not white
if (a >= 125) {
if (!(r > 250 && g > 250 && b > 250)) {
pixelArray.push([r, g, b]);
}
}
}
// Send array to quantize function which clusters values
// using median cut algorithm
var cmap = MMCQ.quantize(pixelArray, colorCount);
var palette = cmap? cmap.palette() : null;
done.apply(this,[ palette ])
} // 200
};
xhr.send();
}
and so I'm using it like
var colorThief = new ColorThief();
colorThief.getColorNoCanvas("/colors2/"+$image.attr('src'), 8, function(colors) {
console.log( "getColorNoCanvas", colors )
styleBackground(colors, $image.parent().parent().attr('id'));
styleText(colors, colors,$image.parent().parent().attr('id'));
})
I have tried the simplest as possibile when getting the r,g,b,a array like:
for (var pxIndex = 0; pxIndex<pixels.length; pxIndex+=4 ) {
var r = pixels[pxIndex+0];
var g = pixels[pxIndex+1];
var b = pixels[pxIndex+2];
var a = pixels[pxIndex+3];
if (a >= 125) {
if (!(r > 250 && g > 250 && b > 250)) {
pixelArray.push([r, g, b]);
}
}
}
But the result is wrong.
Hi, I'm highly interested in this :) ... want to use colorThief on remote images (flickr, etc). Please let me know if you succeed to make it work.
@kosir At this time, I think I've solved 70% of this issue. The remaining 30% is the key part:
When you go through the bytearray, every 4 pixels, I'm not sure I'm collecting the right R-G-B pixels here:
for (var pxIndex = 0; pxIndex<pixels.length; pxIndex+=4 ) {
var r = pixels[pxIndex+0];
var g = pixels[pxIndex+1];
var b = pixels[pxIndex+2];
var a = pixels[pxIndex+3];
if (a >= 125) {
if (!(r > 250 && g > 250 && b > 250)) {
pixelArray.push([r, g, b]);
}
}
}
so when passed to the MCQQ
// Send array to quantize function which clusters values
// using median cut algorithm
var cmap = MMCQ.quantize(pixelArray, colorCount);
var palette = cmap? cmap.palette() : null;
I get back the wrong colors palette now.
I have also submitted the question to StackOverflow
, maybe you can spread the question and/or +1 it in order to make it more visibile:
http://stackoverflow.com/questions/33312362/dominant-color-for-an-rgb-image-via-xmlhttprequest
If we take this sample images
I have different results from the Canvas method
getColor [215, 232, 236]
(index):70 getColor [195, 178, 147]
(index):70 getColor [241, 149, 53]
(index):70 getColor [233, 207, 128]
(index):70 getColor [191, 188, 145]
(index):70 getColor [89, 89, 63]
(index):70 getColor [46, 53, 69]
(index):70 getColor [197, 201, 197]
and without Canvas
using the XMLHttpRequest object and the byte array:
(index):65 getColorNoCanvas [188, 167, 165]
(index):65 getColorNoCanvas [186, 164, 163]
(index):65 getColorNoCanvas [186, 164, 166]
(index):65 getColorNoCanvas [186, 161, 160]
(index):65 getColorNoCanvas [186, 164, 164]
(index):65 getColorNoCanvas [120, 97, 161]
(index):65 getColorNoCanvas [131, 163, 160]
(index):65 getColorNoCanvas [184, 163, 162]
I finally came out with a solution, so now the MCQQ
and Color-Thief works without any canvas
, just using XMLHttpRequest
object.
I'm using jpg.js as JPEG image decoder - details described here: https://github.com/notmasteryet/jpgjs/issues/40
A online demo is here: http://www.parisilabs.com/colors2/
This is the final code:
ColorThief.prototype.getColorNoCanvas = function(sourceImage, quality, done) {
this.getPaletteNoCanvas(sourceImage, 5, quality, function(palette) {
done.apply(this, [palette[0]])
});
};
ColorThief.prototype.getPaletteNoCanvas = function(sourceImageURL, colorCount, quality, done) {
var j = new JpegImage();
j.onload = function() {
// Image Data
var d = new Object();
d.height=350;
d.width=350;
d.data = new Array();
j.copyToImageData(d);
var pixels = d.data;
var pixelArray = [];
var quality = 10;
var pixelCount = d.height * d.width;
for (var i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) {
offset = i * 4;
r = pixels[offset + 0];
g = pixels[offset + 1];
b = pixels[offset + 2];
a = pixels[offset + 3];
// If pixel is mostly opaque and not white
if (a >= 125) {
if (!(r > 250 && g > 250 && b > 250)) {
pixelArray.push([r, g, b]);
}
}
}
var cmap = MMCQ.quantize(pixelArray, colorCount);
var palette = cmap? cmap.palette() : null;
done.apply(this, [palette]);
};//onload
j.load(sourceImageURL);
}
and you can call it like
var colorThief = new ColorThief();
var colors = colorThief.getColorNoCanvas(imageURL, 8, function(colors) {
ColorsHelper.styleColors($image, colors);
$image.bind('click', function(event) {
ColorsHelper.styleColors($image, colors);
});
It works fine on SafariMobile
too
A possibile improvement is to add png.js to decode PNG images: https://github.com/arian/pngjs.
Very nice idea, would be great if we could use color thief on remote images!
@teles yes! next step is to add the same using png.js
, it's almost the same approach, the byte array will consider r,g,b,a
- while for jpeg I'm ignoring the alpha
channel, but everything should work in the same way. Currently I'm making further tests to bring this to Apple's TVML
- https://github.com/notmasteryet/jpgjs/issues/41 as soon as I'm done with this I'm going to check the png.js
if someone else will not.
@loretoparisi Right RGB colors, from an XHR image, Amazing, That's what I was looking for, But jpg.js is 190KB!!! that's so big, Can we avoid using it or something?
@ManarKamel you can play with online demo here http://www.parisilabs.com/colors2/ and check the sources here: https://github.com/loretoparisi/dominant-colors-xmlhttprequest-example. Looking at jpg.js
I think you can definitively shrink it using a minimizer like Yahoo YUI, http://yui.github.io/yuicompressor/
This is easier than making changes to the code, that I do not suggest.
Yes, of course it would be possibile to use Base64 conversion, but you need jpg.js to decode a JPEG image byte array, then you can convert to Base64. This is what jpg.js does normally in their examples. Thanks for sharing the experiment, I will take a look.
2015-12-09 12:11 GMT+01:00 Manar Kamel [email protected]:
@loretoparisi https://github.com/loretoparisi No that's not what I meant, What amazing about color thief is the size, It's just 6kb minified, so you can use it for websites, but unfortunately it doesn't support XHR. You found a solution by using a JPG decoder because you couldn't use canvas for XHR images and get the right colors, right?
But how about another solution, like converting XHR images to base64, then we pass the data to the normal colorThief.getColor(), Did you try that?
If you're interested in some tvOS-like web project, check this experiment: http://codepen.io/ManarKamel/pen/zvqgZa
— Reply to this email directly or view it on GitHub https://github.com/lokesh/color-thief/issues/86#issuecomment-163190936.
Dott. Ing. Loreto Parisi Parisi Labs
Company: [email protected] Personal: [email protected] Twitter: @loretoparisi Web: http://parisilabs.com http://blog.parisilabs.com LinkedIn: http://www.linkedin.com/in/loretoparisi
@loretoparisi I deleted my older comment because I wanted to post solutions instead of questions.
And No, you don't need JPG decoder (jpg.js) at ALL, After many tests, I was able to create other methods a lot better than using jpg.js/png.js decoders (more lightweight also production-ready) (Using Color Thief with XHR and with canvas).
- Using img crossOrigin attribute (Chrome/Firefox) (Not supported in all browsers):
var img = document.createElement('img');
img.crossOrigin = 'Anonymous';
img.onload = function () {
var colorThief = new ColorThief();
var color = colorThief.getColor(img);
console.log('rgb(' + color + ')')
};
img.src = 'https://crossOriginImageUrl.com/image.jpg';
- Using XHR2 with responseType="blob" and FileReader() (Modern browsers/IE10+)
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function() {
var reader = new FileReader();
reader.onload = function() {
var img = document.createElement('img');
img.onload = function() {
var colorThief = new ColorThief();
var color = colorThief.getColor(img);
console.log('rgb(' + color + ')')
}
img.src = reader.result;
}
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', 'https://crossOriginImageUrl.com/image.jpg');
xhr.send();
- Using XHR2 with responseType="blob" and createObjectURL() (Modern browsers/IE10+)
var xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.onload = function () {
window.URL = window.URL || window.webkitURL; // support for Safari/old Chrome
var img = document.createElement('img');
img.src = window.URL.createObjectURL(xhr.response);
img.onload = function() {
var colorThief = new ColorThief();
var color = colorThief.getColor(img);
console.log('rgb(' + color + ')')
// Use this after you're done with the image and no longer needed
// window.URL.revokeObjectURL(img.src)
}
}
xhr.open('GET', 'https://i.scdn.co/image/1a3ca29c2f93fa50f72df85b1a94f62383dbc5cd');
xhr.send();
- Using XHR2 with responseType="arraybuffer" and Blob() with fixed content type (Modern browsers/IE10+)
var xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
window.URL = window.URL || window.webkitURL; // support for Safari/old Chrome
var blob = new Blob([xhr.response], {type: "image/jpeg"}),
img = document.createElement('img');
img.src = window.URL.createObjectURL(blob);
img.onload = function() {
var colorThief = new ColorThief();
var color = colorThief.getColor(img);
console.log('rgb(' + color + ')')
// Use this after you're done with the image and no longer needed
// window.URL.revokeObjectURL(img.src)
}
}
xhr.open("GET", 'https://i.scdn.co/image/1a3ca29c2f93fa50f72df85b1a94f62383dbc5cd');
xhr.send();
- Using XHR2 with responseType="arraybuffer" and Blob() with auto content type (Modern browsers/IE10+)
var xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
window.URL = window.URL || window.webkitURL; // support for Safari/old Chrome
var contentType = xhr.getResponseHeader('content-type'),
blob = new Blob([xhr.response], {type: contentType}),
img = document.createElement('img');
img.src = window.URL.createObjectURL(blob);
img.onload = function() {
var colorThief = new ColorThief();
var color = colorThief.getColor(img);
console.log('rgb(' + color + ')')
// Use this after you're done with the image and no longer needed
// window.URL.revokeObjectURL(img.src)
}
}
xhr.open('GET', 'https://i.scdn.co/image/1a3ca29c2f93fa50f72df85b1a94f62383dbc5cd');
xhr.send();
A working fiddle: http://jsfiddle.net/manarkamel/bmvqavjf/
@kosir You might want to check this out, you could use one of these for Flickr images
@ManarKamel :+1: nice work! Yes if you do not need any jpg.js encoding/decoding, there is no actual need to do that, of course you need to have a cross-browser support for the createObjectURL
or the FileReader
.
Also, in my solution I was not able to make it working in TVML
since I get an error from jpg.js during the decoding phase of the byte array - look here https://github.com/notmasteryet/jpgjs/issues/41
The main problem is that TVML/TVJS
as you know is a subset of HTML / JavaScript, so we do not have access to all browser object but only the ones described in https://developer.apple.com/library/tvos/documentation/TVMLJS/Reference/TVJSFrameworkReference/index.html#//apple_ref/doc/uid/TP40016076.
@loretoparisi tvOS supports XMLHTTPRequest, so I guess there would be no problem, Anyway I have no such experience in tvOS JavaScript engine
EDIT: if there's no Blob() support or If something is missing, Try converting arraybuffer to base64 ( btoa() ) then pass to colorThif.getColor(), If there's no native btoa() support, Try using external base64.js encoding/decoding.
Best wishes
@loretoparisi any updates on this?