优化版——图片背景色去除

在这里插入图片描述

//方式一: 封装为class类
    <img id="original-img"
        src="https://gd1.alicdn.com/imgextra/i1/1674937161/O1CN01IlOmos22lmtklugqq_!!1674937161.jpg_400x400.jpg"> //原图
    <canvas id="result-canvas"></canvas> //处理后的图片
<script>
class ImageBackgroundRemover {
            constructor(options = {}) {
                this.tolerance = options.tolerance || 10;
                this.step = options.step || 1;
                this.maxLoop = options.maxLoop || 10000000;
            }

            removeBackground(imageUrl) {
                return new Promise((resolve, reject) => {
                    const img = new Image();
                    img.crossOrigin = "anonymous";
                    img.src = imageUrl;
                    img.onload = () => {
                        const canvas = document.createElement("canvas");
                        canvas.width = img.width;
                        canvas.height = img.height;
                        const ctx = canvas.getContext("2d");
                        ctx.drawImage(img, 0, 0);
                        const imgData = ctx.getImageData(0, 0, img.width, img.height);
                        const bgColor = this.findBgColor(imgData);
                        if (!bgColor) {
                            reject(new Error("Unable to find background color."));
                            return;
                        }
                        const resultImgData = this.removeBgColor(imgData, bgColor);
                        ctx.clearRect(0, 0, canvas.width, canvas.height);
                        ctx.putImageData(resultImgData, 0, 0);
                        const base64 = canvas.toDataURL("image/png");
                        resolve(base64);
                    };
                    img.onerror = () => {
                        reject(new Error("Failed to load image."));
                    };
                });
            }

            findBgColor(imgData) {
                const count = new Uint32Array(256 * 256 * 256);
                let maxCount = 0;
                let bgColor = null;
                const toleranceSquared = this.tolerance ** 2;
                let loopCount = 0;

                // 遍历图片的像素点
                for (let y = 0; y < imgData.height; y += this.step) {
                    for (let x = 0; x < imgData.width; x += this.step) {
                        const i = (x + y * imgData.width) * 4;
                        const r = imgData.data[i];
                        const g = imgData.data[i + 1];
                        const b = imgData.data[i + 2];
                        const a = imgData.data[i + 3];

                        // 使用哈希表记录每个颜色出现的次数
                        const key = r << 16 | g << 8 | b;
                        count[key]++;

                        // 找到颜色出现次数最多的颜色作为背景色
                        if (count[key] > maxCount) {
                            maxCount = count[key];
                            bgColor = [r, g, b, a];
                        }

                        // 如果已经找到了一个像素的数量超过 50% 的颜色,则认为该颜色为背景色
                        if (maxCount > imgData.width * imgData.height / 2) {
                            return bgColor;
                        }

                        // 如果循环次数超过最大值,则退出循环
                        loopCount++;
                        if (loopCount >= this.maxLoop) {
                            return null;
                        }
                    }
                }

                // 如果找到的背景色与透明色相似,则认为该颜色为透明色
                if (bgColor[3] < 255 && distance(bgColor, [0, 0, 0, 0]) <= toleranceSquared) {
                    return [0, 0, 0, 0];
                }

                return bgColor;
            }

            removeBgColor(imgData, bgColor) {
                const resultImgData = new ImageData(imgData.width, imgData.height);
                const toleranceSquared = this.tolerance ** 2;

                // 遍历图片的像素点,并将背景色变为透明色
                for (let y = 0; y < imgData.height; y++) {
                    for (let x = 0; x < imgData.width; x++) {
                        const i = (x + y * imgData.width) * 4;
                        const r = imgData.data[i];
                        const g = imgData.data[i + 1];
                        const b = imgData.data[i + 2];
                        const a = imgData.data[i + 3];

                        // 如果像素点的颜色与背景色相似,则将其变为透明色
                        if (this.distance([r, g, b, a], bgColor) <= toleranceSquared) {
                            resultImgData.data[i] = 0;
                            resultImgData.data[i + 1] = 0;
                            resultImgData.data[i + 2] = 0;
                            resultImgData.data[i + 3] = 0;
                        } else {
                            resultImgData.data[i] = r;
                            resultImgData.data[i + 1] = g;
                            resultImgData.data[i + 2] = b;
                            resultImgData.data[i + 3] = a;
                        }
                    }
                }

                return resultImgData;
            }

            distance(c1, c2) {
                const dr = c1[0] - c2[0];
                const dg = c1[1] - c2[1];
                const db = c1[2] - c2[2];
                const da = c1[3] - c2[3];
                return dr * dr + dg * dg + db * db + da * da;
            }
        }
        //使用方式
        const remover = new ImageBackgroundRemover({
            tolerance: 10
        });
       remover.removeBackground('https://gd1.alicdn.com/imgextra/i1/1674937161/O1CN01IlOmos22lmtklugqq_!!1674937161.jpg_400x400.jpg').then(base64 => {
            const canvas = document.getElementById('result-canvas');
            canvas.width = 500;
            canvas.height = 500;
            const ctx = canvas.getContext('2d');
            const img = new Image();
            img.onload = function () {
                ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
            };
            img.src = base64;
        }).catch(err => {
            alert(err);
        });
 </script>

//第二种
<img id="original-img" src="https://gd1.alicdn.com/imgextra/i1/1674937161/O1CN01IlOmos22lmtklugqq_!!1674937161.jpg_400x400.jpg">
    <canvas id="result-canvas"></canvas>
    <script>
        const imageUrl = document.getElementById('original-img').src;

        removeBackground(imageUrl, {
            tolerance: 10
        }).then(base64 => {
            const canvas = document.getElementById('result-canvas');
            canvas.width = 500;
            canvas.height = 500;
            const ctx = canvas.getContext('2d');
            const img = new Image();
            img.onload = function () {
                ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
            };
            img.src = base64;
        }).catch(err => {
            alert(err);
        });
        /**
         * 去除图片的背景色
         * @param {string} imageUrl - 图片的 URL
         * @param {Object} options - 去除背景色的选项
         * @param {number} options.tolerance - 容差,取值范围 [0, 255],默认为 10
         * @param {number} options.step - 遍历图片的步长,取值范围 [1, width],默认为 3
         * @param {number} options.maxLoop - 最大循环次数,防止死循环,默认为 1000
         * @returns {Promise} 返回一个 Promise 对象,resolve 后返回去除背景色后的图片的 Base64 编码
         */
        function removeBackground(imageUrl, options = {}) {
            const {
                tolerance = 10, step = 1, maxLoop = 10000000
            } = options;

            // 创建一个 Image 对象,并加载图片
            const img = new Image();
            img.crossOrigin = "anonymous";
            img.src = imageUrl;

            return new Promise((resolve, reject) => {
                img.onload = function () {
                    // 创建一个Canvas 对象并将图片绘制到 Canvas 上
                    const canvas = document.createElement("canvas");
                    canvas.width = img.width;
                    canvas.height = img.height;
                    const ctx = canvas.getContext("2d");
                    ctx.drawImage(img, 0, 0);

                    // 获取图片的 ImageData 对象
                    const imgData = ctx.getImageData(0, 0, img.width, img.height);

                    // 找到背景色
                    const bgColor = findBgColor(imgData, tolerance, step, maxLoop);
                    if (!bgColor) {
                        reject(new Error("Unable to find background color."));
                        return;
                    }

                    // 移除背景色
                    const resultImgData = removeBgColor(imgData, bgColor, tolerance);

                    // 将去除背景色后的 ImageData 对象绘制到 Canvas 上
                    ctx.clearRect(0, 0, canvas.width, canvas.height);
                    ctx.putImageData(resultImgData, 0, 0);

                    // 将 Canvas 转换为 Base64 编码的图片
                    const base64 = canvas.toDataURL("image/png");

                    resolve(base64);
                };

                img.onerror = function () {
                    reject(new Error("Failed to load image."));
                };
            });
        }

        /**
         * 找到图片的背景色
         * @param {ImageData} imgData - 图片的 ImageData对象
         * @param {number} tolerance - 容差,取值范围 [0, 255]
         * @param {number} step - 遍历图片的步长,取值范围 [1, width]
         * @param {number} maxLoop - 最大循环次数
         * @returns {Array|null} 返回背景色的 [r, g, b, a] 数组,如果找不到返回 null
         */
        function findBgColor(imgData, tolerance, step, maxLoop) {
            const count = new Uint32Array(256 * 256 * 256);
            let maxCount = 0;
            let bgColor = null;
            const toleranceSquared = tolerance ** 2;
            let loopCount = 0;

            // 遍历图片的像素点
            for (let y = 0; y < imgData.height; y += step) {
                for (let x = 0; x < imgData.width; x += step) {
                    const i = (x + y * imgData.width) * 4;
                    const r = imgData.data[i];
                    const g = imgData.data[i + 1];
                    const b = imgData.data[i + 2];
                    const a = imgData.data[i + 3];

                    // 使用哈希表记录每个颜色出现的次数
                    const key = r << 16 | g << 8 | b;
                    count[key]++;

                    // 找到颜色出现次数最多的颜色作为背景色
                    if (count[key] > maxCount) {
                        maxCount = count[key];
                        bgColor = [r, g, b, a];
                    }

                    // 如果已经找到了一个像素的数量超过 50% 的颜色,则认为该颜色为背景色
                    if (maxCount > imgData.width * imgData.height / 2) {
                        return bgColor;
                    }

                    // 如果循环次数超过最大值,则退出循环
                    loopCount++;
                    if (loopCount >= maxLoop) {
                        return null;
                    }
                }
            }

            // 如果找到的背景色与透明色相似,则认为该颜色为透明色
            if (bgColor[3] < 255 && distance(bgColor, [0, 0, 0, 0]) <= toleranceSquared) {
                return [0, 0, 0, 0];
            }

            return bgColor;
        }

        /**
         * 移除图片的背景色
         * @param {ImageData} imgData - 图片的 ImageData 对象
         * @param {Array} bgColor - 背景色的 [r, g, b, a] 数组
         * @param {number} tolerance - 容差,取值范围 [0, 255]
         * @returns {ImageData} 返回移除背景色后的 ImageData 对象
         */
        function removeBgColor(imgData, bgColor, tolerance) {
            const resultImgData = new ImageData(imgData.width, imgData.height);
            const toleranceSquared = tolerance ** 2;

            // 遍历图片的像素点,并将背景色变为透明色
            for (let y = 0; y < imgData.height; y++) {
                for (let x = 0; x < imgData.width; x++) {
                    const i = (x + y * imgData.width) * 4;
                    const r = imgData.data[i];
                    const g = imgData.data[i + 1];
                    const b = imgData.data[i + 2];
                    const a = imgData.data[i + 3];

                    // 如果像素点的颜色与背景色相似,则将其变为透明色
                    if (distance([r, g, b, a], bgColor) <= toleranceSquared) {
                        resultImgData.data[i] = 0;
                        resultImgData.data[i + 1] = 0;
                        resultImgData.data[i + 2] = 0;
                        resultImgData.data[i + 3] = 0;
                    } else {
                        resultImgData.data[i] = r;
                        resultImgData.data[i + 1] = g;
                        resultImgData.data[i + 2] = b;
                        resultImgData.data[i + 3] = a;
                    }
                }
            }

            return resultImgData;
        }

        /**
         * 计算两个颜色之间的欧几里得距离的平方
         * @param {Array} c1 - 第一个颜色的 [r, g, b, a] 数组
         * @param {Array} c2 - 第二个颜色的 [r, g, b, a] 数组
         * @returns {number} 返回两个颜色之间的欧几里得距离的平方
         */
        function distance(c1, c2) {
            const dr = c1[0] - c2[0];
            const dg = c1[1] - c2[1];
            const db = c1[2] - c2[2];
            const da = c1[3] - c2[3];
            return dr * dr + dg * dg + db * db + da * da;
        }
    </script>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Allen_CV

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值