Create dominant colors for an RGB image using XMLHttpRequest

Note to readers. This is a long question, but you need a background to understand the question.

Color quantization technology is commonly used to produce dominant image colors. One of the well-known libraries that perform color quantization, Leptonica through Modified Median Middle Curve Quantization (MMCQ) and Octet Quantization (OQ) Github Color-thief by @lokesh is a very simple implementation of the MMCQ algorithm in JavaScript:

var colorThief = new ColorThief(); colorThief.getColor(sourceImage); 

Technically, the image in the <img/> HTML element is supported in the <canvas/> element:

 var CanvasImage = function (image) { this.canvas = document.createElement('canvas'); this.context = this.canvas.getContext('2d'); document.body.appendChild(this.canvas); this.width = this.canvas.width = image.width; this.height = this.canvas.height = image.height; this.context.drawImage(image, 0, 0, this.width, this.height); }; 

And this is a problem with TVML , as we will see later.

Another implementation that I recently learned was related to this article Using imagemagick, awk, and kmeans to find dominant colors in images that reference Using python to create amazing Linux desktop themes . The author published an article on Using python and k-tools to find the dominant colors in the images that were used there (sorry for all these links, but I'm following my story ...).

The author was very productive and added the version of JavaScript that I am posting here: Using JavaScript and k-tools to find dominant colors in images

In this case, we generate the dominant image colors, not using the MMCQ (or OQ) algorithm, but K-Means. The problem is that the image should also be:

 <canvas id="canvas" style="display: none;" width="200" height="200"></canvas> 

and then

 function analyze(img_elem) { var ctx = document.getElementById('canvas').getContext('2d') , img = new Image(); img.onload = function() { var results = document.getElementById('results'); results.innerHTML = 'Waiting...'; var colors = process_image(img, ctx) , p1 = document.getElementById('c1') , p2 = document.getElementById('c2') , p3 = document.getElementById('c3'); p1.style.backgroundColor = colors[0]; p2.style.backgroundColor = colors[1]; p3.style.backgroundColor = colors[2]; results.innerHTML = 'Done'; } img.src = img_elem.src; } 

This is because Canvas has a getContext () method that provides 2D drawing APIs - see Introduction to the Canvas 2D API

This ctx context is passed to the image processing function

  function process_image(img, ctx) { var points = []; ctx.drawImage(img, 0, 0, 200, 200); data = ctx.getImageData(0, 0, 200, 200).data; for (var i = 0, l = data.length; i < l; i += 4) { var r = data[i] , g = data[i+1] , b = data[i+2]; points.push([r, g, b]); } var results = kmeans(points, 3, 1) , hex = []; for (var i = 0; i < results.length; i++) { hex.push(rgbToHex(results[i][0])); } return hex; } 

So, you can draw the image on the canvas through the context and get the image data:

 ctx.drawImage(img, 0, 0, 200, 200); data = ctx.getImageData(0, 0, 200, 200).data; 

Another nice solution is in CoffeeScript, ColorTunes , but it is also used:

 ColorTunes.getColorMap = function(canvas, sx, sy, w, h, nc) { var index, indexBase, pdata, pixels, x, y, _i, _j, _ref, _ref1; if (nc == null) { nc = 8; } pdata = canvas.getContext("2d").getImageData(sx, sy, w, h).data; pixels = []; for (y = _i = sy, _ref = sy + h; _i < _ref; y = _i += 1) { indexBase = y * w * 4; for (x = _j = sx, _ref1 = sx + w; _j < _ref1; x = _j += 1) { index = indexBase + (x * 4); pixels.push([pdata[index], pdata[index + 1], pdata[index + 2]]); } } return (new MMCQ).quantize(pixels, nc); }; 

But wait, we don’t have the <canvas/> in TVML !

Of course, there are own solutions, such as Objective-C ColorCube , DominantColor - this is the use of K-tools

and a very nice and reusable ColorArt from @AaronBrethorst from CocoaControls.

Although this can be used in a TVML application through the built-in JavaScriptCore bridge - see How to link TVML / JavaScriptCore to UIKit / Objective-C (Swift)?

My goal is to completely do this job in TVJS and TVML .

The simplest implementation of MMCQ JavaScript does not require Canvas: see Nick Rabinowitz's main Javascript MMCQ (modified mid-section quantization) section , but you need an RGB image array:

 var cmap = MMCQ.quantize(pixelArray, colorCount); 

which is taken from HTML <canvas/> , and that is its reason!

 function createPalette(sourceImage, colorCount) { // Create custom CanvasImage object var image = new CanvasImage(sourceImage), imageData = image.getImageData(), pixels = imageData.data, pixelCount = image.getPixelCount(); // 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++) { 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.palette(); // Clean up image.removeCanvas(); return palette; } 

[Question] How to create dominant colors of an RGB image without using HTML5 <canvas/> but in pure JavaScript from a ByteArray image extracted using XMLHttpRequest ?

[UPDATE] I posted this question to the Color-Thief github repo, adapting the calculations of the RGB array to the latest code base. The solution I tried is

 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(); } 

but it does not return the correct color for RGB colors.

[UPDATE] Thanks to all the offers, I got his job. Now a complete example is available on Github ,

+5
javascript tvos quantization tvml tvjs
Oct 23 '15 at 22:33
source share
1 answer

The canvas element is used as a convenient way to decode an image into an RGBA array. You can also use pure JavaScript libraries to decode images.

jpgjs is a JPEG decoder and pngjs is a PNG decoder. It seems that the JPEG decoder will work with TVJS as is. However, the PNG decoder looks like it is running in a Node environment or in a web browser, so you may need to modify it a bit.

+1
Nov 18 '15 at 23:49
source



All Articles