canvas 繪製雙線技巧

来源:https://www.cnblogs.com/flyfox1982/archive/2018/12/20/10149735.html
-Advertisement-
Play Games

楔子 最近一個項目,需要繪製雙線的效果,雙線效果表示的是軌道(類似鐵軌之類的),如下圖所示: 負責這塊功能開發的小伙,姑且稱之為L吧,最開始是通過數學計算的方式來實現這種雙線,也就是在原來的路徑的基礎上,計算出兩條路徑。但是這個過程的計算算挺複雜,而是最終實現的效果很耗性能,性能損耗估計主要在於路徑 ...


楔子

最近一個項目,需要繪製雙線的效果,雙線效果表示的是軌道(類似鐵軌之類的),如下圖所示:

負責這塊功能開發的小伙,姑且稱之為L吧,最開始是通過數學計算的方式來實現這種雙線,也就是在原來的路徑的基礎上,計算出兩條路徑。但是這個過程的計算算挺複雜,而是最終實現的效果很耗性能,性能損耗估計主要在於路徑的計算上。

優化技巧

後來他找到我來看這個問題,我在分析了項目背景的情況下,給予了一個簡單的繪製技巧,就是先用較粗的線條繪製路徑,然後再用較細的線條繪製路徑,較細線條的顏色正好是背景顏色。
之所以能夠使用這個技巧,是因為該項目的繪製背景是純色的,而不是漸變色或者圖片。
示例代碼如下:

               ctx.beginPath();
               ctx.fillStyle = 'blue';
               ctx.rect(10,10,1000,1000);
               ctx.fill();

              ctx.save();
              ctx.strokeStyle = 'red';
              ctx.lineWidth = 10;
              ctx.lineCap = "round";
              ctx.beginPath();
              ctx.moveTo(200,100);  //起始點
              ctx.lineTo(400,100);
              ctx.quadraticCurveTo(500,100,500,200);   
              ctx.lineTo(500,400);
              ctx.quadraticCurveTo(500,500,400,500);   
              ctx.lineTo(200,500);
              ctx.quadraticCurveTo(100,500,100,400);   
              ctx.lineTo(100,200);
              ctx.quadraticCurveTo(100,100,200,100);   
              ctx.stroke();

              ctx.strokeStyle= 'blue'
              ctx.lineWidth = 4;
              ctx.stroke();
              ctx.restore();
              

代碼的思路是,首先使用純色blue繪製了一個背景,然後使用線條顏色red繪製一條線,然後使用較小的線寬,並把線條顏色改成背景顏色blue,繪製另外一個條線段。最終的繪製效果如下:

double_line
double_line

到此,項目的這個技術難點問題,算是被解決了。這種解決方法,不僅演算法簡單,不用構思數學方法來構造雙線,而且輕量,不會有性能負擔。

背景不是純色的情況

前面說到:之所以能夠使用這個技巧,是因為該項目的繪製背景是純色的,而不是漸變色或者圖片。
那如果背景是圖片或者漸變顏色的情況下,用這種技巧,肯定就是失效的了。

之所以會思考這個問題,是得益於公司的技術分享會。我會要求員工定期組織分享會,分享一些經驗。在此打個小廣告,可以看出我們公司的技術氛圍是很好的,所以有興趣的小伙伴可以抓緊時間投簡歷。怎麼投簡歷呢,關註微信號ITman彪叔。
過程中,當時小伙伴L也分享了前面提到這種思路。在分享的過程中,我提出了進一步的問題,如果背景不是純色,而是漸變色或者圖片怎麼辦?並且靈感乍現,想到了一個解決方法,就是使用ctx.globalCompositeOperation。

有關globalCompositeOperation的說明,可以參考如下鏈接的說明:
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
http://www.w3school.com.cn/tags/canvas_globalcompositeoperation.asp

globalCompositeOperation的定義和用法

globalCompositeOperation 屬性設置或返回如何將一個源(新的)圖像繪製到目標(已有)的圖像上。其中:

  • 源圖像 = 您打算放置到畫布上的繪圖。
  • 目標圖像 = 您已經放置在畫布上的繪圖
    下圖顯示了globalCompositeOperation的不同的值的解釋:


    globalCompositeOperation的不同的值的解釋
    globalCompositeOperation的不同的值的解釋

要實現雙線的繪製,就要求用同樣的路徑,不同的線寬繪製兩條線路
(我們稱之為目標線路和源線路)。並要達到一條線路摳出另外一條線路的效果。
結合上圖,我們可以看出destination-out,source-out,xor可以達到效果。下麵以destination-out舉例說明。

destination-out繪製原理說明

比如首先通過 css 設置背景圖,並去掉繪製背景顏色,代碼如下:

 <body onload="init()" style="background: url(../test/images/diffuse.png);">

然後繪製代碼如下:

  ctx.save();
              ctx.strokeStyle = 'red';
              ctx.lineWidth = 10;
              ctx.lineCap = "round";
              ctx.beginPath();
              ctx.moveTo(200,100);  //起始點
              ctx.lineTo(400,100);
              ctx.quadraticCurveTo(500,100,500,200);   
              ctx.lineTo(500,400);
              ctx.quadraticCurveTo(500,500,400,500);   
              ctx.lineTo(200,500);
              ctx.quadraticCurveTo(100,500,100,400);   
              ctx.lineTo(100,200);
              ctx.quadraticCurveTo(100,100,200,100);   
              ctx.stroke();

              
              ctx.globalCompositeOperation = 'destination-out';
              ctx.lineWidth = 4;
              ctx.stroke();
              ctx.restore();

首先設置路徑,然後設置線寬為10,調用stroke方法繪製一條線寬為10的路線A。
之後設置globalCompositeOperation為 'destination-out',調整線寬為4,調用stroke方法繪製一條線寬為4的路線B。
看下destination-out的解釋:

在源圖像外顯示目標圖像。只有源圖像外的目標圖像部分會被顯示,源圖像是透明的。

繪製了線路A的canvas圖像是目標圖像,線路B是源圖像。根據上面解釋,只有源圖像之外的目標圖像能夠被顯示。最終繪製的效果如下:

 

destination-out.png
destination-out.png

xor 和 source-out

把上面的代碼的globalCompositeOperation修改成xor,發現效果也是可以的,xor的解釋如下:

使用異或操作對源圖像與目標圖像進行組合。 英文解釋如下:
Shapes are made transparent where both overlap and drawn normal everywhere else.

意思源和目標的像素重疊(overlap)的部分會被變成透明像素,其他部分正常繪製。 所以上面示例中,線條A和線條B重疊的部分會被變成透明。繪製的效果也是線條A的被挖空。

對於source-out,其效果正好和destination-out的效果相反:

在目標圖像之外顯示源圖像。只會顯示目標圖像之外源圖像部分,目標圖像是透明的。

應此只需要取反操作即可,先用寬度4繪製線條A,然後用寬度10繪製線條B,其結果也是一樣的。

背景不是純色的情況2

前面的背景是通過css的方式設置上去的,如果是通過canvas的drawImage直接繪製上去,效果就不一樣了。還是以destination-out為例說明,首先繪製了image,然後繪製線路A,此時的目標圖像不在是線路A組成的圖形,而是image和線路A組合成的圖形,此時用destination-out的方式繪製線路B,不僅會挖空線路A,背景也會被挖空,如下圖所示:

背景不是純色的情況2
背景不是純色的情況2

應此要想達到真正的雙線效果,要麼背景只能是用css設置,要麼用兩個canvas疊加,一個繪製背景圖片,一個繪製路徑。
當然還有一種方式,就是繪製雙線總是在一個臨時的canvas上面進行,然後把這個臨時的canvas繪製結果再次繪製到工作canvas上面,相關實踐留給讀者自己進行。

後記

在網路上面搜索canvas double line,搜索到stackoverflow上的一條結果如下:
https://stackoverflow.com/questions/13441610/double-line-stroke-in-html5-canvas
其中的答案也是採用了globalCompositeOperation設置為destination-out的方式。

歡迎關註公眾號“ITman彪叔”。彪叔,擁有10多年開發經驗,現任公司系統架構師、技術總監、技術培訓師、職業規劃師。熟悉Java、JavaScript、Python語言,熟悉資料庫。熟悉java、nodejs應用系統架構,大數據高併發、高可用、分散式架構。在電腦圖形學、WebGL、前端可視化方面有深入研究。對程式員思維能力訓練和培訓、程式員職業規劃有濃厚興趣。

 

ITman彪叔公眾號
ITman彪叔公眾號
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 聲明 本篇內容摘抄自以下來源: "TypeScript 中文網" 只梳理其中部分知識點,更多更詳細內容參考官網。 正文 TypeScript 今天來講講有 Java 基礎轉 JavaScript 的福音:TypeScript 為什麼學習 TypeScript 如果學習 JavaScript 之前已經 ...
  • 作用:<!DOCTYPE> 聲明幫助瀏覽器正確地顯示網頁。 <!DOCTYPE> 聲明 Web 世界中存在許多不同的文檔。只有瞭解文檔的類型,瀏覽器才能正確地顯示文檔。 HTML 也有多個不同的版本,只有完全明白頁面中使用的確切 HTML 版本,瀏覽器才能完全正確地顯示出 HTML 頁面。這就是 < ...
  • 在javascript中,對象與數組都是這門語言的原生規範中的基本數據類型,處於併列的位置。 類數組:本質是一個對象,只是這個 對象 的屬性有點特殊,模擬出數組的一些特性。 一般來說,如果我們有一個對象obj和一個數組a: obj["attr1"]; //取obj對象的attr1屬性 a[1]; / ...
  • 1.問題出現的場景與解決 實現一個登錄攔截器,重寫doFilter方法,判斷用戶的登錄狀態,在用戶長時間未操作或者異地登錄時前端進行提示,完整代碼如下 第30-31行返回給前端返回提示信息,通過url進行傳參進行提示,前端頁面再進行獲取,往往會出現亂碼和刷新頁面數據還在的問題, 考慮通過後端方式給前 ...
  • JavaScript中易混淆的DOM屬性及方法對比 ParentNode.children VS Node.prototype.childNodes :該屬性繼承自 ,返回值是一個 實例,成員是當前節點的所有 元素子節點 ,該屬性只讀,且該屬性是動態集合。 :該屬性繼承自 ,返回值是一個 實例,成員 ...
  • Array 對象方法 可以改變原數組的方法: 1) pop() 用於刪除並返回數組的最後一個元素。 語法 arrayObject.pop() 返回值 arrayObject 的最後一個元素。 2) push() 可向數組的末尾添加一個或多個元素,並返回新的長度。 語法 arrayObject.pus ...
  • 能力檢測 在編寫代碼之前先檢測特定瀏覽器的能力。例如,腳本在調用某個函數之前,可能要先檢測該函數首付存在。這種檢測方法將開發人員從考慮具體的瀏覽器類型和版本中解放出來,讓他們把註意力集中到相應的能力是否存在上。能力檢測無法精確地檢測特定的瀏覽器和版本。 怪癖檢測 怪癖實際上是瀏覽器實現中存在的bug ...
  • <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...