Это можно сделать, используя только globalCompositeOperations, в два этапа.
- Установить все пиксели ниже порогового значения на 0 (черный).
- «Разделить» это изображение, используя алгоритм, который определяет 0/0 = 0
Определить три холста, один на экране, один для удержания изображения в градациях серого за пределами экрана, а другой вне экрана «рабочий» холст.
//--- 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
- шестнадцатеричная строка для порогового значения между 0-FF для каждого из 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, но существует «уклонение цвета», которое делит нижний слой на инвертированный верхний слой. Таким образом, желаемый результат достигается сначала путем создания инвертированной копии выходных данных шага 1, а затем с помощью операции увлечения цветом (которая определяет 0/0 = 0), чтобы «инвертировать» ее перед делением. В результате ненулевые (выше пороговых) пиксели становятся 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);
Этот метод оказывается в 3-5 раз быстрее, чем for-l oop, по крайней мере Chrome 79 для Ma c и android (Huawei 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
// https://stackoverflow.com/questions/45444097/the-canvas-has-been-tainted-by-cross-origin-data-local-image
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>