利用canvas实现一个抠图小工具

利用canvas实现一个抠图小工具

0 前言

作为新一代的前端开发工程师,PS抠图切图已经不是必备技能了,我们有UI/交互/视觉等更专业的设计同学帮我们做这个事情。但是有时候还是有一些简单的图片处理工作需要我们去做,例如对图片进行剪裁,调整透明度或者调整图片内的文字等等等。这时候如果有一点PS经验那当然更好,如果没有或者当前的开发环境不一定安装了PS等工具,那我们可能需要去找在线图片处理工具来帮我们完成这些工作。

1 Canvas

老话说得好,一个不想当将军的士兵不是好士兵;一个不想自己写工具的程序猿不是好程序猿。那其实上面提及的一些简单图片处理是可以通过Canvas来帮我们实现的。

canvas是一个可以使用脚本(通常为JavaScript)在其中绘制图形的 HTML 元素。它可以用于制作照片集或者制作简单(也不是那么简单)的动画.。

基本的API文档可以看这里

宋丹丹老师说得好:

要把大象放冰箱,总共分三步:一先把冰箱门打开;二把大象放进去;三把冰箱门带上。

整个Canvas图片处理工具大致也可以分为三步。

  • 1、读取图片资源;
  • 2、把图片资源绘制到画板上;
  • 3、作为前端开发的你可以开始为所欲为了;

先看一下很简单的HTML结构和CSS样式

<!DOCTYPE html>
<html>
<head>
    <title>Canvas图片处理演示</title>
</head>
<body>
    <!-- 用于输入本地文件和远程URL -->
    <input type="file" id="input-upload" accept=".jpg,.jpeg,.png,.gif,.svg" placeholder="上传本地图片" />
    <input type="text" id="input-url" placeholder="输入图片URL"></input>
    <!-- 图片处理前后展示 -->
    <p>原图:</p>
    <div class="img-container"><img id="input-img"></div>
    <p>画布:</p>
    <div class="img-container">
        <canvas id="my-canvas"></canvas>
    </div>
    <a href="void(0);" id="download">点我下载</a>
</body>
</html>

样式主要是为了增加图片背景文理,用于显示透明度

#input-img {
    max-width: 400px;
}

#input-upload {
    width: 200px;
}
/* 图片背景纹理 */
.img-container {
    font-size: 0;
    background-image: -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.25, #ccc), color-stop(.25, transparent), to(transparent)),-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, #ccc), color-stop(.25, transparent), to(transparent)),-webkit-gradient(linear, 0 0, 100% 100%, color-stop(.75, transparent), color-stop(.75, #ccc)),-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.75, transparent), color-stop(.75, #ccc));
    background-size: 10px 10px;
}

.err {
    border: 1px solid red;
}

2 读取图片资源

一般来讲,我们的图片来源有两种。一是设计给你的本地资源;二是线上资源,可以通过URL进行访问的。

// 我们使用两个输入框来引入本地资源和线上资源
const oUpload = document.getElementById('input-upload');
const oInput = document.getElementById('input-url');
// 除此之外还需要一个img标签来加载数据和进行展示
const oImg = document.getElementById('input-img');

// 注: 加载图片的img标签不一定需要放到页面上

基本思路就是利用FileReader读取本地文件,并使用img标签加载数据

oUpload.onchange = loadFile;
oInput.onchange = loadUrl;

// 读取本地文件
function loadFile(e) {
    const file = e.target.files[0];
    const reader = new FileReader();
    reader.onload = onFileLoad;
    reader.readAsDataURL(file);
}

// 读取输入URL
function loadUrl(e) {
    oInput.classList.remove('err')
    const url = e.target.value;
    onFileLoad(url);
}

// 加载图片数据
function onFileLoad(src) {
    oImg.onload = function() {
        onImageLoad(oImg)// 这里使用图像数据,后续讲解
    };
    oImg.onerror = onImageErr;
    oImg.src = (src.target ? src.target.result : src);
}

function onImageErr() {
    oInput.classList.add('err');
}

3 canvas 输入输出图像

图像数据有了,那我们就可以开始使用canvas作为载体对图片资源进行处理了。这之前呢我们需要的是从图像到canvas的相互转换,其实就是把图像绘制到画布上,并从画布在上导出图像数据的过程。

这一过程利用了canvas一系列的API先单独拎来说明一下:

canvas - HTML元素

  • getContext 获得渲染上下文和它的绘画功能
  • toDataURL 返回一个包含被类型参数规定的图像表现格式的数据链接

context - 通过getContext获取的渲染上下文

  • drawImage 将图片绘制到画布上
  • getImageData 获得一个包含画布场景像素数据的ImageData对像
  • putImageData 像素数据的写入
// canvas实例
const oCanvas = document.getElementById('my-canvas');
// 上面读取资源的操作后,将图像画到canvas上
function onImageLoad(img) {
    const width = oCanvas.width = img.naturalWidth || img.width;
    const height = oCanvas.height = img.naturalHeight || img.height;
    const ctx = oCanvas.getContext("2d");
    ctx.drawImage(img, 0, 0);

    // 获取画布像素信息
    const imageData = ctx.getImageData(0, 0, width, height);

    // 一个像素点由RGBA四个值组成,data为[R,G,B,A [,R,G,B,A[...]]]组成的一维数组
    // 可以通过修改该数组的数据,达到修改图片内容的目的
    const data = imageData.data;
    filter(data);// 这里对图像数据进行处理

    // 把新的内容画进画布里
    ctx.putImageData(imageData, 0, 0);
}
// 一个A标签,让用户点击用的
const oDownload = document.getElementById('download');
// 从画布上读取数据并保存到本地
function setDownLoad(fileName) {
    const url  = oCanvas.toDataURL();
    oDownload.setAttribute('href', url);
    oDownload.setAttribute('target', '_blank');

    if (fileName) {
        oDownload.setAttribute('download', fileName);
    }
}

4 数据处理

以简单的抠图效果为例,那我们实际上要做的事情就是设置像素数据RGBA中的A的值:

function filter(data) {
    for (let i = 0; i < data.length; i += 4) {
        let r = data[i],
            g = data[i + 1],
            b = data[i + 2];

        // 色值在250-256之间都认为是白色
        if ([r, g, b].every(v => v < 256 && v > 250)) {
            data[i + 3] = 0; // 把白色改成透明的  
        }
    }
}

效果:

原图

导出图

上文的实现是将所有的白色替换成透明,同理可以拓展成指定任意颜色范围替换或者调整。

滤镜效果和选色抠图效果也就都可以很简单的实现出来了~

5 更多与拓展

我们使用PNG图像绝大数场景都是为了保存图像的透明度,但是PNG图片的大小往往差强人意:

PNG采用无损压缩是通过索引色去存储和还原图像的,在存储图像前会先判断图像上哪些地方是相同的哪些地方是不同的,然后对图像上所有出现的颜色进行索引,这些颜色就是索引色。储存的索引色数量越多,文件尺寸越大。8最多只能索引256种颜色,PNG24则可以保存1600多万种颜色,但相应的文件尺寸也会大很多。

我们也可以利用canvas对图片进行压缩,不是说单独的利用canvas的API控制导出图片的质量来进行压缩,虽然说这也是一个思路。而是说

使用 canvas 进行透明度分析,把图片分成透明的 PNG + 不透明的 JPG,然后通过 SVG 将两张图片层叠到一起,减少了不透明部分 alpha 值的储存空间。

基本的流程其实跟上文提到的“大象装冰箱”的过程差不多,在充分利用JPG的压缩率上保留PNG的透明度。基本流程如下:

  • 1、读取图片资源;
  • 2、使用原图像数据去除透明度作为底色画到画板上,这一层可以作为没有透明度的JPG图像,利用JPG图像的压缩效率极大减小图片的存储规模;
  • 3、使用原图像数据将图像颜色数量缩减到一定的数量级(PNG8),并保留透明度,这一层则作为有透明度的PNG图像蒙版盖在上一层图像上,保留图像透明度。
  • 4、将新的图像导出;

这里有一个在线的例子 JPNG.svg

本文代码仓库 本文在线示例

鬼知道一个键盘Z键坏的我是怎么写出这片文章的...

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏coding for love

CSS进阶06-相对定位Relative Positioning

(注1:如果有问题欢迎留言探讨,一起学习!转载请注明出处,喜欢可以点个赞哦!) (注2:更多内容请查看我的目录。)

552
来自专栏bboysoul

1461: C语言实验题――求平均值

描述:求n个数的平均数。 输入:输入数据有2行,第一行为n,第二行是n个数。 输出:输出n个数中的平均数,结果保留小数点2位。 样例输入:5-1 2.1 ...

922
来自专栏逸鹏说道

GIF/PNG/JPG和WEBP/base64/apng图片优点和缺点整理

阅读目录 GIF(Graphics Interchange Format) PNG(Portable Network Graphics) JPG(Joint P...

3539
来自专栏程序员叨叨叨

3.2 Vertex Shader Program

Vertex shader program(顶点着色程序)和 Fragment shader program(片断着 色程序)分别被 Programmable ...

643
来自专栏编程

GitHub上11 月份最热门的开源项目

链接:www.itcodemonkey.com/article/1468.html 转载请注明来源作者 2017 年 11 月份 GitHub 上最热门的开源项...

1880
来自专栏IMWeb前端团队

前端图片优化机制

60%的网站流量来自图片,图片优化可以大幅影响网站流量。 一、现有web图片格式 图片格式 支持透明 动画支持 压缩方式 浏览器支持 相对原图大小 适应场...

4120
来自专栏IT大咖说

听饿了么前端主管如何解析H5渲染性能

内容来源:2018 年 6 月 30 日,饿了么前端主管向勇在“饿了么技术沙龙・第27弹 【前端专场】”进行《h5渲染性能一瞥》演讲分享。IT 大咖说(微信id...

731
来自专栏一“技”之长

iOS开发CoreGraphics核心图形框架之八——层聚合

    正常情况下,在使用CoreGraphics框架中的方法进行图形绘制时,每一闭合的图形都是一个独立的层,如果在绘制时添加了阴影效果,则通过阴影可以很明显的...

702
来自专栏Nian糕的私人厨房

CSS 布局_2 Flex弹性盒

弹性盒,是一种布局方式,当页面需要适应不同的屏幕大小以及设备类型时,它依然能确保元素拥有更恰当的排布行为,弹性盒属于 CSS 3 部分,IE9 以下不支持,现代...

744
来自专栏王磊的博客

fabric.js和高级画板

1032

扫码关注云+社区