
有一些隐藏在代码中的 ASCII 有意思的图片,如:
/*
                                 _
                              _ooOoo_
                             o8888888o
                             88" . "88
                             (| -_- |)
                             O\  =  /O
                          ____/`---'\____
                        .'  \\|     |//  `.
                       /  \\|||  :  |||//  \
                      /  _||||| -:- |||||_  \
                      |   | \\\  -  /'| |   |
                      | \_|  `\`---'//  |_/ |
                      \  .-\__ `-. -'__/-.  /
                    ___`. .'  /--.--\  `. .'___
                 ."" '<  `.___\_<|>_/___.' _> \"".
                | | :  `- \`. ;`. _/; .'/ /  .' ; |    
                \  \ `-.   \_\_`. _.'_/_/  -' _.' /
  ================-.`___`-.__\ \___  /__.-'_.'_.-'================
                              `=--=-'                  
                   佛祖保佑    永无 BUG    永不宕机
*/
可以把一些有意思的图片转成 ASCII 艺术图,嵌到代码中,或者 log 中。
整体原理比较简单,这里用 Rust Wasm 实现一下。
先简单说一下原理。
这里主要说一下灰度的处理过程。
灰度和彩色图片的区别就是 R=G=B。
关于灰度值的计算,有 3 种主流方式:
Max(R, G, B)。(R + G + B) / 3。0.2126 * R + 0.7152 * G + 0.0722 * B0.299 * R + 0.587 * G + 0.114 * BMath.sqrt( (0.299 * R) ** 2 + (0.587 * G) ** 2 + (0.114 * B) ** 2 )效果如下图所示 (演示地址):

这里在 Rust 中用的是加权平均值的第一种方式:
pub fn get_luminance(r: u8, g: u8, b: u8) -> f32 {
    let r = 0.2126 * (r as f32);
    let g = 0.7152 * (g as f32);
    let b = 0.0722 * (b as f32);
    r + g + b
}
这里罗列一下一些注意点。
这里需要转成 Uint8Array 进行传递:
const file = e.target.files[0];
const reader = new FileReader();
reader.onloadend = (evt) => {
  try {
    const u8buffer = new Uint8Array(evt.target.result);
    const result = get_rust_image(u8buffer);
  } catch (error) {
    console.log({ error });
  }
};
file && reader.readAsArrayBuffer(file);
对应的 Rust 按照 Vec<u8> 处理 :
#[wasm_bindgen]
pub fn get_rust_image(raw: Vec<u8>) { ... }
Vec<u8> 传递Rust 部分只要传递 Vec<u8> 即可:
#[wasm_bindgen]
pub fn get_rust_image(raw: Vec<u8>)  -> Vec<u8> { ... }
JS 消费时,按照 Uint8Array 处理即可:
// to Blob
const blob = new Blob([u8buffer.buffer]);
// to File
const file = new File([blob], 'image.unknown');
// to URL
const url = URL.createObjectURL(blob);
Image Crate 将图片加载完后,默认输出的 bytes 是一个解码后的原始数据,传递给 JS 后是无法正常使用的,需要对原始数据进行编码后,输出才行。

// 给编码器一块内存空间,用来写入数据
let mut output_buffer = vec![];
// 创建一个编码器
let mut encoder = JpegEncoder::new_with_quality(&mut output_buffer, 100);
// 编码输出
encoder
    .encode(&img_raw, width, height, ColorType::L8)
    .unwrap();
// 直接把内存输出就行
output_buffer
这里做了两个版本。
这个比较简单,就是去色,匹配,再连接即可:
#[wasm_bindgen]
pub fn get_ascii_by_image(raw: Vec<u8>, scale: u32, reverse: bool) -> String {
    let img = load_from_memory(&raw).unwrap();
    let img = img
        .resize(
            (img.width() * scale / 100) as u32,
            (img.height() * scale / 100) as u32,
            FilterType::Nearest,
        )
        .grayscale();
    let mut pallete = [' ', '.', '\\', '*', '#', '$', '@'];
    let mut current_line = 0;
    let mut result = "".to_string();
    if reverse {
        pallete.reverse();
    }
    for (_, line, rgba) in img.pixels() {
        if current_line != line {
            result.push('\n');
            current_line = line;
        }
        let r = 0.2126 * (rgba.0[0] as f32);
        let g = 0.7152 * (rgba.0[0] as f32);
        let b = 0.0722 * (rgba.0[0] as f32);
        let gray = r + g + b;
        let caracter = ((gray / 255.0) * (pallete.len() - 1) as f32).round() as usize;
        result.push(pallete[caracter]);
        // 填充一下,有些扁
        if caracter < (pallete.len() - 2) {
            result.push('.');
        } else {
            result.push(' ');
        }
    }
    result
}
执行时间在 20ms 左右。

看到一个支持 ASCII 种类挺多的 Rust 项目 https://github.com/MustafaSalih1993/tai ,于是将这个项目的 IO 部分进行了修改,适配 WASM 进行了编译处理。
这个耗时在 50ms 左右。

<script type="module">
  import initWasm, {
    get_gray_image,
    get_ascii_by_image,
    get_ascii_by_image_tai,
  } from "./pkg/rust_wasm_image_ascii.js";
  initWasm()
    .then(() => {});
</script>
可以直接使用仓库中 pkg/ 目录中的文件,也可以使用 upkg 的资源 https://unpkg.com/browse/rust-wasm-image-ascii/ ,也可以 npm install rust-wasm-image-ascii 使用。
接口描述参考这里:pkg/rust_wasm_image_ascii.d.ts
🌟 Github 代码地址:https://github.com/lecepin/rust-wasm-image-ascii
     1 
                    
                    ZiLong      2022-08-18 12:19:57 +08:00 
                    
                    有点东西 
                 | 
            
     2 
                    
                    luxor      2022-08-18 12:42:09 +08:00 
                    
                    我觉得真正的 ASCII ART 是利用每个字符的形状来组成图形,比如佛祖保佑这个图。采用点阵的方式来显示图形就不能称作 ART 了。 
                 | 
            
     3 
                    
                    reter      2022-08-18 13:05:36 +08:00 via Android 
                    
                    不知道有没有纯 js 实现 和 wasm 实现在处理速度上的对比?特别是不同数据大小对处理速度的影响。 还可以对比不同浏览器的 js 引擎的影响。 
                 | 
            
     4 
                    
                    albert0yyyy      2022-08-18 13:18:34 +08:00 
                    
                    偶里偶气 /doge 
                 | 
            
     5 
                    
                    Charod      2022-08-18 13:41:20 +08:00 
                    
                    牛逼 
                 | 
            
     6 
                    
                    WillBC      2022-08-18 13:46:38 +08:00 
                    
                    很有意思, 谢谢分享,Star 奉上 
                 | 
            
     7 
                    
                    CheatGZ      2022-08-19 10:23:57 +08:00 
                    
                    
                 | 
            
     8 
                    
                    ryansvn      2022-08-19 10:56:05 +08:00 
                    
                    那个效果演示的示例图片,看着好眼熟,哈哈哈哈 
                 |