Pythonでなんちゃってアーティスト入門（文字アート）

# はじめに
こんにちは、[estie](https://www.estie.jp/corp/)で機械学習エンジニアをやっている、ぴーまんです。最近ちょっとダウンしており、体力がガタ落ちしてしまいました。今回の投稿は本業とは離れて、趣味で最近作っている「なんちゃってアート」について記事にしてみます。詰まっている部分もあるので、詳しい方に是非コメントなどいただけると幸いです。

# 今回作るもの
- なんちゃって文字アート（↓のようなもの）
    - 文章の文字を敷き詰め、タイトルの文字列の部分だけ色を変えたもの
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/400393/aee3f650-f1c0-ae6e-bc25-52dec5c9fb32.png)
# 用意するもの
- Python環境
- 必要なモジュールのインストール
```Python
from PIL import Image, ImageDraw, ImageFont
import math
import numpy as np
```
- 好きな文章（本記事では青空文庫から、芥川龍之介の「偸盗」を題材にしています）

# コード
### あらかじめ定義しておく定数
作りたい画像サイズや使用したいフォントを自由に設定してください。
```Python
CANVAS_HEIGHT = 2000
CANVAS_WIDTH = 2000
TTFONTNAME = "ヒラギノ明朝 ProN.ttc"
BACKGROUND_RGB = (255, 255, 255)
TEXT_RGB = (0, 0, 0)
TITLE_TEXT_RGB = (255, 0, 0)
```
別途、タイトルと本文それぞれの文字列を格納した変数を用意してください。（本記事ではタイトル: `title`, 本文: `draw_str` という変数で格納しています。）

### メイン関数の流れ
```Python
# 準備パート
## 1行あたりに描画する文字数を計算する
one_line_text_len = calculate_one_line_text_len(draw_str)

## 文字のフォントサイズを決定する
font_size = calculate_font_size(one_line_text_len)


# 描画パート
## 描画の初期位置の計算
position = calculate_text_position(font_size, one_line_text_len)

## 色を変える文字の位置を記録したarrayを用意する
color_array = prepare_color_array(title, one_line_text_len, position, font_size)

## 描画するImageオブジェクトを生成する
img = Image.new('RGB', (CANVAS_HEIGHT, CANVAS_WIDTH), BACKGROUND_RGB)

## 描画する
img = insert_texts(img, draw_str, font_size, position, one_line_text_len, color_array)

## 描画したものの保存
img.save("image.png")
```

### 各関数の説明
```Python
def calculate_one_line_text_len(draw_str):
    return math.ceil(math.sqrt(len(draw_str)))
```
- なるべく正方形に文字を敷き詰めたいので、本文の長さに対して平方根の切り上げをとることで、1行あたりの文字数を計算します。
<br>

```Python
def calculate_font_size(one_line_text_len):
    return CANVAS_WIDTH // one_line_text_len
```
- ↑で計算した一行あたりの文字数と、画像サイズから文字のフォントサイズ（整数値）を決定します。
<br>

```Python
def calculate_text_position(font_size, one_line_text_len):
    return (CANVAS_HEIGHT % (font_size * one_line_text_len)) / 2
```
- 画像の真ん中に文字列を配置したいので、余白を上下左右均等になるように、初期位置を計算します。
<br>

```Python
def prepare_color_array(title, one_line_text_len, position, font_size):
    # タイトルによって自分でサイズと位置を決める
    title_str_size = int(font_size * one_line_text_len / 2)
    position_1 = [position, position]
    position_2 = [position + title_str_size , position + title_str_size]
    positions = [position_1, position_2]
    
    img = Image.new('RGB', (CANVAS_HEIGHT, CANVAS_WIDTH), BACKGROUND_RGB)
    for s, p in zip(title, positions):
        font = ImageFont.truetype(TTFONTNAME, title_str_size)
        draw = ImageDraw.Draw(img)

        draw.text(p, s, (0, 0, 0), font=font)
    # 空のarrayを用意
    color_array = np.full([one_line_text_len,one_line_text_len], False)
    for row in range(one_line_text_len):
        for col in range(one_line_text_len):
            pos_x_start = int(position + font_size * row - 1)
            pos_y_start = int(position + font_size * col)
            obj_pixel = np.array(img)[pos_x_start:pos_x_start+font_size, pos_y_start:pos_y_start+font_size]
            if obj_pixel.mean() <= 255/2:
                color_array[row][col] = True
    return color_array
```
- 少しややこしいですが、
![title.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/400393/38a36a54-0984-00c0-5ea2-4e5f3c94b72f.png)
のようなタイトルの文字列画像を生成して、色がついている部分の座標を計算し、フラグのarrayとして返します。


- ちなみに
```python
if obj_pixel.mean() <= 255/2:
    color_array[row][col] = True
```
の部分で、本文の1文字分のpixelの集合のRGB値の平均が255/2以下、つまり「黒っぽい」かどうかを判定しています。
<br>

```python
def add_text_to_image(img, text, font_size, width, height, color_flg):
    position = (width, height)

    font = ImageFont.truetype(TTFONTNAME, font_size)
    draw = ImageDraw.Draw(img)
    if color_flg:
        draw.text(position, text, TITLE_TEXT_RGB, font=font)
    else:
        draw.text(position, text, TEXT_RGB, font=font)
    return img
```
- 文字列描画の関数です.タイトル部分のフラグが立っている文字は色付き（本記事では赤）、それ以外の文字は黒色で描画しています。
<br>

```python
def insert_texts(image, text, font_size, position, one_line_text_len, color_array):
    width = height = float(position)
    row = 0
    col = 0
    charactor_count =  0
    for character in text:
        charactor_count += 1
        # 1行に配置する文字数に達したら、widthを初期化、heightをfont_size分ずらす
        if charactor_count % one_line_text_len == 0:
            col = 0
            width = position
            row += 1
            height += font_size
            charactor_count = 0
        img = add_text_to_image(image, character, font_size, width, height, color_array[row][col])
        col += 1
        width += (font_size)
    return img
```
- add_text_to_image関数の呼び出し元です。初期位置から一文字ずつ右に文字を描画していき、一行描画し終わったら一行座標をずらして描画を進めます。（突貫で作ったのでかなりイケてない感じになってしまった...）

### 完成!!!
これで冒頭でお見せしたなんちゃってアートが完成します。課題としては、1文字ずつ描画した結果、1文字ごとに文字のx軸方向の配置が少しずれてしまうことがあり、原因探索中です（コード見て問題ありそうな部分指摘いただけると嬉しいです！）

# 終わりに
本記事では、pythonを使ってなんちゃって文字アートを作ってみました。もちろん他の作品の文字列でも作れるほか、タイトルの文字数によって配置の仕方も工夫できるかと思うので、是非お試しあれ〜