Genere los colores dominantes para una imagen RGB con XMLHttpRequest

Una nota para los lectores: esta es una pregunta larga, pero necesita un trasfondo para comprender la pregunta que se hace.

lostécnica de cuantificación de color se usa comúnmente para obtener elcolores dominantes de una imagen Una de las bibliotecas conocidas que hacen cuantificación de color esLeptonica a través deCuantificación de corte mediana modificada (MMCQ) y cuantización de octree (OQ) De GithubLadrón de colores by @lokesh es una implementación muy simple en JavaScript del algoritmo MMCQ:

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

Técnicamente, la imagen en un<img/> El elemento HTML está respaldado en un<canvas/> elemento:

var CanvasImage = function (image) {
    this.canvas  = docum,ent.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);
};

Y ese es el problema conTVML, como veremos más adelante.

Otra implementación que conocí recientemente estaba vinculada a este artículo.Usando imagemagick, awk y kmeans para encontrar colores dominantes en imágenes que enlaza conUsando python para generar increíbles temas de escritorio de linux. El autor publicó un artículo sobreUsando python y k-means para encontrar los colores dominantes en las imágenes que se usó allí (perdón por todos esos enlaces, pero estoy siguiendo mi historial ...).

El autor fue muy productivo y también agregó una versión de JavaScript que estoy publicando aquí:Usando JavaScript y k-means para encontrar los colores dominantes en las imágenes

En este caso, estamos generando los colores dominantes de una imagen, no utilizando el algoritmo MMCQ (u OQ), sino K-Means. El problema es que la imagen también debe ser:

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

y entonces

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

Esto se debe a que Canvas tiene un método getContext (), que expone las API de dibujo de imágenes en 2D.Una introducción a la API 2D de Canvas

Este contexto ctx se pasa a la función de procesamiento de imágenes

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

Para que pueda dibujar una imagen en el lienzo a través del contexto y obtener datos de la imagen:

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

Otra buena solución está en CoffeeScript,ColorTunes, pero esto también está usando un:

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

Pero espera, no tenemos<canvas/> elemento enTVML!

Por supuesto, hay soluciones nativas como Objective-CColorCube, Color dominante - esto está usando K-means

y el muy bonito y reutilizableColorArt por @AaronBrethorst de CocoaControls.

A pesar de que esto podría usarse en una aplicación TVML a través de un puente nativo de JavaScriptCore, vea¿Cómo conectar TVML / JavaScriptCore a UIKit / Objective-C (Swift)?

mi objetivo es hacer que esto funcione completamente enTVJS yTVML.

La implementación de JavaScript MMCQ más simple no necesita un Canvas: veaPuerto Javascript básico del MMCQ (cuantificación de corte medio modificada) porNick Rabinowitz, pero necesita la matriz RGB de la imagen:

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

que se toma del HTML<canvas/> ¡Y esa es la razón!

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

[PREGUNTA] Cómo generar los colores dominantes de una imagen RGB sin usar HTML5<canvas/>, pero en JavaScript puro de una imagenByteArray traído conXMLHttpRequest?

[ACTUALIZAR] He publicado esta pregunta enLadrón de colores repositorio de github, adaptando los cálculos de la matriz RGB a la última base de código. La solución que probé fue esta

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

pero no devuelve la matriz de colores RGB correcta.

[ACTUALIZAR] Gracias a todas las sugerencias, lo hice funcionar. Ahora hay un ejemplo completo disponible enGithub,

Respuestas a la pregunta(1)

Su respuesta a la pregunta