canvas马赛克
对于canvas处理像素功能的一项利用,把图像马赛克化,并且可以调整模糊度。
主要思路
下图为思路解析
先获取马赛克颗粒程度size,然后把整张图片分成很多个size大小的块,然后在每一块里面随机获取一个像素点的rgba信息(也可以固定获取某个点的坐标(相对于一个小块)),然后把整个小块都变成这个颜色,最后形成整张马赛克。
虽然这听起来可能简单,但需要您有一定的数学能力,就说说上面的像素吧,我们获取来的像素信息只是一个一维数组,他从左上角的第一个像素开始,然后向右推进,不断获取像素信息,到了最右端之后再换行,从第二行开始接着向右遍历,直到最后一行的最右边的像素,最后组成一个很长的一维数组。但理想的状态下它应该是一个二维数组,学过数据结构的人应该会对这个数据存储结构有更好的理解,它更像二维数组的顺序结构的存储。所以,我们就需要单独地编写接口,通过坐标来获取当前坐标的颜色信息,也能修改颜色。
还有一个问题事关这个程序的兼容性,它一定要兼容所有尺寸的图片,即马赛克的大小size在无法被长宽整除的情况下也能对边角区域做出很好的处理,上图也是这样,边角的长方形区域也需要马赛克处理。
参考源码
window.onload = () => {
let canvas = document.getElementById('demo')
let ctx = canvas.getContext('2d')
let image = new Image()
image.src = './imgs/cat2.png' // 图片文件路径自己定义
image.onload = () => {
ctx.drawImage(image, 0, 0, image.width * 0.3, image.height * 0.3) // 绘制一张参考图片
// 存储原图的像素信息,便于调用
let oldImage = ctx.getImageData(0, 0, image.width * 0.3, image.height * 0.3)
// 创建一个新的像素数据
let newImage = ctx.createImageData(image.width * 0.3, image.height * 0.3)
// 自定义马赛克的大小 ,然后把横向的像素分为 int(width / size)(如果有余数就 + 1)块,纵向同理
// cross line分别代表横纵的马赛克块的数量(边角料的长方体也包括!)
let size = 7 // 最重要的参数,可以调整马赛克图片的模糊度。
let cross = oldImage.width % size === 0 ? oldImage.width / size : Math.floor(oldImage.width / size) + 1
let line = oldImage.height % size === 0 ? oldImage.height / size : Math.floor(oldImage.height / size) + 1
// 按照像素块为单位,然后遍历。一个像素块一个像素块处理
for (let j = 0; j < line; j++) {
for (let i = 0; i < cross; i++) {
// 然后随机从一块里找到某个像素点,获取像素点的颜色
// 随机像素坐标的声明
let x
let y
// 判断宽是否能被size整除
if (oldImage.width % size !== 0) {
// 判断是不是边角料,如果是,就做特殊处理,取的随机坐标值不可超过边缘。
if (i === cross - 1) {
x = doRandom(cross, size, oldImage.width)
} else {
x = Math.floor(Math.random() * size)
}
} else {
x = Math.floor(Math.random() * size)
}
// 这俩方法其实可以二次封装成一个函数,减少重复代码量,为了大家方便理解,所以分成两个
if (oldImage.height % size !== 0) {
if (j === line - 1) {
y = doRandom(line, size, oldImage.height)
} else {
y = Math.floor(Math.random() * size)
}
} else {
y = Math.floor(Math.random() * size)
}
let color = getImageColor(oldImage, i * size + x, j * size + y)
// 把获取的颜色整到一小块里面去, k,l代表马赛克块的坐标。
for (let k = 0; k < size; k++) {
for (let l = 0; l < size; l++) {
// 越界就不渲染,否则就会出现重复渲染(最右端的像素跑到最左端)
if (k + i * size >= oldImage.width || j * size + l >= oldImage.height){
} else {
setImageColor(newImage, k + i * size, j * size + l, color)
}
}
}
}
}
// 处理完之后最后放回
ctx.putImageData(newImage, image.width * 0.3 + 20, 0)
}
// 给一个坐标,获取该坐标的颜色
function getImageColor(imageData, x, y) {
let width = imageData.width
let color = []
color[0] = imageData.data[(y * width + x) * 4]
color[1] = imageData.data[(y * width + x) * 4 + 1]
color[2] = imageData.data[(y * width + x) * 4 + 2]
color[3] = imageData.data[(y * width + x) * 4 + 3]
return color
}
// 给一个坐标,设置该坐标的颜色
function setImageColor(imageData, x, y, color) {
let width = imageData.width
imageData.data[(y * width + x) * 4] = color[0]
imageData.data[(y * width + x) * 4 + 1] = color[1]
imageData.data[(y * width + x) * 4 + 2] = color[2]
imageData.data[(y * width + x) * 4 + 3] = color[3]
}
// 获取一个随机像素点(假如马赛克块在图片的最右端或最下端且 width(height) % size != 0的时候会用到)
let doRandom = (max, size, border) => {
return Math.floor(Math.random() * (border - size * (max - 1)))
}
}
显示效果
总结
马赛克处理方法是画布像素处理的一个重要应用,应用的数学原理也较为简单,易于理解。大家也可以根据这个原理来绘制通过鼠标来控制马赛克区域(绘制区域变成马赛克),还是那句话,没有代码做不出来的效果,人们大众的需求的无穷尽的,我们的工作就是去尽可能地满足他们的需求。