文字で、文字や絵を書く技術



# 要約

あ…ありのまま 今 思った事を話すぜ！
**「文字が文字で作れたら面白いよね？」**
何を言っているのか わからねーと思うが、
おれも 何を言っているのか分からない。

 * 兎に角、**下記の作例集を見れば何がしたいのかが分かる。まずは見てね**
 * Colaboratoryで、前提一切不要＆ブラウザだけですぐ動かせるよ


~~おれは　奴の前で文字を書いていたと思ったら~~
~~いつのまにか絵を書いていた。と思ったらやっぱり文字を書いていた。~~
~~頭がどうにかなりそうだった~~


# 作例集①

## **殺伐としたウニ**

![ebikani.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/8cbd6854-73b3-e4af-8633-6c5d2cb6a7a6.png)

これがホントの「エビカニ、クス(笑)」

## **殺伐としたスレに鳥取県が！！**

![satubatu_gunma.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/c354b488-1998-9258-221c-8d49dfaa5c69.png)

島根県　（ ※「矛盾塊」と呼ばれているらしい）

## **瀧「リューク、目の取引だ」**

![kiminona.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/09ee69a0-e835-564c-a2e2-b52dbeb1aaa4.png)

アイドルの方の三葉が死ぬっ！


## **EVA**

![syogouki.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/0f5e156b-dd9a-28b2-637a-a3c6494aec20.png)

こんなとき、どんな顔をしたらいいかわからないの

---

~~ごめんなさい。作例集を見ても~~
~~「**何がしたいのか**」は分からなかったかもしれない。~~
「何が出来るようになるのか」は分かったと思う。
作例集②も最後にあるよ。


# 全体的な設計

逆に考えるんだ。

文字（エビ）で絵を書くためには、
文字（エビ）を書く座標が決まっていれば良い。
書く場所の座標 ＝ ０と１で出来た二次元リスト。
二次元リスト ＝ 白黒画像（グレースケール）

あとは、フレームとなる文字（カニ）を画像化して、
その白黒画像に入れれば完成。
まとめると、以下のような流れになる。

**カニ ⇒ 画像化 ⇒ 白黒画像 ⇒ 01二次元リスト ⇒ エビで埋める**

~~↑とても技術解説とは思えない説明文字列だ~~

◆さあ、以下の段取りで開発を進めよう！

* 開発環境構築＝不要（Colaboratory）
* Step1 文字を画像にする技術
* Step2 画像を白黒の０１リストにする技術
* Step3 白黒リストを文字で埋め尽くす技術
* Step4 出来た関数のまとめ＆最終的に画像に変換

# 開発環境構築＝不要（Colaboratory）

今回は **Colaboratory** 上で、Python3 によって実装してみる。
ColaboratoryはGoogle様が用意してくれた
Jupyter&Pythonを簡単に実行出来る**神環境**。
ブラウザでアクセスするだけですぐに本記事のコードが試せる。
お手元の環境を汚さない。エコ仕様。

全コード掲載＆すぐにコピペ実行出来るようになっているので、
ぜひオリジナルの**文字絵アート**＆**文字文字アート**を作ってみてください！

(*´ω｀)つ  [Colaboratory](https://colab.research.google.com/?hl=ja)


# Step1 文字を画像にする技術

## 準備：日本語フォントのインストール

Colaboratoryでは、最初に「!」をつけると
シェルコマンドの実行が出来る。
画像にしちゃう日本語フォントをインストールしてみよう。

```shell-session:Colaboratoryで日本語フォントのインストール
!apt-get -y install fonts-ipafont-gothic
```

インストールされたフォントのパスを確認してみよう。

```py:TTFファイルのパスを確認する
import matplotlib.font_manager as fm
fonts = fm.findSystemFonts()
for font in fonts:
  print(str(font), "  ",fm.FontProperties(fname=font).get_name())
  
# 出力は省略。こんなパスの場所を確認出来る
# /usr/share/fonts/truetype/fonts-japanese-gothic.ttf
```

## 文字列を画像にする関数

Pythonの画像処理ライブラリ（Pillow）で
白色背景画像に文字を書き込み、
全体を画像として保存する。
これで、好きな「文字」を「画像」に出来る。

```py:文字列を画像にする関数
from PIL import Image, ImageDraw, ImageFont
## 与えられた文字列を、画像にする関数
## １文字あたりのサイズ＆縦横の文字数も引数で指定
def str2img(input_str, yoko_mojisuu, tate_mojisuu, moji_size):
  # 真っ白な背景画像を生成する
  # 横（縦）幅 ＝ 文字サイズ× 横（縦）文字数
  img  = Image.new('RGBA', (moji_size * yoko_mojisuu , moji_size * tate_mojisuu), 'white')
  # 背景画像上に描画を行う
  draw = ImageDraw.Draw(img)
  
  # フォントの読み込みを行う。（環境によって異なる）
  myfont = ImageFont.truetype("fonts-japanese-gothic.ttf	/usr/share/fonts/truetype/fonts-japanese-gothic.ttf", moji_size)
  
  # 文字を書く。基本は以下で済むが、今回は１文字ずつ記入
  # draw.text((0, 0), input_str , fill=(0, 0, 0), font = myfont)
  # ※備考：１文字ずつ記入の場合、半角と全角を区別しないといけなくなる
  # （今回は全角前提とする）
  # fillは、文字の色をRBG形式で指定するもの。今回は黒なので0,0,0固定
  # 縦横のサイズに合せて１文字ずつ描画
  yoko_count = 0
  tate_count = 0
  for char in input_str:
    #縦の文字数の許容量を途中でオーバーしてしまった場合は終了
    if tate_count >= tate_mojisuu:
      break
    #所定の位置に1文字ずつ描画
    draw.text( ( yoko_count * moji_size, tate_count * moji_size ), char, fill=(0, 0, 0), font = myfont)
    yoko_count +=1
    if yoko_count >= yoko_mojisuu:
      yoko_count =  0
      tate_count += 1
  
  return img
```

出来た関数は以下のように使える

```py:str2img関数のお試し実行
import matplotlib.pyplot as plt
img = str2img("勝利友情努力", 2, 3, 50)
plt.imshow(img)
```

出力結果：
<img width="214" alt="勝利友情努力.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/0a5c2713-ff9b-1c5f-95a5-2fbe6b8e00c3.png">


~~「三本柱マン」が無事降臨！！~~

なお、以前に、
[どこでもドアを作ってみた物語](https://qiita.com/youwht/items/8ca57766a991371c1cfe)
においてもPillowで画像加工を実施したことがある。
文字だけでなく画像の合成等も可能だ。

# Step2 画像を白黒の０１リストにする技術

「文字」の画像の場合もともと白黒なのだが、
任意の画像を文字で表現することにも対応するため、
まず画像を「白黒化」し、各ピクセルを0～1の少数で表現する。

そして、閾値（その画像全体の平均値とする）と比較して
白い場合は「１」黒い場合は「０」にすれば、
あらゆる画像が「１」と「０」の２次元リストになるというわけ。

```py:画像の白黒化＆０１リスト化
from PIL import Image, ImageDraw, ImageFont
# 与えた画像を、グレースケールのリストに変換する関数（白＝1、灰＝0.5、黒＝0）
# 元がカラー画像でも対応出来るようにしている
def img2graylist(input_img):
  #幅と高さを取得する
  img_width, img_height = input_img.size
  print('幅　: ', img_width)
  print('高さ: ', img_height)
  
  #最終的に出力する二次元リスト
  result_graylist = []
  for y in range(0, img_height, 1):
    # １行ごとのテンポラリリスト
    tmp_graylist=[]
    for x in range(0, img_width, 1):
      # 1ピクセルのデータ（RGB値）を取得
      #(20, 16, 17, 255)のように４つのデータが取れる⇒3つに絞って使う
      r,g,b, = input_img.getpixel((x,y))[0:3]
      
      #RGB値の平均＝グレースケールを求める
      g = (r + g + b)/3
      tmp_graylist.append(g)
    #１行終わるごとにテンポラリリストを最終出力に追加
    result_graylist.append(tmp_graylist)
  return result_graylist

# 与えたグレイリストを、白＝1、黒＝0のリストに変換する関数
# 黒が多い画像⇒全て黒、や、色の薄い画像⇒全て白、にならないように、
# 閾値として、平均値を取得した後で、その閾値との大小で判定する
# よって、薄い画像が全部白に、濃い画像が全部黒に、などはならない
import numpy as np
def graylist2wblist(input_graylist):
  
  #与えられた二次元配列の値の平均値を求める(npを使っても良いが)
  gray_sum_list = []
  for tmp_graylist in input_graylist:
    gray_sum_list.append( sum(tmp_graylist)/len(tmp_graylist) )
  gray_ave = sum(gray_sum_list)/len(gray_sum_list) 
  print("灰色平均値： ", gray_ave)
  
  # 最終的に出力する二次元の白黒リスト
  result_wblist = []
  for tmp_graylist in input_graylist:
    tmp_wblist = []
    for tmp_gray_val in tmp_graylist:
      #閾値と比べて大きいか小さいかによって１か０を追加
      if tmp_gray_val >= gray_ave:
        tmp_wblist.append(1)
      else:
        tmp_wblist.append(0)
    result_wblist.append(tmp_wblist)
  
  return result_wblist
```


出来た関数は以下のように使える

```py:白黒化＆０１リスト化
#与えられた2次元文字列リストをプリントする関数（pprint的なもの）
#（※最終出力時には使わないが、途中経過を見る用途）
def print2Dcharlist(charlist):
  for tmp_charlist in charlist:
    for char in tmp_charlist:
      #改行無しで出力
      print(char, end="")
    #1行終わるごとに改行
    print()

img = str2img("般若波羅蜜多", 6, 1, 20)
graylist = img2graylist(img)
wblist = graylist2wblist(graylist)

print2Dcharlist(wblist)
```

出力結果：

<img width="511" alt="hannya_kakunin.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/503a2565-b0f7-0a68-eb34-df4cd4702f3f.png">


~~０１デジタル化された般若心経~~


# Step3 白黒リストを文字で埋め尽くす技術

作った白黒リストの「０」の部分だけを（または「１」の部分を）
「文字」で置き換えれば、ほとんど完成に近い。

エビエビエビエビエビ・・・と繰り返して文字を取得/出力するため、
Pythonの「ジェネレータ」を使って実装してみる。
yield は return のようなものなので、
return に読み替えると分かりやすいかもしれない（説明雑）

```py:白黒リストを文字で埋め尽くす
# 文字列を一文字ずつ取り出すジェネレータ。半無限ループにより繰り返し
def infinity_gen_str(str):
  for a in range(1000000000):
    for s in str:
        yield s
# 以下のように使う
# 定義：gen_str =  infinity_gen_str("表示したい文字列")
# 使用：next(gen_str)
# これで、使用するたびに１文字ずつ出力される

# 白黒リストの、白黒の部分を文字列で埋め尽くした二次元リストを返す
# 白＝soto_strで埋める。黒＝nakami_strで埋める。
def wblist2wbcharlist(input_wblist, nakami_str, soto_str):
  # １文字ずつ出力できるジェネレータの生成
  gen_nakami_str =  infinity_gen_str(nakami_str)
  gen_soto_str =  infinity_gen_str(soto_str)
  
  # 最終的に出力する二次元の白黒リスト
  result_wbcharlist = []
  for tmp_wblist in input_wblist:
    tmp_wbcharlist = []
    for tmp_wb_val in tmp_wblist:
      # 値が１か０かによって、文字列を入れていく
      # ※空白と等幅になる文字＆フォントでやることが望ましい
      if tmp_wb_val == 1:
        # 1が白
        # 空白固定ならコレでも同じ ⇒ tmp_wbcharlist.append( "　" )
        tmp_wbcharlist.append( next(gen_soto_str))
      else:
        # 0が黒
        tmp_wbcharlist.append( next(gen_nakami_str) )
        
    result_wbcharlist.append(tmp_wbcharlist)
  
  return result_wbcharlist

```


出来た関数は以下のように使える

```py:０１リストを文字列で埋める
img = str2img("般若波羅蜜多", 6, 1, 20)
graylist = img2graylist(img)
wblist = graylist2wblist(graylist)
#print2Dcharlist(wblist)

# 今回は↑の外枠で「般若波羅蜜多」のフレーム（０１）を作り、
# ↓の指定で、中身を「般若波羅密多」の文字列で埋める
wbcharlist = wblist2wbcharlist(wblist, "般若波羅蜜多","　")
print2Dcharlist(wbcharlist)
```

出力結果：
<img width="625" alt="hannya_kakunin2.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/28cbee9c-32af-2a8e-7f8c-dc24cdd48814.png">


~~この技術に狂気と恐怖を覚える~~


# Step4 出来た関数のまとめ＆最終的に画像に変換

ここまでで、以下の流れの全てが実装できた。
カニ ⇒ 画像化 ⇒ 白黒画像 ⇒ 01二次元リスト ⇒ エビで埋める

最後に、これらの処理のまとめと、
出来たエビのリストを画像にして保存するようにしよう。

最後の画像変換では、最初の「文字を画像化する関数（カニ⇒画像化）」を
再利用することが出来る！

```py:今までの関数の一括実行＆画像化
def moji2mojiImg(flame_str, nakami_str, soto_str, yoko_len, tate_len, moji_size, final_moji_size):
  # 引数サンプル
  # flame_str = "般若"
  # nakami_str = "般若波羅蜜多"
  # yoko_len = 2
  # tate_len = 1
  # moji_size = 30
  # 最後に表示する際のフォントサイズ
  # final_moji_size = 12

  img = str2img(flame_str, yoko_len, tate_len, moji_size)
  graylist = img2graylist(img)
  wblist = graylist2wblist(graylist)
  wbcharlist = wblist2wbcharlist(wblist, nakami_str, soto_str)
  # print2Dcharlist(wbcharlist)

  # 作った配列を、str2imgで画像化する
  # 作ったリストを全てつなげて単純文字列にする
  # (※最初に作成したstr2imgに入れるための変換)
  all_str = ""
  for tmp_list in wbcharlist:
    for char in tmp_list:
      all_str += char

  #今回のファイルのサイズは縦横は、moji_size倍されている点に注意
  img = str2img(all_str, yoko_len*moji_size, tate_len*moji_size, final_moji_size)
  
  return img
```


出来た関数は以下のように使える

```py:殺伐とした実行
img = moji2mojiImg("カニ","エビ","　",2,1,20,15)

#正しく表示＆ダウンロード出来るように、一度セーブする
img.save("ebikani.png")

#colaboratoryで表示
import IPython
IPython.display.Image("ebikani.png")
```

出力結果：

![ebikani.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/8cbd6854-73b3-e4af-8633-6c5d2cb6a7a6.png)


~~エビもカニも甲殻類~~


出来た画像をColaboratoryからダウンロードするには以下

```py:セーブしたファイルをローカルにダウンロード
from google.colab import files
files.download("ebikani.png")
```

# （オマケ）画像を文字列で描画する技術

「文字」に文字を埋め込んで画像化することが出来た。
一方で、「画像」に文字を埋め込んで画像化することは、
実はより簡単に出来てしまう。

カニ ⇒ 画像化 ⇒ 白黒画像 ⇒ 01二次元リスト ⇒ エビで埋める

この、最初ステップのカニの画像化がなくなって、
直接画像の白黒化から始められるというだけ。
「アスキーアート」生成ツールの亜種的なものになる。

最後のStep4のまとめ関数をちょっと書き換えて実行してみる。

普段使うには ~~使わないけど~~ こちらのほうが使いやすいかもしれない

```py:画像を文字で描画する関数
def img2mojiImg(input_img, nakami_str, soto_str, final_moji_size):
  img_width, img_height = input_img.size
  #print('幅　: ', img_width)
  #print('高さ: ', img_height)

  #文字から画像を作る必要なく、input画像を使う
  #img = str2img(flame_str, yoko_len, tate_len, moji_size)
  graylist = img2graylist(input_img)
  wblist = graylist2wblist(graylist)
  wbcharlist = wblist2wbcharlist(wblist, nakami_str, soto_str)
  #print2Dcharlist(wbcharlist)

  #作った配列を、str2imgで画像化する
  #作ったリストを全てつなげて単純文字列にする
  #(※作成したstr2imgに入れるため)
  all_str = ""
  for tmp_list in wbcharlist:
    for char in tmp_list:
      all_str += char

  #今回のファイルのサイズは縦横は、moji_size倍されている点に注意
  img = str2img(all_str, img_width, img_height, final_moji_size)
  
  return img
```


出来た関数は以下のように使える
「グンマー」の画像は別途用意して
Colaboratoryにアップロードしておく。
（※左上の「＞」から「ファイル」を選ぶと、アップロード出来る）

```py:ぐんまちゃんに怒られないように気をつけて実行
img = Image.open("Gunma.png")
#幅と高さを取得する
img_width, img_height = img.size
print('幅　: ', img_width)
print('高さ: ', img_height)

#リサイズする場合は以下のような感じ
#元画像は幅640、高さ640
img = img.resize((40, 40))

result_img = img2mojiImg(img, "　", "栃木県", 14)

output_file_name = "satubatu_gunma.png"
result_img.save(output_file_name)

#colaboratoryで表示
import IPython
IPython.display.Image(output_file_name)
```

出力結果：
![satubatu_gunma.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/c354b488-1998-9258-221c-8d49dfaa5c69.png)


~~グンマーは何をやっても面白いのでとてもお得~~

# 作例集②（オマケ）

## **はらみった**

![hannya.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/8b80bc2e-4a7d-f533-4ee8-86c081d240b4.png)

つ[「写経」を自動化し、オートで功徳を積める仕組みを作ってみたのでございます。](https://qiita.com/youwht/items/616ccc062725fa050a34)

## **しろくろ**

![simauma.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/83b6ebac-5491-defd-a804-bb2b51659df9.png)

じわじわくる

## **止まれ。**

![tomare.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/3d351ecc-0536-0d66-96be-891f78fe5978.png)

もう何十回も言ったのよ！？って言える必殺技

## **見よ、人がゴミのようだっ！**

![hitogomi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/fe26f745-7117-97c5-c4a5-829932a89478.png)

「バルス！！」「目がぁ～！目がぁ～！」


## **新時代アート**

![reiwa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/5551b5a0-204f-4e5d-9756-d9633f9eb0d7.png)

つ [【続】平成の次の元号を、AIだけで決めさせる物語（＠テレビ取材）](https://qiita.com/youwht/items/7b1118ecd91bd2e232bb)


## **その…下品なんですが…フフ…勃起…しちゃいましてね…**

![monakira.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/a54135c1-fc01-b9be-8959-c17a85022c12.png)

いいや！限界だ（いいねを）押すね！今だッ！

つ [PythonでHello 世界（ザ・ワールド）止まった時の世界に入門してみる。ジョースターの末裔は必読](https://qiita.com/youwht/items/08d8d3674795d7f6741f)

# あとがき

## 大喜利

技術を使った大喜利として、ネタを考えるのも楽しいかもしれません。

**面白い文字文字アートの案や、作例が出来たら、**
**ぜひコメント欄に張り付けて教えてください！**

## 応用例/アイデアメモ

応用例はいろいろありそう。

* 画像認識した部分を対応する文字に変える（車に認識された部分を「車」で表現など）
* TwitterやSlackのツール/ボット作成、サービス化
* 単純にアスキーアート生成技術として活用
* 文字の色を変更してカラフル化
* 濃淡に合わせて黒い文字/白い文字を使い分ける
* セリフの無いマンガの作成
* 薄い灰色で文字を印刷して、幼児/英単語などの書き取り練習帳に（書き終わると絵が浮かび上がる）
* 究極的に「ざわざわっ...」「ゴゴゴゴゴ」している絵の作成（著作権的な意味で今回はパス）

## いつもの名言

ちょっとした遊び ＆ Colaboratoryの実践入門として
楽しんでいただけたら幸いです♪
ブラウザでColaboにアクセス、上から順にコピペしていくだけですぐ試せます。
**文字文字アート**で一緒に遊びましょう。

> 人類の進化は「遊び」からはじまる。
> こんな「遊び」が出来るならば、というアイデアに触発される人がでて、
> 生活にも役に立つような「発明」が生まれるのだ。
> 　　　　　　　　　　～ 　Char Fuitter (1847～1912 オランダ)　～


長文おつきあいいただきありがとうございました。

---

出力結果画像は自由に転載していただいて構いません。
Char Fuitter （チャー・フイター）は架空の人物です。