我的工具中有这个像素化算法,但是当我今天将它应用到我的绘图应用程序时,它的性能非常糟糕。我想知道你是否可以帮助我。
这是我的算法:
//apply pixalate algorithm
for(var x = 1; x < w; x += aOptions.blockSize)
{
for(var y = 1; y < h; y += aOptions.blockSize)
{
var pixel = sctx.getImageData(x, y, 1, 1);
dctx.fillStyle = "rgb("+pixel.data[0]+","+pixel.data[1]+","+pixel.data[2]+")";
dctx.fillRect(x, y, x + aOptions.blockSize - 1, y + aOptions.blockSize - 1);
}
}
我想知道你是否可以帮助我加快速度,我不确定是什么造成这种性能上升。
(是的,我知道imageSmoothingEnabled techqnique,但它不能让我完全控制块大小)
答案 0 :(得分:4)
GPU可以做到这一点..
为什么不使用GPU来执行drawImage调用,它更快。在像素化中以所需像素数绘制较小的源画布,然后将其绘制到缩放后的源。您只需要确保手动关闭图像平滑或像素化效果不起作用。
无透明像素
对于没有透明像素的画布,它是一个更简单的解决方案,以下函数将执行此操作。要将sctx
作为源的画布进行像素化,dctx
是目标,pixelCount
是目标x轴中的像素块数。你得到filter
选项作为奖励,因为GPU正在为你做出艰苦的工作。
请注意,您应检查2D上下文imageSmoothingEnabled
的供应商前缀,并将其添加到您打算支持的浏览器中。
// pixelate where
// sctx is source context
// dctx is destination context
// pixelCount is the number of pixel blocks across in the destination
// filter boolean if true then pixel blocks as bilinear interpolation of all pixels involved
// if false then pixel blocks are nearest neighbour of pixel in center of block
function pixelate(sctx, dctx, pixelCount, filter){
var sw = sctx.canvas.width;
var sh = sctx.canvas.height;
var dw = dctx.canvas.width;
var dh = dctx.canvas.height;
var downScale = pixelCount / sw; // get the scale reduction
var pixH = Math.floor(sh * downScale); // get pixel y axis count
// clear destination
dctx.clearRect(0, 0, dw, dh);
// set the filter mode
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = filter;
// scale image down;
dctx.drawImage(sctx.canvas, 0, 0, pixelCount, pixH);
// scale image back up
// IMPORTANT for this to work you must turn off smoothing
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = false;
dctx.drawImage(dctx.canvas, 0, 0, pixelCount, pixH, 0, 0, dw, dh);
// restore smoothing assuming it was on.
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = true;
//all done
}
透明像素
如果您有透明像素,则需要工作画布来保持缩小的图像。为此,添加workCanvas
参数并返回相同的参数。它会为你创建一个画布,并在需要时调整它的大小,但是你也应该保留它的副本,这样你就不必在每次像素化时不必要地创建一个新画面
function pixelate(sctx, dctx, pixelCount, filter, workCanvas){
var sw = sctx.canvas.width;
var sh = sctx.canvas.height;
var dw = dctx.canvas.width;
var dh = dctx.canvas.height;
var downScale = pixelCount / sw; // get the scale reduction
var pixH = Math.floor(sh * downScale); // get pixel y axis count
// create a work canvas if needed
if(workCanvas === undefined){
workCanvas = document.createElement("canvas");
workCanvas.width = pixelCount ;
workCanvas.height = pixH;
workCanvas.ctx = workCanvas.getContext("2d");
}else // resize if needed
if(workCanvas.width !== pixelCount || workCanvas.height !== pixH){
workCanvas.width = pixelCount ;
workCanvas.height = pixH;
workCanvas.ctx = workCanvas.getContext("2d");
}
// clear the workcanvas
workCanvas.ctx.clearRect(0, 0, pixelCount, pixH);
// set the filter mode Note the prefix, and I have been told same goes for IE
workCanvas.ctx.mozImageSmoothingEnabled = workCanvas.ctx.imageSmoothingEnabled = filter;
// scale image down;
workCanvas.ctx.drawImage(sctx.canvas, 0, 0, pixelCount, pixH);
// clear the destination
dctx.clearRect(0,0,dw,dh);
// scale image back up
// IMPORTANT for this to work you must turn off smoothing
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = false;
dctx.drawImage(workCanvas, 0, 0, pixelCount, pixH, 0, 0, dw, dh);
// restore smoothing assuming it was on.
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = true;
//all done
return workCanvas; // Return the canvas reference so there is no need to recreate next time
}
要使用透明版本,您需要保存workCanvas referance,否则每次都需要创建一个新版本。
var pixelateWC; // create and leave undefined in the app global scope
// (does not have to be JS context global) just of your app
// so you dont loss it between renders.
然后在你的主循环中
pixelateWC = pixelate(sourceCtx,destContext,20,true, pixelateWC);
因此该函数将在第一次创建它,然后将反复使用它,直到它需要更改其大小或删除它
pixelateWC = undefined;
演示
我已经包含了一个演示(因为我的原始版本有一个错误),以确保它一切正常。显示函数的透明版本。工作得很好全屏60fp我做整个画布而不仅仅是分割部分。演示中的说明。
// adapted from QuickRunJS environment.
// simple mouse
var mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, buttonRaw : 0,
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup".split(",")
};
function mouseMove(e) {
var t = e.type, m = mouse;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
} else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];}
e.preventDefault();
}
mouse.start = function(element, blockContextMenu){
if(mouse.element !== undefined){ mouse.removeMouse();}
mouse.element = element;
mouse.mouseEvents.forEach(n => { element.addEventListener(n, mouseMove); } );
if(blockContextMenu === true){
element.addEventListener("contextmenu", preventDefault, false);
mouse.contextMenuBlocked = true;
}
}
mouse.remove = function(){
if(mouse.element !== undefined){
mouse.mouseEvents.forEach(n => { mouse.element.removeEventListener(n, mouseMove); } );
if(mouse.contextMenuBlocked === true){ mouse.element.removeEventListener("contextmenu", preventDefault);}
mouse.contextMenuBlocked = undefined;
mouse.element = undefined;
}
}
return mouse;
})();
// delete needed for my QuickRunJS environment
function removeCanvas(){
if(canvas !== undefined){
document.body.removeChild(canvas);
}
canvas = undefined;
canvasB = undefined;
canvasP = undefined;
}
// create onscreen, background, and pixelate canvas
function createCanvas(){
canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 1000;
document.body.appendChild(canvas);
canvasP = document.createElement("canvas");
canvasB = document.createElement("canvas");
}
function resizeCanvas(){
if(canvas === undefined){ createCanvas(); }
canvasB.width = canvasP.width = canvas.width = window.innerWidth;
canvasB.height = canvasP.height = canvas.height = window.innerHeight;
canvasB.ctx = canvasB.getContext("2d");
canvasP.ctx = canvasP.getContext("2d");
canvas.ctx = canvas.getContext("2d");
// lazy coder bug fix
joinPos = Math.floor(window.innerWidth/2);
}
function pixelate(sctx, dctx, pixelCount, filter, workCanvas){
var sw = sctx.canvas.width;
var sh = sctx.canvas.height;
var dw = dctx.canvas.width;
var dh = dctx.canvas.height;
var downScale = pixelCount / sw; // get the scale reduction
var pixH = Math.floor(sh * downScale); // get pixel y axis count
// create a work canvas if needed
if(workCanvas === undefined){
workCanvas = document.createElement("canvas");
workCanvas.width = pixelCount;
workCanvas.height = pixH;
workCanvas.ctx = workCanvas.getContext("2d");
}else // resize if needed
if(workCanvas.width !== pixelCount || workCanvas.height !== pixH){
workCanvas.width = pixelCount;
workCanvas.height = pixH;
workCanvas.ctx = workCanvas.getContext("2d");
}
// clear the workcanvas
workCanvas.ctx.clearRect(0, 0, pixelCount, pixH);
// set the filter mode
workCanvas.ctx.mozImageSmoothingEnabled = workCanvas.ctx.imageSmoothingEnabled = filter;
// scale image down;
workCanvas.ctx.drawImage(sctx.canvas, 0, 0, pixelCount, pixH);
// clear the destination
dctx.clearRect(0,0,dw,dh);
// scale image back up
// IMPORTANT for this to work you must turn off smoothing
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = false;
dctx.drawImage(workCanvas, 0, 0, pixelCount, pixH, 0, 0, dw, dh);
// restore smoothing assuming it was on.
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = true;
//all done
return workCanvas; // Return the canvas reference so there is no need to recreate next time
}
var canvas,canvaP, canvasB;
// create and size canvas
resizeCanvas();
// start mouse listening to canvas
mouse.start(canvas,true); // flag that context needs to be blocked
// listen to resize
window.addEventListener("resize",resizeCanvas);
// get some colours
const NUM_COLOURS = 10;
var currentCol = 0;
var cols = (function(count){
for(var i = 0, a = []; i < count; i ++){
a.push("hsl("+Math.floor((i/count)*360)+",100%,50%)");
}
return a;
})(NUM_COLOURS);
var holdExit = 0; // To stop in QuickRunJS environment
var workCanvas;
var joinPos = Math.floor(canvas.width / 2);
var mouseOverJoin = false;
var dragJoin = false;
var drawing = false;
var filterChange = 0;
var filter = true;
ctx = canvas.ctx;
function update(time){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(canvasB, 0, 0, joinPos, canvas.height, 0 , 0, joinPos, canvas.height); // draw background
ctx.drawImage(canvasP,
joinPos, 0,
canvas.width - joinPos, canvas.height,
joinPos, 0,
canvas.width - joinPos, canvas.height
); // pixilation background
if(dragJoin){
if(!(mouse.buttonRaw & 1)){
dragJoin = false;
canvas.style.cursor = "default";
if(filterChange < 20){
filter = !filter;
}
}else{
joinPos = mouse.x;
}
filterChange += 1;
mouseOverJoin = true;
}else{
if(Math.abs(mouse.x - joinPos) < 4 && ! drawing){
if(mouse.buttonRaw & 1){
canvas.style.cursor = "none";
dragJoin = true;
filterChange = 0;
}else{
canvas.style.cursor = "e-resize";
}
mouseOverJoin = true;
}else{
canvas.style.cursor = "default";
mouseOverJoin = false;
if(mouse.buttonRaw & 1){
canvasB.ctx.fillStyle = cols[currentCol % NUM_COLOURS];
canvasB.ctx.beginPath();
canvasB.ctx.arc(mouse.x, mouse.y, 20, 0, Math.PI * 2);
canvasB.ctx.fill();
drawing = true
}else{
drawing = false;
}
}
ctx.fillStyle = cols[currentCol % NUM_COLOURS];
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, 20, 0, Math.PI * 2);
ctx.fill();
}
if(mouse.buttonRaw & 4){ // setp cols on right button
currentCol += 1;
}
if(mouse.buttonRaw & 2){ // setp cols on right button
canvasB.ctx.clearRect(0, 0, canvas.width, canvas.height);
holdExit += 1;
}else{
holdExit = 0;
}
workCanvas = pixelate(canvasB.ctx, canvasP.ctx, 30, filter, workCanvas);
ctx.strokeStyle = "rgba(0,0,0,0.5)";
if(mouseOverJoin){
ctx.fillStyle = "rgba(0,255,0,0.5)";
ctx.fillRect(joinPos - 3, 0, 6, canvas.height);
ctx.fillRect(joinPos - 2, 0, 4, canvas.height);
ctx.fillRect(joinPos - 1, 0, 2, canvas.height);
}
ctx.strokeRect(joinPos - 3, 0, 6, canvas.height);
ctx.font = "18px arial";
ctx.textAlign = "left";
ctx.fillStyle = "black"
ctx.fillText("Normal canvas.", 10, 20)
ctx.textAlign = "right";
ctx.fillText("Pixelated canvas.", canvas.width - 10, 20);
ctx.textAlign = "center";
ctx.fillText("Click drag to move join.", joinPos, canvas.height - 62);
ctx.fillText("Click to change Filter : " + (filter ? "Bilinear" : "Nearest"), joinPos, canvas.height - 40);
ctx.fillText("Click drag to draw.", canvas.width / 2, 20);
ctx.fillText("Right click change colour.", canvas.width / 2, 42);
ctx.fillText("Middle click to clear.", canvas.width / 2, 64);
if(holdExit < 60){
requestAnimationFrame(update);
}else{
removeCanvas();
}
}
requestAnimationFrame(update);
&#13;
答案 1 :(得分:2)
为每个像素获取一个ImageData
对象,然后构造一个颜色字符串,该字符串必须由canvas对象再次解析,然后使用画布填充例程,该例程必须遍历所有画布设置(转换矩阵,组成等。)
你似乎也画出了比他们需要的更大的矩形:fillRect
的第三和第四个参数是宽度和高度,而不是x
和y
协调的右下角。你为什么从像素1开始,而不是从零开始?
对像素操作进行原始数据操作通常要快得多。将整个图像作为图像数据获取,对其进行操作并最终将其放在目标画布上:
var idata = sctx.getImageData(0, 0, w, h);
var data = idata.data;
var wmax = ((w / blockSize) | 0) * blockSize;
var wrest = w - wmax;
var hmax = ((h / blockSize) | 0) * blockSize;
var hrest = h - hmax;
var hh = blockSize;
for (var y = 0; y < h; y += blockSize) {
var ww = blockSize;
if (y == hmax) hh = hrest;
for (var x = 0; x < w; x += blockSize) {
var n = 4 * (w * y + x);
var r = data[n];
var g = data[n + 1];
var b = data[n + 2];
var a = data[n + 3];
if (x == wmax) ww = wrest;
for (var j = 0; j < hh; j++) {
var m = n + 4 * (w * j);
for (var i = 0; i < ww; i++) {
data[m++] = r;
data[m++] = g;
data[m++] = b;
data[m++] = a;
}
}
}
}
dctx.putImageData(idata, 0, 0);
在我的浏览器中,即使有两个额外的内部循环,它也会更快。