我想从画布上的特定点采样颜色。而不只是一个像素的颜色:
context.getImageData(x, y, 1, 1).data;
我想从这一点周围的像素中采样颜色。我想像Quasimondo's StackBlur这样的东西是一个合适的算法,用于计算某个半径点周围的平均颜色。模糊算法是正确的方法吗?对于这种用途,是否有任何特定的模糊算法优于其他算法?
客观答案将比较周围像素的算术平均值与模糊图像或加权平均值。
答案 0 :(得分:1)
模糊在技术上是平均的,但它会在扫描的区域中找到平均每像素。当您只想要整个区域的平均颜色而不是每个像素时,这会过于复杂。使用模糊意味着与线性扫描相比性能较差,无法找到平均值,这是查找该区域平均颜色所需的。
你想要的是该区域中所有像素的平均值。一个过程就像:
计算从(x,y)到中心的距离:
var length = radius * radius; // max distance from center
var diffX = Math.abs(x - centerX);
var diffY = Math.abs(y - centerY);
var dist = diffX*diffX + diffY*diffY; // distance center -> x,y
if (dist <= length) { ...add sample to array... }
// ...
// do average: sum of all r, sum of all g etc..
// r divided on number of entries in array, etc. (round of values)
(提示:在这种情况下你不需要设置距离)
// vars and load some image
var vcanvas = document.querySelector("canvas"),
vctx = vcanvas.getContext("2d"),
canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d"),
w = 500, h = 300,
img = new Image();
img.crossOrigin = "";
img.onload = prep;
img.src = "//i.imgur.com/SetDGOB.jpg";
// setup and prepare image for canvases
function prep() {
vcanvas.width = canvas.width = w;
vcanvas.height = canvas.height = h;
ctx.drawImage(img, 0, 0, w, h); // draw in off-screen canvas
vcanvas.style.backgroundImage = "url(" + canvas.toDataURL() + ")"; // set as bg to visual canvas
vctx.font = "16px sans-serif"; // to draw values to screen
vctx.lineWidth = 8; // lupe ring width
// sample image on mouse move
vcanvas.onmousemove = function(e) {
var rect = vcanvas.getBoundingClientRect(), // correct mouse pos.
x = e.clientX - rect.left,
y = e.clientY - rect.top,
radius = 6, // sample radius
zoom = 4, // zoom (for visuals only)
sx = (w * zoom - w) / w, // calc scale factors
sy = (h * zoom - h) / h,
avg = sample(x, y, radius); // sample area (average)
// draw zoomed circle
vctx.clearRect(0, 0, w, h);
if (null == avg) return; // nothing to show
vctx.beginPath();
vctx.arc(x, y, radius * zoom, 0, 2*Math.PI);
vctx.fill();
//vctx.scale(zoom, zoom);
vctx.translate(-x * sx, -y * sy);
vctx.globalCompositeOperation = "source-atop";
vctx.drawImage(canvas, 0, 0, w*zoom, h*zoom);
vctx.setTransform(1,0,0,1,0,0);
vctx.globalCompositeOperation = "source-over";
// draw black ring
vctx.beginPath();
vctx.arc(x, y, radius * zoom + vctx.lineWidth * 0.5, 0, 2*Math.PI);
vctx.strokeStyle = "#555";
vctx.closePath();
vctx.stroke();
// draw average color ring
vctx.beginPath();
vctx.arc(x, y, radius * zoom + vctx.lineWidth * 0.5 + 1, 0, 2*Math.PI);
vctx.strokeStyle = "rgb(" + avg.r + "," + avg.g + "," + avg.b + ")";
vctx.closePath();
vctx.stroke();
vctx.fillStyle = "#000";
vctx.fillText("x:" + x + " y:" + y + " " + vctx.strokeStyle, 12, 22);
vctx.fillStyle = "#0f0";
vctx.fillText("x:" + x + " y:" + y + " " + vctx.strokeStyle, 10, 20);
}
}
// This will do the color sampling from the circle
function sample(cx, cy, radius) {
var r = 0, g = 0, b = 0, a = 0, cnt = 0, // initialize
length = radius * radius, // calc max distance from center
region, // region with pixels to sample
idata, buffer, len, // data buffers
i, p, x, y, dx, dy, dist;
// calc region:
region = {
x: cx - radius,
y: cy - radius,
w: Math.min(w-cx+radius, radius*2)|0,
h: Math.min(h-cy+radius, radius*2)|0
};
// check and adjust region to fit inside canvas area
if (region.x < 0) {region.w + region.x; region.x = 0}
if (region.y < 0) {region.h + region.y; region.y = 0}
if (region.w < 1 || region.h < 1 ) return null;
// get buffer for region
idata = ctx.getImageData(region.x|0, region.y|0, region.w|0, region.h|0);
buffer = idata.data;
len = buffer.length;
// iterate region and sample pixels with circle
for(y = 0; y < region.h; y++) {
for(x = 0; x < region.w; x++) {
dx = radius - x;
dy = radius - y;
dist = Math.abs(dx*dx + dy*dy); // dist. from center to current x,y in buffer
// add value if within circle
if (dist <= length) {
p = (y * region.h + x)*4;
r += buffer[p];
g += buffer[p+1];
b += buffer[p+2];
a += buffer[p+3];
cnt++;
}
}
}
// no samples? (should never happen!)
if (!cnt) return null;
// calculate and return average
return {
r: (r / cnt + 0.5)|0,
g: (g / cnt + 0.5)|0,
b: (b / cnt + 0.5)|0,
a: (a / cnt + 0.5)|0
}
}
canvas {cursor:crosshair}
<canvas></canvas>
(有一些方法可以通过存储整个图像的缓冲区而不是屏幕外的画布来优化这些代码,根据偏移等获取区域,但为了简单起见我保持这种方式(?)... )。