使用quadraticCurveTo绘制二次贝赛尔曲线

 Canvas提供了一系列的方法来绘制曲线,比如quadraticCurveTo(通过起始两个点以及一个控制点来绘制,前两个参数为控制点横纵坐标,后两个参数为终点横纵坐标,使用的是数学上的二次贝赛尔方程)。下面我们来看一下常见的一些使用。

固定控制点
       如下程序,我们实现了一个固定起始点,使用鼠标位置做为控制点,来绘制二次贝赛尔曲线的应用:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Canvas</title>
    </head>
    <body>
        <canvas id="canvas" width="400" height="400">当前浏览器不支持canvas</canvas>
        <script type="text/javascript" src="utils.js"></script>
        <script type="text/javascript" src="arrow.js"></script>
        <script type="text/javascript">
            window.onload=function(){
                var canvas=document.getElementById("canvas");
                var context=canvas.getContext("2d");
                var mouse=utils.captureMousePosition(canvas);
                                //固定两个端点
                var x0=100,y0=200,x2=300,y2=200;

                canvas.addEventListener('mousemove',function(){
                    //context.clearRect(0,0,canvas.width,canvas.height);
                                        //指定控制点为鼠标位置
                    var x1=mouse.x;
                    var y1=mouse.y;
                                        //绘制曲线
                    context.beginPath();
                    context.moveTo(x0,y0);
                    context.quadraticCurveTo(x1,y1,x2,y2);
                    context.stroke();
                },false);
            };
        </script>
    </body>
</html>

如下,即为其效果图:

曲线过定点

 

      之前我们绘制的曲线是控制点确定的曲线,如果我们已知曲线上的一个点(非起始点),则需要通过贝赛尔方程的一些特性来处理。(n次贝赛尔曲线有n+1个控制点,并且贝赛尔曲线位于这些控制点组成的多边形内部。) 

       已知两个端点(x0,y0)与(x2,y2)及曲线上的一个点(x1,y1).则可以得到其控制点的位置为

x = x1 * 2 - (x0 + x2) / 2;
y = y1 * 2 - (y0 + y2) / 2;

(参见如下文章:

http://learn.gxtc.edu.cn/NCourse/jxcamcad/cadcam/Mains/main4-2.htm http://www.leeyou.net/UTutorial/show.php?cid=153 http://hi.baidu.com/ggggwhw/blog/item/ffd23d35f313543c5ab5f5ed.html)。

所以,我们只需要修正上述固定控制点的代码,通过曲线上的点来求新的固定点的坐标即可:

1.var x1=mouse.x*2-(x0+x2)/2;  
2.var y1=mouse.y*2-(y0+y2)/2;

多点控制的平滑曲线

 

       下面来看一个复杂一点的问题,如果我们随机有N个点,如何来使用这N个点,利用二次贝赛尔曲线来绘制一条平滑的曲线?最简单的想法就是每三个点一组来绘制一个二次贝赛尔曲线,不难得到如下代码:

                      window.onload=function(){
                var canvas=document.getElementById("canvas");
                var context=canvas.getContext("2d");
                var points=[];
                var pointNumber=9;
                //随机生成点
                for(var i=0;i<pointNumber;i++){
                    points.push({
                        x:Math.random()* canvas.width,
                        y:Math.random()* canvas.height
                    });
                }                
                //每三点来绘制一条曲线
                context.beginPath();
                context.moveTo(points[0].x,points[0].y);
                for(var i=1;i< pointNumber; i+=2){
                    context.quadraticCurveTo(points[i].x,points[i].y,points[i+1].x,points[i+1].y);
                }                
                context.stroke();
                //绘制所有给定的点
                for(var i=0;i<pointNumber;i++){
                    context.moveTo(points[i].x,points[i].y);
                    context.beginPath();
                    context.arc(points[i].x,points[i].y,2,0,Math.PI*2,true);
                    context.closePath();
                    context.fill();
                }
            };

查看结果,会发现并不是想要的效果,除了共用起始点外,所有的二次曲线都是独立分隔的,并不是一条平滑的曲线,如下图:

 这是因为使用二次贝赛尔方程来绘制一个多点控制的平滑曲线,不可以每三个已知点为一组依次来绘制曲线,这样绘制出来的每一个二次曲线并不在一个相对坐标系统内。可以从第二个点开始,使用每两个点的中点来做端点,其它点均做为控制点来绘制曲线,如下图:(空心点为已知的给定点,实心点为给定点的中点)

 

按上面的说明,我们来更新代码:

                      window.onload=function(){
                var canvas=document.getElementById("canvas");
                var context=canvas.getContext("2d");
                var points=[];
                var pointNumber=9;
                var ctrlPoint={x:0,y:0};
                
                for(var i=0;i<pointNumber;i++){
                    points.push({
                        x:Math.random()* canvas.width,
                        y:Math.random()* canvas.height
                    });
                }                
                
                context.beginPath();
                context.moveTo(points[0].x,points[0].y);
                               //从第二个点开始,使用之后两点之间的“中点”来做为端点
                               &nbsp;for(var i=1;i< pointNumber-2; i++){
                    ctrlPoint.x=(points[i].x+points[i+1].x)/2;
                    ctrlPoint.y=(points[i].y+points[i+1].y)/2;
                    context.quadraticCurveTo(points[i].x,points[i].y,ctrlPoint.x,ctrlPoint.y);
                }    
                                //最后两个点需要特殊处理,无法使用此两点间的“中点”
                context.quadraticCurveTo(points[i].x,points[i].y,points[i+1].x,points[i+1].y);            
                context.stroke();
                
                
                for(i=0;i<pointNumber;i++){
                    context.moveTo(points[i].x,points[i].y);
                    context.beginPath();
                    context.arc(points[i].x,points[i].y,5,0,Math.PI*2,true);
                    context.closePath();
                    context.fill();
                }
            };

其效果如下图:

 

封闭曲线的实现

        可以看出,上述代码对于第一个点,最后一个点相关的“中点”都没有使用到。如果我们想利用到这两个点相关的“中点”来构成一个封闭的平滑曲线。那把所有的点均做为控制点来处理,使用它们的“中点”来做为每一个二次贝赛尔曲线的端点,如下代码所示:

                     window.onload=function(){
                var canvas=document.getElementById("canvas");
                var context=canvas.getContext("2d");
                var points=[];
                var pointNumber=9;
                var ctrlPoint={x:0,y:0};
                var ctrlPoint1={x:0,y:0};
                
                for(var i=0;i<pointNumber;i++){
                    points.push({
                        x:Math.random()* canvas.width,
                        y:Math.random()* canvas.height
                    });
                }                
                
                                //构造控制点,使用给定点的“中点”来做为曲线的端点
                ctrlPoint1.x=(points[0].x+points[pointNumber-1].x)/2;
                ctrlPoint1.y=(points[0].y+points[pointNumber-1].y)/2;
                context.beginPath();
                context.moveTo(ctrlPoint1.x,ctrlPoint1.y);
                for(var i=0;i< pointNumber-1; i++){
                    ctrlPoint.x=(points[i].x+points[i+1].x)/2;
                    ctrlPoint.y=(points[i].y+points[i+1].y)/2;
                    context.quadraticCurveTo(points[i].x,points[i].y,ctrlPoint.x,ctrlPoint.y);
                }
                               //封闭曲线
                                context.quadraticCurveTo(points[i].x,points[i].y,ctrlPoint1.x,ctrlPoint1.y);            
                context.stroke();
                
                
                for(i=0;i<pointNumber;i++){
                    context.moveTo(points[i].x,points[i].y);
                    context.beginPath();
                    context.arc(points[i].x,points[i].y,2,0,Math.PI*2,true);
                    context.closePath();
                    context.fill();
                }
            }

其效果图如下:

注意上面的代码中的i,我们在第一个循环外也使用了i,这是因为JavaScript的变量作用域不是块级的,而是以函数来区分的,如:

 

console.log(j);          //undefined
for(var j=0;j<10;j++){
    console.log(j);      //1~9
}
console.log(j);          //10

 

 转自http://blessdyb.iteye.com/blog/1403052

 

 

posted on 2012-09-05 14:34  djy_fn  阅读(518)  评论(0编辑  收藏  举报

导航