Ascii Art 像素处理

101 阅读1分钟

2023-04-04 23.28.24_副本.gif

我们可以通过 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)
}