2016-05-16 16 views
1

Für Lernzwecke, ich schreibe etwas Code, um ein Bild nach dem Zufallsprinzip Pixel für Pixel in einer Leinwand zu zeichnen. Hier ist die link für die folgenden Codes.Canvas: Zeichnen Bild Pixel für Pixel und requestAnimationFrame Timing

Ich möchte die Animation innerhalb von 1 Sek. Beendet werden. Wie Sie jedoch von der Konsole aus sehen können, dauert es etwa 7 Sekunden. Ich versuche ein kleineres Bild, die Zahl schließt auf 1 Sekunde.

In diesem Fall ist das Timing von requestAnimationFrame nicht zuverlässig. Ich möchte die Ursache wissen. Ist es, weil putImageData nach einem der Pixel in seinem Daten-Array sucht zu viel Zeit braucht? Oder ist es weil etwas anderes? Ich denke, wenn RequestAnimationFrame Timing ist nicht zuverlässig ist entscheidend für gute Animationen.

Darüber hinaus, gibt es einen besseren Weg zu tun, was ich tun möchte?

// At first, I get the image's data, then I build an array whose 
// length is equal to the number of pixels of the image, each element 
// of the array represents one of the image's pixel. Then I shuffle 
// this array and pop a certain number of elements for 
// `ctx.putImageData` to draw the corresponding pixel per frame. 

var ctx = c.getContext("2d"), 
    img = new Image(); 
img.onload = init; 
img.src = "download.png"; //placehold.it's 300x200 image 

function formArr(w,h){ //Build image pixel outputting sequence array based on image's width and height 
    var arr = []; 
    for (i=0;i<w*h;i++){ 
    arr.push(i); 
    } 
    return arr; 
} 

function Point(i,w){ //locate pixel's X and Y base on image width 
    this.x = i%w; 
    this.y = Math.floor(i/w); 
} 

function shuffleRect(arr){ //shuffle the output sequence array 
    .... 
} 

function init(){ 
    var w = ctx.canvas.width = img.width*2; 
    var h = ctx.canvas.height = img.height*2; 
    //form Image Data 
    ctx.drawImage(img,0,0,w,h); 
    var imageData = ctx.getImageData(0,0,w,h); 
    ctx.clearRect(0,0,w,h); 

    //build output sequence 
    var sequence = formArr(w,h); 
    shuffleRect(sequence); 

    var sec = 1; //animation duration 
    var t1 = Date.now(); 
    function animate(){ 
    var pointsPerFrame = Math.floor(w*h/sec/60)+1; 
    for (i=0;i<Math.min(pointsPerFrame,sequence.length);i++){ 
     var j = sequence.pop(); 
     ctx.putImageData(imageData,0,0,new Point(j,w).x,new Point(j,w).y,1,1); //draw points for next frame 

    } 
    if(sequence.length){requestAnimationFrame(animate)}else{ 
     var t2 = Date.now(); 
     console.log(t2-t1); 

    } 
    } 
    animate(); 
} 
+0

Können Sie erstellen stacksnippets oder jsfiddle zu demonstrieren? – guest271314

Antwort

3

die Clipping-Version von drawImage Verwendung wird schneller sein als putImageData

// directly copy from image to canvas 1 pixel at a time 
context.drawImage(image,x,y,1,1,x,y,1,1); 

Aber Compositing mit möglicherweise schneller als putImageData & drawImage ...

Erstellen Sie zunächst eine zweite Leinwand im Speicher die Größe des Bildes.

dann in jeder Animationsschleife:

  • Füllen die erforderliche Anzahl von neuen Zufall Pixeln auf dem zweiten Bildträger: memoryContext.beginPath + memoryContext.rect(x,y,1,1) jedes neues Pixel + memoryContext.fill().
  • Löschen Sie die Hauptansicht.
  • Zeichnen Sie die sekundäre Leinwand auf die Hauptleinwand: drawImage(memoryCanvas,0,0)
  • Set Compositing zu source-atop auf der Haupt Leinwand.
  • drawImage das Bild auf die Hauptleinwand. Beim Compositing wird das Bild nur dort angezeigt, wo die gefüllten Pixel vorhanden sind.
  • Setzen Sie das Compositing auf der Hauptansicht auf source-over zurück.

var canvas=document.getElementById("canvas"); 
 
var ctx=canvas.getContext("2d"); 
 
var canvas2=document.createElement("canvas"); 
 
var ctx2=canvas2.getContext("2d"); 
 
var cw=canvas.width; 
 
var ch=canvas.height; 
 

 
var newPixelCount; 
 
var accumPixelCount=0; 
 
var totPixels; 
 
var img=new Image(); 
 
img.onload=start; 
 
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/annotateMe.jpg"; 
 
function start(){ 
 
    cw=canvas.width=canvas2.width=img.width; 
 
    ch=canvas.height=canvas2.height=img.height; 
 
    newPixelCount=cw*ch/60; 
 
    totPixels=cw*ch; 
 
    t1=performance.now(); 
 
    requestAnimationFrame(animate); 
 
} 
 

 
function animate(){ 
 
    ctx2.beginPath(); 
 
    for(var i=0;i<newPixelCount;i++){ 
 
     accumPixelCount++; 
 
     if(accumPixelCount<totPixels){ 
 
      var y=parseInt(accumPixelCount/cw); 
 
      var x=accumPixelCount-y*cw; 
 
      ctx2.rect(x,y,1,1); 
 
     } 
 
    } 
 
    ctx2.fill(); 
 
    ctx.clearRect(0,0,cw,ch); 
 
    ctx.drawImage(canvas2,0,0); 
 
    ctx.globalCompositeOperation='source-atop'; 
 
    ctx.drawImage(img,0,0); 
 
    ctx.globalCompositeOperation='source-over'; 
 
    // 
 
    if(accumPixelCount<totPixels){ 
 
     requestAnimationFrame(animate); 
 
    }else{ 
 
     alert('Complete: '+parseInt(performance.now()-t1)+'ms'); 
 
    } 
 
} 
 

 
function animateDrawImage(){ 
 
    ctx2.beginPath(); 
 
    for(var i=0;i<newPixelCount;i++){ 
 
     accumPixelCount++; 
 
     if(accumPixelCount<totPixels){ 
 
      var y=parseInt(accumPixelCount/cw); 
 
      var x=accumPixelCount-y*cw; 
 
      ctx.drawImage(img,x,y,1,1,x,y,1,1); 
 
     } 
 
    } 
 
    // 
 
    if(accumPixelCount<totPixels){ 
 
     requestAnimationFrame(animate); 
 
    }else{ 
 
     alert('Complete: '+parseInt(performance.now()-t1)+'ms'); 
 

 
    } 
 
}
<canvas id="canvas" width=300 height=300></canvas>

+1

Wunderbar danke. Eigentlich habe ich schon daran gedacht, Kompositionen zu verwenden, konnte aber keine Möglichkeit finden, die bereits gezeichneten Punkte wieder herzustellen. – shenkwen