最近在研究html5 canvas的过程中,发现,canvas为前端对图像的处理开辟了一条新的道路,canvas可以做到很多事情,甚至可以做个类似于PhotoShop的东西,曾经本人在一家软件工作就做类似的工作,可以看一下我之前开发的软件:
这个就是canvas实现的类似于Adobe Photoshop,足以见得canvas的强大之处!
本文就是抽出其中一个小的功能点,来简单聊聊canvas强大之处:我们来一步步实现一个简单的智能抠图功能:(具体的代码见我的github:monkeyWangs/Matting)
1.环境准备
本人采用ES6语法作为开发环境,选用webpack作为构建工具,于是乎,我们有了webpack.config.js
/**
* @author monkeyWang
*
*/
/* 引入操作路径模块和webpack */
var path = require('path');
var webpack = require('webpack');
module.exports = {
/* 输入文件 */
entry: './src/index.js',
output: {
/* 输出目录,没有则新建 */
path: path.resolve(__dirname, './dist'),
/* 静态目录,可以直接从这里取文件 */
publicPath: '/dist/',
/* 文件名 */
filename: 'matting.js'
},
module: {
rules: [
/* 用babel来解析js文件并把es6的语法转换成浏览器认识的语法 */
{
test: /\.js$/,
loader: 'babel-loader',
/* 排除模块安装目录的文件 */
exclude: /node_modules/
}
]
}
}
这样变准备好了开发用的基本环境,接下来我们来实现具体的核心代码,为了方便起见,我的代码全写在了inde.js
2.代码实现
首选我们需要新建一个对象:
/**
* @author monkeywang
* Date: 17/3/30
*/
class Matting {
}
然后我们需要接受用户上传的图片文件:
class Matting {
/**
* 构造函数
* @param file
*/
constructor(file) {
this.file = file
}
}
再接着把图片文件转成base64格式,所以我们在类中建了一个createStream方法:
createStream() {
let reader = new FileReader()
let ext = this.file.name.substring(this.file.name.lastIndexOf(".") + 1).toLowerCase()
if (ext != 'png' && ext != 'jpg' && ext != 'jpeg') {
alert("图片的格式必须为png或者jpg或者jpeg格式!")
return
}
reader.onload = (e) => {
let src = e.target.result
let img = new Image()
img.src = src
let w = img.width
let h = img.height
this.fitch(w, h, img)
}
reader.readAsDataURL(this.file)
}
然后,开始我们的抠图逻辑代码之前,先描述一下我的思想:颜色属性其实是由RGBA四个元素组成的,RGB,代表三基色,A代表透明度,当A的值为0,则表示这个颜色是纯透明的,所以我们的主要逻辑就是把背景色设置为透明就好了。
创建一个canvas画布,然后把图片放到这个画布中,接着取这个图片上下左右四个点的像素,接下来要扣除这个图片的背景,那么,就需要去对整个图片的像素点颜色和背景色之前的区别,如果颜色相同,则把这个颜色的透明度设置成0
fitch(width, height, img) {
let dataUrl
let c = document.createElement("canvas")
c.width = width
c.height = height
let ctx = c.getContext("2d")
ctx.drawImage(img, 0, 0)
/**
* 取图片四个脚边的像素点rgba
* @type {*}
*/
let tl = Array.prototype.slice.call(ctx.getImageData(0, 0, 1, 1).data).join(',')
let tr = Array.prototype.slice.call(ctx.getImageData(width - 1, 0, 1, 1).data).join(',')
let br = Array.prototype.slice.call(ctx.getImageData(width - 1, height - 1, 1, 1).data).join(',')
let bl = Array.prototype.slice.call(ctx.getImageData(0, height - 1, 1, 1).data).join(',')
let imgdata = [tl, tr, bl, br] // 四个取色点
let selfImageData = [] // 当前rgba
imgdata.sort()
// 目前只支持纯色背景抠图,简单的判断是否为纯色
let deferNum = this.unique(imgdata).length
if (deferNum <= 1) {
{
selfImageData = imgdata[1].split(",") // 设置要扣除的主题色
let isPNG = true // 判断是否已经扣过
let imgDataUrl = ctx.getImageData(0, 0, width, height) //获取像素点
let data = imgDataUrl.data
for (let i = 0; i < data.length; i += 4) {
// 得到 RGBA 通道的值
let r = data[i]
let g = data[i + 1]
let b = data[i + 2]
/**
* function 判断颜色是不是属于背景色
* @param numerical
* @param index
* @returns {boolean}
*/
let isIn = (numerical, index) => {
if (selfImageData[3] == 0) {
isPNG = false
return false
}
return numerical > parseInt(selfImageData[index]) && numerical < parseInt(selfImageData[index])// 去掉边缘色
}
if ([r, g, b].every(isIn)) {
data[i + 3] = 0 // 设置背景透明
}
}
// 将修改后的代码复制回画布中
ctx.putImageData(imgDataUrl, 0, 0)
dataUrl = c.toDataURL("image/png")
if (isPNG) {
/**
* 创建下载链接 进行图片下载
* @type {Element}
*/
let a = document.createElement('a')
a.href = dataUrl //下载图片
a.download = '未命名.png'
a.click()
}
else {
alert('背景已抠除!')
}
}
}
else {
alert('只支持纯色背景抠图!')
}
}
然后我们测试一下效果:创建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="file" id="file">
<button onclick="matting()">开始抠图</button>
</body>
<script src="./dist/matting.js"></script>
<script>
function matting() {
var file = document.getElementById('file').files[0];
var mat = new Matting(file);
mat.createStream();
}
</script>
</html>
然后我们选择一个纯色背景图
抠图后,我们看看:
确实完成了抠图。
当然实现算法还不是很完善,主要是为了给大家展示canvas的无限可能,当然我也在逐步优化算法中,使得图片抠图更加完美,更加智能,也欢迎大家的star, pullrequest