通过canvas getImageData()裁剪空白区域
引子
用canvas绘制图像时,我们会着重于ROI区域,在一张干净的画布绘制了ROI区域,我们又在上面添加了诸多元素,当我们想要把这部分图clip下来的时候,一般都会裁剪整个画布,受限于浏览器容器的不确定性,容器有大有小,而ROI区域又是固定的,导致有时候裁剪下来的图像会有大部分留白,对于这部分了留白是不需要的,所以最终在裁剪画布的时候也要对这部分留白进行处理掉……
Canvas getImageData()
在Canvas的getImageData
方法中,返回的imageData
对象包含了指定矩形区域像素的信息,其中data
属性是一个一维数组,包含了每个像素的红、绿、蓝和透明度信息。这个数组的长度是矩形区域像素的总数乘以4(每个像素有4个值)。
具体而言,data
数组的结构如下:
- 第一个元素是第一个像素的红色分量(0 到 255)。
- 第二个元素是第一个像素的绿色分量(0 到 255)。
- 第三个元素是第一个像素的蓝色分量(0 到 255)。
- 第四个元素是第一个像素的透明度(0 到 255,0 表示完全透明,255 表示完全不透明)。
- 后续的元素按照相同的顺序依次表示接下来的像素的信息。
const context = canvas.getContext("2d")
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
const width = imageData.width;
const height = imageData.height;
const pixels = _.chunk(imageData.data, 4);
const image = _.chunk(pixels, width);
_.chunk(array, [size=1])
将数组(array)拆分成多个size
长度的区块,并将这些区块组成一个新数组。 如果array
无法被分割成全部等长的区块,那么最后剩余的元素将组成一个区块。
将imageData从一维数组按照RGBA拆成一个一维数组,这样每个数组代表一个像素的颜色信息,然后再将像素数组按照图像的宽度分组成行,得到一个二维数组,表示整个图像。
寻找起始位置
思路
我们需要确定的是ROI的上下左右的四个边界,从左侧开始寻找,当检查的像素点不是纯黑色,即point != [0,0,0,1],那就说明遇到了有颜色的像素,即触碰到ROI的边检,那就找到了左上角……
代码
let startX = 0;
let startY = 0;
let endX = width - 1;
let endY = height - 1;
// 找打左侧边缘
for(let i = 0; i < width; i++) {
for(let j = 0; j < height; j++) {
const point = image[j][i]
if(point[0] + point[1] + point[2] !== 0) {
startX = i;
break;
}
}
if(startX !== 0) break;
}
……
- 初始化变量
startX
,startY
,endX
,endY
分别表示矩形区域的左上角和右下角坐标。 - 使用嵌套的循环遍历图像的每一列和每一行。
- 获取图像中每个像素的颜色信息,存储在
point
变量中。 - 检查像素点的颜色是否等于0(这里是一个简单的条件,可以根据实际情况调整),如果是,说明这是有颜色的像素。
- 如果找到有颜色的像素,记录当前列的索引为
startX
,并立即跳出内层循环。 - 如果
startX
不等于 0,说明找到了左侧边缘的起始位置,于是跳出外层循环。
这段代码的目的是找到图像左侧的第一个有颜色的像素,以确定左侧边缘的起始位置。
图像中含有中“杂质”
let startX = 0;
let startY = 0;
let endX = width - 1;
let endY = height - 1;
// 找打左侧边缘
for(let i = 0; i < width; i++) {
for(let j = 0; j < height; j++) {
const point = image[j][i]
if(point[0] + point[1] + point[2] > 3) {
startX = i;
break;
}
}
if(startX !== 0) break;
}
// 找到右侧边缘
for(let i = width - 1; i > 0; i--) {
for(let j = 0; j < height; j++) {
const point = image[j][i]
if(point[0] + point[1] + point[2] > 3) {
endX = i;
break;
}
}
if(endX !== width - 1) break;
}
// 找到顶部边缘
for(let i = 0; i < height; i++) {
for(let j = 0; j < width; j++) {
const point = image[i][j]
if(point[0] + point[1] + point[2] > 3) {
startY = i;
break;
}
}
if(startY !== 0) break;
}
// 找到底部边缘
for(let i = height - 1; i > 0; i--) {
for(let j = 0; j < width; j++) {
const point = image[i][j]
if(point[0] + point[1] + point[2] > 3) {
endY = i;
break;
}
}
if(endY !== height - 1) break;
}
const bound = {
x: startX,
y: startY,
top: startY,
left: startX,
bottom: endY,
right: endX,
width: endX - startX,
height: endY - startY
};
通过上述代码能简单的判断留白为纯色像素点,但实际上我们会遇到像素点中可能包含一些噪声或杂质,那我们可能需要更加复杂方式来判断了。
单个杂质
对于单个的杂质,我们可以需要判断前一个**“杂质”的位置与当前“杂质”**的位置是否连贯,不连贯那就属于杂质。
let prevPos = 0
for(let i = 0; i < width; i++) {
for(let j = 0; j < height; j++) {
const point = image[j][i]
if(point[0] + point[1] + point[2] > 3) {
startX = i;
break;
}
}
if(startX !== 0) {
if (startX - prevPos === 1) {
break
}
prevPos = startX
};
}
灰度值
一种常见的方法是通过设置一个阈值,只有当像素的颜色超过阈值时才被认为是有颜色的像素。这可以通过计算像素的亮度或灰度值来实现。
const threshold = 50; // 设置一个阈值,根据实际情况调整
// 找到左侧边缘
for (let i = 0; i < width; i++) {
for (let j = 0; j < height; j++) {
const point = image[j][i];
// 计算像素的灰度值
const grayscale = (point[0] + point[1] + point[2]) / 3;
// 检查灰度值是否大于阈值
if (grayscale > threshold) {
startX = i; // 记录左侧边缘的起始位置
break;
}
}
if (startX !== 0) break; // 如果找到了左侧边缘的起始位置,跳出循环
}
在这个例子中,grayscale
表示像素的灰度值,计算方式是将红、绿、蓝通道的值求平均。如果灰度值超过了预定的阈值 threshold
,就认为这个像素是有颜色的。
裁剪
clip(canvas: HTMLCanvasElement, area: BoundingRect): Promise<string> {
return new Promise((resolve) => {
const context = canvas.getContext("2d")
const imageData = context.getImageData(area.x, area.y, area.width, area.height)
const partialCanvas = document.createElement("canvas")
const partialContext = partialCanvas.getContext("2d")
partialCanvas.width = area.width
partialCanvas.height = area.height
partialContext.putImageData(imageData, 0, 0)
resolve(partialCanvas.toDataURL("image/jpeg",1))
})
}
尾声
由于我们的留白基本是纯黑色,图片里可能是rgb(1,1,1)的背景,所以在判断杂质方面没有做过多的研究,以上仅是一个思路。