整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          如何實現使用鼠標繪制直線?

          何一個畫板軟件都有繪制直線的功能,如果想要基于web實現鼠標畫直線該怎么做哪?本文基于html canvas介紹一種實現鼠標繪制直線的方法,最終效果如下。

          畫一條直線

          首先,我們來嘗試先做到畫單條直線,html canvas提供了畫線的方法,這里稍作封裝,draw_line方法使用canvas上下文提供的方法繪制一條直線,start_vector、end_vector使用向量來表示起點和終點。

          // 構造向量[x,y]
          function make_vect(x, y) {
              return [x ,y];
          }
          // 獲取向量x坐標
          function xcor_vect(vector) {
              return vector[0];
          }
          // 獲取向量y坐標
          function ycor_vect(vector) {
              return vector[1];
          }
          
          function draw_line(start_vector, end_vector) {
              ctx.beginPath();
              //移動到起點
              ctx.moveTo(xcor_vect(start_vector), ycor_vect(start_vector));
              //繪制起點到終點的直線段
              ctx.lineTo(xcor_vect(end_vector), ycor_vect(end_vector));
              ctx.stroke();
          }
          

          有了上面的draw_line方法,只要通過監聽鼠標按下事件獲取鼠標按下時的坐標點作為起點,再獲取鼠標釋放時的坐標點作為終點,然后調用draw_line方法就可以做到畫一條直線了,示例代碼如下。

          ...以上代碼省略
          // container為html中canvas元素id
          var canvas = document.getElementById("container");
          var ctx = canvas.getContext("2d");
          
          // 監聽鼠標按下事件
          canvas.onmousedown = function(event) {
              //按下鼠標獲取起點
              let start_point = make_vect(event.clientX, event.clientY);
          
              // 監聽鼠標松開事件
              canvas.onmouseup = function(event) {
                  // 釋放鼠標獲取終點
                  let end_point = make_vect(event.clientX, event.clientY);
                  draw_line(start_point, end_point);
              }
          }
          

          開啟web服務后訪問頁面,選擇canvas合適位置點擊鼠標并拖動可以繪制一條直線,但是只有鼠標釋放時直線才會繪制出來,這樣的體驗顯然是不好的,我們希望直線能夠一直跟蹤鼠標的軌跡。

          跟蹤鼠標軌跡

          要跟蹤鼠標軌跡,還需要監聽鼠標移動事件,在鼠標拖動的每個時刻都將鼠標位置作為終點進行畫線,代碼如下:

          // container為html中canvas元素id
          var canvas = document.getElementById("container");
          var ctx = canvas.getContext("2d");
          var pressed = false;
          
          // 監聽鼠標按下事件
          canvas.onmousedown = function(event) {
              //按下鼠標獲取起點
              let start_point = make_vect(event.clientX, event.clientY);
              pressed = true;
          
              // 監聽鼠標移動事件
              canvas.onmousemove = function(event) {
                  //只有鼠標按下時拖動才需要畫線
                  if (pressed) {
                      let end_point = make_vect(event.clientX, event.clientY);
                      draw_line(start_point, end_point);
                  }    
              }
          
              // 監聽鼠標松開事件
              canvas.onmouseup = function(event) {
                  let end_point = make_vect(event.clientX, event.clientY);
                  draw_line(start_point, end_point);        
                  pressed = false;
              }
          }
          

          再次測試發現結果很離譜,竟然畫出了很多條線,但值得肯定的是這樣做到了跟蹤鼠標的拖動軌跡,之所以會出現很多條線,是因為每個鼠標拖動位置的直線都已經畫在畫布上了,只要每次鼠標移動事件處理前把之前已畫的線擦除掉就可以了。

          // container為html中canvas元素id
          var canvas = document.getElementById("container");
          var ctx = canvas.getContext("2d");
          var pressed = false;
          
          // 監聽鼠標按下事件
          canvas.onmousedown = function(event) {
              //按下鼠標獲取起點
              let start_point = make_vect(event.clientX, event.clientY);
              pressed = true;
          
              // 監聽鼠標移動事件
              canvas.onmousemove = function(event) {
                  //只有鼠標按下時推動才需要畫線
                  if (pressed) {
          	        // 先擦除畫布
                      ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
                      let end_point = make_vect(event.clientX, event.clientY);
                      draw_line(start_point, end_point);
                  }    
              }
          
              // 監聽鼠標松開事件
              canvas.onmouseup = function(event) {
                  let end_point = make_vect(event.clientX, event.clientY);
                  draw_line(start_point, end_point);        
                  pressed = false;
              }
          }
          

          再次測試發現可以很方便的使用鼠標畫一條直線了,而且可以實時跟蹤鼠標的位置來調整直線直到鼠標釋放才會確定繪制哪條直線。

          保留痕跡

          還有一個問題需要解決,現在畫完一條直線后,繼續畫下一條直線就會把之前的清掉,畫布上始終只有一條直線,這樣的畫板是沒什么價值的,我們需要畫布既要能夠實時跟蹤當前繪制直線的樣子,還要能夠保留以前繪制好的直線,就像本文開頭展示的效果一樣。顯然,可以通過定義一個數組來存放歷史直線的坐標數據,每次清空畫布后再把它們繪制出來就可以輕松做到想要的功能了。完整代碼如下:

          據直線的一般表示形式y=mx+b我們可以很容易得出經過兩點p0(x0,y0)、p1(x1,y1)的直接方程為

          稍作變換表示成函數的形式如下

          顯示屏幕是由如下圖所示一個個微小的像素組成的,屏幕坐標是離散的,繪制一條線段實際上就是設置一系列像素的顏色來近似模擬一條直線,Bresenham方式是一種采用中點算法來實現直線繪制的方法。

          這里以直線斜率在(0,1]區間的情況為例來介紹Bresenham算法的核心內容,斜率在此區間內x軸的增長要快于y軸的增長,因此我們直接遍歷x0~x1之間的像素(像素坐標都是整數),關鍵步驟就是確定x對應的y軸像素值。

          假如已經繪制了像素(x,y),下一步就是確定是繪制(x+1,y+1)還是(x+1,y),Bresenham算法采用計算直線方程的函數形式的值f(x+1, y+0.5),也就是y+1和y的中點來進行判別。

          • 如果f(x+1, y+0.5)>0說明直線在y+1和y中點的下方,此時繪制(x+1,y)比較合理;
          • 如果f(x+1, y+0.5)<0說明直線在y+1和y中點的上方,此時繪制(x+1,y+1)比較合理;
          • 如果f(x+1, y+0.5)=0說明直線剛好穿過y+1和y中點,此時繪制那一個都是可以的;
            表示為偽代碼的形式為:
          y = y0
          for x = x0 to x1 do
          	draw(x,y)
          	if f(x+1, y+0.5) < 0 then
          		y = y + 1
          


          其他斜率曲線的判斷方法是類似的,需注意的是如果y的變化快于x的變化,需要遍歷y0到y1,通過中點算法來確定x的值。另外需要特別注意的就是直接方程f(x,y)的值大于0還是小于0的判斷要仔細判別,這里判斷方法并不是復雜,只是比較容易混淆。

          x、y象限內直線斜率可以換分為(-∞,-1]、(-1,0]、(0,1]、(1,+∞)四個區間,見下圖。

          最后,我們用html的canvas元素來演示Bresenham畫線算法,理解了該算法的原理,代碼也就順理成章了。示例繪制了四個斜率的線段,效果如下,可以看到某些斜率下的鋸齒效果還是非常明顯的,后續會繼續介紹如何利用抗鋸齒算法來生成更平滑的直線。


          注:canvas的y軸正向是向下的。
          index.html

          <!DOCTYPE html>
          
          <html lang="en">
          
          <head>
          
              <meta charset="UTF-8">
          
              <meta http-equiv="X-UA-Compatible" content="IE=edge">
          
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
          
              <title>Bresenham畫線算法示例</title>
          
          </head>
          
          <body>
          
              <canvas id="canvas" width="780" height="780"></canvas>
          
              <script src="./bresenham.js"></script>
          
          </body>
          
          </html>
          

          bresenham.js

          var canvas = document.getElementById("canvas");
          
          var ctx = canvas.getContext("2d");
          
            
          
          function setPixel(x, y, color='rgba(255, 255, 255, 1.0)') {
          
              ctx.fillStyle = color;
          
              ctx.fillRect(x, y, 1, 1);
          
          }
          
            
          
          function  drawLine(p0, p1, color='rgba(255, 255, 255, 1.0)') {
          
              let x0, y0, x1, y1;
          
              // 保持x1>=x0
          
              if (p0.x > p1.x) {
          
                  [x0, y0] = [Math.round(p1.x), Math.round(p1.y)];
          
                  [x1, y1] = [Math.round(p0.x), Math.round(p0.y)];
          
              } else {
          
                  [x0, y0] = [Math.round(p0.x), Math.round(p0.y)];
          
                  [x1, y1] = [Math.round(p1.x), Math.round(p1.y)];
          
              }
          
              let m = (y1-y0)/(x1-x0);
          
              // 直線方程
          
              let f = (x,y) => (y0-y1)*x + (x1-x0)*y + x0*y1 - x1*y0;
          
              // 區間(0,1]
          
              if (m > 0 && m <= 1) {
          
                  let y = y0;
          
                  for (let x = x0; x < x1; x++) {
          
                      this.setPixel(x, y, color);
          
                      if (f(x+1, y+0.5) < 0) {
          
                          y += 1;
          
                      }
          
                  }
          
              }
          
              // 區間(1,正無窮)
          
              else if (m > 1) {
          
                  let x = x0;
          
                  for (let y = y0; y < y1; y++) {
          
                      this.setPixel(x, y, color);
          
                      if (f(x+0.5, y+1) > 0) {
          
                          x += 1;
          
                      }
          
                  }
          
              }
          
              // 區間(-1,0]
          
              else if (m > -1 && m <= 0) {
          
                  let y = y0;
          
                  for (let x = x0; x < x1; x++) {
          
                      this.setPixel(x, y, color);
          
                      if (f(x+1, y-0.5) > 0) {
          
                          y -= 1;
          
                      }
          
                  }
          
              }
          
              // 區間(負無窮,-1]
          
              else if (m <= -1) {
          
                  let x = x0;
          
                  for (let y = y0; y > y1; y--) {
          
                      this.setPixel(x, y, color);
          
                      if (f(x+0.5, y-1) < 0) {
          
                          x += 1;
          
                      }
          
                  }
          
              }
          
          }
          
          // (1,+∞)
          
          drawLine({x: 300, y: 590}, {x: 480, y: 190}, 'rgba(255, 0, 0, 1.0)');  
          
          // (-1,0]
          
          drawLine({x: 300, y: 400}, {x: 480, y: 380}, 'rgba(255, 255, 0, 1.0)');
          
          // (0,1]
          
          drawLine({x: 300, y: 380}, {x: 480, y: 400}, 'rgba(0, 0, 255, 1.0)');  
          
          // (-∞,-1]
          
          drawLine({x: 300, y: 190}, {x: 480, y: 590}, 'rgba(0, 255, 0, 1.0)');
          

          參考文獻

          [1]. 《fundamentals of computer graphics》9.1.1 line drawing, p179.

          制各種圖形步驟

          1 開始新路徑

          beginPath() 開始新路徑 繪制矩形和填充矩形可省略此步驟

          2 設置路徑

          moveTo(x,y) 移動起始點到x,y

          lineTo(x,y) 繪制目前端點到x,y的直線

          arc(x,y,r,startAngle,endAngle,antiClockwise) 繪制圓形或圓弧

          fillRect(x,y,width,height) 繪制填滿矩形

          strokeRect(x,y,width,height) 繪制輪廓矩形(只有邊框,不填充顏色)

          3 將路徑頭尾相連

          closePath() 關閉路徑

          4 將路徑繪制到canvas繪圖區

          Stroke() 繪制邊框

          Fill() 填充圖形



          繪制直線


          主站蜘蛛池模板: 丰满岳乱妇一区二区三区| 精品国产a∨无码一区二区三区| 91在线一区二区三区| 日本一区中文字幕日本一二三区视频| 国产精品亚洲一区二区三区久久| 国产精品自在拍一区二区不卡| 在线一区二区三区| 精品视频一区二区三三区四区| 日韩一区二区三区视频| 国模大胆一区二区三区| 欧美日本精品一区二区三区 | 日韩一区二区精品观看| 午夜福利无码一区二区| 欲色aV无码一区二区人妻 | 亚洲成人一区二区| 亚洲国产一区二区视频网站| 国产欧美一区二区精品仙草咪| 国产精品高清一区二区三区不卡 | 精品国产AⅤ一区二区三区4区 | 一区二区在线免费观看| 午夜影视日本亚洲欧洲精品一区| 风流老熟女一区二区三区| 91视频一区二区三区| 国产精品久久一区二区三区| 久热国产精品视频一区二区三区| 国产伦精品一区二区三区不卡| 欧亚精品一区三区免费| 久久国产一区二区三区| 成人区人妻精品一区二区不卡视频 | 日韩精品一区二区三区毛片| 亚洲综合无码一区二区痴汉| 日韩一区二区视频| 亚洲.国产.欧美一区二区三区 | 亚洲国产情侣一区二区三区| 精品国产鲁一鲁一区二区| 综合激情区视频一区视频二区| 四虎永久在线精品免费一区二区 | 一区二区三区免费视频观看| 69久久精品无码一区二区| 一区二区三区视频观看| 精品国产AⅤ一区二区三区4区 |