「青训营 X 码上掘金」主题创作活动 | 我的名片

72 阅读3分钟

当青训营遇上码上掘金

零、优雅的自我介绍

作为一名程序员,咱怎么进行优雅地自我介绍?

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  greet() {
    alert('hello')
  }
}

这样?这只是简单的面向对象编程罢了,一点都不酷,一点都不优雅,只要有点代码基础就能够写出来。

作为一名程序员,会装是一门必修课,而制作出自己一份漂漂亮亮的名片,则是会装的基础。这让我回想起在某网站上使用字符画拼出自我介绍的名片,同时也在同学使用Springboot的时候见过类似的字符画:

image.png

像这样。

如果能根据一张图片,用代码自己生成一幅字符画,绝对是自我介绍的绝佳名片!

二、实现原理

首先准备一组排好序的不同 着色密度  的ascii字符(事实上你可以用任何字符),接着将源图转为灰度图,然后遍历图中的像素,根据r/g/b通道的值来匹配字符串中相应着色密度的字符,值越小则颜色越深,字符的“密度”也应越大。

如果需要保留颜色,只需将灰度图和原图的像素位置一一对应即可。

在开始实现功能之前,我们需要先了解一下颜色矩阵(ColorMatrix)。在计算机中,每个像素的颜色可以用一个向量矩阵表示:[R, G, B, A]。颜色变换矩阵通常是用一个 5x5 的矩阵来表示,和空间中一个 n 维向量的平移变换需要用一个 n+1 维的矩阵来表示一样,颜色矩阵也需要引入一个齐次坐标来进行「平移操作」。

三、前期准备

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <title>ascii art</title>
    <style>
 
        * {
            margin: 0;
            padding: 0;
        }
 
        canvas, img, #container {
            display: block;
            margin: auto;
        }
 
        #container {
            line-height: 12px;
            font-size: 12px;
            font-family: 'SimHei', monospace;
            letter-spacing: 6px;
        }
 
    </style>
</head>
<body>
<img src="https://i.postimg.cc/mrFwCvBG/name.png"/>
<div id="container"></div>
<script>
  (function () {
    // 这里是js代码
  })()
</script>
</body>
</html>

先准备好一个容器,并在互联网上上传自己需要的图片。后期,通过js代码实现图片转字符画功能

四、核心功能实现

var container = document.getElementById('container')
var offScreenCvs = document.createElement('canvas') // 创建离屏canvas
var offScreenCtx = offScreenCvs.getContext('2d', { alpha: false }) // 关闭透明度
var offScreenCvsWidth, offScreenCvsHeight
var samplerStep = 4 // 采样间隔
 
var img = new Image()
var onImgLoaded = function () {
  offScreenCvsWidth = img.width
  offScreenCvsHeight = img.height
  offScreenCvs.width = offScreenCvsWidth
  offScreenCvs.height = offScreenCvsHeight
  offScreenCtx.drawImage(img, 0, 0, offScreenCvsWidth, offScreenCvsHeight)
  imageData = offScreenCtx.getImageData(0, 0, offScreenCvsWidth, offScreenCvsHeight)
  // 采样点数 = 图片宽度 / 采样间隔;容器边长 = 采样点数 × 字体大小
  container.style.width = (offScreenCvsWidth / samplerStep * 12) + 'px'
  container.style.height = (offScreenCvsHeight / samplerStep * 12) + 'px'
  render()
}
img.src = './trump.png'
img.complete ? onImgLoaded() : (img.onload = onImgLoaded) // 确保onImgLoaded被执行
 
var imageData
var x, y, pos
var asciiCharArray = '#KDGLftji+;,:.'.split('') // 准备不同密度的字符数组(降序)
var durationPerChar = Math.ceil(255 / asciiCharArray.length) // 每个字符代表的密度阈值
 
function render () {
  var imageDataContent = imageData.data
  var strArray = []
  var part1, part2
  var letter
  var value
  for (y = 0; y < offScreenCvsHeight; y += samplerStep) {
    strArray.push('<p>') // 使用P标签换行
    for (x = 0; x < offScreenCvsWidth; x += samplerStep) {
      pos = y * offScreenCvsWidth + x
      // 获取RBG加权平均后的灰度值
      value = imageDataContent[pos * 4] * 0.3086 + imageDataContent[pos * 4 + 1] * 0.6094 + imageDataContent[pos * 4 + 2] * 0.0820
      imageDataContent[pos * 4] = imageDataContent[pos * 4 + 1] = imageDataContent[pos * 4 + 2] = value
      // 判断灰度值落在那个密度范围中,拿到对应的字符
      part1 = Math.floor(value / durationPerChar)
      part2 = value % durationPerChar
      letter = part2 ? asciiCharArray[part1] : (part1 ? asciiCharArray[part1 - 1] : 'æ')
 
      strArray.push(letter)
    }
    strArray.push('</p>')
  }
  container.innerHTML = strArray.join('')
}

解释一下onImgLoaded核心函数:

通常来说img.onload必须要放在 img.src之前,来保证onload回调一定会执行,否则的话如果图片在执行这段代码之前已经被浏览器缓存了,则有可能不会触发onload回调。

但是有时候由于业务的需要,有些操作必须要在图片载入完成后执行,可是不一定立即执行,碰到这种情况,就可以用到Image对象的complete属性,该属性会返回当前图片是否加载完成的bollean值。

于是,通过上面这行代码,就可以确保onImgLoaded函数在图片载入完成后一定会被触发。