今天 12 月了,本来应该开始做外包的项目,但是好歹不好的长智齿了,
下午去拔了牙之后看着电脑发呆了一个下午,看来真的麻药影响 🧠 脑子。
所以,先写篇文章醒醒脑….
前天初步实现了下使用 canvas
来识别图片内容,今天就把它记录下来。
毕竟 canvas
这部分的内容是真的不懂,现学现卖,欢迎吐槽。📧 MailTo
前几天,领导和我说要制作一个年会的活动页面,需要有用户签到展示、企业形象展示、抽奖小游戏。
其中一个需求如下:
Logo 签到墙
年会的签到墙,使用微信扫码签到,然后后台拿到微信授权之后传给我用户的头像昵称,然后我就在前台展示并且完成一些特效。
需要用头像图片拼接组成图形以及文字内容。
这个图形和文字是用户上传的一个纯色内容+透明/白色底的图片。👇
我就直接想到用 canvas 来识别图片内容,
前天晚上初步实现了需求,
直接暴力的按照设置的 size 大小
从左上角不断循环识别到右下角,然后保存有内容的坐标点,
再按照坐标来绘制矩形和图片填充。
这边是我实现的效果 👇
识别成栅格
用户头像填充
识别内容:
const maxWidth = document.body.clientWidth; // 获取视窗宽度
const maxHeight = document.body.clientHeight; // 获取视窗高度
const size = 10; // 设置栅格大小
const points = []; // 坐标数组
const canvas = document.querySelector("canvas"); // 拿到canvas实例
canvas.width = maxWidth; // 设置canvas宽度 不能使用canvas.style.width来设置,会造成内容拉伸
canvas.height = maxHeight; // 设置canvas高度
const ctx = canvas.getContext("2d"); // 获取canvas上下文,这边获取的是二维绘图,还有一个3D内容 "webgl"
let img = new Image(); // 创建图片实例
img.src = require("assets/img/text.jpg"); // 获取设置图片url
let w = maxWidth; // 设置临时宽度,后边给绘制图片的时候会用到
let h = 0; // 设置临时高度
img.onload = () => {
// img 设置 url 后会立即加载,加载完成后触发 onload 事件
// 图片加载完成
h = (w / img.width) * img.height; // 计算图片高度
ctx.drawImage(img, 0, 0, w, h); // 绘制图片从(0,0)坐标,w为绘制的图片高度,h为绘制的图片高度
// 开始识别
for (let x = 0; x <= w - 10; x += size + 1) {
// x轴开始循环 因为我需要有1像素的间隙所以是 size+1 ,如果不需要间隙则 +=size 即可
for (let y = 0; y <= h - 10; y += size + 1) {
// y轴开始循环
let color = ctx.getImageData(x, y, size, size).data; // 识别区块内容会 一个像素内返回RGBA四个参数
let count = 0; // 设置计数器
// 以4个一组开始循环
for (let i = 0; i < color.length; i += 4) {
// 如果区块中有颜色内容则 count++,我这边识别的是黑色内容
if (color[i] <= 100 || color[i + 1] <= 100 || color[i + 2] <= 100) {
count++;
}
}
// 颜色比例超过10%则记录,其实50%也可以,但是会出现 特殊情况1 的问题,下边会提到,但是内容会相对粗一些
if (count >= size * size * 0.1) {
points.push({ x: x, y: y }); // 添加到坐标数组
}
}
}
ctx.clearRect(0, 0, maxWidth, maxHeight); // 清除绘制的图片
};
绘制栅格图
ctx.fillStyle = "rgba(255,0,0,.55)"; // 设置填充色
// 遍历坐标数组内的所有数据
points.forEach(piont => {
ctx.fillRect(piont.x, piont.y, size, size); // 绘制矩形
});
填充图片
填充图片和绘制矩形的原理相似所以就不举例了。
考虑到签到的人数有可能达不到坐标的数量,所以在最后可以重新循环用户头像列表来填充满整个栅格区
以上是简单的使用 canvas
试别图片内容,并且栅格化且使用图片填充。
🎈 尾声
可能遇到的一些问题
打印points一直是空数组
因为时机不正确,图片加载是异步的,你写的同步代码会先执行,需要把绘制栅格的部分代码放到图片 onload
之后。
栅格没有绘制
#1 因为异步加载的问题,points
数组为空,没有没办法绘制。
#2 因为 canvas
容器的高度不够,识别之后绘制的内容不够显示。
#3 我用的 Vue.js 写的 Demo,写笔记的时候忘记去掉 this
了,所以 this.size
会有问题,已经修改了。
加载微信头像或者其他来源的头像出现跨域/403的情况
尝试在绘制图片的时候为 new Image()
出来的图片实例 增加 img.crossOrigin = "Anonymous";
属性
识别之后底部、右侧出现一整排/列的栅格
尝试调整栅格大小,一般这种情况是因为,识别的栅格太大,最后一排、一列超出的绘制范围拾取不到颜色,各项颜色值都会是0,会被认为是黑色
考虑把笔记重新整理
感觉上每年的年底都会收到邮件来询问这篇笔记的,可能真的要抽时间来完整梳理成文章,而不只是笔记记录一下思路,并且提供一些可预览的DEMO,便于各位学习。P.S.
去年就这样和小伙伴说过了,但是还是一拖再拖,真的自己是拖延症晚期没救了 😂
📌 附
- 特殊情况 1:如果识别超过 50% 时,笔画交汇时折角会超过 50%,但是没有处在交汇处的内容可能并没有超过 50%
调小栅格尺寸会避免大多数的这类情况