画像から点字 AA を生成するツールを作った話

画像から点字 AA を生成する Node.js のライブラリ tenjify と、それの Web アプリ tenjify-web を作ったので紹介します。

目的

Twitch でたまに流れてくる、点字を使った AA を画像から自動で生成したい。

↓ こんな感じのやつ

⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠛⢛⣉⣩⣤⣬⣉⣉⣉⠛⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⠿⠋⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣈⠻⢿⣿⣿⣿⣿⣿
⣿⣿⣿⠟⢁⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⡙⠿⣿⣿⣿
⣿⣿⠏⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠙⢻⣷⡆⠹⣿⣿
⣿⡇⢠⣿⣿⣿⣿⣿⣿⡟⠋⠛⢻⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⣀⣴⣿⣿⡄⢹⣿
⡟⢀⣿⣿⣿⣿⣿⣿⣿⣧⣀⣤⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⢻
⠁⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠟⢛⣋⣉⣉⣉⠙⢿⣿⣿⣿⣿⣿⡇⢸
⡄⢸⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⣡⣴⣶⣿⣿⣿⣿⣿⣿⣧⠄⢿⣿⣿⣿⣿⡇⢸
⣇⠈⣿⣿⣿⣿⣿⣿⣿⡟⠁⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⢸⣿⣿⣿⣿⠃⣼
⣿⣆⠘⣿⣿⣿⣿⣿⣿⡇⣴⣤⣤⣬⣉⡛⠻⣿⣿⣿⣿⣿⡇⢸⣿⣿⣿⠃⢸⣿
⣿⣿⣆⠘⢿⣿⣿⣿⣿⢀⣿⣿⣿⣿⣿⠿⠷⠌⠛⢛⣋⣉⣁⣸⣿⡿⠋⣠⣿⣿
⣿⣿⣿⣶⡈⠙⢿⣿⣟⣈⣉⣩⣥⣤⣶⣶⣶⣾⣿⣿⣿⣿⣿⡿⠟⢁⣾⣿⣿⣿
⣿⣿⣿⣿⣿⣶⣄⠉⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⣉⣤⣶⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣤⣈⡉⠉⠛⣋⣉⣉⣤⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿

⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣮⡻⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⠿⣿⣿⣿⣿⡿⠿⣷⣌⠛⣿⣿⣿
⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣧⡘⢿⡿⢋⣼⣿⣿⣿⣿⣷⣄⠢⣭⣓⠌⠻⣿
⣿⣿⠿⣿⣿⡃⠻⣿⣿⣿⣿⣿⣿⣷⠌⢡⣾⡧⠭⠍⣛⠿⢿⣿⣷⣜⠻⣷⣆⠙
⣿⣿⡇⢻⣿⡇⢰⣮⣝⡻⢿⣿⣿⠋⣴⣆⠩⣶⣶⣶⣶⣭⣶⣦⣭⣍⣓⠈⢿⣌
⣿⣿⢣⣎⢻⣿⡜⣿⣿⣿⣶⣬⣁⣘⠻⠿⠦⠙⠿⠿⠟⣛⣛⣛⣛⣩⡍⢩⣤⣌
⣿⡏⡼⣛⣬⠩⣽⡘⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣿
⣿⢠⣴⣿⣿⣷⣌⡳⢌⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠛⢻⡷⢸⣿⣿
⡇⣾⣿⣿⣿⣿⣿⣿⣶⣤⣽⣿⣿⣿⣿⣿⣿⣿⣟⠕⢋⣤⣴⣾⣿⣿⣷⢸⣿⣿
⡇⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣿⣿⡟⡿⡿⣿⢸⣿⣿
⣿⡌⢿⣿⣿⠿⠟⠛⠒⣀⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣵⣵⣧⣿⢸⣿⣿
⡰⣭⣌⠻⣧⢴⡶⣿⡿⣿⣿⣿⣿⣿⣿⠿⠟⣛⣋⣍⡻⣿⣿⣿⣿⣿⡿⠆⣿⣿
⣧⠻⣿⣧⡹⣾⣧⣿⣱⣿⣿⣿⣿⡏⢁⣶⣿⣿⣿⣿⣧⢻⣿⣿⡿⠋⠁⠄⢹⣿
⣿⣧⡹⣿⣇⢿⣿⣿⣿⣿⣿⣿⣿⣧⠸⣿⣿⣿⣿⡿⢟⣼⡿⠋⢀⣀⠄⠄⠈⣿
⣿⣿⣧⠻⣿⣦⣙⠿⠿⢿⣿⣿⣿⣿⣷⣮⠭⠭⠍⣒⣫⡵⢞⣨⡼⠁⠄⠄⠄⡸

方法

canvas(Node.js は node-canvas)を使いました。

画像を読み込み canvas に描画し、各ピクセルについて RGB 値の平均をとって二値化し、点字に変換しています。

点字は一文字あたり 2 × 4 のドットを持っているので、200 × 200 の画像は 100 × 50 の点字で表すことができます。元画像をそのままの大きさで AA に変換すると巨大になってしまうことが多いと思うので、tenjify では AA の 1 行あたりの文字数を指定して生成できるようにしています。指定された文字数をもとに描画すべき画像の幅と高さを算出し、その大きさで画像を canvas に描画することで点字のドットと画像のピクセルが対応づけられます。

// 実装イメージ

const width = 40; // 1 行あたりの文字数

const image = await loadImage(src);
const canvasWidth = 2 * width;
const canvasHeight = Math.round(image.height * (canvasWidth / image.width));
const canvas = createCanvas(canvasWidth, canvasHeight);

const ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, canvasWidth, canvasHeight);

canvas だと画像のフォーマットを気にせずに画像処理ができるのがよかったです。

使い方

tenjify では画像のソース(URL、パス、Buffer など)と任意でオプションを指定し、AA を生成します。プログラムに AA を生成する機能を組み込みたいときは使えますが(そんなときはあるのか)、未知の画像はしきい値の調整が必要なことがほとんどだと思うので少し使いづらいです。

import { tenjify } from "tenjify";

const text = await tenjify("./twitter.png", {
  width: 40, // 1 行あたりの文字数(default: 30)
  threshold: 150, // 二値化する際のしきい値(range: 0-255、default: 128)
  reverse: false, // 白黒を反転させるか(default: false)
});
console.log(text);

//⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣶⣶⣶⣦⣄⠀⠀⢀⣀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⢠⣿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⠟⠁⡀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠸⣿⣿⣿⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠀⢻⣿⣿⣿⣿⣿⣷⣦⣤⣀⣀⠀⠀⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⢠⣀⣙⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠀⠈⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣈⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠛⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠈⠙⠻⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⣤⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡄⠀⣤⡀⠀⠀⠀⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⣿⣧⣀⣀⣀⠀⣠⡀⠀⢀⣄⠀⠀⣠⠀⢀⣄⠀⣿⣇⣀⣀⠀⣿⣄⣀⡀⠀⢀⣠⣤⣀⠀⠀⠀⣀⣀⣀⠀
//⠀⣿⡏⠉⠉⠉⠀⣿⡇⠀⢸⣿⠀⠀⣿⡇⢸⣿⠀⣿⡏⠉⠁⠀⣿⡋⠉⠁⢰⣿⣥⣤⣽⡷⠀⣾⠋⠉⠉⠀
//⠀⠙⢿⣶⣶⣶⠀⠹⢷⣶⡾⠿⣶⣶⠟⠀⢸⡿⠀⠹⢷⣶⣶⠀⠻⢷⣶⡦⠈⠻⣦⣤⣤⡄⠀⣿⠀⠀⠀⠀

tenjify-web では Web 画面上で AA を生成できます。前述のライブラリを使うのと違って、しきい値を変更するとすぐに視覚的なフィードバックが得られるので調整がしやすいです。

tenjify-web のサンプル

おわりに

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⠛⣲⠀⠀⠀⡜⠑⣲⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢠⠛⠃⣰⡟⠉⣦⠞⠀⣀⣀⡀⠀⢨⠆⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⡄⠉⢀⠾⣅⡠⢄⠈⠚⠁⡠⠃⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢰⠒⠃⠐⠛⠀⠹⠴⠊⢀⣀⠈⠑⠢⢤⡀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠘⡶⢶⠀⢰⠷⢦⡤⣶⠉⠓⠻⣶⢤⡜⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢰⠃⢸⡄⢸⡇⢸⡤⠮⠭⣶⣤⡰⠃⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠸⣀⣼⠃⢸⠧⠼⠳⠤⢄⣀⠉⠉⢲⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠒⠚⠀⠀⠀⠀⠀⠀⠉⠒⠊⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢠⡤⣄⣠⡤⡀⢀⢤⡤⣄⣀⠀⠀⠀⢀⣤⡤⣤⢤⣄⠀⢠⢤⡤⣀⡀⠀⠀
⠀⠀⣗⡂⣒⡇⡇⣧⡎⣞⣤⢶⣾⠀⡴⢤⢀⡿⠥⠨⠚⢽⣠⠋⣞⡄⣷⣾⠀⠀
⠀⠀⣇⡆⣆⡧⢇⡇⢹⡏⣿⡭⠝⠀⠑⠉⠐⢯⠁⣶⡏⡏⠀⡇⣿⡇⡭⠟⠀⠀
⢀⣀⣈⣉⣁⣈⣉⣀⣈⣁⣈⣁⣀⣀⣀⣀⣀⣀⣉⣉⣉⣁⣀⣈⣁⣉⣁⣀⣀⡀
⠈⠛⠛⠛⠛⠛⠛⣛⣛⣛⡛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⣛⣛⣛⡛⠛⠛⠛⠛⠁
⠀⠀⠀⠀⠀⣠⡾⢋⠉⢉⠻⡶⡿⠛⡛⠿⣶⣄⣀⣴⣟⡛⢉⠙⣿⡄⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣷⢣⢇⡹⢩⠃⣏⡇⡼⢙⡛⡸⠹⣿⢇⡏⡙⢵⠃⣿⡇⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣿⡘⠊⠙⣪⣾⠟⢟⣗⠋⠓⠁⣰⣿⣎⠚⠉⠚⣰⡽⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠙⠯⠯⠟⠋⠀⠀⠀⠙⠯⠿⠽⠋⠀⠙⠯⠯⠽⠋⠀⠀⠀⠀⠀⠀