How would I animate the process of a transformed canvas image going back into its original state?

跟風遠走 提交于 2020-01-11 13:33:48

问题


I am at a transform of (1, 0, 0, -0.7, 0.4, 320, 70) and I want to gradually end up at (1, 0, 0, 1, 0, 0), how do I do this?

This is the code that transforms the images:

 document.addEventListener("DOMContentLoaded", function(event) {
        image = new Image();
        image2 = new Image();
        image3 = new Image();
        image4 = new Image();
        window.onload = function() {
            //first image
            var width = image.width,
            height = image.height;
            canvas1 = document.getElementById("num1Canvas");
            bottomSlice = canvas1.getContext("2d");
            //second image
            var width2 = image2.width,
            height2 = image2.height;
            canvas2 = document.getElementById("num2Canvas");
            topSlice = canvas2.getContext("2d");
            //third image
            newCanvas1 = document.getElementById("newNum1Canvas");
            newBottomSlice = newCanvas1.getContext("2d");
            //fourth image
            newCanvas2 = document.getElementById("newNum2Canvas");
            newTopSlice = newCanvas2.getContext("2d");

            for (var i = 0; i <= height / 2; ++i) {
                //first image transform
                bottomSlice.setTransform(1, 0, -0.7, .4, 320, 70);
                bottomSlice.drawImage(image,
                    0, height / 2 - i, width, 2,
                    0, height / 2 - i, width, 2);
                bottomSlice.setTransform(1, 0, -0.7, 0.4, 320, 70);
                bottomSlice.drawImage(image,
                    0, height / 2 + i, width, 2,
                    0, height / 2 + i, width, 2);
                //second image transform 
                topSlice.setTransform(1, 0, -0.7, .4, 320, 0.2);
                topSlice.drawImage(image2,
                    0, height2 / 2 - i, width2, 2,
                    0, height2 / 2 - i, width2, 2);
                topSlice.setTransform(1, 0, -0.7, 0.4, 320, 0.2);
                topSlice.drawImage(image2,
                    0, height2 / 2 + i, width2, 2,
                    0, height2 / 2 + i, width2, 2);
            }

        };
        image.src = "bottom.png";
        image2.src = "top.png";
        image3.src = "bottom.png";//
        image4.src ="top.png";
    });

And I basically want something like this to occur:

  function b(){
      bottomSlice.clearRect(0, 0, canvas1.width, canvas1.height+60);
      bottomSlice.setTransform(1, 0, -0.6, 0.3, 200, 40);
      bottomSlice.drawImage(image, 0, 0);

      bottomSlice.clearRect(0, 0, canvas1.width, canvas1.height+60);
      bottomSlice.setTransform(1, 0, -0.4, 0.6, 150, 30);
      bottomSlice.drawImage(image, 0, 0);

      bottomSlice.clearRect(0, 0, canvas1.width, canvas1.height+60);
      bottomSlice.setTransform(1, 0, -0.1, 0.8, 100, 20);
      bottomSlice.drawImage(image, 0, 0);

      bottomSlice.clearRect(0, 0, canvas1.width, canvas1.height+60);
      bottomSlice.setTransform(1, 0, 0, 1, 0, 0);
      bottomSlice.drawImage(image, 0, 0);
    }

However, the code above doesn't work and is hard coded, which isn't what I want. I was thinking about somehow using a setTimeout kind of thing to do this but i'm not sure how I would go about it.


回答1:


Tweening.

Most animations involve key frames. At its most simple you start at one state and over time you progress to the next.

Simple tweening (AKA lerp)

For example we have some key frames with a value and a time.

var keys[  // time in seconds
   {value : 10, time : 0},  
   {value : 20, time : 30},
}

At some time we want what the value should be. So ignoring times outside the range we can write a simple function that gets the value for a given time. It first converts the time to a normalised time (0 to 1) between the keys, where 0 is time at first key and 1 is time at second key.

function lerpKeys(time, fromKey, toKey){
    var relativeTime = time - fromKey.time;  
    var timeDiferance  = toKey.time - fromKey.time;
    var normalisedTime = relativeTime / timeDiferance;
    var valueDiferance = toKey.value - fromKey.value; 
    var currentValue = valueDiferance * normalisedTime + fromKey.value;
    return currentValue;
}

That is the detailed example and can be simplified to

function lerpKeys (time, fromKey, toKey){
    var nt = (time - fromKey.time) / (toKey.time - fromKey.time); // normalised time
    return (toKey.value - fromKey.value) * nt + fromKey.value;
}

And easing

The beauty of doing it this way is that the normalised time can also have one of many easing functions applied to it. A easing function takes a value from 0 to 1 and returns a new value from 0 to 1 but puts a curve in place of the linear line.

An example of easing functions

// ease in out
function ease (value, strength) {  // strength is the amount of easing 1= no easing 
                                   //  1  < starts slow to fast then back to slow
                                   //  0 < 1 fast to slow to fast
    var v = Math.pow(Math.min(1, Math.max(0, value )), strength);
    return v / (v + Math.pow(1 - value, strength));
}

// linear (no easing just clamps the value)
function linear (value){
    return Math.max(0, Math.min(1, value));
}

To use it (note that the ease function clamps the value to the range 0 to 1 so that the if the time is outside the range the animation stops

// time in seconds
// from and to keys
// ease function to use
function lerpKeysEase(time, fromKey, toKey, easeFunc){
    var nt = easeFunc((time - fromKey.time) / (toKey.time - fromKey.time), 2); // normalised time
    return (toKey.value - fromKey.value) * nt + fromKey.value;
}
var currentValue  = lerpKeysEase(time, keys[0], keys[1], ease);

Most of the common easing function can be found at Github simple easing

Update

Oh I should have read the above github page before posting as the functions are just variations on the ease in out function in the snippet above. An excellent easing function page Easing examples and code and another page for a quick visual easing referance

The Transform

So that is the basics of tweening and is easy to adapt for things like positions and transforms.

// keys with transforms
var keys[  // time in seconds
   {value : [1, 0, -0.7, 0.4, 320, 70] , time : 0},  
   {value : [1, 0, 0, 1, 0, 0] , time : 30},
}

// lerp the transform
function lerpTranformKeysEase(time, fromKey, toKey, easeFunc){
    var f = fromKey.value; // to save some typing
    var t = toKey.value;
    var i = 0;
    var nt = easeFunc((time - fromKey.time) / (toKey.time - fromKey.time), 2); // normalised time
    // return an array with the new transform
    return [
        (t[i] - f[i]) * nt + f[i++],
        (t[i] - f[i]) * nt + f[i++],
        (t[i] - f[i]) * nt + f[i++],
        (t[i] - f[i]) * nt + f[i++],
        (t[i] - f[i]) * nt + f[i++],
        (t[i] - f[i]) * nt + f[i++]
    ];
}

All rather simple realy... BUT....

A better lerp (fixing the disfigured)

The transformation matrix has some special properties and encodes many things like position, rotation, scale X/Y, skew, shearing and applying a simple lerp (tween) function will not get the correct results as the object being transformed will be deformed.

What you need to do is decompose the transform into a more usable form. This simple means take a normal transform and return the various encoded values we want to animate. The transform has 3 basic parts, the X axis (first two values), Y axis (second two values), and origin (last two). The X,Y axis have a direction and a scale, and the origin is just a simple coordinate.

So lets create a decompose function that returns an array that holds the x,y axis direction, x,y scales and the origin

// NOTE Math.hypot is not supported on all browsers use 
// Math.sqrt(matrix[1] * matrix[1] +  matrix[0] * matrix[0])
// if needed
function matDecompose(matrix){  // matrix as array
     return [
          Math.atan2(matrix[1], matrix[0]), // x axis direction
          Math.atan2(matrix[3], matrix[2]), // y axis direction
          Math.hypot(matrix[1], matrix[0]), // x axis scale
          Math.hypot(matrix[3], matrix[2]), // y axis scale
          matrix[4], matrix[5]   // origin
     ];  
}

Now that we have this function we can convert the transform to the decomposed values and put them in the keys

var keys[  // time in seconds
   {value : matDecompose([1, 0, -0.7, 0.4, 320, 70]) , time : 0},  
   {value : matDecompose([1, 0, 0, 1, 0, 0]) , time : 30},
}

Then you just tween the keys as before, but this time the rotations, scales, and skewing will more accurately be tweened.

Of course the decomposed matrix is of no use so we need to convert it back to a usable transformation matrix.

// Returns a matrix as array from the decomposed matrix
function reCompose(m) { // m is the decomposed matrix as array
    return [
       Math.cos(m[0]) * m[2],  // reconstruct X axis and scale
       Math.sin(m[0]) * m[2],
       Math.cos(m[1]) * m[3],  // reconstruct Y axis and scale
       Math.sin(m[1]) * m[3],
       m[4], m[5]  // origin
   ];
}

Now you can apply the tween to get the new (decomposed transform) and covert it back to the standard matrix to apply to the canvas

var currentMatrix  = reCompose( lerpTranformKeysEase(time, keys[0], keys[1], ease));
ctx.setTransform(
    currentMatrix[0],
    currentMatrix[1],
    currentMatrix[2],
    currentMatrix[3],
    currentMatrix[4],
    currentMatrix[5]
);
// Now render the object.

So now you have the start of a very handy keyframe animation interface. With a little more logic for multiple key frames you can do any animation you wish, now the problem will be where to get the keyframes from ????




回答2:


I used some of your code and added an animation loop. You could use setTimeout in place of requestAnimationFrame. setTimeout(animationLoop, milliseconds);

document.addEventListener("DOMContentLoaded", function(event) {
  image = new Image();
  image2 = new Image();
  image3 = new Image();
  image4 = new Image();


  window.onload = function() {
    //first image
    var width = image.width,
      height = image.height;
    canvas1 = document.getElementById("num1Canvas");
    bottomSlice = canvas1.getContext("2d");
    //second image
    var width2 = image2.width,
      height2 = image2.height;
    canvas2 = document.getElementById("num2Canvas");
    topSlice = canvas2.getContext("2d");
    //third image
    newCanvas1 = document.getElementById("newNum1Canvas");
    newBottomSlice = newCanvas1.getContext("2d");
    //fourth image
    newCanvas2 = document.getElementById("newNum2Canvas");
    newTopSlice = newCanvas2.getContext("2d");

    var i = 0;
    function animationLoop() {
      
      if (i > height / 2) {
        alert('done!');
        return;
      }
      
      //first image transform
      bottomSlice.setTransform(1, 0, -0.7, .4, 320, 70);
      bottomSlice.drawImage(image,
        0, height / 2 - i, width, 2,
        0, height / 2 - i, width, 2);
      bottomSlice.setTransform(1, 0, -0.7, 0.4, 320, 70);
      bottomSlice.drawImage(image2,
        0, height / 2 + i, width, 2,
        0, height / 2 + i, width, 2);
      //second image transform 
      topSlice.setTransform(1, 0, -0.7, .4, 320, 0.2);
      topSlice.drawImage(image3,
        0, height2 / 2 - i, width2, 2,
        0, height2 / 2 - i, width2, 2);
      topSlice.setTransform(1, 0, -0.7, 0.4, 320, 0.2);
      topSlice.drawImage(image4,
        0, height2 / 2 + i, width2, 2,
        0, height2 / 2 + i, width2, 2);
      
      i++;
      requestAnimationFrame(animationLoop);
    }
    animationLoop();

  };
  
  var can = document.createElement('canvas');
  var w = can.width=300;
  var h = can.height=300;
  var ctx = can.getContext('2d');
  
  ctx.fillStyle="red";
  ctx.fillRect(0,0,w,h);
  image.src = can.toDataURL("image/png");//"bottom.png";
  
  ctx.fillStyle="blue";
  ctx.fillRect(0,0,w,h);
  image2.src = can.toDataURL("image/png");//"top.png";
  
  ctx.fillStyle="green";
  ctx.fillRect(0,0,w,h);
  image3.src = can.toDataURL("image/png");//"bottom.png";
  
  ctx.fillStyle="black";
  ctx.fillRect(0,0,w,h);
  image4.src = can.toDataURL("image/png");//"top.png";
});
<canvas id="num1Canvas"></canvas>
<canvas id="num2Canvas"></canvas>
<canvas id="newNum1Canvas"></canvas>
<canvas id="newNum2Canvas"></canvas>


来源:https://stackoverflow.com/questions/37622915/how-would-i-animate-the-process-of-a-transformed-canvas-image-going-back-into-it

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!