這篇文章主要為大家詳細介紹了JavaScript純色二維碼變成彩色二維碼的方案,具有一定的參考價值,有興趣的小伙伴們可以參考一下
本文章主要討論的是如何將一個純色二維碼變成彩色的。
前段時間公司業務上有這麼一個需求,客戶不喜歡後台生成的純色二維碼,純藍,純紫,純綠都不行,想要彩色二維碼。然後這個任務都落到我頭上了,因為是圖片處理,那主要思路就是靠canvas,canvas可以進行像素操作,所以我進行了一些嘗試,也踩了一點小坑,具體記錄如下。
前知識
drawImage方法可以把圖片畫到canvas上,getImageData 方法可以獲得一個矩形區域所有像素點的信息,返回值的data屬性是一個一維數組,儲存了所有像素點的信息,一個像素點的信息會佔四個元素,分別代表r,g,b和透明度。而像素點在一維數組中的順序是從左到右,從上到下。最後就是putImageData方法,把更改過的像素資訊陣列重新丟回畫布上。
一些小坑
第一個坑就是canvas用屬性去給寬高,別用css;
第二個坑,做圖片處理好像得伺服器環境,本地是不行的,聽說是基於什麼安全考慮,最後我是透過搭本地伺服器解決了canvas的報錯。
第三個坑,堆疊溢出,這個目前還沒找到原因,後面會詳細講
變色的思路
##主要想法來自於《啊哈!演算法! 》裡面深度優先搜尋和廣度優先搜尋的章節,該章節的最後一部分的「寶島探險」實現了給不同的區域依次編號,把編號看成染色,其實是一樣的。具體實現
其實所謂的彩色二維碼,不是那種每個像素點顏色隨機的二維碼。仔細觀察二維碼就會發現,黑色的部分是一塊一塊的,他們分佈在白色當中,就好像島嶼分佈在海裡,我們要做的就是把每個黑色塊單獨染色。黑色塊的實質就是一個一個黑色的像素點。 前面也提到,我們使用canvas是因為可以進行像素操作,所以我們的操作其實是給像素點染色,我們顯然不希望給背景色染色,所以背景色需要進行一個判斷;前面也提到,背景色好像海洋分割了黑色的色塊,那也就是說我們讀一個像素點進行染色之後,不停的判斷它右側的像素點顏色,當出現背景色的時候就說明到達了邊界,可以停止右方向的染色,但是每個像素點其實有四個相連接的方向,當一個像素點右邊就是背景色,我們應該也去嘗試別的方向的可能性,這個就是深度優先搜索,透過遞歸,不斷的驗證當前像素點的下一個位置的顏色,是背景色,那就回來,嘗試別的方向;不是背景色,那就染色,然後對染色之後的這個像素點進行四個方向的驗證。 有幾點提一下,判斷是不是背景色,肯定得比對rgba的值,所以顏色參數得做處理,另一個就是像素點資訊的數組,每四個元素代表一個像素,所以想要比對正確的像素訊息,這部分也要處理。可能說的有點亂,我們看一下程式碼
第一部分,canvas
// canvas 部分 var canvas = $("canvas")[0]; var ctx = canvas.getContext("2d"); var img = new Image(); img.src = path; //这里的path就是图片的地址
第二部分,顏色的處理
// 分离颜色参数 返回一个数组 var colorRgb = (function() { var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/; return function(str) { var sColor = str.toLowerCase(); if (sColor && reg.test(sColor)) { if (sColor.length === 4) { var sColorNew = "#"; for (var i = 1; i < 4; i += 1) { sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1)); } sColor = sColorNew; } //处理六位的颜色值 var sColorChange = []; for (var i = 1; i < 7; i += 2) { sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2))); } return sColorChange; } else { var sColorChange = sColor.replace(/(rgb\()|(\))/g, "").split(",").map(function(a) { return parseInt(a); }); return sColorChange; } } })();
第三部分,給初始參數
為了避免多餘的操作,我們用一個標記數組來記錄判斷過的位置// 参数 var bg = colorRgb("#fff"); //忽略的背景色 var width = 220; var height = 220; var imgD; //预留给 像素信息 var colors = ["#368BFF", "#EF2767", "#F17900", "#399690", "#5aa6f7", "#fd417e", "#ffc000", "#59b6a6"]; //染色数组 // 随机colors数组的一个序号 var ranNum = (function() { var len = colors.length; return function() { return Math.floor(Math.random() * len); } })(); // 标记数组 var book = []; for (var i = 0; i < height; i++) { book[i] = []; for (var j = 0; j < width; j++) { book[i][j] = 0; } }
第四部分,取得像素訊息,對每個像素點進行遍歷處理,最後扔回canvas
#如果標記過,那就跳過,如果沒標記過,那就隨機一個色,深度優先搜尋並染色img.onload = function() { ctx.drawImage(img, 0, 0, width, height); imgD = ctx.getImageData(0, 0, width, height); for (var i = 0; i < height; i++) { for (var j = 0; j < width; j++) { if (book[i][j] == 0 && checkColor(i, j, width, bg)) { //没标记过 且是非背景色 book[i][j] = 1; var color = colorRgb(colors[ranNum()]); dfs(i, j, color); //深度优先搜索 } } } ctx.putImageData(imgD, 0, 0); } // 验证该位置的像素 不是背景色为true function checkColor(i, j, width, bg) { var x = calc(width, i, j); if (imgD.data[x] != bg[0] && imgD.data[x + 1] != bg[1] && imgD.data[x + 2] != bg[2]) { return true; } else { return false; } } // 改变颜色值 function changeColor(i, j, colorArr) { var x = calc(width, i, j); imgD.data[x] = colorArr[0]; imgD.data[x + 1] = colorArr[1]; imgD.data[x + 2] = colorArr[2]; } // 返回对应像素点的序号 function calc(width, i, j) { if (j < 0) { j = 0; } return 4 * (i * width + j); }
關鍵程式碼
我們透過一個方向數組,來簡化一下操作,我們約定好,嘗試的方向為順時針,從右邊開始。// 方向数组 var next = [ [0, 1], //右 [1, 0], //下 [0, -1], // 左 [-1, 0] //上 ]; // 深度优先搜索 function dfs(x, y, color) { changeColor(x, y, color); for (var k = 0; k <= 3; k++) { // 下一个坐标 var tx = x + next[k][0]; var ty = y + next[k][1]; //判断越界 if (tx < 0 || tx >= height || ty < 0 || ty >= width) { continue; } if (book[tx][ty] == 0 && checkColor(tx, ty, width, bg)) { // 判断位置 book[tx][ty] = 1; dfs(tx, ty, color); } } return; }我遇到的最後一個坑就是當長寬大於220時就會棧溢出,但是小於這個值就不會有問題,具體的原因還不清楚,猜測可能是判斷那裡有問題,導致死循環了。 全部程式碼在這裡
// 分离颜色参数 返回一个数组 var colorRgb = (function() { var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/; return function(str) { var sColor = str.toLowerCase(); if (sColor && reg.test(sColor)) { if (sColor.length === 4) { var sColorNew = "#"; for (var i = 1; i < 4; i += 1) { sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1)); } sColor = sColorNew; } //处理六位的颜色值 var sColorChange = []; for (var i = 1; i < 7; i += 2) { sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2))); } return sColorChange; } else { var sColorChange = sColor.replace(/(rgb\()|(\))/g, "").split(",").map(function(a) { return parseInt(a); }); return sColorChange; } } })(); // 验证该位置的像素 不是背景色为true function checkColor(i, j, width, bg) { var x = calc(width, i, j); if (imgD.data[x] != bg[0] && imgD.data[x + 1] != bg[1] && imgD.data[x + 2] != bg[2]) { return true; } else { return false; } } // 改变颜色值 function changeColor(i, j, colorArr) { var x = calc(width, i, j); imgD.data[x] = colorArr[0]; imgD.data[x + 1] = colorArr[1]; imgD.data[x + 2] = colorArr[2]; } // 返回对应像素点的序号 function calc(width, i, j) { if (j < 0) { j = 0; } return 4 * (i * width + j); } // 方向数组 var next = [ [0, 1], //右 [1, 0], //下 [0, -1], // 左 [-1, 0] //上 ]; // 深度优先搜索 function dfs(x, y, color) { changeColor(x, y, color); for (var k = 0; k <= 3; k++) { // 下一个坐标 var tx = x + next[k][0]; var ty = y + next[k][1]; //判断越界 if (tx < 0 || tx >= height || ty < 0 || ty >= width) { continue; } if (book[tx][ty] == 0 && checkColor(tx, ty, width, bg)) { // 判断位置 book[tx][ty] = 1; dfs(tx, ty, color); } } return; } /*****上面为封装的函数*****/ /***参数***/ var bg = colorRgb("#fff"); //忽略的背景色 var width = 220; var height = 220; var imgD; //预留给 像素信息数组 var colors = ["#368BFF", "#EF2767", "#F17900", "#399690", "#5aa6f7", "#fd417e", "#ffc000", "#59b6a6"]; //染色数组 // 随机colors数组的一个序号 var ranNum = (function() { var len = colors.length; return function() { return Math.floor(Math.random() * len); } })(); // 标记数组 var book = []; for (var i = 0; i < height; i++) { book[i] = []; for (var j = 0; j < width; j++) { book[i][j] = 0; } } // canvas 部分 var canvas = $("canvas")[0]; var ctx = canvas.getContext("2d"); var img = new Image(); img.src = path; //这里的path就是图片的地址 img.onload = function() { ctx.drawImage(img, 0, 0, width, height); imgD = ctx.getImageData(0, 0, width, height); for (var i = 0; i < height; i++) { for (var j = 0; j < width; j++) { if (book[i][j] == 0 && checkColor(i, j, width, bg)) { //没标记过 且是非背景色 book[i][j] = 1; var color = colorRgb(colors[ranNum()]); dfs(i, j, color); //深度优先搜索 } } } ctx.putImageData(imgD, 0, 0); }
總結
雖然看起來有點長,但其實大部分函數都在處理像素點的資訊。實現起來,主要就是得對深度優先搜索有所了解,每個像素點都進行深度優先搜索,染過色的自然被標記過,所以當一個新的沒標記過的像素點出現時,自然意味著新的色塊。細節方面,就是注意一下imgD.data和像素點序號之間的對應關係,別的也就還好了。不過要注意一點就是,因為像素點很小,所以肉眼覺得不相連的色塊也有可能是連在一起的,會染成一樣的顏色。
忘了放圖了,這裡放幾張,拿qq截的,把外面的邊框不小心也截了,嘛,湊活看看吧
以上是如何將純色二維碼變成彩色二維碼(JavaScript)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

WebStorm Mac版
好用的JavaScript開發工具

Dreamweaver CS6
視覺化網頁開發工具