javascript - 具有半径(模糊?)的颜色选择器算法

标签 javascript algorithm canvas

<分区>

我想从 Canvas 上的特定点采样颜色。而不是只有一个像素的颜色:

context.getImageData(x, y, 1, 1).data;

我想从该点周围的像素中采样颜色。我在想像 Quasimondo's StackBlur 这样的东西将是计算某个点周围特定半径的平均颜色的合适算法。模糊算法是正确的方法吗?对于这种用途,是否有任何特定的模糊算法优于其他算法?

客观答案会将周围像素的算术平均值与模糊图像或加权平均值进行比较。

最佳答案

模糊在技术上是平均的,但它会在它扫描的区域中找到平均每像素。当您只想要整个区域的平均颜色而不是每个像素时,这就过于复杂了。与查找平均值的线性扫描相比,使用模糊意味着性能较差,这是查找区域平均颜色所需要的。

您想要的是该区域中所有像素的简单平均值。一个过程是这样的:

  • 定义拾取器半径
  • 计算长度(半径 x 半径)
  • 定义一个圆形适合的正方形区域
  • 遍历每条线,对每个点做:
    • 计算当前x,y到中心的距离
    • 判断是否在预先计算的长度内
    • 如果不跳过,如果是,则将像素存入数组
  • 计算像素数,将它们相加并除以计数 => 该圆的平均像素

计算从 (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>

(有一些方法可以通过存储整个图像的缓冲区而不是屏幕外的 Canvas 来优化此代码,根据偏移量获取区域等,但为了简单起见,我保持这种方式(?)...... ).

关于javascript - 具有半径(模糊?)的颜色选择器算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28750724/

相关文章:

algorithm - 有多少可能的记分卡与输入记分卡一致?

algorithm - Rust 有没有计算乘积之和的好方法?

java - 使用动态编程查找添加到特定数字的列表的所有集合

javascript - 鼠标悬停在 Canvas 上与鼠标在 html 对象上输入的性能

javascript - gulp 中的//@codekit-prepend ""相当于什么?

javascript - 如何在Polymer 2.x中正确使用IronA11yKeysBehavior

javascript - JQuery 点击事件附加到多个元素

javascript - Canvas 上未绘制线条

javascript - 为不同的 HTML 形状填充颜色

javascript - 如何在单一条件下检测键码和点击事件?