通过 Javascript 获得图像的平均颜色

不确定这是否可行,但是希望编写一个脚本来返回图像的平均 hexrgb值。我知道它可以在 AS 中完成,但希望在 JavaScript 中完成。

195069 次浏览

Javascript 不能访问图像的单个像素颜色数据。至少,也许直到 html5... 在这一点上,理所当然地,您将能够绘制一个图像到一个画布,然后检查画布(也许,我从来没有这样做自己)。

我会说通过 HTML 画布标记。

你可以通过@Georg 找到一篇关于 Opera 开发人员的小代码的文章:

// Get the CanvasPixelArray from the given coordinates and dimensions.
var imgd = context.getImageData(x, y, width, height);
var pix = imgd.data;


// Loop over each pixel and invert the color.
for (var i = 0, n = pix.length; i < n; i += 4) {
pix[i  ] = 255 - pix[i  ]; // red
pix[i+1] = 255 - pix[i+1]; // green
pix[i+2] = 255 - pix[i+2]; // blue
// i+3 is alpha (the fourth element)
}


// Draw the ImageData at the given (x,y) coordinates.
context.putImageData(imgd, x, y);

通过使用每个像素的 R、 G 和 B 值来反转图像。您可以轻松地存储 RGB 值,然后四舍五入红色,绿色和蓝色数组,最后将它们转换回一个十六进制代码。

AFAIK,唯一的方法是使用 <canvas/>..。

演示文稿 V2 : < a href = “ http://jsfiddle.net/xLF38/818/”rel = “ noReferrer”> http://jsfiddle.net/xlf38/818/

注意,这只适用于同一个域和支持 HTML5画布的浏览器中的图像:

function getAverageRGB(imgEl) {


var blockSize = 5, // only visit every 5 pixels
defaultRGB = {r:0,g:0,b:0}, // for non-supporting envs
canvas = document.createElement('canvas'),
context = canvas.getContext && canvas.getContext('2d'),
data, width, height,
i = -4,
length,
rgb = {r:0,g:0,b:0},
count = 0;


if (!context) {
return defaultRGB;
}


height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;


context.drawImage(imgEl, 0, 0);


try {
data = context.getImageData(0, 0, width, height);
} catch(e) {
/* security error, img on diff domain */
return defaultRGB;
}


length = data.data.length;


while ( (i += blockSize * 4) < length ) {
++count;
rgb.r += data.data[i];
rgb.g += data.data[i+1];
rgb.b += data.data[i+2];
}


// ~~ used to floor values
rgb.r = ~~(rgb.r/count);
rgb.g = ~~(rgb.g/count);
rgb.b = ~~(rgb.b/count);


return rgb;


}

对于 IE,请查看 检查

首先: 不需要 HTML5 Canvas 或 SVG 就可以完成。
事实上,有人刚刚设法 使用 JavaScript 生成客户端 PNG 文件没有画布或 SVG,使用 数据 URI 方案

编辑: 您可以使用 document.getElementById("canvas").toDataURL("image/png", 1.0)简单地创建一个 PNG

其次: 您实际上可能根本不需要 Canvas、 SVG 或以上任何一种。
如果您只需要在客户端 程序图像,没有修改它们,所有这些都是不需要的。

您可以从页面上的 img 标记获得源地址,然后为其发出 XHR请求——它很可能来自浏览器缓存——并将其作为来自 Javascript 的字节流进行处理。
您需要对图像格式有很好的理解。(上面的生成器部分基于 libpng 源,可能提供了一个很好的起点。)

“支配颜色”很棘手。你要做的就是比较每个像素和颜色空间中其他像素(欧几里得度量)之间的距离,然后找到颜色最接近其他颜色的像素。那个像素是主色调。平均颜色通常是泥土。

我希望这里有 MathML 给你看看欧几里得度量,谷歌一下。

我在这里使用 PHP/GD 在 RGB 颜色空间中完成了上面的执行: https://gist.github.com/cf23f8bddb307ad4abd8

然而,这在计算上是非常昂贵的。它会使你的系统在大图片上崩溃,如果你在客户端尝试它,它肯定会使你的浏览器崩溃。我一直在重构我的执行: - 将结果存储在一个查找表中,以备将来在每个像素上的迭代中使用。 - 将大图像分割成20px 20px 的网格以获得局部优势。 - 利用 x1y1和 x1y2之间的欧几里得度量计算出 x1y1和 x1y3之间的距离。

如果你在这方面有进展,请告诉我。我很乐意看看。我也会这么做。

Canvas 绝对是在客户机中完成此操作的最佳方式。SVG 不是,SVG 是基于向量的。在执行完成之后,接下来要做的事情就是在画布中运行它(可能需要一个 webworker 来计算每个像素的总体距离)。

另一件需要考虑的事情是,RGB 不是一个很好的颜色空间,因为在 RGB 空间中颜色之间的欧几里得度量不是很接近视觉距离。一个更好的色彩空间可能是 LUV,但我还没有找到一个很好的库,或者任何算法来转换 RGB 到 LUV。

另一种完全不同的方法是在彩虹中对颜色进行分类,并构建一个具有容差的直方图来说明颜色的不同深浅。我还没有尝试过这种方法,因为在彩虹中分类颜色是很困难的,颜色直方图也是如此。我下次可能会试试这个。再说一次,如果你们有什么进展就告诉我。

我想我应该发布一个我最近遇到的项目来获得主色调:

彩色大盗

一种用于从图像中抓取主要颜色或代表性调色板的脚本。使用 javascript 和画布。

其他解决方案提到和建议主色从来没有真正回答问题在适当的上下文(“在 javascript”)。希望这个项目能够帮助那些想要这样做的人。

这是关于“图像量化”的,它有几种方法,如 MMCQ(修正的中值切割量化)或 OQ (八叉树量化)。不同的方法使用 K-Means来获得颜色簇。

我在这里将所有内容放在一起,因为我正在为 tvOS寻找一个解决方案,其中包含一个没有 <canvas/>元素的 XHTML 子集:

使用 XMLHttpRequest 为 RGB 图像生成主导颜色

我最近遇到了一个 jQuery 插件,它可以实现我最初想要的 https://github.com/briangonzalez/jquery.adaptive-backgrounds.js功能,即从图像中获得主导颜色。

有一个在线工具 Pickimagecolor.com,可以帮助您找到平均或图像的主导颜色。你只需要从你的计算机上传一个图像,然后点击图像。它给出的平均颜色在十六进制,RGB 和 HSV。它还可以找到与该颜色相匹配的颜色阴影。我用过很多次了。

使用 datauri支持获得图像平均颜色的最快方法:

function get_average_rgb(img) {
var context = document.createElement('canvas').getContext('2d');
if (typeof img == 'string') {
var src = img;
img = new Image;
img.setAttribute('crossOrigin', '');
img.src = src;
}
context.imageSmoothingEnabled = true;
context.drawImage(img, 0, 0, 1, 1);
return context.getImageData(1, 1, 1, 1).data.slice(0,3);
}

一体化解决方案

我个人将 彩贼 彩贼 与这个 修改版本 < strong > Name that Color 结合起来,以获得一个比图像的主导颜色结果更充分的数组。

例如:

看看下面的图片:

enter image description here

您可以使用以下代码提取与主色相关的图像数据:

let color_thief = new ColorThief();
let sample_image = new Image();


sample_image.onload = () => {
let result = ntc.name('#' + color_thief.getColor(sample_image).map(x => {
const hex = x.toString(16);
return hex.length === 1 ? '0' + hex : hex;
    

}).join(''));
  

console.log(result[0]); // #f0c420     : Dominant HEX/RGB value of closest match
console.log(result[1]); // Moon Yellow : Dominant specific color name of closest match
console.log(result[2]); // #ffff00     : Dominant HEX/RGB value of shade of closest match
console.log(result[3]); // Yellow      : Dominant color name of shade of closest match
console.log(result[4]); // false       : True if exact color match
};


sample_image.crossOrigin = 'anonymous';
sample_image.src = document.getElementById('sample-image').src;

正如在其他答案中指出的那样,通常你真正想要的是主色,而不是一般的棕色。我写了一个脚本,得到最常见的颜色和张贴在这个 大意

编辑: 只有在发表了这篇文章之后,我才意识到@350D 的回答做了同样的事情。

令人惊讶的是,这只需要4行代码就可以完成:

const canvas = document.getElementById("canvas"),
preview = document.getElementById("preview"),
ctx = canvas.getContext("2d");


canvas.width = 1;
canvas.height = 1;


preview.width = 400;
preview.height = 400;


function getDominantColor(imageObject) {
//draw the image to one pixel and let the browser find the dominant color
ctx.drawImage(imageObject, 0, 0, 1, 1);


//get pixel color
const i = ctx.getImageData(0, 0, 1, 1).data;


console.log(`rgba(${i[0]},${i[1]},${i[2]},${i[3]})`);


console.log("#" + ((1 << 24) + (i[0] << 16) + (i[1] << 8) + i[2]).toString(16).slice(1));
}






// vvv all of this is to just get the uploaded image vvv
const input = document.getElementById("input");
input.type = "file";
input.accept = "image/*";


input.onchange = event => {
const file = event.target.files[0];
const reader = new FileReader();


reader.onload = readerEvent => {
const image = new Image();
image.onload = function() {
//shows preview of uploaded image
preview.getContext("2d").drawImage(
image,
0,
0,
preview.width,
preview.height,
);
getDominantColor(image);
};
image.src = readerEvent.target.result;
};
reader.readAsDataURL(file, "UTF-8");
};
canvas {
width: 200px;
height: 200px;
outline: 1px solid #000000;
}
<canvas id="preview"></canvas>
<canvas id="canvas"></canvas>
<input id="input" type="file" />

工作原理:

创建画布上下文

const context = document.createElement("canvas").getContext("2d");

这将只将图像绘制到一个画布像素,使浏览器为您找到主导颜色。

context.drawImage(imageObject, 0, 0, 1, 1);

然后,获取像素的图像数据:

const i = context.getImageData(0, 0, 1, 1).data;

最后,转换为 rgbaHEX:

const rgba = `rgba(${i[0]},${i[1]},${i[2]},${i[3]})`;


const HEX = "#" + ((1 << 24) + (i[0] << 16) + (i[1] << 8) + i[2]).toString(16).slice(1);

但是这种方法有一个问题,那就是 getImageData有时会抛出错误 Unable to get image data from canvas because the canvas has been tainted by cross-origin data.,这就是为什么需要在演示中上传图像而不是输入 URL 的原因。

该方法还可以通过增加图像的宽度和高度来对图像进行像素化。

这可以在 chrome 上工作,但可能不适用于其他浏览器。

这是@350D 的答案,但是是异步的(因为有些图像可能需要时间加载) ,而且是打印的

async function get_average_rgb(src: string): Promise<Uint8ClampedArray> {
/* https://stackoverflow.com/questions/2541481/get-average-color-of-image-via-javascript */
return new Promise(resolve => {
let context = document.createElement('canvas').getContext('2d');
context!.imageSmoothingEnabled = true;


let img = new Image;
img.src = src;
img.crossOrigin = "";


img.onload = () => {
context!.drawImage(img, 0, 0, 1, 1);
resolve(context!.getImageData(0, 0, 1, 1).data.slice(0,3));
};
});
}

为了获得图像的 一般(而不是 主导)颜色,创建一个缩小后的原始图像的小画布(最大尺寸: 10px)。然后循环画布区域 image 数据来构造平均 RGB:

const getAverageColor = (img) => {
  

const max = 10; // Max size (Higher num = better precision but slower)
const {naturalWidth: iw, naturalHeight: ih} = img;
const ctx = document.createElement`canvas`.getContext`2d`;
const sr = Math.min(max / iw, max / ih); // Scale ratio
const w = Math.ceil(iw * sr); // Width
const h = Math.ceil(ih * sr); // Height
const a = w * h;              // Area
  

img.crossOrigin = 1;
ctx.canvas.width = w;
ctx.canvas.height = h;
ctx.drawImage(img, 0, 0, w, h);
  

const data = ctx.getImageData(0, 0, w, h).data;
let r = g = b = 0;
  

for (let i=0; i<data.length; i+=4) {
r += data[i];
g += data[i+1];
b += data[i+2];
}


r = ~~(r/a);
g = ~~(g/a);
b = ~~(b/a);


return {r, g, b};
};


const setBgFromAverage = (img) => {
img.addEventListener("load", () => {
const {r,g,b} = getAverageColor(img);
img.style.backgroundColor = `rgb(${r},${g},${b})`;
});
};


document.querySelectorAll('.thumb').forEach(setBgFromAverage);
.thumbs { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.thumb { height: 100px; padding: 20px; }
<div class="thumbs">
<img class="thumb" alt="image" src="https://i.imgur.com/22BrBjx.jpeg">
<img class="thumb" alt="image" src="https://i.imgur.com/MR0dUpw.png">
<img class="thumb" alt="image" src="https://i.imgur.com/o7lpiDR.png">
<img class="thumb" alt="image" src="https://i.imgur.com/egYvHp6.jpeg">
<img class="thumb" alt="image" src="https://i.imgur.com/62EAzOY.jpeg">
<img class="thumb" alt="image" src="https://i.imgur.com/3VxBMeF.jpeg">
</div>