我们可以通过 getImageData() 方法来取得所有的像素点的rgba数据。
使用ctx.getImageData()获取所有的数据,是一个数组,它的长度是所有像素点个数 * 4。为什么是乘以4,是因为rgba是四个值,这个数组从0开始每隔4个就是一个像素点的r/g/b/a值。所以数组的第0,1,2,3个值分别代表第一个像素点的r,g,b,a的值。
获取像素点值
for (let y = 0; y < this.#pixels.height; y += cellSize) {
for (let x = 0; x < this.#pixels.width; x += cellSize) {
const posX = x * 4
const posY = y * 4
const pos = posY * this.#pixels.width + posX
const red = this.#pixels.data[pos]
const green = this.#pixels.data[pos + 1]
const blue = this.#pixels.data[pos + 2]
const alpha = this.#pixels.data[pos + 3]
}
}
完整代码:
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
const image = new Image()
image.src = '' // cross-origin问题转成base64吧
const inputSlider = document.getElementById('resolution')
const inputLabel = document.getElementById('resolutionLabel')
inputSlider.addEventListener('change', handleSlider)
class Cell {
constructor(x, y, sysmbol, color) {
this.x = x
this.y = y
this.sysmbol = sysmbol
this.color = color
}
draw(ctx) {
ctx.fillStyle = 'white'
ctx.fillText(this.sysmbol, this.x + 0.5, this.y + 0.5)
ctx.fillStyle = this.color
ctx.fillText(this.sysmbol, this.x, this.y)
}
}
class AsciiEffect {
#imageCellArr = []
#pixels = []
#ctx
#width
#height
constructor(ctx, width, height) {
this.#ctx = ctx
this.#width = width
this.#height = height
this.#ctx.drawImage(image, 0, 0, this.#width, this.#height)
this.#pixels = this.#ctx.getImageData(0, 0, canvas.width, canvas.height)
}
#convertToSysmbol(g) {
if (g > 250) return '@'
else if (g > 240) return '*'
else if (g > 220) return '+'
else if (g > 200) return '#'
else if (g > 180) return '&'
else if (g > 160) return '%'
else if (g > 140) return '_'
else if (g > 120) return ':'
else if (g > 100) return '£'
else if (g > 80) return '/'
else if (g > 60) return '-'
else if (g > 40) return 'X'
else if (g > 20) return 'W'
else return ''
}
#scanImage(cellSize) {
this.#imageCellArr = []
for (let y = 0; y < this.#pixels.height; y += cellSize) {
for (let x = 0; x < this.#pixels.width; x += cellSize) {
const posX = x * 4
const posY = y * 4
const pos = posY * this.#pixels.width + posX
if (this.#pixels.data[pos + 3] > 128) {
const red = this.#pixels.data[pos]
const green = this.#pixels.data[pos + 1]
const blue = this.#pixels.data[pos + 2]
const total = red + green + blue
const averageColor = total / 3
const color = 'rgba(' + red + ',' + green + ',' + blue + ')'
const sysmbol = this.#convertToSysmbol(averageColor)
if (total > 200) {
this.#imageCellArr.push(new Cell(x, y, sysmbol, color))
}
}
}
}
}
#drawAscii() {
this.#ctx.clearRect(0, 0, this.#width, this.#height)
for (let i = 0; i < this.#imageCellArr.length; i++) {
this.#imageCellArr[i].draw(this.#ctx)
}
}
draw(cellSize) {
this.#scanImage(cellSize)
this.#drawAscii()
}
}
function handleSlider () {
if (inputSlider.value == 1) {
inputLabel.innerHTML = 'Original image'
ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
} else {
inputLabel.innerHTML = 'Resolution: ' + inputSlider.value + ' px'
effect.draw(parseInt(inputSlider.value))
ctx.font = parseInt(inputSlider.value) * 1.2 + 'px Verdna'
}
}
let effect
image.onload = function () {
canvas.width = image.width / 2
canvas.height = image.height / 2
effect = new AsciiEffect(ctx, canvas.width, canvas.height)
effect.draw(5)
}