第一步当然是获取图片咯
<div class="group">
<p>原图:</p>
<canvas id="out1"></canvas>
</div>
<div class="group">
<p>马赛克:</p>
<canvas id="out2"></canvas>
</div>
const getImgData = (url) => {
let dom = document.createElement('canvas');
const ctx = dom.getContext('2d');
const image = new Image();
image.src = url;
return new Promise(resolve => {
image.onload = () => {
const width = image.width;
const height = image.height;
dom.width = width;
dom.height = height;
console.log(`图片宽高:${width}, ${height};像素点:${width * height}`);
ctx.drawImage(image, 0, 0, width, height);
// 获取图片信息
const imgData = ctx.getImageData(0, 0, width, height);
dom = null;
resolve({
data: imgData.data,
width,
height,
});
}
})
}
很好理解吧~记得在服务下打开html哦 这里推荐http-server 好用的一批。
ctx.getImageData是获取图片数据的接口(有跨域问题),返回数组:[r1,g1,b1,a1,r2,g2,b2,a2...],其中是从左至右、从上至下的像素的rgba一维数组。
随便塞一张图执行一下吧~
getImgData('./3.jpg').then(({
data,
width,
height,
}) => {
consolle.log(data, width, height);
})
getImgData里得出结果:图片宽高:225, 224;像素点:50400
then里面得出结果:Unit8ClampedArray(201600)[255,255,255,255,255,255,...] 225 224
第二步就是打印图片
我们要弄个马赛克,当然是得把一维数组做成矩阵,先把这一步做了吧~
打个比方,这是一个4个像素的2x2的方块
[255,0,0,255,0,255,0,255,0,0,255,255,0,0,0,255]
我们要弄成一个矩阵
[
[
[255,0,0,255],[0,255,0,255]
],
[
[0,0,255,255],[0,0,0,255]
]
]
不多说,上代码
/**
* 图片数组+宽+高 => 矩阵数组
* @param {Array<number>} arr 图片数组
* @param {number} width 宽
* @param {number} height 高
*/
const splitImgData = (arr, width, height) => {
const list = [];
const result = [];
const len = Math.ceil(arr.length / 4);
// 行数获取
for (let i = 0; i < len; i++) {
const start = i * 4;
list.push([arr[start], arr[start + 1], arr[start + 2], arr[start + 3]]);
}
// 纵列嵌套
for (let h = 0; h < height; h++) {
const temp = [];
for (let w = 0; w < width; w++) {
temp.push(list[h * width + w]);
}
result.push(temp);
}
return result
}
然后就是打印图片代码
/**
* 矩阵数组=>图片
* @param {Array<Array<Array<number>>>} matrix
* @param {Document} dom canvas DOM
*/
const printImg = (matrix, dom) => {
const ctx = dom.getContext('2d');
const width = matrix[0].length;
const height = matrix.length;
dom.width = width;
dom.height = height;
// 创建图片数组对象
const matrixObj = ctx.createImageData(width, height);
// 矩阵解耦,空数组设为白色
const _matrix = matrix.flat(1).map(arr => arr.length ? arr : [0,0,0,0]).flat(1);
// 图片数组 加入图片数组对象
matrixObj.data.set(_matrix)
// 绘制
ctx.putImageData(matrixObj, 0, 0);
}
createImageData接受一维数组,所以要用flat转换。
这里flat解释一下吧,是个ES6的新方法,用来数组的降维打击(笑),传1则是向下降维一层,传Infinity则是降维到一维,一向箔了属于是。
执行一下吧 不贴图了
const canvas_out1 = document.getElementById('out1');
getImgData('./3.jpg').then(({
data,
width,
height,
}) => {
const matrix = splitImgData(data, width, height);
printImg(matrix, canvas_out1);
})
马赛克!
咋搞马赛克呢,先设定x y width height,然后加入granular(颗粒度)概念,每一个granular*granular的方块内部颜色替换为rgba四项值的平均。
1、第一步解决切割granular*granular方块
/**
* 获取 x y 那个点的颜色数组
* @param {Array<Array<Array<number>>>} matrix
* @param {number} y
* @param {number} x
*/
const getMatrix_XY = (matrix, y, x) => {
return matrix[y] ? matrix[y][x] || [] : []
}
/**
* 打码(矩阵数组=>矩阵数组)
* @param {Array<Array<Array<number>>>} matrix
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
* @param {number} granular 颗粒度 像素
*/
const imgCoding = (matrix, x, y, width, height, granular) => {
const _matrix = JSON.parse(JSON.stringify(matrix));
/* 设定一个对象存颗粒方块
* {
* 'g_x, g_y': {
* 'x,y': [r,g,b,a],
* 'x,y': [r,g,b,a],
* },
* 'g_x, g_y': {
* 'x,y': [r,g,b,a],
* 'x,y': [r,g,b,a],
* }
* }
*/
const codingBlock = {};
for (let w = 0; w < width; w++) {
const groupX = Math.ceil((w + 1) / granular) - 1
for (let h = 0; h < height; h++) {
const groupY = Math.ceil((h + 1) / granular) - 1
const target = getMatrix_XY(_matrix, w + x, h + y);
if (target.length) {
if (codingBlock[`${groupX},${groupY}`]) {
codingBlock[`${groupX},${groupY}`][`${w + x},${h + y}`] = target
} else {
codingBlock[`${groupX},${groupY}`] = {}
codingBlock[`${groupX},${groupY}`][`${w + x},${h + y}`] = target
}
}
}
}
}
其中'g_x、g_y'只是用来定义颗粒块的下标,'x,y'用来定位,codingBlock就储存了每个颗粒块内每个像素的颜色,后面就是取平均数了。
/**
* 获取平均数(返回整数)
* @param {Array<number>} arr
*/
const getAverage = arr => Math.ceil(eval(arr.join('+')) / arr.length);
/**
* 打码(矩阵数组=>矩阵数组)
* @param {Array<Array<Array<number>>>} matrix
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
* @param {number} granular 颗粒度像素
*/
const imgCoding = (matrix, x, y, width, height, granular) => {
const _matrix = JSON.parse(JSON.stringify(matrix));
/* 打码块
* {
* 'g_x, g_y': {
* 'x,y': [r,g,b,a],
* 'x,y': [r,g,b,a],
* },
* 'g_x, g_y': {
* 'x,y': [r,g,b,a],
* 'x,y': [r,g,b,a],
* }
* }
*/
const codingBlock = {};
for (let w = 0; w < width; w++) {
const groupX = Math.ceil((w + 1) / granular) - 1
for (let h = 0; h < height; h++) {
const groupY = Math.ceil((h + 1) / granular) - 1
const target = getMatrix_XY(_matrix, w + x, h + y);
if (target.length) {
if (codingBlock[`${groupX},${groupY}`]) {
codingBlock[`${groupX},${groupY}`][`${w + x},${h + y}`] = target
} else {
codingBlock[`${groupX},${groupY}`] = {}
codingBlock[`${groupX},${groupY}`][`${w + x},${h + y}`] = target
}
}
}
}
for (const k in codingBlock) {
const arr_r = [];
const arr_g = [];
const arr_b = [];
const arr_a = [];
for (const key in codingBlock[k]) {
arr_r.push(codingBlock[k][key][0]);
arr_g.push(codingBlock[k][key][1]);
arr_b.push(codingBlock[k][key][2]);
arr_a.push(codingBlock[k][key][3]);
}
const color = [
getAverage(arr_r),
getAverage(arr_g),
getAverage(arr_b),
getAverage(arr_a),
];
for (const key in codingBlock[k]) {
const [_x, _y] = key.split(',');
_matrix[Number(_x)][Number(_y)] = color
}
}
return _matrix
}
以10为颗粒度执行!
const canvas_out2 = document.getElementById('out2');
getImgData('./3.jpg').then(({
data,
width,
height,
}) => {
const matrix = splitImgData(data, width, height);
printImg(imgCoding(matrix, 0, 0, 160, 160, 10), canvas_out2)
})
得到返回图片:

好家伙 这码厚的!
结束啦 附赠个图片旋转代码,totate(Math.PI/2)出来挨打
/**
* 旋转
* @param {Array<Array<Array<number>>>} matrix
* @param {'left' | 'right'} direction
*/
const rotate = (matrix, direction) => {
const _matrix = JSON.parse(JSON.stringify(matrix));
const new_width = matrix[0].length;
const new_height = matrix.length;
const newMatrix = [];
// 左转
if (direction === 'left') {
for (let x = 0; x < new_width; x++) {
const temp = [];
for (let y = 0; y < new_height; y++) {
temp.push(getMatrix_XY(_matrix, y, new_width - 1 - x))
}
newMatrix.push(temp);
}
}
// 右转
if (direction === 'right') {
for (let x = 0; x < new_width; x++) {
const temp = [];
for (let y = 0; y < new_height; y++) {
temp.push(getMatrix_XY(_matrix, new_height - 1 - y, x))
}
newMatrix.push(temp);
}
}
return newMatrix;
}
下班无聊 代码无聊 各种无聊