CanvasDay02 繪製詳解 繪製一片星空 淺析圖形變換的本質

目錄

0x00 繪製線條的一些細節

0x01 繪製多條不同顏色的折線

0x02 繪製封閉圖形

0x03 填充圖形

0x04 繪製矩形:

0x05 線條的屬性:

0x06 繪製五角星

0x07 優化:五角星的繪製 (圖形變換)繪製一片星空

0x08 圖形變換的本質


0x00 繪製線條的一些細節

ctx.moveTo() 表示將筆尖移動到某個位置,並不會將兩點連接

ctx.lineTo() 表示從上一個座標點 連接 到當前座標點

ctx.beginPath() + ctx.lineTo() 相當於 ctx.moveTo 因爲上一個座標點被beginPath清空了,清空並不代表上一個座標點爲(0,0),而是代表上一個座標點沒有。

繪製多條不連續的線段就需要應用moveTo

效果:

代碼:

<script>
    var canvas = document.getElementById('canvas');
    
    canvas.width = 1024;
    canvas.height = 689;
    
    var ctx = canvas.getContext('2d');

    ctx.moveTo(100,200);
    ctx.lineTo(300,400);
    ctx.lineTo(100,600);

    ctx.moveTo(300,200);
    ctx.lineTo(500,400);
    ctx.lineTo(300,600);

    ctx.moveTo(500,200);
    ctx.lineTo(700,400);
    ctx.lineTo(500,600);

    ctx.lineWidth = 10;
    ctx.strokeStyle ='#058';
    ctx.stroke();
</script>

0x01 繪製多條不同顏色的折線

ctx.beginPath() 表示開始一段全新的路徑,清空原有狀態。

但是如果一個狀態沒有被改變,則該狀態不會被清空。

例如下文中ctx.lineWidth = 10

  var canvas = document.getElementById('canvas');
    
    canvas.width = 1024;
    canvas.height = 689;
    
    var ctx = canvas.getContext('2d');

    ctx.lineWidth = 10;

    ctx.beginPath();
    ctx.moveTo(100,200);
    ctx.lineTo(300,400);
    ctx.lineTo(100,600);
    ctx.strokeStyle ='green';
    ctx.stroke(); 

    ctx.beginPath();
    ctx.moveTo(300,200);
    ctx.lineTo(500,400);
    ctx.lineTo(300,600);
    ctx.strokeStyle ='red';
    ctx.stroke(); 

    ctx.beginPath();
    ctx.moveTo(500,200);
    ctx.lineTo(700,400);
    ctx.lineTo(500,600);
    ctx.strokeStyle ='blue';
    ctx.stroke(); 

效果:

0x02 繪製封閉圖形

ctx.closePath()  表示當前路徑要被封閉,並且結束路徑。

如果路徑沒有被封閉,那麼closePath會自動封閉

封閉圖形建議放在ctx.beginPath() 和 ctx.closePath() 之間,否則可能會有缺口

修正:

    var canvas = document.getElementById('canvas');
    
    canvas.width = 1024;
    canvas.height = 689;
    
    var ctx = canvas.getContext('2d');

    ctx.lineWidth = 10;

    ctx.beginPath();
    ctx.moveTo(100,200);
    ctx.lineTo(300,400);
    ctx.lineTo(100,600);
    ctx.lineTo(100,200);
    ctx.closePath();
    ctx.strokeStyle ='green';
    ctx.stroke(); 

0x03 填充圖形

如果先描邊再填充顏色的話,邊框的內側5px將會被填充色覆蓋

如果先填充後描邊的話,邊框的內側的5px將會覆蓋填充色

<script>
    var canvas = document.getElementById('canvas');
    
    canvas.width = 1024;
    canvas.height = 689;
    
    var ctx = canvas.getContext('2d');

    ctx.lineWidth = 10;

    ctx.beginPath();
    ctx.moveTo(100,200);
    ctx.lineTo(300,400);
    ctx.lineTo(100,600);
    ctx.lineTo(100,200);
    ctx.closePath();

    ctx.strokeStyle ='green';
    ctx.stroke(); 
        ctx.fillStyle = 'yellow';
    ctx.fill();
   
</script>

0x04 繪製矩形:

rect(x,y,width,height) 只是規劃出了矩形的路徑

    var canvas = document.getElementById('canvas');
    
    canvas.width = 1024;
    canvas.height = 689;
    
    var ctx = canvas.getContext('2d');

    
    ctx.beginPath();
    ctx.rect(100,100,200,200);
    ctx.closePath();
    
    ctx.lineWidth = 10;
    ctx.stroke();
   
    ctx.fillStyle='red';
    ctx.fill();

fillRect(x,y,width,height) 不但規劃出了路徑,而且將直接使用當前的fillStyle,繪製出一個填充的矩形

strokeRect(x,y,width,height) 不但規劃出了路徑,而且將直接使用當前的strokeStyle,繪製出一個矩形邊框

        /**
        * 修改填充色爲紅色
        */
        ctx.fillStyle = 'red';
        /**
        *繪製矩形
        *@ 起點的x軸座標,以canvas畫布的左上角爲原點,向右爲x軸的正方向,向下爲y軸的正方向建立座標系
        *@ 起點的y軸座標
        *@ 寬度
        *@ 高度
        */
        ctx.fillRect(0,0,50,50);

0x05 線條的屬性:

#lineCap 線條的帽子(線條兩端):只能用於線段的開始和結尾處,不能用於線段的連接處

butt

round 多出一個圓形的頭

stroke 多出一個方形的頭

代碼:

    var canvas = document.getElementById('canvas');
    
    canvas.width = 1024;
    canvas.height = 689;
    
    var ctx = canvas.getContext('2d');
    
    ctx.lineWidth = 10;
    ctx.strokeStyle = "#005588";

    ctx.beginPath();
    ctx.moveTo(100,200);
    ctx.lineTo(700,200);
    ctx.lineCap = "butt";
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(100,400);
    ctx.lineTo(700,400);
    ctx.lineCap = "round";
    ctx.stroke();
    
    ctx.beginPath();
    ctx.moveTo(100,600);
    ctx.lineTo(700,600);
    ctx.lineCap = "square";
    ctx.stroke();

    //baseline
    ctx.lineWidth = 1;
    ctx.strokeStyle = '#27a';
    ctx.moveTo(100,100);
    ctx.lineTo(100,700);
    ctx.moveTo(700,100);
    ctx.lineTo(700,700);
    ctx.stroke();

#lineJoin 設置線條與線條相接時的樣式

ctx.lineJoin = 'miter';

miter(默認) 尖角,當設置爲miter時,還可以設置miterLimit屬性

ctx.miterLimit = 20

miterLimit是什麼呢?是內角尖端 與 外角尖端之間的距離的最小值,當小於該值時,lineJoin將會被修改爲bevel

產生miterLimit的前提是線條有寬度

bevel 斜接的形式,類似於長條紙帶的摺疊

round 圓角的形式

0x06 繪製五角星

思路:

先假設座標原點爲 五角星的中心

那麼就可以通過三角函數算出五角星各個角的位置座標了

因爲不論 五角星大圓的點還是小圓上的點,相鄰的點之間都相差 360/5 = 72°,所以可以 寫一個循環:畫大圓上的點 >> 畫小圓上的點。

然後將畫好的五角星平移一下,就可以了。

 

代碼:

<script>
    var canvas = document.getElementById('canvas');
    
    canvas.width = 1024;
    canvas.height = 689;
    
    var ctx = canvas.getContext('2d');
    

 


    /**
     * 繪製五角星
     * ctx 上下文
     * r 小圓半徑
     * R 大圓半徑
     * x x軸方向上的平移
     * y y軸方向上的平移
     * rot 順時針旋轉角度
    */
    function  drawStar( ctx,r,R,x,y,rot){
        ctx.beginPath();
        for(var i=0;i<5;i++){
            // 大圓半徑爲300 ,平移400px
            ctx.lineTo( 
                Math.cos((18 +i*72 - rot)*Math.PI/180)*R + x,
                -Math.sin((18 +i*72 - rot )*Math.PI/180)*R + y
            );
            //小圓半徑爲150,平移400px;
            ctx.lineTo( 
                Math.cos((54 +i*72 - rot)*Math.PI/180)*r +x,
                -Math.sin((54 +i*72 - rot)*Math.PI/180)*r +y
            );
        } 
        ctx.closePath();
        ctx.lineWidth = 10;
        ctx.stroke();
   0}
    drawStar(ctx,3,7,500,400,12);

</script>

0x07 優化:五角星的繪製 (圖形變換) 繪製一片星空

圖形學中繪製圖形的套路:

先繪製一個標準圖形,然後通過圖形變換得到我們想要的形狀。

在圖形學中,圖形變換一般有三種:

位移:translate(x,y)將圖像原點位移到x,y的位置

旋轉:rotate(deg)

縮放:scale(sx,sy)在橫向進行sx倍的縮放,在縱向進行sy倍的縮放

因爲ctx.translate是可以疊加的,所以當我們繪製第二個矩形的時候,矩形的原點座標已經移動到400,400的位置

    var canvas = document.getElementById('canvas');
    
    canvas.width = 1024;
    canvas.height = 689;
    
    var ctx = canvas.getContext('2d');
    
    ctx.fillStyle = "red";
    ctx.translate(100,100);
    ctx.fillRect(0,0,400,400);
    
    ctx.fillStyle ='green';
    ctx.translate(300,300);
    ctx.fillRect(0,0,400,400);

解決方法:

方法1:反向translate抵消(過於麻煩)

    var canvas = document.getElementById('canvas');
    
    canvas.width = 1024;
    canvas.height = 689;
    
    var ctx = canvas.getContext('2d');
    
    ctx.fillStyle = "red";
    ctx.translate(100,100);
    ctx.fillRect(0,0,400,400);
    ctx.translate(-100,-100);
    
    ctx.fillStyle ='green';
    ctx.translate(300,300);
    ctx.fillRect(0,0,400,400);

方法2:ctx.save() 保存當前圖形的狀態

ctx.restore() 恢復在ctx.save() 時保存的所有狀態

    ctx.save();
    ctx.fillStyle = "red";
    ctx.translate(100,100);
    ctx.fillRect(0,0,400,400);
    ctx.restore();
    
    ctx.save();
    ctx.fillStyle ='green';
    ctx.translate(300,300);
    ctx.fillRect(0,0,400,400);
    ctx.restore();

#scale 縮放

scale縮放不僅僅縮放 寬度 和 高度,而且會縮放 圖像的原點座標,已經線條的寬度

案例:

   var canvas = document.getElementById('canvas');
    
    canvas.width = 1024;
    canvas.height = 689;
    
    var ctx = canvas.getContext('2d');
    
    ctx.lineWidth = 5;
    ctx.save();
    ctx.scale(1,1);
    ctx.strokeRect(50,50,200,200);
    ctx.restore();

    ctx.save();
    ctx.scale(2,2);
    ctx.strokeRect(50,50,200,200);
    ctx.restore();

    ctx.save();
    ctx.scale(3,3);
    ctx.strokeRect(50,50,200,200);
    ctx.restore();

效果:

#繪製一片星空

效果:

代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        #canvas{
            border:1px solid black;
            position:absolute;
            left:50%;
            top:50%;
            transform: translate(-50%,-50%);
        }
    </style>
</head>
<body>
    <canvas id='canvas'></canvas>
</body>
<script>
    window.onload=function(){
        var canvas = document.getElementById('canvas');
    
        canvas.width = 1024;
        canvas.height = 569;

        var ctx = canvas.getContext('2d');
        //黑夜
        ctx.fillStyle = 'black';
        ctx.fillRect(0,0,canvas.width,canvas.height);
        //200個星星
        for(var i=0;i<40;i++){
            var r = Math.random() * 5 + 2; //2 - 7之間的隨機值
            var x = Math.random() * canvas.width;
            var y = Math.random() * canvas.height / 3;
            var rot = Math.random() * 360;
            drawStar(ctx,r,x,y,rot);
        }
       
    }
    /**
    * 產生一個標準星星的路徑
    * 一個位於(0,0)點 外圓半徑爲1,內圓半徑爲0.5的星星
    * 
    */
    function starPath(ctx){
        var R = 1;
        var r = 0.5 * R;
        var rot =0;
        var x = 0;
        var y =0;

        ctx.beginPath();
        for(var i=0;i<5;i++){
            // 大圓半徑爲300 ,平移400px
            ctx.lineTo( 
                Math.cos((18 +i*72 - rot)*Math.PI/180)*R + x,
                -Math.sin((18 +i*72 - rot )*Math.PI/180)*R + y
            );
            //小圓半徑爲150,平移400px;
            ctx.lineTo( 
                Math.cos((54 +i*72 - rot)*Math.PI/180)*r +x,
                -Math.sin((54 +i*72 - rot)*Math.PI/180)*r +y
            );
        } 
        ctx.closePath(); 
    }
    /**
     * 繪製一個五角星
     * ctx 上下文
     * r 小圓半徑
     * x x軸方向上的平移
     * y y軸方向上的平移
     * rot 順時針旋轉角度
    */
    function  drawStar( ctx,r,x,y,rot){
        ctx.save();
        //因爲 我們左上角座標是0,0,所以scale不會改變左上角的座標
        ctx.translate(x,y);
        ctx.rotate(rot * Math.PI/180);
        ctx.scale(r,r);
        starPath(ctx);
      
        ctx.lineJoin = 'round';
        ctx.fillStyle = '#fb3';
        ctx.strokeStyle = '#fd5'; 
        ctx.fill();
        ctx.restore();

   }


</script>
</html>

 

0x08 圖形變換的本質

圖形變換的本質是對一個圖形的所有頂點座標的一次再計算

這個計算是由一個叫變換矩陣的 矩陣來完成的

對於二維繪圖系統來說,變換矩陣是一個3x3的矩陣

如果是三維的繪圖系統來說,變換矩陣則是一個4x4的矩陣

a 控制 水平縮放的值, 默認值爲1

圖形原有的頂點座標 乘以 該矩陣 即可得到 新的頂點座標

canvas中便提供了一個函數讓我們可以改變這個矩陣

transform(a,b,c,d,e,f) 設置變換矩陣

注意:transform 是可以疊加的,爲了解決這個問題,canvas提供了一個函數 setTransfrom(a,b,c,d,e,f) 即忽略之前設置的所有transfrom,設置一個全新的transform

<script>
    var canvas = document.getElementById('canvas');
    
    canvas.width = 1024;
    canvas.height = 689;
    
    var ctx = canvas.getContext('2d');
    ctx.fillStyle = "red";
    ctx.strokeStyle = "#058";
    ctx.lineWidth = 5;
    /**
     * a,d 水平,垂直縮放
     * b,c 水平,垂直傾斜
     * e,f 水平,垂直位移
     * 
    */
   
    ctx.save();
    ctx.transform(1,0,0,1,50,100)
    ctx.fillRect(50,50,300,300);
    ctx.strokeRect(50,50,300,300);
    ctx.restore();
    

</script>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章