mysql连接问题
2026/4/8 20:52:27
平时开发中可能会遇到一些图像处理的工作,本文就通过HTML5 Canvas实现一个类似油漆桶工具的颜色填充功能。
功能:将该区域中颜色相近的像素填充为目标颜色,实现类似Photoshop中油漆桶工具的效果。
颜色填充算法的核心是种子填充算法,也称为"泛洪填充"。基本思路是从一个起始点(种子)开始,向四周扩散,将颜色相近的像素替换为目标颜色。
如上图,是一个3px,3px 像素的图片。如果鼠标点击到(1,1)坐标的像素,扩散算法就是向上下左右去扩散,被扩散的位置继续向上下左右去扩散,直到超出边界,或者颜色不相近,或者颜色和目标颜色完全相同为止。
<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Canvas颜色填充工具</title><style>*{margin:0;padding:0;}canvas{display:block;border:1px solid #ccc;/* 添加边框便于观察 */}</style></head><body><canvas></canvas><scriptsrc="main.js"></script></body></html>2.初始化Canvas和图像加载
constcvs=document.querySelector('canvas');constctx=cvs.getContext('2d',{willReadFrequently:true// 优化性能,频繁读取图像数据});// 初始化:加载图片到Canvasfunctioninit(){constimg=newImage();img.onload=()=>{cvs.width=img.width;cvs.height=img.height;ctx.drawImage(img,0,0,img.width,img.height);};img.src='./img/image.png';// 替换为你的图片路径}init();// 监听Canvas点击事件cvs.addEventListener('click',(e)=>{// 1. 获取点击位置的像素信息constx=e.offsetX;consty=e.offsetY;constimgData=ctx.getImageData(0,0,cvs.width,cvs.height);constclickColor=getColor(x,y,imgData.data);// 2. 改变颜色(目标颜色为半透明黑色)changeColor(x,y,[0,0,0,20],imgData.data,clickColor);// 3. 将修改后的图像数据重新绘制到Canvasctx.putImageData(imgData,0,0);});/** * 颜色填充算法 - 递归实现 * @param {number} x - 当前像素点的x坐标 * @param {number} y - 当前像素点的y坐标 * @param {Array} targetColor - 目标颜色 [R, G, B, A] * @param {Uint8ClampedArray} imgData - 图像像素数据 * @param {Array} clickColor - 初始点击位置的颜色 * @returns {void} */functionchangeColor(x,y,targetColor,imgData,clickColor){// 边界检查:确保坐标在Canvas范围内if(x<0||x>=cvs.width||y<0||y>=cvs.height){return;}// 获取当前像素颜色constcurColor=getColor(x,y,imgData);// 终止条件1:当前颜色与点击颜色差异过大(不是同一区域)if(diff(clickColor,curColor)>100){return;}// 终止条件2:当前颜色已经是目标颜色if(diff(curColor,targetColor)===0){return;}// 将当前像素颜色修改为目标颜色constindex=point2Index(x,y);imgData.set(targetColor,index);// 递归扩散:向上下左右四个方向继续填充changeColor(x+1,y,targetColor,imgData,clickColor);changeColor(x-1,y,targetColor,imgData,clickColor);changeColor(x,y+1,targetColor,imgData,clickColor);changeColor(x,y-1,targetColor,imgData,clickColor);}/** * 计算两个颜色之间的差异 * @param {Array} color1 - 颜色数组 [R, G, B, A] * @param {Array} color2 - 颜色数组 [R, G, B, A] * @returns {number} 颜色差异值 */functiondiff(color1,color2){returnMath.abs(color1[0]-color2[0])+Math.abs(color1[1]-color2[1])+Math.abs(color1[2]-color2[2])+Math.abs(color1[3]-color2[3]);}/** * 将像素坐标转换为图像数据数组的索引 * @param {number} x - x坐标 * @param {number} y - y坐标 * @returns {number} 数组索引 */functionpoint2Index(x,y){// 每个像素由4个值表示:R, G, B, Areturn(y*cvs.width+x)*4;}/** * 获取指定位置的颜色 * @param {number} x - x坐标 * @param {number} y - y坐标 * @param {Uint8ClampedArray} imgData - 图像像素数据 * @returns {Array} 颜色数组 [R, G, B, A] */functiongetColor(x,y,imgData){constindex=point2Index(x,y);return[imgData[index],// RimgData[index+1],// GimgData[index+2],// BimgData[index+3]// A];}上述递归实现在处理大面积区域时可能导致栈溢出,因为递归深度可能非常大。此外,性能也有优化空间。
/** * 优化版颜色填充算法 - 使用队列迭代实现 */functionchangeColorOptimized(startX,startY,targetColor,imgData,clickColor){// 创建队列存储待处理的像素点constqueue=[];queue.push({x:startX,y:startY});// 记录已访问的像素,避免重复处理constvisited=newSet();while(queue.length>0){const{x,y}=queue.shift();constkey=`${x},${y}`;// 检查边界if(x<0||x>=cvs.width||y<0||y>=cvs.height){continue;}// 检查是否已访问if(visited.has(key)){continue;}visited.add(key);// 获取当前像素颜色constcurColor=getColor(x,y,imgData);// 检查颜色是否匹配if(diff(clickColor,curColor)>100){continue;}if(diff(curColor,targetColor)===0){continue;}// 修改颜色constindex=point2Index(x,y);imgData.set(targetColor,index);// 将相邻像素加入队列queue.push({x:x+1,y});queue.push({x:x-1,y});queue.push({x,y:y+1});queue.push({x,y:y-1});}}前面采用的颜色比对算法,比较简单粗暴,color1和color2的rgba进行相减取绝对值,但是这种遇到极端情况下,效果不好。
这里比较科学的算法是实用欧几里得距离也就是简单的勾股定理
functiondiff(color1,color2){// 计算RGB差值(不考虑Alpha)constrDiff=color1[0]-color2[0];constgDiff=color1[1]-color2[1];constbDiff=color1[2]-color2[2];// 欧几里得距离returnMath.sqrt(rDiff*rDiff+gDiff*gDiff+bDiff*bDiff);}至此,我们实现了一个基于Canvas的颜色填充工具,并讲解了:
注意:实际使用中,建议使用优化版的队列迭代算法,避免递归可能导致的栈溢出问题。同时,对于大型图像,可以考虑分块处理或使用Web Worker进行多线程处理以提高性能。