有没有比 for 循环更快的方法来对 javascript 中的图像进行阈值处理?
Is there a faster way than a for loop for thresholding an image in javascript?
我想使用滑块对大图像进行客户端实时交互式阈值处理。是否可以在不对所有像素使用 for 循环的情况下对 javascript 中的图像进行阈值处理以生成二值图像?如果是这样,它是否更快?
这可以分两个阶段仅使用 globalCompositeOperations 来完成。
- 将所有低于阈值的像素设置为 0(黑色)。
- 'Divide' 这个图像本身,使用定义 0/0 = 0
的算法
定义三个 canvases,一个在屏幕上,一个在屏幕外保持灰度图像,另一个在屏幕外 'working' canvas.
//--- on-screen canvas
var onScreenCanvas=document.getElementById("canvasTest");
var ctxOnScreen=onScreenCanvas.getContext("2d");
//--- off-screen working canvas
var drawingCanvas = document.createElement('canvas');
var ctx=drawingCanvas.getContext("2d");
//--- off-screen canvas to store the greyscale image
var greyscaleImageCanvas = document.createElement('canvas');
var ctxGreyscaleImage=greyscaleImageCanvas.getContext("2d");
将灰度图像加载到greyscaleImageCanvas上,然后下面两个操作实现步骤1,其中thresh_str
是一个十六进制字符串,表示RGB
中每个阈值在0-FF之间
//(1a) Threshold the image on the offscreen working canvas,
// reducing values above threshold to have threshold value
ctx.drawImage(greyscaleImageCanvas, 0, 0);
ctx.globalCompositeOperation='darken';
ctx.fillStyle=thresh_str;
ctx.fillRect(0,0, drawingCanvas.width, drawingCanvas.height);
//(1b) Set everything *below* threshold to 0 (black) since that part is unchanged
// from the original image. Pixels above threshold are all non-zero.
ctx.globalCompositeOperation='difference';
ctx.drawImage(greyscaleImageCanvas, 0, 0);
没有为 HTML globalCompositeOperations 定义直接的 'divide' 操作,但是有一个 'color-dodge',它将底层与倒置的顶层分开。因此,通过首先对步骤 1 的输出进行倒置复制,然后在划分之前使用颜色减淡操作(定义 0/0=0)到 'un-invert' 来实现所需的结果。结果是非零(高于阈值)像素变为 1,零(低于阈值)像素保持为零。
//(2a) Copy the result of (1b) to the onscreen canvas
ctxOnScreen.globalCompositeOperation='copy';
ctxOnScreen.drawImage(drawingCanvas, 0, 0);
//(2b) Invert the result of step (1b) so that it can be 'un-inverted' by color dodge
ctx.globalCompositeOperation='difference';
ctx.fillStyle='white';
ctx.fillRect(0,0,onScreenCanvas.width,onScreenCanvas.height);
//(2c) 'color-dodge' the results of (1b) with it's own inverse (2b)
ctxOnScreen.globalCompositeOperation='color-dodge';
ctxOnScreen.drawImage(drawingCanvas, 0, 0);
这个方法似乎比 for 循环快 3-5 倍,至少在 Chrome 79 在 Mac 和 android(华为 P10)JSPerf
function img2grey(canvasContext) {
canvasContext.globalCompositeOperation='color';
canvasContext.fillStyle='white';
canvasContext.fillRect(0,0,onScreenCanvas.width,onScreenCanvas.height);
}
//--- on-screen canvas
var onScreenCanvas=document.getElementById("canvasTest");
var ctxOnScreen=onScreenCanvas.getContext("2d");
//--- off-screen working canvas
var drawingCanvas = document.createElement('canvas');
var ctx=drawingCanvas.getContext("2d");
//--- off-screen canvas to store the greyscale image
var greyscaleImageCanvas = document.createElement('canvas');
var ctxGreyscaleImage=greyscaleImageCanvas.getContext("2d");
var image = new Image();
function thresholdImage(thresh_val) {
if(thresh_val.length == 1){
thresh_val = '0' + thresh_val;
}
thresh_str = '#'+thresh_val+thresh_val+thresh_val;
ctxOnScreen.clearRect(0, 0, onScreenCanvas.width, onScreenCanvas.height);
ctx.clearRect(0, 0, drawingCanvas.width, drawingCanvas.height);
//----- (1) Threshold the image on the offscreen working canvas,
// reducing values above threshold to have threshold value
ctx.drawImage(greyscaleImageCanvas, 0, 0);
ctx.globalCompositeOperation='darken';
ctx.fillStyle=thresh_str;
ctx.fillRect(0,0,onScreenCanvas.width,onScreenCanvas.height);
//----- (2) Set everything *below* threshold to 0 (black) since that part is unchanged
// from the original image
ctx.globalCompositeOperation='difference';
ctx.drawImage(greyscaleImageCanvas, 0, 0);
//----- (3) Copy the result to the onscreen canvas
ctxOnScreen.globalCompositeOperation='copy';
ctxOnScreen.drawImage(drawingCanvas, 0, 0);
//----- (4) Invert the result of step (2) so that it can be 'un-inverted' by color dodge
ctx.globalCompositeOperation='difference';
ctx.fillStyle='white';
ctx.fillRect(0,0,onScreenCanvas.width,onScreenCanvas.height);
//----- (5) 'color-dodge' the results of (2) with it's own inverse (4)
//----- This makes use of 0/0 defined as 0 in this globalCompositeOperation,
//----- so that non-zero (suprathreshold) pixels become 1, zero (sub-threshold) pixels stay zero
//~ ctxOnScreen.globalCompositeOperation='color-dodge';
ctxOnScreen.globalCompositeOperation='color-dodge';
ctxOnScreen.drawImage(drawingCanvas, 0, 0);
}
image.onload = function() {
onScreenCanvas.width = image.width;
onScreenCanvas.height = image.height;
drawingCanvas.width = image.width;
drawingCanvas.height = image.height;
greyscaleImageCanvas.width = image.width;
greyscaleImageCanvas.height = image.height;
//!!NB Doesn't work on chrome for local files, use firefox
//
ctxGreyscaleImage.drawImage(image, 0, 0);
img2grey(ctxGreyscaleImage);
thresholdImage((Math.round(rng.value)).toString(16));
};
var rng = document.querySelector("input");
var listener = function() {
window.requestAnimationFrame(function() {
thresholdImage( (Math.round(rng.value)).toString(16) );
});
};
rng.addEventListener("mousedown", function() {
listener();
rng.addEventListener("mousemove", listener);
});
rng.addEventListener("mouseup", function() {
rng.removeEventListener("mousemove", listener);
});
image.src = "https://i.imgur.com/vN0NbVu.jpg";
.slider-width100 {
width: 255px;
}
<html>
<head>
</head>
<body>
<canvas id="canvasTest"></canvas>
<input class="slider-width100" type="range" min="0" max="254" value="122" />
</body>
</html>
我想使用滑块对大图像进行客户端实时交互式阈值处理。是否可以在不对所有像素使用 for 循环的情况下对 javascript 中的图像进行阈值处理以生成二值图像?如果是这样,它是否更快?
这可以分两个阶段仅使用 globalCompositeOperations 来完成。
- 将所有低于阈值的像素设置为 0(黑色)。
- 'Divide' 这个图像本身,使用定义 0/0 = 0 的算法
定义三个 canvases,一个在屏幕上,一个在屏幕外保持灰度图像,另一个在屏幕外 'working' canvas.
//--- on-screen canvas
var onScreenCanvas=document.getElementById("canvasTest");
var ctxOnScreen=onScreenCanvas.getContext("2d");
//--- off-screen working canvas
var drawingCanvas = document.createElement('canvas');
var ctx=drawingCanvas.getContext("2d");
//--- off-screen canvas to store the greyscale image
var greyscaleImageCanvas = document.createElement('canvas');
var ctxGreyscaleImage=greyscaleImageCanvas.getContext("2d");
将灰度图像加载到greyscaleImageCanvas上,然后下面两个操作实现步骤1,其中thresh_str
是一个十六进制字符串,表示RGB
//(1a) Threshold the image on the offscreen working canvas,
// reducing values above threshold to have threshold value
ctx.drawImage(greyscaleImageCanvas, 0, 0);
ctx.globalCompositeOperation='darken';
ctx.fillStyle=thresh_str;
ctx.fillRect(0,0, drawingCanvas.width, drawingCanvas.height);
//(1b) Set everything *below* threshold to 0 (black) since that part is unchanged
// from the original image. Pixels above threshold are all non-zero.
ctx.globalCompositeOperation='difference';
ctx.drawImage(greyscaleImageCanvas, 0, 0);
没有为 HTML globalCompositeOperations 定义直接的 'divide' 操作,但是有一个 'color-dodge',它将底层与倒置的顶层分开。因此,通过首先对步骤 1 的输出进行倒置复制,然后在划分之前使用颜色减淡操作(定义 0/0=0)到 'un-invert' 来实现所需的结果。结果是非零(高于阈值)像素变为 1,零(低于阈值)像素保持为零。
//(2a) Copy the result of (1b) to the onscreen canvas
ctxOnScreen.globalCompositeOperation='copy';
ctxOnScreen.drawImage(drawingCanvas, 0, 0);
//(2b) Invert the result of step (1b) so that it can be 'un-inverted' by color dodge
ctx.globalCompositeOperation='difference';
ctx.fillStyle='white';
ctx.fillRect(0,0,onScreenCanvas.width,onScreenCanvas.height);
//(2c) 'color-dodge' the results of (1b) with it's own inverse (2b)
ctxOnScreen.globalCompositeOperation='color-dodge';
ctxOnScreen.drawImage(drawingCanvas, 0, 0);
这个方法似乎比 for 循环快 3-5 倍,至少在 Chrome 79 在 Mac 和 android(华为 P10)JSPerf
function img2grey(canvasContext) {
canvasContext.globalCompositeOperation='color';
canvasContext.fillStyle='white';
canvasContext.fillRect(0,0,onScreenCanvas.width,onScreenCanvas.height);
}
//--- on-screen canvas
var onScreenCanvas=document.getElementById("canvasTest");
var ctxOnScreen=onScreenCanvas.getContext("2d");
//--- off-screen working canvas
var drawingCanvas = document.createElement('canvas');
var ctx=drawingCanvas.getContext("2d");
//--- off-screen canvas to store the greyscale image
var greyscaleImageCanvas = document.createElement('canvas');
var ctxGreyscaleImage=greyscaleImageCanvas.getContext("2d");
var image = new Image();
function thresholdImage(thresh_val) {
if(thresh_val.length == 1){
thresh_val = '0' + thresh_val;
}
thresh_str = '#'+thresh_val+thresh_val+thresh_val;
ctxOnScreen.clearRect(0, 0, onScreenCanvas.width, onScreenCanvas.height);
ctx.clearRect(0, 0, drawingCanvas.width, drawingCanvas.height);
//----- (1) Threshold the image on the offscreen working canvas,
// reducing values above threshold to have threshold value
ctx.drawImage(greyscaleImageCanvas, 0, 0);
ctx.globalCompositeOperation='darken';
ctx.fillStyle=thresh_str;
ctx.fillRect(0,0,onScreenCanvas.width,onScreenCanvas.height);
//----- (2) Set everything *below* threshold to 0 (black) since that part is unchanged
// from the original image
ctx.globalCompositeOperation='difference';
ctx.drawImage(greyscaleImageCanvas, 0, 0);
//----- (3) Copy the result to the onscreen canvas
ctxOnScreen.globalCompositeOperation='copy';
ctxOnScreen.drawImage(drawingCanvas, 0, 0);
//----- (4) Invert the result of step (2) so that it can be 'un-inverted' by color dodge
ctx.globalCompositeOperation='difference';
ctx.fillStyle='white';
ctx.fillRect(0,0,onScreenCanvas.width,onScreenCanvas.height);
//----- (5) 'color-dodge' the results of (2) with it's own inverse (4)
//----- This makes use of 0/0 defined as 0 in this globalCompositeOperation,
//----- so that non-zero (suprathreshold) pixels become 1, zero (sub-threshold) pixels stay zero
//~ ctxOnScreen.globalCompositeOperation='color-dodge';
ctxOnScreen.globalCompositeOperation='color-dodge';
ctxOnScreen.drawImage(drawingCanvas, 0, 0);
}
image.onload = function() {
onScreenCanvas.width = image.width;
onScreenCanvas.height = image.height;
drawingCanvas.width = image.width;
drawingCanvas.height = image.height;
greyscaleImageCanvas.width = image.width;
greyscaleImageCanvas.height = image.height;
//!!NB Doesn't work on chrome for local files, use firefox
//
ctxGreyscaleImage.drawImage(image, 0, 0);
img2grey(ctxGreyscaleImage);
thresholdImage((Math.round(rng.value)).toString(16));
};
var rng = document.querySelector("input");
var listener = function() {
window.requestAnimationFrame(function() {
thresholdImage( (Math.round(rng.value)).toString(16) );
});
};
rng.addEventListener("mousedown", function() {
listener();
rng.addEventListener("mousemove", listener);
});
rng.addEventListener("mouseup", function() {
rng.removeEventListener("mousemove", listener);
});
image.src = "https://i.imgur.com/vN0NbVu.jpg";
.slider-width100 {
width: 255px;
}
<html>
<head>
</head>
<body>
<canvas id="canvasTest"></canvas>
<input class="slider-width100" type="range" min="0" max="254" value="122" />
</body>
</html>