Canvas Image Animation Gif

ドラッグ&ドロップで追加された画像からアニメーションgifを生成

JavaScript

// 処理手順
// 1. HTML5 ドラッグ&ドロップで画像取得
// 2. canvasに埋め込む
// 3. canvasを変形
// 4. png生成
// 5. 3,4をフレーム風に繰り返し
// 6. pngを集めてアニメーションgifに変換


// HTML5 ドラッグ&ドロップで取得
(function(){
  'use strict';
  var inputFile = document.getElementById('file');
  var dragArea = document.getElementById('dragArea');
  var fileName = 'ani.gif';


  var background = '#FFFFFF';
  var ele_background = document.getElementById('background');
  ele_background.addEventListener('change', function(ev){
    background = ev.target.value;
  });


  // animetionPattern
  // scaleX, scaleY, dpX, dpY, 
  var frames = {
      bound: [
        [1.6 ,2],
        [1  ,  1,  0,  0],
        [1.1,  .9, 0, .2],
        [1.2,  .8, 0, .4],
        [1.6,  .4, 0, .9],
        [1.2,  .8, 0, .7],
        [1  , 1  , 0, .5],
        [ .4, 1.6 ,0,  0],
        [ .8, 1.2, 0, -.4],
        [1.2,  .8, 0, -.6],
        [1.1,  .9, 0, -.4],
      ],
      zoom: [
        [2 ,2],
        [1  , 1  , 0, 0],
        [1.1, 1.1, 0, 0],
        [1.2, 1.2, 0, 0],
        [1.6, 1.6, 0, 0],
        [2  , 2  , 0, 0],
        [3  , 3  , 0, 0],
        [4  , 4  , 0, 0],
        [8  , 8  , 0, 0],
        [ .1,  .1, 0, 0],
        [ .2,  .2, 0, 0],
        [ .5,  .5, 0, 0],
      ],
      beat: [
        [1.1 ,1.1],
        [1   , 1   , 0, 0],
        [1.05, 1.05, 0, 0],
        [1.1 , 1.1 , 0, 0],
        [1.05, 1.05, 0, 0],
        [1   , 1,    0, 0],
        [.95 ,  .95, 0, 0],
        [.9  ,  .9 , 0, 0],
        [.95 ,  .95, 0, 0],
      ],
      momimomi: [
        [1.2 ,1.2],
        [1   , 1  , 0, 0],
        [0.9 , 1.1, 0, 0],
        [0.8 , 1.2, 0, 0],
        [0.9 , 1.1, 0, 0],
        [1   , 1  , 0, 0],
        [1.1 ,  .9, 0, 0],
        [1.2 ,  .8, 0, 0],
        [1.1 ,  .9, 0, 0],
      ],
       marquee: [
        [2 ,1],
        [1  ,  1, 2,  0],
        [1  ,  1, 1,  0],
        [1  ,  1, .9,  0],
        [1  ,  1, .8,  0],
        [1  ,  1, .8,  0],
        [1  ,  1, .7,  0],
        [1  ,  1, .6,  0],
        [1  ,  1, .5,  0],
        [1  ,  1, .4,  0],
        [1  ,  1, .3,  0],
        [1  ,  1, .2,  0],
        [1  ,  1, .1,  0],
        [1  ,  1,  0,  0],
        [1  ,  1, -.1,  0],
        [1  ,  1, -.2,  0],
        [1  ,  1, -.3,  0],
        [1  ,  1, -.4,  0],
        [1  ,  1, -.5,  0],
        [1  ,  1, -.6,  0],
        [1  ,  1, -.7,  0],
        [1  ,  1, -.8,  0],
        [1  ,  1, -.9,  0],
        [1  ,  1, -1,  0],
        [1  ,  1, -1.2,  0],
        [1  ,  1, -1.4,  0],
        [1  ,  1, -1.6,  0],
        [1  ,  1, -1.8,  0],
        [1  ,  1, -2,  0],
      ],
      slideX: [
        [2 ,1],
        [1  ,  1,  0,  0],
        [1  ,  1,-.1,  0],
        [1  ,  1,-.2,  0],
        [1  ,  1,-.4,  0],
        [1  ,  1,-.2,  0],
        [1  ,  1,  0,  0],
        [1  ,  1, .1,  0],
        [1  ,  1, .2,  0],
        [1  ,  1, .4,  0],
        [1  ,  1, .2,  0],
      ],
      moveX: [
        [2 ,1],
        [-1  ,  1,  0,  0],
        [-1  ,  1,-.1,  0],
        [-1  ,  1,-.2,  0],
        [1   ,  1,-.4,  0],
        [1   ,  1,-.2,  0],
        [1   ,  1,  0,  0],
        [1   ,  1, .1,  0],
        [1   ,  1, .2,  0],
        [-1  ,  1, .4,  0],
        [-1  ,  1, .2,  0],
      ],
      kyoroX: [
        [2 ,1],
        [1  ,  1,  0,  0],
        [-1 ,  1,  0,  0],
      ],
      kasakasaX: [
        [2 ,1],
        [1  ,  1, -1,  0],
        [1  ,  1, -.7,  0],
        [1  ,  1, -.8,  0],
        [1  ,  1, -.6,  0],
        [1  ,  1, -.5,  0],
        [1  ,  1, -.3,  0],
        [1  ,  1, -.4,  0],
        [1  ,  1, -.1,  0],
        [1  ,  1, -.2,  0],
        [1  ,  1, .1,  0],
        [1  ,  1,  0,  0],
        [1  ,  1, .3,  0],
        [1  ,  1, .2,  0],
        [1  ,  1, .5,  0],
        [1  ,  1, .4,  0],
        [1  ,  1, .7,  0],
        [1  ,  1, .6,  0],
        [1  ,  1, .9,  0],
        [1  ,  1, .8,  0],
        [1  ,  1, 1,  0],
        [1  ,  1, 2,  0],
      ],
      rocketY: [
        [1, 2],
        [1  ,  1,  0,  0],
        [1  ,  1,  0,  -.1],
        [1  ,  1,  0,  -.2],
        [1  ,  1,  0,  -.4],
        [1  ,  1,  0,  -.8],
        [1  ,  1,  0,  -1.2],
        [1  ,  1,  0,  .9],
        [1  ,  1,  0,  .6],
        [1  ,  1,  0,  .3],
        [1  ,  1,  0,  .1],
      ],
      fallXY: [
        [2, 2],
        [1  ,  1,  2,  -2],
        [1  ,  1,  1.5,  -1.5],
        [1  ,  1,  1,  -1],
        [1  ,  1,  .5,  -.5],
        [1  ,  1,  .3,  -.3],
        [1  ,  1,  .2,  -.2],
        [1  ,  1,  .0,  -.0],
        [1  ,  1,  -.1,  .1],
        [1  ,  1,  -.2,  .2],
        [1  ,  1,  -.5,  .5],
        [1  ,  1,  -.6,  .6],
        [1  ,  1,  -.7,  .7],
        [1  ,  1,  -.8,  .8],
        [1  ,  1,  -.9,  .9],
        [1  ,  1,  -1.0,  1.0],
        [1  ,  1,  -1.2,  1.2],
        [1  ,  1,  -1.4,  1.4],
        [1  ,  1,  -1.6,  1.6],
        [1  ,  1,  -2,  2],
      ],
      tremble: [
        [1 ,1],
        [1  ,  1,  0,  0],
        [1  ,  1,  .05,  0],
        [1  ,  1, -.05,  0],
        [1  ,  1,  .05,  0],
        [1  ,  1, -.05,  0],
        [1  ,  1,  .05,  0],
        [1  ,  1, -.05,  0],
        [1  ,  1,  .05,  0],
        [1  ,  1, -.05,  0],
      ],
      rotate: [
        [1 ,1],
        [1  ,  1,  0,  0, 0],
        [1  ,  1,  0,  0, 30],
        [1  ,  1,  0,  0, 60],
        [1  ,  1,  0,  0, 90],
        [1  ,  1,  0,  0, 120],
        [1  ,  1,  0,  0, 150],
        [1  ,  1,  0,  0, 180],
        [1  ,  1,  0,  0, 210],
        [1  ,  1,  0,  0, 240],
        [1  ,  1,  0,  0, 270],
        [1  ,  1,  0,  0, 300],
        [1  ,  1,  0,  0, 330],
      ],
      rotateAccel: [
        [1 ,1],
        [1  ,  1,  0,  0, 0],
        [1  ,  1,  0,  0, 15],
        [1  ,  1,  0,  0, 45],
        [1  ,  1,  0,  0, 90],
        [1  ,  1,  0,  0, 150],
        [1  ,  1,  0,  0, 225],
        [1  ,  1,  0,  0, 315],
      ],
      rotate90: [ //模様に使える
        [1 ,1],
        [1  ,  1,  0,  0, 0],
        [1  ,  1,  0,  0, 90],
      ],
      blink: [
        [1 ,1],
        [1  ,  1,  0,  0],
        [1  ,  1,  3,  0],
      ],
      round: [
        [2.4 ,2.4],
        [1  ,  1,  .3, -.5],
        [1  ,  1,  .5, -.3],
        [ .8,1.5,  .8,  .0],
        [1  ,  1,  .5,  .3],
        [1  ,  1,  .3,  .5],
        [1.5, .8,  .0,  .8],
        [1  ,  1, -.3,  .5],
        [1  ,  1, -.5,  .3],
        [ .8,1.5, -.8,  .0],
        [1  ,  1, -.5, -.3],
        [1  ,  1, -.3, -.5],
        [1.5, .8,  .0, -.8],
      ],
      boundRotate: [
        [2 ,2],
        [1  ,  1,  0,  0, 0],
        [1.1, 0.9, 0, .2, 45],
        [1.2, 0.8, 0, .4, 90],
        [1.6, 0.4, 0, .9, 135],
        [1.2, 0.8, 0, .7, 180],
        [1  , 1  , 0, .5, 225],
        [0.4, 1.6 ,0,  0, 270],
        [0.8, 1.2, 0, -.4, 300],
        [1.2, 0.8, 0, -.6, 310],
        [1.1, 0.9, 0, -.4, 330],
      ],
  };

  // アニメパターン選択肢
  var labels =[];
  var animetionPattern = 'bound';
  var inputArea = document.getElementById('inputArea');
  for(var key in frames){
    var val = key;
    var label = document.createElement('label');
    var input = document.createElement('input');
    var text = document.createTextNode(val);
    labels.push(label);
    input.setAttribute('type', 'radio');
    input.setAttribute('value', val);
    input.setAttribute('name', 'animetionPattern');
    inputArea.appendChild(label);
    label.appendChild(input);
    label.appendChild(text);
    input.addEventListener('change', function(ev){
      for(var i=0,len=labels.length;i<len;i++){
        labels[i].classList.remove('selected');
      };
      ev.target.parentElement.classList.add('selected');
    });
    if(val === animetionPattern){
      input.checked = true;
      label.classList.add('selected');
    }
  }


  var labels = document.getElementsByTagName('label');
  for(var i=0,len=labels.length;i<len;i++){
    labels[i].addEventLister
  }


  // ファイルアップロード
  dragArea.classList.add('ready');
  dragArea.addEventListener('dragstart', function(event){
    event.dataTranfer.addElement(container);
  });
  dragArea.addEventListener('dragover', function(event){
    event.preventDefault();
    this.classList.add('drag');
  });
  dragArea.addEventListener('dragleave', function(event){
    event.preventDefault();
    this.classList.remove('drag');
  });
  dragArea.addEventListener('drop', function(event){
    event.preventDefault();
    this.classList.remove('drag');
    console.log(event.dataTransfer.files);
    if(!event.dataTransfer.files.length){
      return;
    }
    var file = event.dataTransfer.files[0];
    fileName = file.name.substring(0,12).split('.')[0] || 'name';
    console.log(file);

    var reader = new FileReader();
    reader.readAsDataURL(file)

    reader.addEventListener('load', function(){
      var dataURL = reader.result;
      console.log(dataURL);

      var newImage = new Image();
      newImage.src = dataURL;
      newImage.onload = function(){
        insertCanvas(dataURL, this);
      }

    });
  });

  //ドラッグでなくフォームで受け取る場合
  //http://www.html5rocks.com/ja/tutorials/file/dndfiles/
  inputFile.addEventListener('change', function(event){
    var file = event.target.files[0];
    if (!file.type.match('image.*')){
        return;
    }
    console.log(file);
    var reader = new FileReader();
    reader.onload = function(){
      var dataURL = reader.result;
      console.log(dataURL);

      var newImage = new Image();
      newImage.src = dataURL;
      newImage.onload = function(){
        insertCanvas(dataURL, this);
      }
    }
    reader.readAsDataURL(file);
  });




  //canvas内でアニメーション毎にpng生成
  var insertCanvas = function(dataURL, newImage){
    console.log(newImage.naturalWidth);

    var dataURLDefault = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAABZUlEQVR4Xu3TQREAAAiEQK9/aWvsAxMw4O06ysAommCuINgTFKQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgtpCCYAQynhRQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCcFlIQzACG00IKghnAcFpIQTADGE4LKQhmAMNpIQXBDGA4LaQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgtpCCYAQynhRQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCcFlIQzACG00IKghnAcFpIQTADGE4LKQhmAMNpIQXBDGA4LQQL8oTPAGUY76lBAAAAAElFTkSuQmCC';

    var image = new Image();
    var x,y;
    var dX=0,dY=0;
    var scaleX=1,scaleY=1;
    var canvas = document.getElementById('canvas');
    var ctx;
    var pngArea = document.getElementById('pngArea');
    ctx = canvas.getContext('2d');

    var radioList = document.getElementsByName('animetionPattern');
    for(var i=0,len=radioList.length; i<len; i++){
      if (radioList[i].checked) {
        animetionPattern = radioList[i].value;
        break;
      }
    }

    var scaleCanvas = frames[animetionPattern].shift() || [1, 1];
    fileName = 'ani-' + animetionPattern + '-' + fileName + '.gif';
    console.log(scaleCanvas);


    pngArea.innerHTML = '';
    image.src = dataURL || dataURLDefault;
    image.width = newImage.naturalWidth;
    image.height = newImage.naturalHeight;

    //縮小
    if(image.naturalWidth>800 && image.naturalHeight>800){
      canvas.width = image.width/10;
      canvas.height = image.height/10;
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(image, 0, 0, image.width, image.height,
         0, 0, canvas.width, canvas.height);
      image.src = canvas.toDataURL();
      image.width = canvas.width;
      image.height = canvas.height;
    }


    console.log(image.width, image.height);
    canvas.width = image.width * scaleCanvas[0];
    canvas.height = image.height * scaleCanvas[1];
    ctx = canvas.getContext('2d');
    ctx.fillStyle = background;


    var drawAnimateImage = function(frame){
        if(!frame){return;}
        var scaleX=frame[0], scaleY=frame[1],
        dpX=frame[2], dpY=frame[3], rot=frame[4] || 0;
        var dX = image.width  * dpX;
        var dY = image.height * dpY;
        ctx.translate(0, 0);
        ctx.setTransform(1,0,0,1,0,0);
        ctx.rotate(0);
        //ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillRect(0,0,canvas.width, canvas.height);
        if(rot){
          ctx.translate(canvas.width / 2, canvas.height / 2);
          var rad = rot * Math.PI /180;
          ctx.rotate(rad);
          ctx.translate(-canvas.width / 2, -canvas.height / 2);
        } else {
          ctx.setTransform(scaleX,0,0,scaleY,dX,dY);
        }
        x = (canvas.width  - image.width  * scaleX) / 2 / scaleX;
        y = (canvas.height - image.height * scaleY) / 2 / scaleY;
        ctx.drawImage(image, x, y);
        var png = new Image();
        png.setAttribute('class', 'png');
        pngArea.appendChild(png);
        png.src = canvas.toDataURL();
    }

    console.log([image.width,image.height]);




    for(var i=0,len=frames[animetionPattern].length;i<len;i++){
      drawAnimateImage(frames[animetionPattern][i]);
    }

    //複数画像のnaturalWidthを正常に取得するため、タイマー処理を行う
    setTimeout(function(){
      createGif();
    },1000);
  
  }



  //動画gif生成
  //https://jnordberg.github.io/gif.js/
  var createGif = function(){
    var pngs = document.getElementsByClassName('png') || [];
    var gifArea = document.getElementById('gifArea');

    var width = pngs[0].naturalWidth || 200;
    var height = pngs[0].naturalHeight || 200;
    var quality = $('[name="quality"]').val() || 10;
   
    var gif = new GIF({
      width:width,
      height:height,
      workers: 2,
      quality: quality,
      background: '#ffffff',
      //transparent: '0x00FF00',
    });

    for(var i=0,len=pngs.length;i<len;i++){
      gif.addFrame(pngs[i], {delay: 100});
    }

    gif.on('finished', function(blob) {
      var uri = URL.createObjectURL(blob);
      //window.open(uri, 'ani-gif');
      blobToDataURL(blob, function(dataURL){
        var img = new Image();
        var a = document.createElement('a');
        a.setAttribute('title',fileName);
        a.setAttribute('download',fileName);
        a.setAttribute('href',dataURL);
        img.src= dataURL;
        gifArea.appendChild(a);
        a.appendChild(img);
      });
    });

    var blobToDataURL = function(blob, callback) {
      var fr = new FileReader();
      fr.onload = function(e) {callback(e.target.result);}
      fr.readAsDataURL(blob);
    }
   
    gif.render();
  }





})();


CSS

#dragArea.drag{
  background-color:#ffaaaa;
}


.area{
  min-height:80px;
}

canvas{
  border:1px solid #ffcccc;
}

canvas,
img{
  min-width:100px;
  min-height:100px;
  max-width:200px;
  max-height:200px;
}

input[type="color"],
label{
  display:inline-block;
  padding:6px 12px;
  margin:4px 4px;
  border:1px solid #000000;
  border-radius:4px;
  cursor:pointer;
}

label.selected,
label:hover{
  background-color:#ffaaaa;
}

input[type="radio"]{
  margin:4px;
  display:none;
}

input[type="file"]{
  display:none;
}

#code{
  display:none;
}