哈哈,趁着平安夜来凑凑热闹,其实好久以前就作过一个相似功能的毕业季生成邀请函小应用。这种有关图像合成的应用其实只须要熟悉h5中canvas的相关API和一点几何常识就ok了,是很容易实现的,这里就带你们探究一下实现的过程。再加上最近会有一大波节日到来,相信这个小工具值得你的拥有,若是以为不错,不如点个赞
呗。也可关注个人公众号BRandF
(有小工具的图文信息)推荐给朋友用哈,二维码在文末。html
上传图片
自动调整
成合适的尺寸
饰品
图形边框
保存/下载
生成的图片在线制做头像应用git
初始化
,在这个阶段会将预置的饰品和图形画到canvas上面去。状态更改
,在这个阶段用户能更改饰品,图形边框和上传图片,咱们须要将最新的用户图片,饰品图片,图形边框画到canvas中。结果输出
,最后将canvas的内容输出成可保存下载的图片。嗯没错,思路是很是直观
的。下面会对核心功能详细解析。github
首先须要明确的是,既然要作头像
,那么基本能够认为最终用户须要的图片是长和宽相等
的。但实际上用户上传的图片极可能并不规整
,这里有三种状况,宽大于高,宽小于高,宽等于高,同时又要等比放大or缩小,那应该怎么办呢?web
有一个很巧妙的办法
就是,好比宽大于高时,咱们能够将原图的高等比
缩放到canvas画布的高度,原图的宽也跟着等比缩放
。就像下图,这样既能够保持原图的比例
,canvas又不用留白
,虽然会丢失掉原图的一小部分,可是从实际效果来看仍是能够接受的。好吧,作这一步就是为了替(tou)换(lan)掉用户自定义裁剪这个功能。sql
// 不急,等下讲到这两个函数
const base64Url = await this.file2Base64(sourceImage);
const imgObj = await this.createImage(base64Url);
const CANVANS_SIZE = 256;
const type = imgObj.width - imgObj.height;
// 三种状况
if (type > 0) {
// 不管宽大于高仍是宽小于高,都会进行等比缩放
const w = imgObj.width * CANVANS_SIZE / imgObj.height;
context.drawImage(imgObj, 0, 0, w, CANVANS_SIZE);
} else if (type < 0) {
const h = imgObj.height * CANVANS_SIZE / imgObj.width;
context.drawImage(imgObj, 0, 0, CANVANS_SIZE, h);
} else {
context.drawImage(imgObj, 0, 0, CANVANS_SIZE, CANVANS_SIZE);
}
复制代码
使用FileReader
对象读取用户上传的文件,并转为base64
格式。因为读取图片的过程是异步
的,这里使用Promise
封装了一下。canvas
file2Base64(domFile) {
return new Promise((resolve, rejest) => {
const reader = new FileReader();
reader.readAsDataURL(domFile);
reader.onload = (e) => {
resolve(reader.result);
};
});
}
复制代码
因为canvas
绘制图片时只接受图片对象
,获得base64
格式的图片以后,须要再包装成图片对象才能绘制到canvas
中。因为载入成图片对象的过程也是异步
的,这里也使用Promise
封装了一下后端
createImage(imgUrl) {
return new Promise((resolve, rejest) => {
const imgObj = new Image();
imgObj.src = imgUrl;
imgObj.onload = (e) => {
resolve(imgObj);
};
});
}
复制代码
就是如何在canvas上绘制几何图形,以为这个处理方式很不错,这里借鉴了一下绘制圆角和圆形的方法。 《在Canvas中绘制圆角矩形》安全
将异步操做封装成Promise的好处在这里就体现出来了,能很是直观
的使用同步的编写形式将异步操做
表达出来。要注意的是用户图片须要先绘制到canvas
中,不然饰品和图形边框就会被图片覆盖。app
/** * @param {string} imgUrl (进过处理的用户图片url) * @param {object} decorationCurrent (饰品对象) * @returns imageUrl * @memberof App */
async handleMakeImage(imgUrl, decorationCurrent) {
if (!(imgUrl || decorationCurrent)) { return ''; }
const { border } = this.state;
const { value } = border;
const { source, style } = decorationCurrent;
const { width, height, top, left } = style;
const { canvas } = this.refs;
this.clearCanvas(canvas);
const context = canvas.getContext('2d');
if (imgUrl) {
const bgImg = await this.createImage(imgUrl);
context.drawImage(bgImg, 0, 0, bgImg.width, bgImg.height);
}
this.drawBorder(value, context);
if (decorationCurrent && source) {
const imgObj = await this.createImage(source);
context.drawImage(imgObj, left, top, width, height);
}
const targetUrl = canvas.toDataURL('image/png');
return targetUrl;
}
复制代码
最后模拟一下a
标签的点击事件,完成图片的下载。若是是移动端用户,长按图片便可保存图片。dom
downloadImage(url) {
if (url) {
const aLink = document.createElement('a');
const evt = document.createEvent('HTMLEvents');
evt.initEvent('click', true, true);
// 指定图片文件名
aLink.download = 'protrait.png';
aLink.href = url;
aLink.click();
}
}
复制代码
发现若是按正常的图片比例去载入图片,最后生成出来的图片会出现模糊。有一个简易的方法就是将canvas的长宽扩大到原来的两倍,但图片仍是按原来的比例来显示,简单的来讲就是将大图片缩小了显示。这样就能有效缓解图片模糊的问题了。
...
<canvas width="512" height="512" className="shadow d-n" ref="canvas" />
...
<img className="w256 h256" src={targetUrl} alt="" />
复制代码
ok,就这样,核心功能代码就是这些了,一共200行
不到。
因为接下来各类节日不断接近,这个小工具的素材也会不断更新,也欢迎你们贡献一些有趣的素材哈哈。