Home  >  Article  >  Web Front-end  >  Learn while playing with HTML5 (6) - Detailed introduction to Autobots and transformations

Learn while playing with HTML5 (6) - Detailed introduction to Autobots and transformations

黄舟
黄舟Original
2017-03-29 15:07:211625browse

1. State and its preservation and restoration

Before starting this section, we must first understand what state is and the preservation and restoration of state. . People who have played MFC programming often encounter code like this:

pOldPen=pDC->SelectObject(pNewPen)

When we select a new brush object, we always have to save it. Live the old brush object, why do you want to do this? Because the new brush object is only for temporary use. When it is used up and we want to restore the original brush configuration, if the old configuration is not saved in advance, these configurations will be lost and there is no way to restore it.

In HTML5 drawing, the state at a certain moment is the configuration value of a series of properties of the context object at the current moment. However, there are relatively few properties that determine the state of a brush, such as color, thickness, line type, etc. , and there are many attributes that determine the context state, including the following:

1. Movement, rotation, and scaling configuration of the current context object

2. StrokeStyle, fillStyle, globalAlpha, of the current context object, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation attribute values

3. Clipping path configuration of the current context object

The above series of configurations determine the current moment The state of the context object, including Move, rotate, scale, globalCompositeOperation (combination), and cropWe will talk about it soon.

2. Saving and Restoring the State

We said above that the state at a certain moment is determined by so many attributes. If we want to save the state at this moment, we must use these attributes. It would be too troublesome to save the values ​​one by one and set them back one by one when restoring. This is indeed the case, so the context provides two simple methods to save and restore the state. They are:

save( ) and restore( )

save and restore The method can be called multiple times. Each time the save method is called, the state at the time of the call (that is, a series of attribute values) is pushed into a stack.

Every time the restore method is called, the state of the last save is restored, that is, popped off the stack.

Imagine a magazine. The first bullet fired is always the last one pressed into the magazine.

3. Variation

1. Move: translate(dx,dy)

This The method seems very simple, but in fact it contains certain mathematical meanings. You can think that the origin of the entire coordinate system has moved. Any point (x, y) in the new coordinate system is equivalent to the coordinates in the original coordinate system:

x'=x+dx
y'=y+dy

If we call ctx.translate(5,8) to change the coordinate system state of the context object, then in the new state Point (3,2) drawing is equivalent to the image being drawn to point (8,10) in the original state, that is,

x'=5+3=8
y'=5+ 2=10

Maybe you will ask, why bother to draw the line directly at (8,10)? For example, change

ctx.translate(5,8)
ctx.drawImage(img,3,2)

to

ctx.drawImage(img,8 ,10)

Isn’t this simpler and more direct?

My understanding is that when the movement is more, it serves other graphics transformations. Appropriately changing the coordinate origin can make graphics calculations better understood and bring great convenience. Let me give you an example. A simple example, if:

There is a line segment, which is a small segment in the positive direction of the x-axis

y = 0 (1 <= x <= 3),

If the coordinate origin is the center of the circle and the circle is rotated 90 degrees counterclockwise, the line segment will coincide with the positive direction of the y-axis. The rotated line segment is:

x = 0 (1 <= y <= 3)

But it is impossible for us to rotate with the origin as the center of the circle every time. If we rotate with one endpoint (1,0) of the line segment as the center of the circle, how can we get the coordinate value of each point on the line segment after rotation? Woolen cloth? In fact, this process can be divided into three steps:

The first step: Move the origin coordinate to (1,0), the new line segment is still on the x-axis, but the equation becomes: y = 0 (0 <= x <= 2)

Second step: rotate with the origin of the new coordinate system as the center of the circle, and obtain the line segment x = 0 under the new coordinate system (0 <= y <= 2)

Step 3: Move the origin of the new coordinate system to (-1,0) under the new coordinate system, that is, restore the origin To the original position, the line segment at this time is: x = 1 (0 <= y <= 2)

The line segment obtained in the third step is the final line segment that needs to be drawn.

We can see from this example that even in such a simple case, it is difficult to directly calculate the rotated figure without moving the coordinate origin.

Tips:Before you move the coordinate origin, don’t forget to save the state. Of course, don’t restore the state after drawing.

2. Scaling

scale(sx, sy)

This is also very simple. sx, sy are scaling factors, and the new coordinates after scaling Any point (x, y) in the system is equivalent to the coordinates in the original coordinate system:

x' = x * sx

y' = y * sy

Similarly, Always don’t forget to save and restore the state when changing the coordinate system

3. Rotaterotate(A)

angle is rotation Angle, any point (x, y) in the new coordinate system after rotation is equivalent to the coordinates in the original coordinate system:

x' = x cosA - y sinA
y' = x sinA + y cosA

Similarly, Always don’t forget to save and restore the state when changing the coordinate system

4. Transformationtransform(m11, m12, m21, m22, dx, dy)

In fact, the movement, scaling, and rotation mentioned earlier are special cases of transformation. The six parameters of the transform method form a transformation matrix, as follows

m11 m21 dx
m12 m22 dy
0 0 1

Calling the transform method is equivalent to using these parameters to set a new transformation matrix. For the specific content of the transformation matrix, please refer to Graphics Related information, here are a few simple special cases:

Move translate(dx,dy): equivalent to transform(1,0,0,1,dx,dy)

Scale scale(sx,xy): equivalent to transform(sx,0,0,sy,0,0)

Rotate rotate(A) : Equivalent to transform(cosA,sinA,-sinA,cosA,0,0)

Rotate angle A with (dx,dy) as the reference point: transform(cosA, sinA, -sinA, cosA, dx(1-cosA) + dysinA, dy(1-cosA) - dxsinA)

Use (dx,dy) as the reference point to perform (sx,sy) scaling Scaling: transform(sx, 0, 0, sy, dx(1-sx), dy(1-sy))

There are many other more complex transformations, you can refer to them Graphics related information.

The following is an example of reference point deformation. If the mouse is kept pressed on a certain point on the image, the image will be scaled or rotated based on that point as the reference point. The image will be restored after releasing the button.

Tip: This example of scaling and rotating does not use a deformation matrix, but uses a combination of four simple deformations. The effect is as follows:


Move - hold down the mouse on the image and move
Base point scaling - hold down the mouse at a certain point in the image
Base point rotation - at a certain point in the image Press and hold the mouse at a certain point
The reference point zooms and rotates at the same time——Hold the mouse at a certain point of the image

4. Combination

The so-called combination is what effect will appear when one graphic is drawn on top of another graphic. By default, the upper graphic covers the lower graphic, which is called source-over.

There are a total of twelve combination types in the context object. The attribute globalCompositeOperation is used to set the combination type, as follows:

globalCompositeOperation = type

type is one of the following 12 string values 1:

Note: In all the above examples, the blue square is drawn first, that is, "existing canvas content", RedThe circle is drawn after , which is the "new shape".

5. Clipping path

In the first article, we introduced the two major types of drawing methods for context objects, namely stroke# for drawing lines. ## series of methods and the fill series of methods that fill the area. In fact, the context object also has a drawing method called clipping clip.

What is cropping? To use an inappropriate analogy, you cover the TV screen with a piece of cloth. At this time, you cannot see any changes on the TV screen.

However, if you cut out an area on the cloth, then at least you can see the screen changes in this area.

Of course

The screen outside the cropping area is also constantly changing (that is, it is also being redrawn), but you can't see it. This is called cropping, and you often encounter this need when processing images.

So what is

clipping path? It says that you need to cut out an area on the cloth. How did this area come about?

This area is set by the drawing path before the clipping action

clip. It can be a square, circle, five-star shape or any other outline shape that can be drawn.

So, the clipping path is actually the drawing path, but this path is not used for drawing, but to set a dividing line between the display area and the occlusion area.

If you don’t understand what a drawing path is, it is introduced in the previous article HTML5 Learning by Playing (2): Basic Drawing.

The following example uses two methods for cropping. The first method displays a circular cropping area that moves back and forth. The general process is as follows:

1. Clear the canvas

2. Change the center position of the circle

3. In the new Set a circular cropping area at the center of the circle

4. Draw a beauty image on the canvas

Since we keep setting the cropping area at new positions, we can see the cropping area While moving, the image outside the cropping area is not displayed

We use the clip method to set the cropping area. The graphics drawn afterwards can only display part of the cropping area, and the canvas is always displayed outside the cropping area. background color.

If you don’t want to completely block the image outside the cropping area, for example, we want the image within the cropping area to be fully displayed, but the image outside the cropping area is displayed in a semi-transparent manner, what should we do? Woolen cloth?

This requires the combination knowledge we mentioned above. The second method displays translucent occlusion. The general idea is as follows:

1. Clear the canvas

2. Fill all areas of the canvas with a translucent color. Here I use It is gray, and the transparency is 0.9

3. Change the center position of the circle

4、在新的圆心位置处以 XOR 方式绘制圆,这样和圆形重叠的部分将被擦除掉
这时候我们得到的图形效果是一个半透明的画布,上面有一块完全透明的圆形区域

5、 在第 4 步的基础上,以 destination-over 方式绘制美女图像,这时候美女图像将会出现在第 4 步图形效果的下方,想象一下,正好是我们想要的效果吧?!

代码



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--><canvas id="canvas1" width="250" height="300" 

    onmousedown="trans.transform(event);"  

    onmouseup="trans.init(event);" 

    onmousemove="trans.translate(event);" 

    style="background-color:black">

    你的浏览器不支持 &lt;canvas&gt;标签,请使用 Chrome 浏览器 或者 FireFox 浏览器

</canvas><br/>

<input type="radio" name="r" id="r1" checked="checked">移动——在图像上按住鼠标并移动<br />

<input type="radio" name="r" id="r2">基准点缩放——在图像某点处按住鼠标<br />

<input type="radio" name="r"  id="r3">基准点旋转——在图像某点处按住鼠标<br />

<input type="radio" name="r"  id="r4">基准点缩放同时旋转——在图像某点处按住鼠标<br />



<canvas id="canvas3" width="250" height="300" style="background-color:black">

    你的浏览器不支持 &lt;canvas&gt;标签,请使用 Chrome 浏览器 或者 FireFox 浏览器

</canvas><br/>

<input type="button" onclick="move(1);" value="移动裁剪区域">

<input type="button" onclick="move(2);" value="移动蒙版">

<input type="button" onclick="stop();" value="停止移动"><br />



        <div>

            <table>

                <tr>

                    <td><canvas id="tut0" width="125" height="125"></canvas><br/><label id="lab0"></label></td>

                <td><canvas id="tut1" width="125" height="125"></canvas><br/><label id="lab1"></label></td>

                <td><canvas id="tut2" width="125" height="125"></canvas><br/><label id="lab2"></label></td>

                <td><canvas id="tut3" width="125" height="125"></canvas><br/><label id="lab3"></label></td>

                </tr>

                <tr>

                    <td><canvas id="tut4" width="125" height="125"></canvas><br/><label id="lab4"></label></td>

                <td><canvas id="tut5" width="125" height="125"></canvas><br/><label id="lab5"></label></td>

                <td><canvas id="tut6" width="125" height="125"></canvas><br/><label id="lab6"></label></td>

                <td><canvas id="tut7" width="125" height="125"></canvas><br/><label id="lab7"></label></td>

                </tr>

                <tr>

                    <td><canvas id="tut8" width="125" height="125"></canvas><br/><label id="lab8"></label></td>

                <td><canvas id="tut9" width="125" height="125"></canvas><br/><label id="lab9"></label></td>

                <td><canvas id="tut10" width="125" height="125"></canvas><br/><label id="lab10"></label></td>

                <td><canvas id="tut11" width="125" height="125"></canvas><br/><label id="lab11"></label></td>

                </tr>

            </table>

        </div>





<script type="text/javascript">

    //美女图的 Base64 编码

    IMG_SRC=&#39;data:image/gif;base64,/9j/4QDfRXhpZgAASUkqAAgAAAAFABIBAwA......&#39;;//省略四十字节



    //==========================================

    //基准点变形类

    //==========================================

    function Transform(){

        //获取画布对象

        this.ctx = document.getElementById("canvas1").getContext("2d");

        //创建图像对象

        this.img=new Image();

        //指定图像源

        this.img.src=IMG_SRC;

        this.interval = null;

        //鼠标按钮状态

        this.pressed=false;

        this.init();

    }

    

    //初始化图形

    Transform.prototype.init=function(){

        //鼠标按钮状态

        this.pressed=false;

        //停止计时器

        if(this.interval) clearInterval(this.interval);

        //变化值

        this.delta = 0.06;

        //清空

        this.ctx.clearRect(0,0,250,300);

        //重绘

        this.paint();

    }

    

    //绘制图像

    Transform.prototype.paint=function(){

        var that=this;

        var img=this.img

        if(img.complete)

            that.ctx.drawImage(img,0,0);

        else 

            var interval = setInterval(function(){

                if(img.complete){

                    that.ctx.drawImage(img,0,0);

                    clearInterval(interval);

                }

            },300);

    }

    

    //鼠标按钮按下后,开始变形

    Transform.prototype.transform = function(){

        //获取基准点

        this.dx=event.offsetX;

        this.dy=event.offsetY;

        //获取基准点

        this.startx=event.offsetX;

        this.starty=event.offsetY;

        //初始缩放比例

        this.sc=1;

        //初旋转角度

        this.angle=0;

        

        var that=this;

        if(document.getElementById("r1").checked)

            //鼠标按钮状态

            this.pressed=true;

        else if(document.getElementById("r2").checked)

            this.interval = setInterval(function(){that.scale()},50);

        else if((document.getElementById("r3").checked))

            this.interval = setInterval(function(){that.rotate()},50);

        else 

            this.interval = setInterval(function(){that.scaleAndRotate()},50);

    }

    

    //移动

    Transform.prototype.translate = function(){

        this.ddx=event.offsetX-this.startx;

        this.ddy=event.offsetY-this.starty;

        if(this.pressed){

            //清空

            this.ctx.clearRect(0,0,250,300);

            //保存状态

            this.ctx.save();

            //平移

            this.ctx.translate(this.ddx,this.ddy);

            //重绘

            this.paint();

            //绘制基准点

            this.ctx.fillStyle="red";

            this.ctx.fillRect(this.dx-5,this.dy-5,10,10);

            //恢复状态

            this.ctx.restore();

        }

    }



    //缩放变形

    Transform.prototype.scale = function(){

        //清空

        this.ctx.clearRect(0,0,250,300);

        //改变缩放比例

        this.sc=this.sc - this.delta;

        if(this.sc<0.2 || this.sc>2) 

            this.delta = -this.delta;

        //保存状态

        this.ctx.save();

        //以 (dx,dy) 为基准点进行 (sx,sy)比例缩放:transform(sx, 0, 0, sy, dx(1-sx), dy(1-sy))

        this.ctx.transform(this.sc, 0, 0, this.sc, this.dx*(1-this.sc), this.dy*(1-this.sc))

        //用新的变形矩阵重绘

        this.paint();

        //绘制基准点

        this.ctx.fillStyle="red";

        this.ctx.fillRect(this.dx-5,this.dy-5,10,10);

        //恢复状态

        this.ctx.restore();

    }

    

    //旋转变形

    Transform.prototype.rotate = function(){

        //清空

        this.ctx.clearRect(0,0,250,300);

        //改变缩放比例

        var PI = Math.PI;

        this.angle=this.angle + PI/60;

        //保存状态

        this.ctx.save();

        //以 (dx,dy) 为基准点旋转角度 A:transform(cosA, sinA, -sinA, cosA, dx(1-cosA) + dysinA, dy(1-cosA) - dxsinA)

        this.ctx.transform(Math.cos(this.angle), Math.sin(this.angle), 

                -Math.sin(this.angle), Math.cos(this.angle), 

                this.dx*(1-Math.cos(this.angle)) + this.dy*Math.sin(this.angle), 

                this.dy*(1-Math.cos(this.angle)) - this.dx*Math.sin(this.angle))

        //用新的变形矩阵重绘

        this.paint();

        //绘制基准点

        this.ctx.fillStyle="red";

        this.ctx.fillRect(this.dx-5,this.dy-5,10,10);

        //恢复状态

        this.ctx.restore();

    }

    

    //即缩放又旋转变形,没有使用变形矩阵

    Transform.prototype.scaleAndRotate = function(){

        //清空

        this.ctx.clearRect(0,0,250,300);

        //改变缩放比例

        this.sc=this.sc - this.delta;

        if(this.sc<0.2 || this.sc>2) 

            this.delta = -this.delta;

        var PI = Math.PI;

        this.angle=this.angle + PI/60;

        //保存状态

        this.ctx.save();

        //先移动原点到基点

        this.ctx.translate(this.dx,this.dy);

        this.ctx.scale(this.sc,this.sc);

        this.ctx.rotate(this.angle);

        this.ctx.translate(-this.dx,-this.dy);

        //用新的变形矩阵重绘

        this.paint();

        //绘制基准点

        this.ctx.fillStyle="red";

        this.ctx.fillRect(this.dx-5,this.dy-5,10,10);

        //恢复状态

        this.ctx.restore();

    }

    

    var trans = new Transform();

    

    //==========================================

    function Clip(){

        var canvas = document.getElementById("canvas3");

        this.ctx = canvas.getContext("2d");

        this.img=new Image();

        this.img.src=IMG_SRC;

        //移动方向

        this.delta=[3,3];

        //起始点

        this.pos_x = 225;

        this.pos_y = 120;

        //半径

        this.radius = 40;

        //画布的长和宽

        this.w = parseInt(canvas.getAttribute("width"));

        this.h = parseInt(canvas.getAttribute("height"));

    }

    

    Clip.prototype.draw1=function(){

        //碰撞检测

        if (this.pos_x < this.radius) {

            this.delta[0] = Math.random() % 4 + 5;

        } else if (this.pos_x > this.w - this.radius) {

            this.delta[0] = -(Math.random() % 4 + 5);

        }

        if (this.pos_y < this.radius) {

            this.delta[1] = Math.random() % 4 + 5;

        } else if (this.pos_y > this.h - this.radius) {

            this.delta[1] = -(Math.random() % 4 + 5);

        }

        this.pos_x += this.delta[0];

        this.pos_y += this.delta[1];



        this.ctx.clearRect(0, 0, this.w, this.h);

        //保存状态

        this.ctx.save()

        //移动变形

        this.ctx.translate(this.pos_x,this.pos_y);

        //设置裁剪区域

        this.ctx.beginPath();

        this.ctx.arc(0 ,0,this.radius,0,Math.PI*2,true);

        this.ctx.clip();         

        // 将图片画到画布上

        this.ctx.drawImage(this.img, -this.pos_x, -this.pos_y,this.w, this.h);

        //恢复状态

        this.ctx.restore();

    }

    

    Clip.prototype.draw2=function(){

        //碰撞检测

        if (this.pos_x < this.radius) {

            this.delta[0] = Math.random() % 4 + 5;

        } else if (this.pos_x > this.w - this.radius) {

            this.delta[0] = -(Math.random() % 4 + 5);

        }

        if (this.pos_y < this.radius) {

            this.delta[1] = Math.random() % 4 + 5;

        } else if (this.pos_y > this.h - this.radius) {

            this.delta[1] = -(Math.random() % 4 + 5);

        }

        this.pos_x += this.delta[0];

        this.pos_y += this.delta[1];



        this.ctx.clearRect(0, 0, this.w, this.h);

        //绘制灰色的半透明蒙版

        this.ctx.fillStyle="rgba(125,125,125,0.9)"

        this.ctx.fillRect(0, 0, this.w, this.h);

        //保存状态

        this.ctx.save()

        //移动坐标

        this.ctx.translate(this.pos_x,this.pos_y);

        //裁剪透明的圆形区域

        this.ctx.globalCompositeOperation = "xor";   

        this.ctx.fillStyle="white"

        this.ctx.beginPath();

        this.ctx.arc(0 ,0,this.radius,0,Math.PI*2,true);

        this.ctx.fill();       

        // 将图片画到蒙版的下面,即只露出透明区域

        this.ctx.globalCompositeOperation = "destination-over";   

        this.ctx.drawImage(this.img, -this.pos_x, -this.pos_y,this.w, this.h);

        //恢复状态

        this.ctx.restore();

    }



    var cl=new Clip();

    cl.interval=null;

    

    function move(id){      

        if(cl.interval)

            clearInterval(cl.interval)

        if(id==1){

            cl.ctx.clearRect(0, 0, 450, 300);    

            cl.interval=setInterval(function(){cl.draw1()},20);

        }

        else{

            cl.ctx.clearRect(0, 0, 450, 300);    

            cl.interval=setInterval(function(){cl.draw2()},20);

        }

    }



    function stop(){

        clearInterval(cl.interval)

    }

    

    var compositeTypes = [

        &#39;source-over&#39;,&#39;source-in&#39;,&#39;source-out&#39;,&#39;source-atop&#39;,

        &#39;destination-over&#39;,&#39;destination-in&#39;,&#39;destination-out&#39;,&#39;destination-atop&#39;,

        &#39;lighter&#39;,&#39;darker&#39;,&#39;copy&#39;,&#39;xor&#39;

    ];

    function drawComp(){

        for (i=0;i<compositeTypes.length;i++){

            var label = document.createTextNode(compositeTypes[i]);

            document.getElementById(&#39;lab&#39;+i).appendChild(label);

            var ctx = document.getElementById(&#39;tut&#39;+i).getContext(&#39;2d&#39;);



            // draw rectangle

            ctx.fillStyle = "#09f";

            ctx.fillRect(15,15,70,70);



            // set composite property

            ctx.globalCompositeOperation = compositeTypes[i];



            // draw circle

            ctx.fillStyle = "#f30";

            ctx.beginPath();

            ctx.arc(75,75,35,0,Math.PI*2,true);

            ctx.fill();

        }

    }

    drawComp();

</script>

The above is the detailed content of Learn while playing with HTML5 (6) - Detailed introduction to Autobots and transformations. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn