在这个例子中,Canvas.Context Save and Restore的目的是什么? [英] What's the purpose of Canvas.Context Save and Restore in this example?

查看:128
本文介绍了在这个例子中,Canvas.Context Save and Restore的目的是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此页面显示了HTML5画布中的一些动画。如果你看一下卷轴的来源,那就是清除矩形并在动画后恢复它时保存上下文的语句。如果我用另一个 ctx.clearRect(0,0,can.width,can.height 语句)替换restore语句,则无效。我认为还原正在恢复已清除的矩形但它似乎恢复了更多信息。下一帧需要的额外信息是什么?



我不是在寻找保存和恢复的HTML5教科书定义但是我想要了解为什么在这个具体示例中需要它们。



更新



这令人沮丧为了得到我在问题中已经特别提到的答案,我不想得到save()和restore()的定义。我已经知道Save()保存了上下文的状态,Restor()e恢复它。我的问题非常具体。为什么在所有Save do保存为空画布的情况下以示例方式使用restore()。为什么还原空白画布与清除它相同? / p>

解决方案

画布状态不是它所绘制的。它是一组属性,用于定义 工具 的当前状态,用于绘制 next 事物。



Canvas是一种立即模式位图。
和MS Paint一样。一旦它存在,它就在那里,所以没有必要保存当前的图像数据,因为这就像保存整个JPEG一样,每次你做出改变,每一帧...



...不,你保存的状态是指定用于绘制NEXT东西的坐标方向,尺寸比例,颜色等的状态(以及之后的所有内容,直到你改变这些值是手工的。)

  var canvas = document.createElement(canvas),
easel = canvas。的getContext( 2D);

easel.fillStyle =rgb(80,80,120);
easel.strokeStyle =rgb(120,120,200);

easel.fillRect(x,y,width,height);
easel.strokeRect(x,y,width,height);

easel.save(); //将所有当前状态属性存储在堆栈中

easel.rotate(degrees * Math.PI / 180); // radians
easel.scale(scale_X,scale_Y); //任何新的坐标/尺寸现在将乘以这些
easel.translate(new_X,new_Y); //新的原点坐标,基于旋转方向,乘以比例因子

easel.fillStyle =gold;
easel.fillRect(x,y,width,height); //全新的矩形
//原点不同,旋转也不同,因为你在一个新的坐标空间

easel.clearRect(0,0,width,height) ; //甚至不能保证清除实际画布,不再是
easel.strokeRect(width / 2,height / 2,width,height); //仍然在新的坐标空间中,仍然使用新颜色


easel.restore(); //重新分配所有以前的状态属性
easel.clearRect(0,0,width,height);

假设您在堆栈深处只有一个状态变化,那最后一行,现在你的canvas'之前的状态已经恢复,应该已经成功清除了自己(尽管有亚像素诡计)。



所以你可以看到,它与擦除它非常非常相似帆布。

实际上,它与删除它完全无关。



它与想要画东西有关,做基本的轮廓和扫描颜色/样式,然后手动在顶部的较小细节的颜色写入,然后手动写回所有样式回到以前的方式,回到下一个对象的扫描笔划, ...等等,保存将被重用的一般状态,为较小的细节创建一个新状态,并返回到一般状态,而不必每次硬编码,或编写setter函数来设置canvas ov上经常使用的值呃及以上(重置比例/旋转/仿射变换/颜色/字体/线宽/基线对齐/等)。



在您的确切示例中,然后,如果你正在注意,你会发现唯一改变的是步骤的价值。



他们为画布设置了一堆值的状态(颜色/字体/等)。

然后他们保存。嗯,他们节省了什么?

你看起来不够深。他们实际上保存了 默认翻译(即原始世界空间中的原点= 0,0)

但是你没看到他们定义它?

那是因为它被定义为默认值。



他们然后增加第1步像素(实际上,他们首先执行此操作,但在第一次循环后无关紧要 - 请留在我这里)。 >
然后他们为0,0设置一个新的原点(即:从现在开始,当他们输入 0,0 时,新的原点将指向一个完整的画布上的不同位置。)



该原点等于x是画布的正中间,y等于当前步(即:像素1或像素2等...以及为什么从0开始到1开始之间的差异确实无关紧要。)



那他们又做了什么? >
他们恢复。



嗯,他们恢复了什么?

......好吧,他们改变了什么?



他们将原点恢复为0,0

为什么?



嗯,如果他们没有会发生什么?

如果画布是500px x 200px,它在我们当前的屏幕空间中从0,0开始......那太棒了......

然后他们把它翻译成宽度/ 2,1

好​​的,所以现在当他们要求在0,0画出文字时,他们实际上会画250,1,



精彩。但下次会发生什么?



现在他们按宽度/ 2翻译2,
你认为,那很好...... ... 0,0将发生在250,2,因为他们已将其设置为清除数字:canvas.width / 2,2



不。因为根据我们的屏幕,当前0,0实际上是250,1。一个翻译是相对于它以前的翻译...



...所以现在你告诉画布从它的当前坐标'0,0开始向左走250,向下走2.

根据屏幕(就像一个窗口,看地图,而不是地图,本身)我们现在是500px向右,3像素从我们开始的地方开始......只有一个框架已经过去了。



因此他们将地图的坐标恢复为与屏幕坐标相同的原点(以及旋转在设置新的之前,它们是相同的,比例尺和倾斜度等等。



正如你可能猜到的那样,通过观察它,现在,您可以看到文本实际上应该从上到下移动。从右到左,就像页面说的那样...



为什么这样做?

为什么要去改变坐标系的麻烦绘图上下文,当绘图命令在函数中给你一个 x y 时?



如果你想在画布上画一幅画,你知道它的高度和宽度,以及你想要左上角的位置,为什么你不能这样做:

  easel.drawImage(myImg,x,y,myImg.width,myImg.height ); 

嗯,你可以。

你可以完全做到这一点。什么都没有阻止你。



事实上,如果你想让它在屏幕上缩放,你可以只更新 x y ,并称之为一天。



但是如果你正在画画呢?一个游戏角色?如果这个角色戴着帽子,戴着手套的手和大靴子,所有这些东西都与角色分开了怎么办?



首先你要说好吧,他站在世界的x和y,所以x加上他的手与他的身体相关的是x + body.x - hand.x ......或者是那个加......



...现在你已经为他的所有部分打了一个电话,这些部分看起来都像是一本满是5年级数学作业的笔记本。



相反,你可以说:他是这里。设置我的坐标,让0,0正好在我的家伙中间。现在您的绘制调用就像我的右手是身体右侧6个像素,左手是左侧3个像素一样简单。



当你完成了你的角色绘制,你可以将你的原点设置回0,0然后可以绘制下一个角色。或者,如果你想尝试它,你可以根据从一个到另一个的增量从那里翻译到下一个字符的原点(这将为每个翻译节省一个函数调用)。然后,如果你只在整个时间(原始状态)保存状态,最后,你可以通过调用 .restore 返回到0,0。


This page shows some animations in HTML5 canvas. If you look at the source of the scroller, there's a statement to save the context after clearing the rectangle and restoring it after the animation. If I substitute the restore statement with another ctx.clearRect(0, 0, can.width, can.height statement, nothing works. I thought the restore is restoring the cleared rectangle but it seems its restoring more info. What's that extra info that's needed for the next frame?

I am not looking for HTML5 textbook definitions of Save and Restore but I want to understand why they are needed in this specific example.

UPDATE

It's frustrating to get an answer where I specifically already mentioned in the question I don't want to get the definitions of save() and restore(). I already know Save() saves the state of the context and Restor()e restores it. My question is very specific. Why is restore() used in the manner in the example when all the Save did is saved an empty canvas. Why is restoring an empty canvas not the same as clearing it?

解决方案

Canvas state isn't what's drawn on it. It's a stack of properties which define the current state of the tools which are used to draw the next thing.

Canvas is an immediate-mode bitmap. Like MS Paint. Once it's there, it's there, so there's no point "saving" the current image data, because that would be like saving the whole JPEG, every time you make a change, every frame...

...no, the state you save is the state which will dictate what coordinate-orientation, dimension-scale, colour, etc, you use to draw the NEXT thing (and all things thereafter, until you change those values by hand).

var canvas = document.createElement("canvas"),
    easel  = canvas.getContext("2d");

easel.fillStyle = "rgb(80, 80, 120)";
easel.strokeStyle = "rgb(120, 120, 200)";

easel.fillRect(x, y, width, height);
easel.strokeRect(x, y, width, height);

easel.save();  // stores ALL current status properties in the stack

easel.rotate(degrees * Math.PI / 180); // radians
easel.scale(scale_X, scale_Y); // any new coordinates/dimensions will now be multiplied by these
easel.translate(new_X, new_Y); // new origin coordinates, based on rotated orientation, multiplied by the scale-factor

easel.fillStyle = "gold";
easel.fillRect(x, y, width, height); // completely new rectangle
// origin is different, and the rotation is different, because you're in a new coordinate space

easel.clearRect(0, 0, width, height); // not even guaranteed to clear the actual canvas, anymore
easel.strokeRect(width/2, height/2, width, height); // still in the new coordinate space, still with the new colour


easel.restore(); // reassign all of the previous status properties
easel.clearRect(0, 0, width, height);

Assuming that you were only one state-change deep on the stack, that last line, now that your canvas' previous state was restored, should have successfully cleared itself (subpixel shenanigans notwithstanding).

So as you can see, it has very, VERY little to do with erasing the canvas.
In fact, it has nothing to do with erasing it, at all.

It has to do with wanting to draw something, and doing the basic outlining and sweeping colours/styles, and then manually writing in the colours for the smaller details on top, and then manually writing all of the styles back the way they were before, to go back to sweeping strokes for the next object, and on and on...

Instead, save general states that will be reused, create a new state for smaller details, and return to the general state, without having to hard-code it, every time, or write setter functions to set frequently-used values on the canvas over and over (resetting scale/rotation/affine-transforms/colours/fonts/line-widths/baseline-alignment/etc).

In your exact example, then, if you're paying attention, you'll see that the only thing that's changing is the value of step.

They've set the state of a bunch of values for the canvas (colour/font/etc).
And then they save. Well, what did they save?
You're not looking deep enough. They actually saved the default translation (ie: origin=0,0 in original world-space).
But you didn't see them define it?
That's because it's defined as default.

They then increase the step 1 pixel (actually, they do this first, but it doesn't matter after the first loop -- stay with me here).
Then they set a new origin point for 0,0 (ie: from now on, when they type 0,0 that new origin will point to a completely different place on the canvas).

That origin point is equal to x being the exact middle of the canvas, and y being equal to the current step (ie: pixel 1 or pixel 2, etc... and why the difference between starting at 0 and starting at 1 really doesn't matter).

Then what do they do?
They restore.

Well, what have they restored?
...well, what have they changed?

They're restoring the point of origin to 0,0
Why?

Well, what would happen if they didn't?
If the canvas is 500px x 200px, and it starts at 0,0 in our current screen space... ...that's great...
Then they translate it to width/2, 1
Okay, so now when they ask to draw text at 0,0 they'll actually be drawing at 250, 1

Wonderful. But what happens next time?

Now they're translating by width/2, 2
You think, well, that's fine... ...the draw call for 0,0 is going to happen at 250, 2, because they've set it to clear numbers: canvas.width/2, 2

Nope. Because current 0,0 is actually 250,1 according to our screen. And one translation is relative to its previous translation...

...so now you're telling the canvas to start at it's current-coordinates' 0,0 and go left 250, and down 2.
According to the screen (which is like a window, looking at the map, and not the map, itself) we're now 500px to the right, and 3 pixels down from where we started... And only one frame has gone by.

So they restore the map's coordinates to be the same origin as the screen's coordinates (and the rotation to be the same, and the scale, and the skew, etc...), before setting the new one.

And as you might guess, by looking at it, now, you can see that the text should actually move top to bottom. Not right to left, like the page says...

Why do this?
Why go to the trouble of changing the coordinate-system of the drawing-context, when the draw commands give you an x and y right there in the function?

If you want to draw a picture on the canvas, and you know how high and wide it is, and where you'd like the top-left corner to be, why can't you just do this:

easel.drawImage(myImg, x, y, myImg.width, myImg.height);

Well, you can.
You can totally do that. There's nothing stopping you.

In fact, if you wanted to make it zoom around the screen, you could just update the x and y on a timer, and call it a day.

But what about if you were drawing a game character? What if the character had a hat, and had gloved hands, and big boots, and all of those things were drawn separate from the character?

So first you'd say "well, he's standing at x and y in the world, so x plus where his hand is in relation to his body would be x + body.x - hand.x...or was that plus..."

...and now you have draw calls for all of his parts that are all looking like a notebook full of Grade 5 math homework.

Instead, you can say: "He's here. Set my coordinates so that 0,0 is right in the middle of my guy". Now your draw calls are as simple as "My right hand is 6 pixels to the right of the body, my left hand is 3 pixels to the left".

And when you're done drawing your character, you can set your origin back to 0,0 and then the next character can be drawn. Or, if you want to attempt it, you can then translate from there to the origin of the next character, based on the delta from one to the other (this will save you a function call per translation). And then, if you only saved state once the whole time (the original state), at the end, you can return to 0,0 by calling .restore.

这篇关于在这个例子中,Canvas.Context Save and Restore的目的是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆