在实际的业务中需要对图像做特殊处理,比如获取图像的像素点,获取图像的边缘信息,图像和图像之间需要做到吸附。有的小伙伴不知道如何操作,那么这篇文章会给你带来一定的收获。知道的小伙伴或者有更简单的方法,还请多多指教,虚心接受并学习。
1. 如何获取图像的像素点
JavaScript
中获取图像的相关信息主要是靠Canvas
来获取,借住api getImageData
来实现。
假设我们有这个一张图片
这是一张PNG-32 452 * 452
的图片,现在通过getImageData
拿到他上面的像素点信息
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 假设我们的图片路径是
const source = 'http://xxx.com/test.png';
// 创建img对象
const img = new Image();
// 请求资源不需要凭证,如果服务器有做特殊限制,则这段代码无效
img.crossOrigin = 'anonymous';
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
// 将图片绘制到canvas中,绘制起始点为x: 0, y: 0
ctx.drawImage(img, 0, 0);
// 获取图片像素点信息,从x: 0, y: 0开始,到图片的宽度和高度
const imgData = ctx.getImageData(0, 0, img.width, img.height);
console.log(imgData);
}
img.src = source;
上面代码打印出图片像素点的数据
红色区域当前的像素点集合452 * 452 = 204304
,总像素点有204304
,那为什么是817216
呢,像素点集合是每四个为一个点,这四个分别代表r(0~255),g(0~255), b(0~255), a(0~255)
, 那么应该在原有的基础上204304 * 4 = 817216
这时的像素点就对了。
2. 处理像素点
// imgData是第一步获取到的像素点信息
const points = imgData.data;
const len = points.length;
// 定义每一行row
let row = 0;
// 定义存储所有坐标的集合
const imgPoints = [];
for (let i = 0; i < len; i += 4) {
//每4个的第0个代表r
const r = i;
// 每4个的第1个代表g
const g = i + 1;
// 每4个的第2个代表b
const b = i + 2;
// 每4个的第3个代表a
const a = i + 3;
// 把像素点转换成坐标点
/**
* 这里判断是否为当前行的信息,用y来做判断
* 图片宽度是452,如果是第一行的话y为1,第二行y为2...
* 用当前递增i的值除以4在除以图片的宽度,向上取整,计算出第几行
*/
const y = Math.ceil(i / 4 / imgData.width);
// 如果y与当前row不相等,也就是从下一行开始
if (y && y !== row) {
// 将当前y赋值给row;
row = y;
// 将当前行设置成集合
imgPoints[row] = [];
}
// 如果当前集合存在
if (imgPoints[row]) {
// 如果透明度a的值存在
if (a) {
// 算出当前这一行上每一个像素点的位置
const x = (i / 4) - (imgData.width * (y - 1));
// 将x, y,当前这一行,数据结构为map
imgPoints[row].push(
{
x,
y
}
);
} else {
// 如果当前的透明值a不存在则存放0
imgPoints[row].push(0);
}
}
}
这样我们的一个数据结构已经成功了
// 最终的数据结果,每一行都是一个集合,每一行集合里面有数据的则是map结构,没有则是0
[
// 第一行的坐标, y为1
[
{
x: 1,
y: 1
},
{
x: 2,
y: 1,
},
// 当前没有坐标点,是透明点,记录0
0
// ......
],
// 第二行的坐标, y为2
[
{
x: 1,
y: 2
},
{
x: 2,
y: 2
},
// 当前没有坐标点,是透明点,记录0
0
// ......
]
]
2. 获取图像边缘
// 假设我们的数据结构
[0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0]
// 0代表没有坐标点,1代表我们的map集合
// 现在我们需要将其实和结尾连续的0清除
// 得到的结果
[1, 1, 1, 0, 0, 1, 1, 1]
// 现在继续将数据结构进一步优化,将第一个1, 到第一个0的位置但不包含0取出
[1, 1, 0, 0, 1, 1, 1]
// 继续将后面的数据结构一同取出
[1, 1, 0, 0, 1, 1]
// 最终将0过滤掉
[1, 1, 1, 1]
// 上面是我们需要的数据结构
// 由此得出判断判断条件
const f = 数组的第0个 === 1;
const l = 数组的最后一个 === 1;
const conditionLeft = 当前值 !== 0 && 当前值的前一个值 === 0;
const conditionRight = 当前值 !== 0 && 当前值的后一个值 === 0;
// 这个判断则过滤当前这一行所有的数据,并不是y,而是x
// 接下来过滤y
// 假设数据结构
[
[0, 0, 1, 1, 0, 0],
[1 ,1, 0, 1, 1, 0]
]
// 此时我们应该将所有列,注意:不是行,行是x,现在我们要过滤列。
// 判断条件和行的判断条件基本一致
const f = 第1行 === 1;
const l = 最后1行 === 1;
const conditionTop = 当前行的当前值的上一行对应的值 === 0 && 当前行的当前值 !== 0;
const conditionBottom = 当前行的当前值的下一个对应的值 === 0 && 当前行的当前值 !== 0;
// 当前判断条件将过滤所有的y
接下来我们需要将过滤x和过滤y的条件,筛选出来的数据存放在不同的数据结构中
// 定义存放x的坐标点
const xPoints = [];
// 定义存放y的坐标点
const yPoints = [];
// imgPoints是存放所有的数据结构,是个多维数组(二维数组)
imgPoints.forEach((points, i) => {
points.forEach((row, j) => {
// 首先套用x的判断
if (
(j === 0 && row !== 0) ||
(j === points.length - 1 && row !== 0) ||
(row !== 0 && typeof points[j - 1] !== 'undefined' && points[j - 1] === 0) ||
(row !== 0 && typeof points[j + 1] !== 'undefined' && points[j + 1] === 0)
) {
xPoints.push(row);
}
if (
(i === 0 && row !== 0) ||
(i === imgPoints[imgPoints.length - 1] && k !== 0) ||
(typeof imgPoints[i - 1] !== 'undefined' && imgPoints[i - 1][j] === 0 && row !== 0) ||
(typeof imgPoints[i + 1] !== 'undefined' && imgPoints[i + 1][j] === 0 && row !== 0)
) {
yPoints.push(row);
}
})
})
// 上面的代码就拿到了图形边缘的所有坐标点,不管这个图形有多复杂,还是这个图形分开的多个小图形
假设我们上述不知道,并且我们的程序中不知道这个图形的颜色。推荐大家使用第三方的库,可以转换成边缘图像。
本人推荐https://github.com/miguelmota/sobel,这个库的代码很少,有兴趣可以阅读一下他里面的核心算法。我还使用了一些其他的图像转边缘图像的其他库,但出来的效果并不是很好。
3. 图像吸附
当你拿到边缘点的时候,你就可以对你的图像进行吸附的功能操作。吸附功能还要根据各自的业务去实现,在这里不在coding。
总结
写到这里,有的人会说到,为什么不用一元二次方程,或者点斜式方程去计算斜边的点
。
其实也是可以的,但这种情况仅限于固定程序。已知图形的外边缘的点。比如一个三角形,已知3个点
,那么这个之后你可以通过方程式求出斜边的坐标点。但仅限于已知程序,如果一张图像是通过程序导入,或者外部资源引入的方式,那方程式计算出来要比直接取点要来的麻烦,麻烦的因素是多边形和不规则图形,以及分散图形。套用方程式会有额外的工作。