Animated GIF on Fabric.js Canvas

2020-02-09 18:20发布

问题:

I'm working on a project where I've been asked to support animated GIF on a fabric.js canvas.

As per https://github.com/kangax/fabric.js/issues/560, I've followed the advice to render on regular intervals, using fabric.util.requestAnimFrame. Video renders just fine with this method, but GIFs don't seem to update.

var canvas = new fabric.StaticCanvas(document.getElementById('stage'));

fabric.util.requestAnimFrame(function render() {
    canvas.renderAll();
    fabric.util.requestAnimFrame(render);
});

var myGif = document.createElement('img');
myGif.src = 'http://i.stack.imgur.com/e8nZC.gif';

if(myGif.height > 0){
    addImgToCanvas(myGif);
} else {
    myGif.onload = function(){
        addImgToCanvas(myGif);
    }
}

function addImgToCanvas(imgToAdd){
    var obj = new fabric.Image(imgToAdd, {
        left: 105,
        top: 30,
        crossOrigin: 'anonymous',
        height: 100,
        width:100
    }); 
    canvas.add(obj);
}

JSFiddle here: http://jsfiddle.net/phoenixrizin/o359o11f/

Any advice will be greatly appreciated! I've been searching everywhere, but haven't found a working solution.

回答1:

According to specs about the Canvas 2DRenderingContext drawImage method,

Specifically, when a CanvasImageSource object represents an animated image in an HTMLImageElement, the user agent must use the default image of the animation (the one that the format defines is to be used when animation is not supported or is disabled), or, if there is no such image, the first frame of the animation, when rendering the image for CanvasRenderingContext2D APIs.

This means that only the first frame of our animated canvas will be drawn on the canvas.
This is because we don't have any control on animations inside an img tag.

And fabricjs is based on canvas API and thus regulated by the same rules.

The solution is then to parse all the still-images from your animated gif and to export it as a sprite-sheet. You can then easily animate it in fabricjs thanks to the sprite class.



回答2:

var canvas = new fabric.Canvas(document.getElementById('stage'));
var url = 'https://themadcreator.github.io/gifler/assets/gif/run.gif';
fabric.Image.fromURL(url, function(img) {
  img.scaleToWidth(80);
  img.scaleToHeight(80);
  img.left = 105;
  img.top = 30;
  gif(url, function(frames, delay) {
    var framesIndex = 0,
      animInterval;
    img.dirty = true;
    img._render = function(ctx) {
      ctx.drawImage(frames[framesIndex], -this.width / 2, -this.height / 2, this.width, this.height);
    }
    img.play = function() {
      if (typeof(animInterval) === 'undefined') {
        animInterval = setInterval(function() {
          framesIndex++;
          if (framesIndex === frames.length) {
            framesIndex = 0;
          }
        }, delay);
      }
    }
    img.stop = function() {
      clearInterval(animInterval);
      animInterval = undefined;
    }
    img.play();
    canvas.add(img);
  })

})


function gif(url, callback) {

  var tempCanvas = document.createElement('canvas');
  var tempCtx = tempCanvas.getContext('2d');

  var gifCanvas = document.createElement('canvas');
  var gifCtx = gifCanvas.getContext('2d');

  var imgs = [];


  var xhr = new XMLHttpRequest();
  xhr.open('get', url, true);
  xhr.responseType = 'arraybuffer';
  xhr.onload = function() {
    var tempBitmap = {};
    tempBitmap.url = url;
    var arrayBuffer = xhr.response;
    if (arrayBuffer) {
      var gif = new GIF(arrayBuffer);
      var frames = gif.decompressFrames(true);
      gifCanvas.width = frames[0].dims.width;
      gifCanvas.height = frames[0].dims.height;

      for (var i = 0; i < frames.length; i++) {
        createFrame(frames[i]);
      }
      callback(imgs, frames[0].delay);
    }

  }
  xhr.send(null);

  var disposalType;

  function createFrame(frame) {
    if (!disposalType) {
      disposalType = frame.disposalType;
    }

    var dims = frame.dims;

    tempCanvas.width = dims.width;
    tempCanvas.height = dims.height;
    var frameImageData = tempCtx.createImageData(dims.width, dims.height);

    frameImageData.data.set(frame.patch);

    if (disposalType !== 1) {
      gifCtx.clearRect(0, 0, gifCanvas.width, gifCanvas.height);
    }

    tempCtx.putImageData(frameImageData, 0, 0);
    gifCtx.drawImage(tempCanvas, dims.left, dims.top);
    var dataURL = gifCanvas.toDataURL('image/png');
    var tempImg = fabric.util.createImage();
    tempImg.src = dataURL;
    imgs.push(tempImg);
  }
}
render()

function render() {
  if (canvas) {
    canvas.renderAll();
  }

  fabric.util.requestAnimFrame(render);
}
#stage {
  border: solid 1px #CCCCCC;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.13/fabric.min.js"></script>
<script src="http://matt-way.github.io/gifuct-js/bower_components/gifuct-js/dist/gifuct-js.js"></script>
<canvas id="stage" height="160" width="320"></canvas>