[英]JavaScript pixel by pixel canvas manipulation
我正在使用一個簡單的Web應用程序,該應用程序將上傳的圖像的顏色簡化為用戶選擇的調色板。 該腳本可以工作,但是要花很長時間才能遍歷整個圖像(對於大圖像來說,這需要幾分鍾的時間),因此需要更改像素。
最初,我是在寫畫布本身,但是我更改了代碼,以便對ImageData
對象進行更改,並且僅在腳本末尾更新畫布。 但是,這並沒有太大的區別。
// User selects colours:
colours = [[255,45,0], [37,36,32], [110,110,105], [18,96,4]];
function colourDiff(colour1, colour2) {
difference = 0
difference += Math.abs(colour1[0] - colour2[0]);
difference += Math.abs(colour1[1] - colour2[1]);
difference += Math.abs(colour1[2] - colour2[2]);
return(difference);
}
function getPixel(imgData, index) {
return(imgData.data.slice(index*4, index*4+4));
}
function setPixel(imgData, index, pixelData) {
imgData.data.set(pixelData, index*4);
}
data = ctx.getImageData(0,0,canvas.width,canvas.height);
for(i=0; i<(canvas.width*canvas.height); i++) {
pixel = getPixel(data, i);
lowestDiff = 1024;
lowestColour = [0,0,0];
for(colour in colours) {
colour = colours[colour];
difference = colourDiff(colour, pixel);
if(lowestDiff < difference) {
continue;
}
lowestDiff = difference;
lowestColour = colour;
}
console.log(i);
setPixel(data, i, lowestColour);
}
ctx.putImageData(data, 0, 0);
在整個過程中,網站被完全凍結,因此我什至無法顯示進度欄。 有什么方法可以對此進行優化,從而減少時間嗎?
改進的一個問題或選擇顯然是您的slice
函數,該函數將在每次調用它時創建一個新數組,而您不需要這樣做。 我會像這樣更改for循環:
for y in canvas.height {
for x in canvas.width {
//directly alter the canvas' pixels
}
}
無需在每次迭代時對數組進行切片。 (如niklas所述)。
我將遍歷數據數組,而不是遍歷畫布尺寸並直接編輯數組。
for(let i = 0; i < data.length; i+=4) { // i+=4 to step over each r,g,b,a pixel
let pixel = getPixel(data, i);
...
setPixel(data, i, lowestColour);
}
function setPixel(data, i, colour) {
data[i] = colour[0];
data[i+1] = colour[1];
data[i+2] = colour[2];
}
function getPixel(data, i) {
return [data[i], data[i+1], data[i+2]];
}
另外,如果您打開控制台,console.log可以使瀏覽器崩潰。 如果您的圖像是1920 x 1080,那么您將登錄到控制台2,073,600次。
您還可以將所有處理傳遞給Web Worker,以獲得最終的線程性能。 例如。 https://jsfiddle.net/pnmz75xa/
我添加一個答案,因為您使用了非常差的色彩匹配算法。
如果您將每種可能的顏色想象為3D空間中的一個點,那么最好找到一種顏色與另一種顏色的匹配程度最佳。 紅色,綠色和藍色值代表x,y,z坐標。
然后,您可以使用一些基本幾何圖形來定位一種顏色到另一種顏色的距離。
// the two colours as bytes 0-255
const colorDist = (r1, g1, b1, r2, g2, b2) => Math.hypot(r1 - r2, g1 - g2, b1 - b2);
同樣重要的是要注意,通道值0-255是壓縮值,實際強度接近於該值的平方( channelValue ** 2.2
)。 這意味着紅色= 255的強度是紅色= 1的65025倍
以下函數是兩種顏色之間色差的近似值。 避免使用Math.hypot
函數,因為它非常慢。
const pallet = [[1,2,3],[2,10,30]]; // Array of arrays representing rgb byte
// of the colors you are matching
function findClosest(r,g,b) {
var closest;
var dist = Infinity;
r *= r;
g *= g;
b *= b;
for (const col of pallet) {
const d = ((r - col[0] * col[0]) + (g - col[1] * col[1]) + (b - col[2] * col[2])) ** 0.5;
if (d < dist) {
if (d === 0) { // if same then return result
return col;
}
closest = col;
dist = d;
}
}
return closest;
}
至於性能,最好的選擇是通過網絡工作者,或使用webGL實時進行轉換。
如果要簡化操作,以防止代碼阻塞頁面,請使用計時器為頁面騰出空間,將作業切成較小的切片。
該示例使用setTimeout
和performance.now()
進行10ms的切片,讓其他頁面事件和渲染執行該操作。 它返回一個承諾,該承諾將在處理所有像素時解決
function convertBitmap(canvas, maxTime) { // maxTime in ms (1/1000 second)
return new Promise(allDone => {
const ctx = canvas.getContext("2d");
const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = pixels.data;
var idx = data.length / 4;
processPixels(); // start processing
function processPixels() {
const time = performance.now();
while (idx-- > 0) {
if (idx % 1024) { // check time every 1024 pixels
if (performance.now() - time > maxTime) {
setTimeout(processPixels, 0);
idx++;
return;
}
}
let i = idx * 4;
const col = findClosest(data[i], data[i + 1], data[i + 2]);
data[i++] = col[0];
data[i++] = col[1];
data[i] = col[2];
}
ctx.putImageData(pixels, 0, 0);
allDone("Pixels processed");
}
});
}
// process pixels in 10ms slices.
convertBitmap(myCanvas, 10).then(mess => console.log(mess));
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.