我需要使用JavaScript
从图像的矩形区域获取平均颜色。
我尝试使用tracking.js
,但不允许指定区域而不是单个像素。
答案 0 :(得分:7)
如你所说从图像中获取矩形区域的颜色,我会假设您需要获得给定区域的平均颜色,而不是颜色一个像素。
无论如何,两者都以非常相似的方式完成:
要获得单个像素的颜色,首先要将该图像绘制到画布上:
const image = document.getElementById('image');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const width = image.width;
const height = image.height;
canvas.width = width;
canvas.height = height;
context.drawImage(image, 0, 0, width, height);
然后像这样获得单个像素的值:
const data = context.getImageData(X, Y, 1, 1).data;
// RED = data[0]
// GREEN = data[1]
// BLUE = data[2]
// ALPHA = data[3]
您需要使用相同的CanvasRenderingContext2D.getImageData()来获取更宽(多像素)区域的值,您可以通过更改其第三个和第四个参数来完成。该功能的签名是:
ImageData ctx.getImageData(sx, sy, sw, sh);
sx
:将从中提取ImageData的矩形左上角的x坐标。sy
:将从中提取ImageData的矩形左上角的y坐标。sw
:将从中提取ImageData的矩形的宽度。sh
:将从中提取ImageData的矩形的高度。您可以看到它返回ImageData
个对象whatever that is。这里的重要部分是该对象具有.data
属性,其中包含我们所有的像素值。
但是,请注意,.data
属性是一维Uint8ClampedArray
,这意味着所有像素的组件都已展平,因此您得到的内容如下所示:< / p>
让我们假设你有一个像这样的2x2图像:
RED PIXEL | GREEN PIXEL
BLUE PIXEL | TRANSPARENT PIXEL
然后,你会得到这样的:
[ 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0 ]
| 1ST PIXEL | 2ND PIXEL | 3RD PIXEL | 4TH PIXEL |
在此示例中,您可以透明地在50 × 50px
100 × 100px
图片上移动PNG
区域/画笔。该区域的平均颜色将显示在图片下方的两个样本中,一个包含alpha
频道的平均值,另一个包含该频道的平均颜色:
const brush = document.getElementById('brush');
const avgSolidColor = document.getElementById('avgSolidColor');
const avgAlphaColor = document.getElementById('avgAlphaColor');
const avgSolidWeighted = document.getElementById('avgSolidWeighted');
const image = document.getElementById('image');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const width = image.width;
const height = image.height;
canvas.width = width;
canvas.height = height;
context.drawImage(image, 0, 0, width, height);
document.onmousemove = function(e) {
const pageX = Math.max(Math.min(e.clientX - 25, 110), 10);
const pageY = Math.max(Math.min(e.clientY - 25, 110), 10);
brush.style.left = pageX + 'px';
brush.style.top = pageY + 'px';
const imageX = pageX - 10;
const imageY = pageY - 10;
let R = 0;
let G = 0;
let B = 0;
let A = 0;
let wR = 0;
let wG = 0;
let wB = 0;
let wTotal = 0;
const data = context.getImageData(imageX, imageY, 50, 50).data;
const components = data.length;
for (let i = 0; i < components; i += 4) {
// A single pixel (R, G, B, A) will take 4 positions in the array:
const r = data[i];;
const g = data[i + 1];
const b = data[i + 2];
const a = data[i + 3];
// Update components for solid color and alpha averages:
R += r;
G += g;
B += b;
A += a;
// Update components for alpha-weighted average:
const w = a / 255;
wR += r * w;
wG += g * w;
wB += b * w;
wTotal += w;
}
const pixelsPerChannel = components / 4;
// The | operator is used here to perform an integer division:
R = R / pixelsPerChannel | 0;
G = G / pixelsPerChannel | 0;
B = B / pixelsPerChannel | 0;
wR = wR / wTotal | 0;
wG = wG / wTotal | 0;
wB = wB / wTotal | 0;
// The alpha channel need to be in the [0, 1] range:
A = A / pixelsPerChannel / 255;
avgSolidColor.style.background = `rgb(${ R }, ${ G }, ${ B })`;
avgAlphaColor.style.background = `rgba(${ R }, ${ G }, ${ B }, ${ A })`;
avgSolidWeighted.style.background = `rgb(${ wR }, ${ wG }, ${ wB })`;
};
&#13;
html {
cursor: crosshair;
font-size: 0;
}
body {
margin: 10px;
}
#image {
border: 1px solid #000;
border-bottom: none;
width: 150px;
box-sizing: border-box;
}
#brush {
position: absolute;
top: 0;
left: 0;
background: rgba(0, 255, 255, .5);
pointer-events: none;
width: 50px;
height: 50px;
}
.sample {
float: left;
box-sizing: border-box;
border: 1px solid #000;
border-right: none;
width: 50px;
height: 25px;
}
.sample:last-child {
border-right: 1px solid #000;
}
&#13;
<img id="image" src="data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" />
<div id="brush"></div>
<div>
<div class="sample" id="avgSolidColor"></div>
<div class="sample" id="avgAlphaColor"></div>
<div class="sample" id="avgSolidWeighted"></div>
</div>
&#13;
⚠️注意如果我尝试使用较长的数据URI,如果我包含外部图像或答案大于允许值,我会使用小数据URI来避免Cross-Origin
问题。
如果将画笔移动到左上角,您会看到.avgSolidColor
的颜色(第一个)几乎是黑色。这是因为该区域中的大多数像素都是完全透明的,因此它们的值恰好或非常接近0, 0, 0, 255
。这意味着,对于我们处理的每个广告,R
,G
和B
都不会改变或改变很少,而pixelsPerChannel
仍会将其考虑在内,所以我们最后将一个小数字(因为我们为大多数人添加0
)除以一个大数字(画笔中的像素总数),这给了我们一个非常接近{{1}的值(黑色)。
例如,如果我们只有两个像素0
和0, 0, 0, 255
,我们可能会认为255, 0, 0, 0
频道的平均值为R
。但是,它将是255
。但不要担心,我们会看到下一步该怎么做。
另一方面,(0 + 255) / 2 | 1 = 127
的颜色(第二个)几乎是白色。嗯,实际上并不是真的,它只是看起来白色,因为我们现在正在使用alpha通道,它接近.avgAlphaColor
,它几乎是透明的,我们只是看到页面的背景,在这种情况下是白色。
然后,我们可以做些什么来解决这个问题?好吧,事实证明我们只需要使用alpha通道作为我们(现在加权)平均值的权重:
这意味着,如果某个像素为0
,r, g, b, a
位于a
区间,我们将更新变量,如下所示:
[0, 255]
请注意像素越透明(const w = a / 255; // w is in the interval [0, 1]
wR += r * w;
wG += g * w;
wB += b * w;
wTotal += w;
越接近0),我们在计算中关注它的值就越少。
答案 1 :(得分:0)
不确定使用画布和ImageData在DOM中这种技术是否可行,但是在使用Flash和Actionscript的日子里,这是一个很好的捷径。 在我的用例中,所讨论的区域是统一的:所有区域都是大小相同的正方形,例如我有1280x720 来自网络摄像头或视频的BitmapData(相当于ImageData),我需要平均颜色才能形成一个网格,例如代表输入图像的16x9区域。 我所做的就是:将整个输入图像绘制为一个位图,其像素大小值等于区域列和行的数量。例如。将1280x720像素的图片绘制为16x9像素的位图。 然后,只需使用本机方法即可在缩小的图像中获得恰好一个像素的颜色。 此方法与手动操作一样精确,并且速度快得多,代码也简单得多。