title: Generate the Dominant Colors for an RGB image with XMLHttpRequest
body: <p><strong>A Note For Readers: This is a long question, but it needs a background to understand the question asked.</strong></p>
  
  <p>The <a href="https://en.wikipedia.org/wiki/Color_quantization" rel="nofollow noreferrer">color quantization technique</a> is commonly used to get the <em>dominant colors</em> of an image.
  One of the well-known libraries that do color quantization is <a href="http://www.leptonica.com/" rel="nofollow noreferrer">Leptonica</a> through the <a href="http://www.leptonica.com/color-quantization.html" rel="nofollow noreferrer">Modified Median Cut Quantization (MMCQ) and octree quantization (OQ)</a>
  Github's <a href="https://github.com/lokesh/color-thief" rel="nofollow noreferrer">Color-thief</a> by @lokesh is a very simple implementation in JavaScript of the MMCQ algorithm:</p>
  
  <pre><code>var colorThief = new ColorThief();
  colorThief.getColor(sourceImage);
  </code></pre>
  
  <p>Technically, the image on a <code>&lt;img/&gt;</code> HTML element is backed on a <code>&lt;canvas/&gt;</code> element:</p>
  
  <pre><code>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);
  };
  </code></pre>
  
  <p>And that is the problem with <code>TVML</code>, as we will see later on.</p>
  
  <p>Another implementation I recently came to know was linked on this article <a href="http://javier.io/blog/en/2015/09/30/using-imagemagick-and-kmeans-to-find-dominant-colors-in-images.html" rel="nofollow noreferrer">Using imagemagick, awk and kmeans to find dominant colors in images</a> that links to <a href="http://charlesleifer.com/blog/using-python-to-generate-awesome-linux-desktop-themes/" rel="nofollow noreferrer">Using python to generate awesome linux desktop themes</a>. 
  The author posted an article about <a href="http://charlesleifer.com/blog/using-python-and-k-means-to-find-the-dominant-colors-in-images/" rel="nofollow noreferrer">Using python and k-means to find the dominant colors in images</a> that was used there (sorry for all those links, but I'm following back my History...).</p>
  
  <p>The author was super productive, and added a JavaScript version too that I'm posting here: <a href="https://gist.github.com/loretoparisi/c147ca437ab9d5e163b7" rel="nofollow noreferrer">Using JavaScript and k-means to find the dominant colors in images</a></p>
  
  <p>In this case, we are generating the dominant colors of an image, not using the MMCQ (or OQ) algorithm, but K-Means.
  The problem is that the image must be a  as well:</p>
  
  <pre><code>&lt;canvas id="canvas" style="display: none;" width="200" height="200"&gt;&lt;/canvas&gt;
  </code></pre>
  
  <p>and then</p>
  
  <pre><code>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;
        }
  </code></pre>
  
  <p>This is because the Canvas has a getContext() method, that expose 2D image drawing APIs - see <a href="http://html5doctor.com/an-introduction-to-the-canvas-2d-api/" rel="nofollow noreferrer">An introduction to the Canvas 2D API</a></p>
  
  <p>This context ctx is passed to the image processing function</p>
  
  <pre><code>  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 &lt; 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 &lt; results.length; i++) {
        hex.push(rgbToHex(results[i][0]));
      }
      return hex;
    }
  </code></pre>
  
  <p>So you can draw an image on the Canvas through the Context and get image data:</p>
  
  <pre><code>ctx.drawImage(img, 0, 0, 200, 200);
  data = ctx.getImageData(0, 0, 200, 200).data;
  </code></pre>
  
  <p>Another nice solution is in CoffeeScript, <a href="https://github.com/dannvix/ColorTunes" rel="nofollow noreferrer">ColorTunes</a>, but this is using a  as well:</p>
  
  <pre><code>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 &lt; _ref; y = _i += 1) {
        indexBase = y * w * 4;
        for (x = _j = sx, _ref1 = sx + w; _j &lt; _ref1; x = _j += 1) {
          index = indexBase + (x * 4);
          pixels.push([pdata[index], pdata[index + 1], pdata[index + 2]]);
        }
      }
      return (new MMCQ).quantize(pixels, nc);
    };
  </code></pre>
  
  <p>But, wait, we have no <code>&lt;canvas/&gt;</code> element in <code>TVML</code>!</p>
  
  <p>Of course, there are native solutions like Objective-C <a href="https://github.com/pixelogik/ColorCube" rel="nofollow noreferrer">ColorCube</a>, <a href="https://github.com/indragiek/DominantColor" rel="nofollow noreferrer">DominantColor</a> - this is using K-means </p>
  
  <p>and the very nice and reusable <a href="https://github.com/panicinc/ColorArt" rel="nofollow noreferrer">ColorArt</a> by @AaronBrethorst from CocoaControls.</p>
  
  <p>Despite the fact that this could be used in a TVML application through a native to JavaScriptCore bridge - see <a href="https://stackoverflow.com/questions/33081565/tvos-tvml-and-objective-c-swift-putting-all-together">How to bridge TVML/JavaScriptCore to UIKit/Objective-C (Swift)?</a></p>
  
  <p>my aim is to make this work completely in <code>TVJS</code> and <code>TVML</code>.</p>
  
  <p>The simplest MMCQ JavaScript implementation does not need a Canvas: see <a href="https://gist.github.com/loretoparisi/fe6e5cf889bb2c8f2099" rel="nofollow noreferrer">Basic Javascript port of the MMCQ (modified median cut quantization)</a> by <a href="https://gist.github.com/nrabinowitz" rel="nofollow noreferrer">Nick Rabinowitz</a>, but needs the RGB array of the image:</p>
  
  <pre><code>var cmap = MMCQ.quantize(pixelArray, colorCount);
  </code></pre>
  
  <p>that is taken from the HTML <code>&lt;canvas/&gt;</code> and that is the reason for it!</p>
  
  <pre><code>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 &lt; 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 &gt;= 125) {
              if (!(r &gt; 250 &amp;&amp; g &gt; 250 &amp;&amp; b &gt; 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;
  }
  </code></pre>
  
  <p><strong>[QUESTION]</strong>
  How to generate the dominant colors of a RGB image without using the HTML5 <code>&lt;canvas/&gt;</code>, but in pure JavaScript from an image's <code>ByteArray</code> fetched with <code>XMLHttpRequest</code>?</p>
  
  <p><strong>[UPDATE]</strong>
  I have posted this question to <a href="https://github.com/lokesh/color-thief/issues/86" rel="nofollow noreferrer">Color-Thief</a> github repo, adapting the RGB array calculations to the latest codebase.
  The solution I have tried was this</p>
  
  <pre><code>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 &lt; 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 &lt; 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 &gt;= 125) {
                if (!(r &gt; 250 &amp;&amp; g &gt; 250 &amp;&amp; b &gt; 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();
  }
  </code></pre>
  
  <p>but it does not gives back the right RGB colors array.</p>
  
  <p><strong>[UPDATE]</strong>
  Thanks to all the suggestions I got it working. Now a full example is available on <a href="https://github.com/loretoparisi/dominant-colors-xmlhttprequest-example" rel="nofollow noreferrer">Github</a>, </p>