本文介绍了我如何动画一个转换的画布图像的过程恢复到其原始状态的过程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在(1,0,0,-0.7,0.4,320,70)的转换,我想逐渐结束于(1,0,0,1,0,0),我如何做这个?



这是转换图像的代码:

  addEventListener(DOMContentLoaded,function(event){
image = new Image();
image2 = new Image();
image3 = new Image();
image4 = new Image();
window.onload = function(){
//第一个图片
var width = image.width,
height = image.height;
canvas1 = document.getElementById(num1Canvas);
bottomSlice = canvas1.getContext(2d);
//第二个图像
var width2 = image2.width,
height2 = image2.height;
canvas2 = document.getElementById(num2Canvas);
topSlice = canvas2.getContext(2d);
//第三个图片
newCanvas1 = document .getElementById(newNum1Canvas);
newBottomSlice = newCanvas1.getContext(2d);
//第四张图片
newCanvas2 = document.getElementById(newNum2Canvas);
newTopSlice = newCanvas2.getContext(2d);

for(var i = 0; i //第一个图像转换
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);
//第二个图像转换
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;
});

我基本上想要这样的事情发生:

  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);
}

但是,上面的代码不工作,我想要什么。我正在考虑以某种方式使用setTimeout类的事情做到这一点,但我不知道我该怎么去。

解决方案

补间。



大多数动画都涉及关键帧。



简单补间(AKA lerp)



例如,我们有一些关键帧的值和时间。

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

有时候我们想要的是什么值。因此,忽略范围之外的时间,我们可以写一个简单的函数来获取给定时间的值。它首先将时间转换为键之间的标准化时间(0到1),其中0是第一个键的时间,1是第二个键的时间。

  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;
}

这是详细的示例,可以简化为

 函数lerpKeys(time,fromKey,toKey){
var nt =(time - fromKey.time)/(toKey.time - fromKey。时间); // normal time
return(toKey.value - fromKey.value)* nt + fromKey.value;
}

放松 b

这样做的好处是,标准化的时间也可以应用许多缓动函数之一。缓动函数取从0到1的值,并返回一个从0到1的新值,但放置一个曲线代替线性线。



缓动函数的示例

  b $ b function ease(value,strength){// strength是宽松的量1 =没有宽松
// 1<开始慢到快然后回慢慢
// 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));
}

//线性(没有缓动只是钳制值)
函数线性(值){
返回Math.max(0,Math.min ,value));
}

要使用它(请注意,ease函数将值锁定到范围0如果时间在动画停止的范围之外

  //以秒为单位的时间
//从和到键
//缓和函数使用
函数lerpKeysEase(time,fromKey,toKey,easeFunc){
var nt = easeFunc((time - fromKey.time)/(toKey。时间 - fromKey.time),2); //标准化时间
return(toKey.value - fromKey.value)* nt + fromKey.value;
}
var currentValue = lerpKeysEase ,key [0],keys [1],ease);

可在中找到



更新



我应该在发布之前阅读上面的github页面,因为函数只是上面代码片断中out函数的变体。一个优秀的缓动函数页面,另一个页面为



变换



这是补间的基础,很容易适应位置和变换等。

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

//变换
函数lerpTranformKeysEase(time,fromKey, toKey,easeFunc){
var f = fromKey.value; //保存一些类型
var t = toKey.value;
var i = 0;
var nt = easeFunc((time - fromKey.time)/(toKey.time - fromKey.time),2); // normal time
//返回一个带有新转换的数组
return [
(t [i] -f [i])* nt + f [i ++],
(t [i] -f [i])* nt + f [i ++],
(t [i] ] f [i ++],
(t [i] -f [i])-f [i] i])* nt + f [i ++]
];
}

所有相当简单的内容... BUT ....



更好的lerp (修复损坏的)



转换矩阵有一些特殊的属性,许多东西,比如位置,旋转,缩放X / Y,倾斜,剪切和应用一个简单的lerp(补间)函数将无法获得正确的结果,因为被转换的对象将被变形。



您需要做的是将转换分解为更可用的形式。这个简单的手段采取正常的变换,并返回我们想要动画化的各种编码值。变换有3个基本部分,X轴(前两个值),Y轴(后两个值)和原点(后两个)。 X,Y轴有一个方向和一个刻度,原点只是一个简单的坐标。



让我们创建一个分解函数,返回一个包含x,y轴方向,x,y尺度和原点的数组

  //注意Math.hypot不是所有浏览器都支持使用
// Math.sqrt(matrix [1] * matrix [1] + matrix [ 0] * matrix [0])
//如果需要
function matDecompose(matrix){//矩阵作为数组
return [
Math.atan2(matrix [1]矩阵[0]),// x轴方向
Math.atan2(矩阵[3],矩阵[2]),// y轴方向
Math.hypot 0]),// x轴刻度
Math.hypot(matrix [3],matrix [2]),// y轴刻度
matrix [4],matrix [5] // origin
];
}



现在我们有了这个函数,我们可以将转换转换为分解值将它们放在键中

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

然后你只需像以前一样补间键,但这次旋转,缩放和倾斜更准确地补间。



当然,分解矩阵是没有用的,所以我们需要将它转换回一个可用的变换矩阵。



//从分解矩阵返回一个矩阵数组
function reCompose(m){// m是数组的分解矩阵
return [
Math.cos(m [0])* m [2],//重建X轴并缩放
Math.sin(m [0])* m [2],
Math.cos(m [1])* m [3],//重构Y轴并缩放
Math.sin(m [1])* m [3],
m [4] m [5] // origin
];
}



现在你可以应用补间来获得新的它返回到应用到画布的标准矩阵

  var currentMatrix = reCompose(lerpTranformKeysEase(time,keys [0],keys [1],ease)); 
ctx.setTransform(
currentMatrix [0],
currentMatrix [1],
currentMatrix [2],
currentMatrix [3],
currentMatrix [ 4],
currentMatrix [5]
);
//现在渲染对象。

现在你有一个非常方便的关键帧动画界面的开始。有了更多的逻辑多个关键帧,你可以做任何你想要的动画,现在的问题是从哪里得到关键帧从????


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.

解决方案

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 ????

这篇关于我如何动画一个转换的画布图像的过程恢复到其原始状态的过程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-28 17:17