The project requires the extraction of similar colors in canvas, and the specific requirements will not be introduced. Here we will draw out and write an article separately.
In the project, the canvas here is actually a map. In order to demonstrate, there is no need to go on the map. You can directly load a picture with canvas
const canvasDom = document.createElement("canvas") canvasDom.width = 1200 canvasDom.height = 960 document.body.appendChild(canvasDom) const ctx = canvasDom.getContext("2d") const img = new Image() img.onload = () => { ctx.drawImage(img, 0, 0) } img.src = 'show.jpg'
The picture here is one I found casually from Baidu picture, picture address
https://www.vcg.com/creative/1030316923
Then it is to specify the color used to compare similar colors. Here you can directly specify a color, but according to the normal idea, this color should be extracted from canvas. We can frame an area and calculate the average color value in this area. Here we need to use the frame selection function I wrote before again
https://blog.csdn.net/luoluoyang23/article/details/122653363
Remember to add a button to move the binding event. Here you can also refer to the code in my last article
https://blog.csdn.net/luoluoyang23/article/details/122763696
See the effect
The next step is to extract the real color value, which is used to extract the rgb value of a point on the canvas
canvasDom.getContext('2d').getImageData(PointX, PointY, 1, 1).data
Here, we frame a region, cycle through all the points in the selected region, and then directly calculate the average RGB of these points (although this may not be reasonable). In my last article, the box selection function provides four values: the XY coordinate of the starting point of the box selection, the box width and the box height. Here, we write these four values into an array and pass it to our function to calculate the average value, as follows
const list = [canvasX, canvasY, canvasWidth, canvasHeight]
Our function for calculating the average value is directly based on this array to facilitate all points in the box
function computedSquareColor(canvasDom, selectedArea) { const ctx = canvasDom.getContext('2d') //Traverse all pixels in the region, calculate the rgb value, and get the average rgb value let colorR = 0 let colorG = 0 let colorB = 0 for (let i = 0; i < selectedArea[2]; i++) { for (let j = 0; j < selectedArea[3]; j++) { let mapPointX = selectedArea[0] + i let mapPointY = selectedArea[1] + j let colorArray = ctx.getImageData(mapPointX, mapPointY, 1, 1).data colorR += colorArray[0] colorG += colorArray[1] colorB += colorArray[2] } } colorR = Math.ceil(colorR / (selectedArea[2] * selectedArea[3])) colorG = Math.ceil(colorG / (selectedArea[2] * selectedArea[3])) colorB = Math.ceil(colorB / (selectedArea[2] * selectedArea[3])) return [colorR, colorG, colorB] }
The location of the code can refer to my previous article. Now let's see the effect
Note that cross domain errors may be reported here. Refer to the following articles for solutions
https://blog.csdn.net/Treeee_/article/details/111996118
Check whether this color is the color we extracted
It is indeed the color near the sky. For better observation, we add a node next to it to display this color
Well, the next step is to extract the approximate color. It still depends on our selection. Select the color of each point in the reading box, and then compare it. The points that meet the approximate color conditions will be marked. What are the conditions for the approximate color? Here is an article for reference
https://www.jianshu.com/p/a13b96d00c71
It is very detailed about approximate color. Thank you for your great article. However, the original text is written in C + +, and we need to rewrite it into JavaScript
//For LAB model calculation const param_1_3 = 1.0 / 3.0 const param_16_116 = 16.0 / 116.0 const Xn = 0.950456 const Yn = 1.0 const Zn = 1.088754 //Color correction function gamma(colorX) { //Determine whether it is not a floating point number if (~~colorX === colorX) { colorX = parseFloat(colorX + '') } return colorX > 0.04045 ? Math.pow((colorX + 0.055) / 1.055, 2.4) : colorX / 12.92 } //Convert RGB to XYZ function RGB2XYZ(colorR, colorG, colorB) { let colorX = 0.4124564 * colorR + 0.3575761 * colorG + 0.1804375 * colorB let colorY = 0.2126729 * colorR + 0.7151522 * colorG + 0.072175 * colorB let colorZ = 0.0193339 * colorR + 0.119192 * colorG + 0.9503041 * colorB return [colorX, colorY, colorZ] } //XYZ to LAB function XYZ2LAB(colorX, colorY, colorZ) { colorX = colorX / Xn colorY = colorY / Yn colorZ = colorZ / Zn let fX = colorX > 0.008856 ? Math.pow(colorX, param_1_3) : 7.787 * colorX + param_16_116 let fY = colorY > 0.008856 ? Math.pow(colorY, param_1_3) : 7.787 * colorY + param_16_116 let fZ = colorZ > 0.008856 ? Math.pow(colorZ, param_1_3) : 7.787 * colorZ + param_16_116 let colorL = parseFloat('116') * fY - parseFloat('16') colorL = colorL > parseFloat('0.0') ? colorL : parseFloat('0.0') let colorA = parseFloat('500') * (fX - fY) let colorB = parseFloat('200') * (fY - fZ) return [colorL, colorA, colorB] } //Chromaticity calculation function computeCaidu(colorA, colorB) { return Math.pow(colorA * colorA + colorB * colorB, 0.5) } //Hue angle calculation function computeSeDiaoJiao(colorA, colorB) { if (colorA === 0) return 90 const h = (180 / Math.PI) * Math.atan(colorB / colorA) let hab if (colorA > 0 && colorB > 0) { hab = h } else if (colorA < 0 && colorB > 0) { hab = 180 + h } else if (colorA < 0 && colorB < 0) { hab = 180 + h } else { hab = 360 + h } return hab } //Compare the approximation of color value and use CIEDE2000 color difference formula function differenceColor(firstColor, secondColor) { let L1 = firstColor[0] let A1 = firstColor[1] let B1 = firstColor[2] let L2 = secondColor[0] let A2 = secondColor[1] let B2 = secondColor[2] //Principle and application of modern color technology p88 reference constant let delta_LL, delta_CC, delta_hh, delta_HH let kL, kC, kH let SL, SC, SH, T kL = parseFloat('1') kC = parseFloat('1') kH = parseFloat('1') let mean_Cab = (computeCaidu(A1, B1) + computeCaidu(A2, B2)) / 2 let mean_Cab_pow7 = Math.pow(mean_Cab, 7) //When the weight and color value change regularly, the human eye observation is not regular, because the human eye perceives the color of different channels differently. Increasing the weight can alleviate this problem let G = 0.5 * (1 - Math.pow(mean_Cab_pow7 / (mean_Cab_pow7 + Math.pow(25, 7)), 0.5)) let LL1 = L1 let aa1 = A1 * (1 + G) let bb1 = B1 let LL2 = L2 let aa2 = A2 * (1 + G) let bb2 = B2 let CC1 = computeCaidu(aa1, bb1) let CC2 = computeCaidu(aa2, bb2) let hh1 = computeSeDiaoJiao(aa1, bb1) let hh2 = computeSeDiaoJiao(aa2, bb2) delta_LL = LL1 - LL2 delta_CC = CC1 - CC2 delta_hh = computeSeDiaoJiao(aa1, bb1) - computeSeDiaoJiao(aa2, bb2) delta_HH = 2 * Math.sin((Math.PI * delta_hh) / 360) * Math.pow(CC1 * CC2, 0.5) //Calculate weighting function let mean_LL = (LL1 + LL2) / 2 let mean_CC = (CC1 + CC2) / 2 let mean_hh = (hh1 + hh2) / 2 SL = 1 + (0.015 * Math.pow(mean_LL - 50, 2)) / Math.pow(20 + Math.pow(mean_LL - 50, 2), 0.5) SC = 1 + 0.045 * mean_CC T = 1 - 0.17 * Math.cos(((mean_hh - 30) * Math.PI) / 180) + 0.24 * Math.cos((2 * mean_hh * Math.PI) / 180) + 0.32 * Math.cos(((3 * mean_hh + 6) * Math.PI) / 180) - 0.2 * Math.cos(((4 * mean_hh - 63) * Math.PI) / 180) SH = 1 + 0.015 * mean_CC * T //Calculate RT let mean_CC_pow7 = Math.pow(mean_CC, 7) let RC = 2 * Math.pow(mean_CC_pow7 / (mean_CC_pow7 + Math.pow(25, 7)), 0.5) let delta_xita = 30 * Math.exp(-Math.pow((mean_hh - 275) / 25, 2)) let RT = -Math.sin((2 * delta_xita * Math.PI) / 180) * RC let L_item, C_item, H_item L_item = delta_LL / (kL * SL) C_item = delta_CC / (kC * SC) H_item = delta_HH / (kH * SH) //Reference constant E00 return Math.pow( L_item * L_item + C_item * C_item + H_item * H_item + RT * C_item * H_item, 0.5 ) } //Calculate the approximation of two RGB colors, and only call the methods in this file, which only provides a reference for the calling process function differenceRGB(rgbA, rgbB) { let xyzA = RGB2XYZ(...rgbA) let xyzB = RGB2XYZ(...rgbB) let labA = XYZ2LAB(...xyzA) let labB = XYZ2LAB(...xyzB) return differenceColor(labA, labB) }
I won't introduce the source code in detail here. The principle is still suggested to look at the link I put above. Here, I just rewrite the C + + code into JavaScript code.
When using, just call the last function in the above code directly. Pass in two arrays. The rgb value ([R, G, B]) in the array
I'll write it as a separate file and then reference it in html
Add a button to frame the selection range (css writes as you like)
<button id="screen-square">Box selection range</button>
Then, because the function of the screenshot box needs to be used again, it is recommended to separate this function here. At the end of the screenshot, the function of the screenshot box needs to give a signal back, and then carry out other operations. At this time, it is best to use the callback function. It is easy to use here, and an interval is directly used to monitor whether the screenshot is completed
let screenFinished = false document.getElementById('screen-button').addEventListener('click', (e) => { screenEvent(e) const colorInterval = setInterval(() => { if (screenFinished) { const list = [canvasX, canvasY, canvasWidth, canvasHeight] selectColor = computedSquareColor(canvasDom, list) document.getElementById("show-color").style.backgroundColor = 'rgb(' + selectColor + ')' clearInterval(colorInterval) } }, 100) }) ··· function screenEvent(e) { screenFinished = false const mousedownEvent = (e) => { ··· window.removeEventListener("mousedown", mousedownEvent) document.body.removeChild(divDom) screenFinished = true ···
Pay attention to the location of the code
Now, we will directly write the click event of the selection range
document.getElementById('screen-square').addEventListener('click', (e) => { screenEvent(e) const squareInterval = setInterval(() => { if (screenFinished) { const squareDom = document.createElement('canvas') squareDom.width = canvasWidth squareDom.height = canvasHeight squareDom.style.position = 'absolute' squareDom.style.left = canvasX squareDom.style.top = canvasY document.body.appendChild(squareDom) const list = [canvasX, canvasY, canvasWidth, canvasHeight] squareAnalyse(canvasDom, squareDom, list) clearInterval(squareInterval) } }, 100) })
The code here adds a canvas box to draw the recognition result
So our last job is to write the squareanalyze function, which mainly completes these things
1. Traverse the rgb values of all points within the selected range, calculate the color value approximation with the selected color, and filter the qualified points
2. Draw the qualified points in 1 on the new canvas for display to the user
This function has certain proficiency requirements for the application of canvas. I won't expand it in detail here. I'm also a half bucket of water myself. Go directly to the source code first
async function squareAnalyse(canvasDom, squareDom, selectedArea) { let alw = 15 let stride = 1 //canvasDom is the original drawing, and squareDom is used to draw let squareCtx = squareDom.getContext('2d') let ctx = canvasDom.getContext('2d') //Fill background squareCtx.fillStyle = 'rgba(255,255,255,0.5)' squareCtx.fillRect(0, 0, canvasWidth, canvasHeight) let squareImgData = squareCtx.getImageData(0, 0, canvasWidth, canvasHeight) await areaTotalAnalyse() squareCtx.putImageData(squareImgData, 0, 0) //The whole area is identified as a whole, and the whole area will be marked in the form of face async function areaTotalAnalyse() { for (let i = 0; i < selectedArea[2]; i += stride) { for (let j = 0; j < selectedArea[3]; j += stride) { let mapPointX = selectedArea[0] + i let mapPointY = selectedArea[1] + j let rgbaArray = ctx.getImageData(mapPointX, mapPointY, 1, 1).data let rgbArray = [rgbaArray[0], rgbaArray[1], rgbaArray[2]] if ((await differenceRGB(selectColor, rgbArray)) <= alw) { squareImgData = await editCanvas(i, j, canvasWidth, squareImgData) } } console.log('In circulation') } return squareImgData } //Change canvas pixel color async function editCanvas(i, j, canvasWidth, squareImgData) { squareImgData.data[4 * j * canvasWidth + 4 * i] = 0 squareImgData.data[4 * j * canvasWidth + 4 * i + 1] = 0 squareImgData.data[4 * j * canvasWidth + 4 * i + 2] = 0 squareImgData.data[4 * j * canvasWidth + 4 * i + 3] = 255 return squareImgData } }
There are two variables at the top. Alw is the tolerance, which is related to the calculation of color value approximation. Generally speaking, if alw is 15, there is a 15% difference between the color values of the two colors allowed to be compared.
Stripe is the stride, which defaults to 1
In this way, let's finish this function. Now let's see the effect
Here, a part of the tree is intercepted, and then the road extending on the right is selected. Under the tolerance of 15, the final test result is still good, and a similar part of the color is successfully extracted (because there are shadows, there are more blank parts, which is only the comparison of color values, not AI)
Another thing to note is that the range of our screenshot is clientX taken directly, and we have added a row of buttons at the top of the page, so at this time, the position coordinates can not accurately correspond to the corresponding point coordinates on the canvas. You should pay attention to your own treatment, subtract the distance above, and here is the complete code
<!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>Document</title> <style> html, body { margin: 0; padding: 0; } #screenshot { border: 3px solid white; } #box { width: 100%; height: 5px; } #screen-button { float: left; } #show-color { float: left; width: 20px; height: 20px; margin-top: 2px; margin-left: 10px; margin-right: 10px; } </style> </head> <body> <div id="box"> <button id="screen-button">Extract color</button> <div id="show-color"></div> <button id="screen-square">Box selection range</button> </div> <br /> <script src='colorTool.js'></script> <script> let canvasWidth, canvasHeight let canvasX, canvasY let selectColor let screenFinished = false const canvasDom = document.createElement("canvas") canvasDom.width = 1000 canvasDom.height = 800 document.body.appendChild(canvasDom) const ctx = canvasDom.getContext("2d") const img = new Image() img.onload = () => { ctx.drawImage(img, 0, 0) } img.src = 'show.jpg' document.getElementById('screen-button').addEventListener('click', (e) => { screenEvent(e) const colorInterval = setInterval(() => { if (screenFinished) { const list = [canvasX, canvasY, canvasWidth, canvasHeight] selectColor = computedSquareColor(canvasDom, list) document.getElementById("show-color").style.backgroundColor = 'rgb(' + selectColor + ')' clearInterval(colorInterval) } }, 100) }) document.getElementById('screen-square').addEventListener('click', (e) => { screenEvent(e) const squareInterval = setInterval(() => { if (screenFinished) { const squareDom = document.createElement('canvas') squareDom.width = canvasWidth squareDom.height = canvasHeight squareDom.style.position = 'absolute' squareDom.style.left = canvasX squareDom.style.top = canvasY document.body.appendChild(squareDom) const list = [canvasX, canvasY, canvasWidth, canvasHeight] squareAnalyse(canvasDom, squareDom, list) clearInterval(squareInterval) } }, 100) }) function screenEvent(e) { screenFinished = false const mousedownEvent = (e) => { const [startX, startY] = [e.clientX, e.clientY] const divDom = document.createElement("div") divDom.id = 'screenshot' divDom.width = '1px' divDom.height = '1px' divDom.style.position = "absolute" canvasX = startX canvasY = startY divDom.style.top = startY + "px" divDom.style.left = startX + "px" document.body.appendChild(divDom) const moveEvent = (e) => { const moveX = e.clientX - startX const moveY = e.clientY - startY if (moveX > 0) { divDom.style.width = moveX + 'px' canvasWidth = moveX } else { divDom.style.width = -moveX + 'px' divDom.style.left = e.clientX + 'px' canvasWidth = -moveX canvasX = e.clientX } if (moveY > 0) { divDom.style.height = moveY + 'px' canvasHeight = moveY } else { divDom.style.height = -moveY + 'px' divDom.style.top = e.clientY + 'px' canvasHeight = -moveY canvasY = e.clientY } } window.addEventListener("mousemove", moveEvent) window.addEventListener("mouseup", () => { window.removeEventListener("mousemove", moveEvent) window.removeEventListener("mousedown", mousedownEvent) document.body.removeChild(divDom) screenFinished = true }) } window.addEventListener("mousedown", mousedownEvent) } function computedSquareColor(canvasDom, selectedArea) { const ctx = canvasDom.getContext('2d') //Traverse all pixels in the region, calculate the rgb value, and get the average rgb value let colorR = 0 let colorG = 0 let colorB = 0 for (let i = 0; i < selectedArea[2]; i++) { for (let j = 0; j < selectedArea[3]; j++) { let mapPointX = selectedArea[0] + i let mapPointY = selectedArea[1] + j let colorArray = ctx.getImageData(mapPointX, mapPointY, 1, 1).data colorR += colorArray[0] colorG += colorArray[1] colorB += colorArray[2] } } colorR = Math.ceil(colorR / (selectedArea[2] * selectedArea[3])) colorG = Math.ceil(colorG / (selectedArea[2] * selectedArea[3])) colorB = Math.ceil(colorB / (selectedArea[2] * selectedArea[3])) return [colorR, colorG, colorB] } async function squareAnalyse(canvasDom, squareDom, selectedArea) { let alw = 15 let stride = 1 //canvasDom is the original drawing, and squareDom is used to draw let squareCtx = squareDom.getContext('2d') let ctx = canvasDom.getContext('2d') //Fill background squareCtx.fillStyle = 'rgba(255,255,255,0.5)' squareCtx.fillRect(0, 0, canvasWidth, canvasHeight) let squareImgData = squareCtx.getImageData(0, 0, canvasWidth, canvasHeight) await areaTotalAnalyse() squareCtx.putImageData(squareImgData, 0, 0) //The whole area is identified as a whole, and the whole area will be marked in the form of face async function areaTotalAnalyse() { for (let i = 0; i < selectedArea[2]; i += stride) { for (let j = 0; j < selectedArea[3]; j += stride) { let mapPointX = selectedArea[0] + i let mapPointY = selectedArea[1] + j let rgbaArray = ctx.getImageData(mapPointX, mapPointY, 1, 1).data let rgbArray = [rgbaArray[0], rgbaArray[1], rgbaArray[2]] if ((await differenceRGB(selectColor, rgbArray)) <= alw) { squareImgData = await editCanvas(i, j, canvasWidth, squareImgData) } } console.log('In circulation') } return squareImgData } //Change canvas pixel color async function editCanvas(i, j, canvasWidth, squareImgData) { squareImgData.data[4 * j * canvasWidth + 4 * i] = 0 squareImgData.data[4 * j * canvasWidth + 4 * i + 1] = 0 squareImgData.data[4 * j * canvasWidth + 4 * i + 2] = 0 squareImgData.data[4 * j * canvasWidth + 4 * i + 3] = 255 return squareImgData } } </script> </body> </html>
Note that there is also colorTool. The complete code has been pasted on it
If you fail to run successfully, you must take a good look at what is wrong. Generally speaking, there is no problem