【前端】【canvas】图片颜色填充工具实现详解

概述

平时开发中可能会遇到一些图像处理的工作,本文就通过HTML5 Canvas实现一个类似油漆桶工具的颜色填充功能。

功能:将该区域中颜色相近的像素填充为目标颜色,实现类似Photoshop中油漆桶工具的效果。

实现原理

颜色填充算法的核心是种子填充算法 ,也称为"泛洪填充"。基本思路是从一个起始点(种子)开始,向四周扩散,将颜色相近的像素替换为目标颜色。

如上图,是一个3px,3px 像素的图片。如果鼠标点击到(1,1)坐标的像素,扩散算法就是向上下左右去扩散,被扩散的位置继续向上下左右去扩散,直到超出边界,或者颜色不相近,或者颜色和目标颜色完全相同为止。

实现步骤

  1. 需要先加载一张图片到canvas画布中。
  2. 给画布添加点击事件,获取到点击的坐标。
  3. 根据坐标获取到点击位置的颜色
  4. 修改当前像素点的位置颜色,并且扩散修改相邻位置 颜色相近的 像素点颜色,(递归)

代码结构

1.HTML结构

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="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>
    <script src="main.js"></script>
</body>
</html>

2.初始化Canvas和图像加载

javascript 复制代码
const cvs = document.querySelector('canvas');
const ctx = cvs.getContext('2d', {
    willReadFrequently: true // 优化性能,频繁读取图像数据
});

// 初始化:加载图片到Canvas
function init() {
    const img = new Image();
    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();

核心算法:颜色填充

javascript 复制代码
// 监听Canvas点击事件
cvs.addEventListener('click', (e) => {
    // 1. 获取点击位置的像素信息
    const x = e.offsetX;
    const y = e.offsetY;
    const imgData = ctx.getImageData(0, 0, cvs.width, cvs.height);
    const clickColor = getColor(x, y, imgData.data);
    
    // 2. 改变颜色(目标颜色为半透明黑色)
    changeColor(x, y, [0, 0, 0, 20], imgData.data, clickColor);
    
    // 3. 将修改后的图像数据重新绘制到Canvas
    ctx.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}
 */
function changeColor(x, y, targetColor, imgData, clickColor) {
    // 边界检查:确保坐标在Canvas范围内
    if (x < 0 || x >= cvs.width || y < 0 || y >= cvs.height) {
        return;
    }
    
    // 获取当前像素颜色
    const curColor = getColor(x, y, imgData);
    
    // 终止条件1:当前颜色与点击颜色差异过大(不是同一区域)
    if (diff(clickColor, curColor) > 100) {
        return;
    }
    
    // 终止条件2:当前颜色已经是目标颜色
    if (diff(curColor, targetColor) === 0) {
        return;
    }
    
    // 将当前像素颜色修改为目标颜色
    const index = 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} 颜色差异值
 */
function diff(color1, color2) {
    return Math.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} 数组索引
 */
function point2Index(x, y) {
    // 每个像素由4个值表示:R, G, B, A
    return (y * cvs.width + x) * 4;
}

/**
 * 获取指定位置的颜色
 * @param {number} x - x坐标
 * @param {number} y - y坐标
 * @param {Uint8ClampedArray} imgData - 图像像素数据
 * @returns {Array} 颜色数组 [R, G, B, A]
 */
function getColor(x, y, imgData) {
    const index = point2Index(x, y);
    return [
        imgData[index],     // R
        imgData[index + 1], // G
        imgData[index + 2], // B
        imgData[index + 3]  // A
    ];
}

算法优化

问题分析

上述递归实现在处理大面积区域时可能导致栈溢出,因为递归深度可能非常大。此外,性能也有优化空间。

优化方案:使用队列的迭代算法

javascript 复制代码
/**
 * 优化版颜色填充算法 - 使用队列迭代实现
 */
function changeColorOptimized(startX, startY, targetColor, imgData, clickColor) {
    // 创建队列存储待处理的像素点
    const queue = [];
    queue.push({ x: startX, y: startY });
    
    // 记录已访问的像素,避免重复处理
    const visited = new Set();
    
    while (queue.length > 0) {
        const { x, y } = queue.shift();
        const key = `${x},${y}`;
        
        // 检查边界
        if (x < 0 || x >= cvs.width || y < 0 || y >= cvs.height) {
            continue;
        }
        
        // 检查是否已访问
        if (visited.has(key)) {
            continue;
        }
        visited.add(key);
        
        // 获取当前像素颜色
        const curColor = getColor(x, y, imgData);
        
        // 检查颜色是否匹配
        if (diff(clickColor, curColor) > 100) {
            continue;
        }
        
        if (diff(curColor, targetColor) === 0) {
            continue;
        }
        
        // 修改颜色
        const index = 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进行相减取绝对值,但是这种遇到极端情况下,效果不好。

这里比较科学的算法是实用欧几里得距离 也就是简单的勾股定理

javascript 复制代码
function diff(color1,color2){
    // 计算RGB差值(不考虑Alpha)
    const rDiff = color1[0] - color2[0];
    const gDiff = color1[1] - color2[1];
    const bDiff = color1[2] - color2[2];
    // 欧几里得距离
    return Math.sqrt(rDiff * rDiff + gDiff * gDiff + bDiff * bDiff);
}

总结

至此,我们实现了一个基于Canvas的颜色填充工具,并讲解了:

  1. Canvas图像数据处理的基本原理
  2. 种子填充算法的递归和迭代实现
  3. 性能优化技巧
    这个工具可以作为图像处理应用的基础,进一步扩展可实现更复杂的功能,如图像编辑、滤镜应用等。

注意:实际使用中,建议使用优化版的队列迭代算法,避免递归可能导致的栈溢出问题。同时,对于大型图像,可以考虑分块处理或使用Web Worker进行多线程处理以提高性能。

相关推荐
Bigger2 小时前
Tauri (23)——为什么每台电脑位置显示效果不一致?
前端·rust·app
¥懒大王¥2 小时前
XSS-Game靶场教程
前端·安全·web安全·xss
ssshooter2 小时前
为什么移动端 safari 用 translate 移动元素卡卡的
前端·css·性能优化
闲云一鹤2 小时前
Claude Code 接入第三方AI模型(MiMo-V2-Flash)
前端·后端·claude
惜.己2 小时前
前端笔记(四)
前端·笔记
小北方城市网2 小时前
第 5 课:Vue 3 HTTP 请求与 UI 库实战 —— 从本地数据到前后端交互应用
大数据·前端·人工智能·ai·自然语言处理
踢球的打工仔2 小时前
ajax的基本使用(上传文件)
前端·javascript·ajax
樊小肆2 小时前
ollmam+langchain.js实现本地大模型简单记忆对话-内存版
前端·langchain·aigc
徐小夕2 小时前
pxcharts 多维表格开源!一款专为开发者和数据分析师打造的轻量化智能表格
前端·架构·github