以前用canvas绘制图片的时候,若是图片有格子背景javascript
须要找一张格子背景的图片看成canvas的background-image。html
或者使用linear-gradient来画一个格子背景。java
才能实现给图片添加格子背景的效果:git
因此就在想,能不能不用图片来实现格子背景图,而是使用canvas的强大绘制功能,由于格子背景图也是数据,经过绘制数据来实现格子背景。github
首先 添加一个 canvascanvas
<canvas id="img-box" width="400" height="400"></canvas>
复制代码
而后使用 createImageData 建立一个新的空白 ImageData 对象。 而后往新的空白 ImageData 对象中设置表明格子背景的数据。数组
在往 ImageData 对象设置数据以前,咱们须要了解一下 ImageData 对象的 data 属性。函数
data属性的值是一个元素都是数字的数组,并且每一个数字的范围在 0-255ui
例如这样的:spa
console.log(imageData.data) // [255, 255, 255, 255, 78, 78, 78, 96 ....]
复制代码
它们每四个数据合起来表示一个像素点,每一个数据的意义按顺序来表述为 red, green, blue, alpha
接下来就往 ImageData 对象中设置表明格子背景的数据。
let canvas = document.getElementById("img-box");
let ctx = canvas.getContext("2d");
function createTransparentData(width, height) {
let emptyBox = ctx.createImageData(width, height);
let emptyBoxData = emptyBox.data
// 经过 canvas宽高 来遍历一下 canvas 上的全部像素点
for (let i = 0; i < height; i++)
for (let j = 0; j < width; j++) {
let point = i * width + j << 2 // << 至关于 * 4
let rgbData = (i >> 2) + (j >> 2) & 1 == 1 ? 204 : 255; // >> 2 至关于 / 4 取整, & 1至关于 % 2
emptyBoxData[point] = rgbData;
emptyBoxData[point + 1] = rgbData;
emptyBoxData[point + 2] = rgbData;
emptyBoxData[point + 3] = 255
}
return emptyBox
}
const transparentData = createTransparentData(canvas.width, canvas.height)
ctx.putImageData(transparentData, 0, 0);
复制代码
效果图以下:
拿到背景图的数据数组(transparentData)了,接下来就要把图像的数据和背景图的数据(transparentData)融合了。
首先须要获取图像的数据。因此先往canvas上画一张图片,为了更好的适应各类状况,这里特地选了一张多边形图片。
<canvas id="img-box" width="400" height="400"></canvas>
<button class="btn" onClick="addTransparent()">添加格子背景</button>
复制代码
let canvas = document.getElementById("img-box");
let ctx = canvas.getContext("2d");
let img = new Image();
img.src = 'https://raw.githubusercontent.com/Fathands/images/master/canvas-share/pic.png';
img.onload = function() {
ctx.drawImage(img, 0, 0, 400, 400);
}
复制代码
此时已经在canvas上把图像画上去了:
而且为图片添加格子背景的操做都放在 addTransparent 函数中,而且把 addTransparent 函数绑定到 “添加格子背景” 按钮上
接下来是重点。须要获取:
1.原始图的有效图像数据,就是下图中红色边框框住的部分
2.有效图像的起点,宽和高, 起点是以图像的左上角为原点的。
以下图所示:
在这以前,咱们还须要获取原图中全部表示透明度的数据
function getAllAlphaData() {
let originImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
let alphaDataLength = new Uint8Array(originImageData.data.length >> 2)
let alphaDataList = new Uint8Array(alphaDataLength)
let originData = new Uint8Array(originImageData.data)
let len = alphaDataList.length
for (let i = 0; i < len; i++) {
alphaDataList[i] = originData[(i << 2) + 3]
}
return alphaDataList
}
复制代码
拿到全部表示透明度的数据后,咱们就能够来获取有效图像数据,有效图像的起点,宽和高了
function getValidImageInfo(O, i) {
const alphaDataList = getAllAlphaData()
let originWidth = canvas.width // 原始图像的宽
let originHeight = canvas.height // 原始图像的高
let x = 0 // 有颜色的数据部分 最小横坐标
let y = 0 // 有颜色的数据部分 最小纵坐标
let width = 0 // 从右往左 至有颜色的部分的 横向距离
let bottomHeight = 0 // 从底部到 有颜色的部分的 纵向距离
let flag
flag = false
for (let i = 0; i < originHeight; i++) { // 从头开始 从上到下的扫描
let line = i * originWidth;
for (let j = 0; j < originWidth; j++)
if (alphaDataList[line + j] !== 0) // 不等于0 就是 有透明度
flag = true; // 设为true
if (flag) // 若是这一行有一个透明度不为0的就跳过,不然最小纵坐标+1
break;
else
y++
}
flag = false;
for (let i = originHeight - 1; i >= 0; i--) { // 从底部开始 从下到上的扫描
let line = i * originWidth;
for (let j = 0; j < originWidth; j++)
if (alphaDataList[line + j] !== 0)
flag = true;
if (flag) // 若是这一行有一个透明度不为0的就跳过,不然底部纵向距离+1
break;
else
bottomHeight++
}
flag = false;
for (let j = 0; j < originWidth; j++) { // 从左到右的扫描
for (let i = 0; i < originHeight; i++)
if (alphaDataList[i * originWidth + j] !== 0)
flag = true;
if (flag)
break;
else
x++
}
flag = false;
for (let j = originWidth - 1; j >= 0; j--) { // 从右到左的扫描
for (let i = 0; i < originHeight; i++)
if (alphaDataList[i * originWidth + j] !== 0)
flag = true;
if (flag)
break;
else
width++
}
return {
x: x,
y: y,
width: originWidth - x - width,
height: originHeight - y - bottomHeight
}
}
复制代码
咱们这张图片的出来的数据以下:
const validData = getValidImageInfo()
console.log(validData)
// {
// height: 343
// width: 283
// x: 49
// y: 24
// }
复制代码
即这张图片的有效颜色的起点为 [49, 24] ,有效图像的 宽为 283,高为 343
拿到有效颜色图像的 起点,宽高以后。就能够获取了 有效颜色的数据对象
以下:
const validImageData = new Uint8Array(ctx.getImageData(validData.x, validData.y, validData.width, validData.height).data)
复制代码
拿到 有效颜色的数据对象 以后,接下来就是把 经过createTransparentData获取的透明颜色的数据对象transparentData 和 有效颜色的数据对象validImageData 融合到一块儿。
// .......
function addTransparent() {
const transparentData = createTransparentData(canvas.width, canvas.height)
const transparentDataInfo = transparentData.data
const validData = getValidImageInfo()
const validImageData = new Uint8Array(ctx.getImageData(validData.x, validData.y, validData.width, validData.height).data)
let validImageX = validData.x // 有效图像横坐标
let validImageXandWidth = validData.x + validData.width // 有效图像横坐标 加上 宽度
let validImageY = validData.y // 有效图像纵坐标
let validImageYandHeight = validData.y + validData.height; // 有效图像纵坐标 加上 高度
for (let i = 0; i < canvas.height; i++) // 遍历高度
for (let j = 0; j < canvas.width; j++) { // 遍历宽度
let numberPoint = i * canvas.width + j // 第几个点
let pixelPoint = numberPoint * 4
let pointerX = j // 横坐标
let pointerY = i // 纵坐标
// 设置边界
if (pointerX < validImageX || pointerX >= validImageXandWidth || pointerY < validImageY || pointerY >= validImageYandHeight) {} else {
let red = transparentDataInfo[pixelPoint] // 透明背景的r
let green = transparentDataInfo[pixelPoint + 1] // 透明背景的g
let blue = transparentDataInfo[pixelPoint + 2] // 透明背景的b
let alpha = transparentDataInfo[pixelPoint + 3] // 透明背景的a
// 融合像素点
let K = ((pointerY - validData.y) * validData.width + (pointerX - validData.x)) * 4;
alpha = validImageData[K + 3] / 255;
red = validImageData[K + 0] * alpha + red * (1 - alpha);
green = validImageData[K + 1] * alpha + green * (1 - alpha);
blue = validImageData[K + 2] * alpha + blue * (1 - alpha);
// 从新赋值
transparentDataInfo[pixelPoint] = red;
transparentDataInfo[pixelPoint + 1] = green;
transparentDataInfo[pixelPoint + 2] = blue;
transparentDataInfo[pixelPoint + 3] = 255
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.putImageData(transparentData, 0, 0);
}
// .......
复制代码
最后的效果图: