在画布上加扰图像产生空白

时间:2017-07-16 00:30:07

标签: javascript animation canvas

我从一个canvas元素开始。我让左半边是红色,右边是蓝色。每半秒,setInterval调用一个函数,即扰乱,它将RHS和LHS分成几部分,并将它们混洗。

这是一个小提琴:https://jsfiddle.net/aeq1g3yb/

代码如下。我使用window.onload的原因是因为这个东西应该扰乱图片,我希望首先加载图片。我正在使用颜色 here 因为我不太了解的跨领域业务,所以这是我的住所。

var n = 1;
var v = 1;

function scramble() {

  //get the canvas and change its width
  var c = document.getElementById("myCanvas");
  c.width = 600;
  var ctx = c.getContext("2d");

  //drawing 2 different colors side by side
  ctx.fillStyle = "red";
  ctx.fillRect(0, 0, c.width/2, c.height);
  ctx.fillStyle = "blue";
  ctx.fillRect(c.width/2, 0, c.width/2, c.height);

  //how big will each shuffled chunk be
  var stepsA = (c.width/2) / n;
  var stepsB = (c.width/2) / n;
  var step = stepsA + stepsB;

  var imgDataA = [];
  var imgDataB = [];

  for (var i = 0; i < n; i++) {
    var imgDataElementA = ctx.getImageData(stepsA*i, 0, stepsA, c.height);
    var imgDataElementB = ctx.getImageData(c.width/2+stepsB*i, 0, stepsB, c.height);
    imgDataA.push(imgDataElementA);
    imgDataB.push(imgDataElementB);
  }

  //clearing out the canvas before laying on the new stuff
  ctx.fillStyle = "white";
  ctx.fillRect(0, 0, c.width, c.height);

  //put the images back
  for (var i = 0; i < n; i++) {
    ctx.putImageData(imgDataA[i], step*i, 0);
    ctx.putImageData(imgDataB[i], step*i+stepsA, 0);
  }

  //gonna count the steps
  var count = document.getElementById("count");
  count.innerHTML = n;

  n += v;

  if (n >= 100 || n <= 1) {
    v *= -1;
  }

}; //closing function scramble

window.onload = function() { //gotta do this bc code executes before image loads
  scramble();
};

window.setInterval(scramble, 500);

或多或少,这个东西按照我想要的方式工作。但有一个问题:有时候会有垂直的白线。

我的问题是:

为什么会出现白线?如果您查看小提琴,您将看到这会损害随机播放效果的程度。

1 个答案:

答案 0 :(得分:1)

你不能划分像素

问题可以解决,但会引入一些其他的工件,因为你不能将整数像素分成几部分。

快速解决方案

现有代码的以下解决方案向下舍入以显示部分的开头,向上舍入为宽度。

  for (var i = 0; i < n; i++) {
    var imgDataElementA = ctx.getImageData(
        Math.floor(stepsA * i), 0, 
        Math.ceil(stepsA + stepsA * i)  - Math.floor(stepsA * i), c.height
    );
    var imgDataElementB = ctx.getImageData(
        Math.floor(c.width / 2 + stepsB * i), 0, 
        Math.ceil(c.width / 2 + stepsB * i + stepsB) - Math.floor(c.width / 2 + stepsB * i), c.height);
    imgDataA.push(imgDataElementA);
    imgDataB.push(imgDataElementB);
  }

更快的选项

但是通过像素图像数据执行此操作是您可以找到的最慢的方法。您可以使用2D context.imageDraw函数为您进行移动。或者,如果你想要在性能方面做得最好,那么片段着色器作为并行解决方案为你加扰时,WebGL解决方案将是最好的。

没有完美的解决方案

但最终你不能将一个像素切成两半,有很多方法可以尝试解决这个问题,但每种方法都有自己的工件。理想情况下,如果规则image.width % slices === 0在所有其他情况下您将有一个或多个不适合整数像素的切片,则应该只对图像进行切片。

4种舍入方法的例子。

该演示展示了4种不同的方法和2种颜色。将鼠标移到查看更近的视图。每种方法都用白线水平分开。按住鼠标按钮可增加切片计数器。

顶部是您的原件。 接下来的三种处理分数像素宽度的方法有三种。

const mouse  = {x : 0, y : 0, button : false}
function mouseEvents(e){
    const m = mouse;
    if(m.element){
    	m.bounds = m.element.getBoundingClientRect();
    	m.x = e.pageX - m.bounds.left - scrollX;
    	m.y = e.pageY - m.bounds.top - scrollY;
    	m.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : m.button;
    }
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));

const counterElement = document.getElementById("count");

// get constants for the demo
const c = document.getElementById("myCanvas");
mouse.element = c;
// The image with the blue and red
const img = document.createElement("canvas");
// the zoom image overlay
const zoom = document.createElement("canvas");
// the scrambled image
const scram = document.createElement("canvas");
// Set sizes and get context
const w = scram.width = zoom.width = img.width = c.width = 500;
const h = scram.height = zoom.height = img.height = c.height;
const dCtx = c.getContext("2d");  // display context
const iCtx = img.getContext("2d");  // source image context
const zCtx = zoom.getContext("2d"); // zoom context
const sCtx = scram.getContext("2d"); // scrambled context

// some constants
const zoomAmount = 4;
const zoomRadius = 60;
const framesToStep = 10;

function createTestPattern(ctx){
  ctx.fillStyle = "red";
  ctx.fillRect(0, 0, c.width/2, c.height/2);
  ctx.fillStyle = "blue";
  ctx.fillRect(c.width/2, 0, c.width/2, c.height/2);
  ctx.fillStyle = "black";
  ctx.fillRect(0, c.height/2, c.width/2, c.height/2);
  ctx.fillStyle = "#CCC";
  ctx.fillRect(c.width/2, c.height/2, c.width/2, c.height/2);
}
createTestPattern(iCtx);
sCtx.drawImage(iCtx.canvas, 0, 0);
// Shows a zoom area so that blind men like me can see what is going on.
function showMouseZoom(src,dest,zoom = zoomAmount,radius = zoomRadius){
  dest.clearRect(0,0,w,h);
  dest.imageSmoothingEnabled = false;
  if(mouse.x >= 0 && mouse.y >= 0 && mouse.x < w && mouse.y < h){
      dest.setTransform(zoom,0,0,zoom,mouse.x,mouse.y)
      dest.drawImage(src.canvas, -mouse.x, -mouse.y);
      dest.setTransform(1,0,0,1,0,0);
      dest.globalCompositeOperation = "destination-in";
      dest.beginPath();
      dest.arc(mouse.x,mouse.y,radius,0,Math.PI * 2);
      dest.fill();
      dest.globalCompositeOperation = "source-over";
      dest.lineWidth = 4;
      dest.strokeStyle = "black";
      dest.stroke();
  }
  
}


function scramble(src,dest,y,height) {
 const w = src.canvas.width;
  const h = src.canvas.height;
  const steps = (w/2) / slices;
  dest.fillStyle = "white";
  dest.fillRect(0, y, w, height);

  for (var i = 0; i < slices * 2; i++) {      
      dest.drawImage(src.canvas,
          ((i / 2) | 0) * steps + (i % 2) * (w / 2)- 0.5, y,
          steps + 1, height,
          i * steps - 0.5, y,
          steps+ 1, height
      );
  }
}
function scrambleFloor(src,dest,y,height) {
 const w = src.canvas.width;
  const h = src.canvas.height;
  const steps = (w/2) / slices;
  dest.fillStyle = "white";
  dest.fillRect(0, y, w, height);

  for (var i = 0; i < slices * 2; i++) {      
      dest.drawImage(src.canvas,
          (((i / 2) | 0) * steps + (i % 2) * (w / 2)- 0.5) | 0, y,
          steps + 1, height,
          (i * steps - 0.5) | 0, y,
          steps + 1, height
      );
  }
}
function scrambleNoOverlap(src,dest,y,height) {
  const w = src.canvas.width;
  const h = src.canvas.height;
  const steps = (w / 2) / slices;
  dest.fillStyle = "white";
  dest.fillRect(0, y, w, height);

  for (var i = 0; i < slices * 2; i++) {      
      dest.drawImage(src.canvas,
          ((i / 2) | 0) * steps + (i % 2) * (w / 2), y,
          steps, height,
          i * steps - 0.5, y,
          steps, height
      );
  }
}


function scrambleOriginal(src,dest,y,height) {
  const w = src.canvas.width;
  const h = src.canvas.height;

  //how big will each shuffled chunk be
  var stepsA = (w/2) / slices;
  var stepsB = (w/2) / slices;
  var step = stepsA + stepsB;

  var imgDataA = [];
  var imgDataB = [];
   
  for (var i = 0; i < slices; i++) {
    var imgDataElementA = src.getImageData(stepsA*i, y, stepsA, height);
    var imgDataElementB = src.getImageData(w/2+stepsB*i, y, stepsB, height);
    imgDataA.push(imgDataElementA);
    imgDataB.push(imgDataElementB);
  }

  //clearing out the canvas before laying on the new stuff
  dest.fillStyle = "white";
  dest.fillRect(0, y, w, height);

  //put the images back
  for (var i = 0; i < slices; i++) {
    dest.putImageData(imgDataA[i], step*i, y);
    dest.putImageData(imgDataB[i], step*i+stepsA, y);
  }



}; //closing function scramble



const scrambleMethods = [scrambleOriginal,scramble,scrambleFloor,scrambleNoOverlap];


var frameCount = 0;
var sliceStep = 1;
var slices = 1;
function mainLoop(){
    if(mouse.button){
        if(frameCount++ % framesToStep === framesToStep-1){  // every 30 Frames
            slices += sliceStep;
            if(slices > 150 || slices < 2){ sliceStep = -sliceStep }
            counterElement.textContent = slices; // Prevent reflow  by using textContent
            sCtx.clearRect(0,0,w,h);
            sCtx.imageSmoothingEnabled = true;
            const len = scrambleMethods.length;
            for(var i = 0; i < len; i ++){
                scrambleMethods[i](iCtx,sCtx,(128/len) * i, 128/len-2);
                scrambleMethods[i](iCtx,sCtx,(128/len) * i + 128, 128/len-2);
            }
        }
    }
    dCtx.fillStyle = "white";
    dCtx.fillRect(0,0,w,h);

    dCtx.drawImage(sCtx.canvas,0,0);
    showMouseZoom(dCtx,zCtx);
    dCtx.drawImage(zCtx.canvas,0,0);
    requestAnimationFrame(mainLoop);

}
//scramble(iCtx,sCtx);
requestAnimationFrame(mainLoop);
canvas {
  border: 1px solid black;
}
#count {
  position : absolute;
  top : 0px;
  left : 10px;
  font-family: monospace;
  font-size: 20px;
}
<canvas id="myCanvas" height = "256" title="Hold mouse button to chance slice count"></canvas>

<p id="count"></p>