使用 XMLHttpRequest 为 RGB 图像生成主色

Generate the Dominant Colors for an RGB image with XMLHttpRequest

读者注意事项:这是一个很长的问题,但需要背景知识才能理解所提问题。

color quantization technique 通常用于获取图像的主色。 进行颜色量化的著名库之一是 Leptonica through the Modified Median Cut Quantization (MMCQ) and octree quantization (OQ) @lokesh Github 的 Color-thief 是 MMCQ 算法 JavaScript 中的一个非常简单的实现:

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

从技术上讲,<img/> HTML 元素上的图像支持 <canvas/> 元素:

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

这就是 TVML 的问题,我们稍后会看到。

本文 Using imagemagick, awk and kmeans to find dominant colors in images that links to Using python to generate awesome linux desktop themes 链接了我最近了解到的另一个实现。 作者发布了一篇关于 Using python and k-means to find the dominant colors in images 的文章,该文章在那里使用(抱歉所有这些链接,但我正在追溯我的历史……)。

作者的工作效率很高,还添加了一个 JavaScript 版本,我在这里发布:Using JavaScript and k-means to find the dominant colors in images

在这种情况下,我们生成图像的主色,不是使用 MMCQ(或 OQ)算法,而是 K-Means。 问题是图像也必须是:

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

然后

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

这是因为 Canvas 有一个 getContext() 方法,它公开了 2D 图像绘制 API - 请参阅 An introduction to the Canvas 2D API

这个上下文ctx被传递给图像处理函数

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

所以可以通过Context在Canvas上绘制图像并获取图像数据:

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

另一个不错的解决方案是在 CoffeeScript 中,ColorTunes,但这也使用了:

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

但是,等等,我们在 TVML!

中没有 <canvas/> 元素

当然,也有像Objective-C ColorCube, DominantColor这样的原生解决方案-这是使用K-means

以及来自 CocoaControls 的@AaronBrethorst 非常漂亮且可重复使用的 ColorArt

尽管这可以通过 JavaScriptCore 桥接的本机在 TVML 应用程序中使用 - 请参阅 How to bridge TVML/JavaScriptCore to UIKit/Objective-C (Swift)?

我的目标是在 TVJSTVML 中完成这项工作。

最简单的MMCQ JavaScript实现不需要Canvas:见Basic Javascript port of the MMCQ (modified median cut quantization) by Nick Rabinowitz,但需要图像的RGB数组:

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

取自 HTML <canvas/> 这就是原因!

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

[问题] 如何在不使用 HTML5 <canvas/> 的情况下生成 RGB 图像的主色,而是从使用 XMLHttpRequest 获取的图像的 ByteArray 中纯 JavaScript?

[更新] 我已将此问题发布到 Color-Thief github 存储库,使 RGB 数组计算适应最新的代码库。 我试过的解决方案是这个

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

但它没有返回正确的 RGB 颜色数组。

[更新] 感谢所有的建议,我让它工作了。 Github

上提供了完整示例

canvas 元素用作将图像解码为 RGBA 数组的便捷方式。您还可以使用纯 JavaScript 库来进行图像解码。

jpgjs is a JPEG decoder and pngjs 是一个 PNG 解码器。看起来 JPEG 解码器将按原样与 TVJS 一起工作。然而,PNG 解码器看起来像是在 Node 或 Web 浏览器环境中工作,因此您可能需要稍微调整一下。