<jupyter_start><jupyter_text>ascii_art_generator  importセクション<jupyter_code>import os

import IPython.display
import emoji
import numpy
import zenhan
from PIL import Image, ImageDraw, ImageFont, ImageOps, ImageFilter<jupyter_output><empty_output><jupyter_text>パラメータセクション パラメータをセットしてください。<jupyter_code># 画像のパス
image_path = 'input.jpg'

# アスキーアートに使用する文字一覧 (全角)
# (デフォルトで月の絵文字が追加される)
ascii_chars = set()

# 文字用フォントのファイル名
ascii_char_font_file_name = 'Symbola_hint'

# アスキーアート用フォントの大きさ
ascii_art_font_size = 20

# アスキーアートの大きさを決めるために必要なパラメータ
ascii_art_size_params = {
    # TODO: 最大の大きさを元に大きさを調整する場合、以下に最大の大きさ (横 * 縦) を代入し、コメントアウトを解除
    'max size': numpy.array([150, 150])
    # TODO: 最大文字数を元に大きさを調整する場合、以下に最大文字数を代入し、コメントアウトを解除
    # 'max str len': 140
}<jupyter_output><empty_output><jupyter_text>関数定義セクション<jupyter_code>def calc_mean_square_error(a1, a2):
    """
    画像の平均二乗誤差を算出する
    :param a1: 比較する画像1
    :param a2: 比較する画像2
    :return: 画像の平均二乗誤差
    """
    return numpy.average(numpy.power(numpy.array(a1) - numpy.array(a2), 2))<jupyter_output><empty_output><jupyter_text>処理セクション<jupyter_code># 月の絵文字をアスキーアートに使用する文字として追加
for c in (':new_moon:', ':waxing_crescent_moon:', ':first_quarter_moon:', ':waxing_gibbous_moon:',
          ':waning_crescent_moon:', ':last_quarter_moon:', ':waning_gibbous_moon:', ':full_moon:'):
    ascii_chars.add(emoji.emojize(c))

# アスキーアートに使用する文字を全角化
ascii_chars = {zenhan.h2z(c) for c in ascii_chars}

# 文字用フォント
# ascii_fonts['ascii']: アスキーアート化の際に使用
# ascii_fonts['image']: 出力の際に使用
ascii_fonts = {'ascii': ImageFont.truetype(ascii_char_font_file_name),
               'image': ImageFont.truetype(ascii_char_font_file_name, ascii_art_font_size)}

# 文字一覧
# ascii_images['ascii']: アスキーアート化の際に使用
# ascii_images['image']: 出力の際に使用
ascii_images = {'ascii': dict(), 'image': dict()}

# 文字の大きさ
# ascii_size['ascii']['size']: アスキーアート化の際に使用する画像の大きさ (横 * 縦)
# ascii_size['ascii']['shape']: アスキーアート化の際に使用する行列の大きさ (縦 * 横)
# ascii_size['image']['size']: 出力の際に使用する画像の大きさ (横 * 縦)
# ascii_size['image']['shape']: 出力の際に使用する行列の大きさ (縦 * 横)
ascii_size = {'ascii': dict(), 'image': dict()}

# 文字をグレースケール化
for kind in ascii_images.keys():
    for ascii_char in ascii_chars:
        ascii_char_image = Image.new('L', ascii_fonts[kind].getsize(ascii_char))
        ImageDraw.Draw(ascii_char_image).text((0, 0), ascii_char, 'white', ascii_fonts[kind])
        ascii_images[kind][ascii_char] = ImageOps.invert(ascii_char_image.crop(ascii_char_image.getbbox()))

    ascii_size_ = numpy.array([mi.size for mi in ascii_images[kind].values()]).min()
    ascii_size[kind]['size'] = numpy.array([ascii_size_ for _ in range(2)])
    ascii_size[kind]['shape'] = ascii_size[kind]['size'][::-1]

    for ascii_char in ascii_images[kind].keys():
        # 文字の大きさを揃える
        ascii_images[kind][ascii_char] = ascii_images[kind][ascii_char].resize(ascii_size[kind]['size'],
                                                                               Image.ANTIALIAS)

        # 文字を行列化
        if kind == 'ascii':
            ascii_images[kind][ascii_char] = numpy.array(ascii_images[kind][ascii_char])

# 画像
# image['base']: ベースとなる画像
# image['image']: アスキーアート化する画像 (ベースとなる画像を加工)
# image['ascii']: 行列化した画像
image = {'base': Image.open(image_path)}

# アスキーアートの大きさ (横 * 縦)
ascii_art_size = numpy.array(image['base'].size) / ascii_size['ascii']['size']
if 'max size' in ascii_art_size_params:
    argmax_image_size_ = numpy.argmax(image['base'].size)
    max_ascii_art_size_ = ascii_art_size_params['max size'][argmax_image_size_]
    image_size_ = numpy.max(image['base'].size)
    if max_ascii_art_size_ * ascii_size['ascii']['size'][argmax_image_size_] < image_size_:
        ascii_art_size = max_ascii_art_size_ * numpy.array(image['base'].size) / image_size_
elif 'max str len' in ascii_art_size_params:
    if ascii_art_size_params['max str len'] * ascii_size['ascii']['size'].prod() < numpy.prod(image['base'].size):
        ascii_art_size = numpy.sqrt(ascii_art_size_params['max str len'] * numpy.array(image['base'].size)
                                    / numpy.array(image['base'].size[::-1]))

# アスキーアートの大きさの小数点以下切り捨て
ascii_art_size = ascii_art_size.astype(numpy.int64)

# 画像の余白を切り抜き
image['image'] = image['base'].crop(numpy.array(image['base'].getbbox()))

# 画像の透過部を白で塗色
if image['image'].mode == 'RGBA' or 'transparency' in image['image'].info:
    image['image'] = Image.alpha_composite(Image.new(image['image'].mode, image['image'].size, 'white'),
                                           image['image'])

# 画像の輪郭を強調
image['image'] = image['image'].filter(ImageFilter.UnsharpMask(10, 200, 5))

# 画像をグレースケール化
image['image'] = image['image'].convert('L')

# 画像をリサイズ
image['image'] = image['image'].resize(ascii_art_size * ascii_size['ascii']['size'], Image.ANTIALIAS)

# 画像を行列化
image['ascii'] = numpy.array(image['image'])

# アスキーアート
# ascii_art['ascii']: 文字で表されるアスキーアート
# ascii_art['image']['image']: 画像で表されるアスキーアートのImageオブジェクト
# ascii_art['image']['draw']: 画像で表されるアスキーアートのImageDrawオブジェクト
# ascii_art['matrix']: 行列で表されるアスキーアート
ascii_art = {'ascii': list(), 'image': dict()}
ascii_art['image']['image'] = Image.new('L', tuple(ascii_art_size * ascii_size['image']['size']), 'white')
ascii_art['image']['draw'] = ImageDraw.Draw(ascii_art['image']['image'])

# 画像をアスキーアート化
for i in range(image['ascii'].shape[0] // ascii_size['ascii']['shape'][0]):
    ascii_art['ascii'].append('')
    ascii_art_matrix_row = None
    for j in range(image['ascii'].shape[1] // ascii_size['ascii']['shape'][1]):
        # 画像の一部
        part_image = image['ascii'][ascii_size['ascii']['shape'][0] * i
                                    :ascii_size['ascii']['shape'][0] * (i + 1),
                     ascii_size['ascii']['shape'][1] * j
                     :ascii_size['ascii']['shape'][1] * (j + 1)]

        # 最小平均二乗誤差
        min_error = None

        # 画像の一部ともっとも類似している文字
        min_ascii_char = None

        # 画像の一部ともっとも類似する文字を導出
        for ascii_char, ascii_char_image in ascii_images['ascii'].items():
            # 平均二乗誤差
            error = calc_mean_square_error(part_image, ascii_char_image)

            if min_error is None or error < min_error:
                min_error = error
                min_ascii_char = ascii_char

        # 画像の一部ともっとも類似している文字をアスキーアートに追加
        ascii_art['ascii'][-1] += min_ascii_char
        ascii_art['image']['draw'].bitmap((ascii_size['image']['size'][0] * j,
                                           ascii_size['image']['size'][1] * i),
                                          ascii_images['image'][min_ascii_char])

        if ascii_art_matrix_row is None:
            ascii_art_matrix_row = numpy.array(ascii_images['ascii'][min_ascii_char])
        else:
            ascii_art_matrix_row = numpy.hstack((ascii_art_matrix_row, ascii_images['ascii'][min_ascii_char]))

    if 'matrix' in ascii_art:
        ascii_art['matrix'] = numpy.vstack((ascii_art['matrix'], ascii_art_matrix_row))
    else:
        ascii_art['matrix'] = ascii_art_matrix_row

# アスキーアート (画像) の余白を切り抜き
ascii_art['image']['image'] = ascii_art['image']['image'].crop(ascii_art['image']['image'].getbbox())

# アスキーアート (画像) の色を反転
ascii_art['image']['image'] = ImageOps.invert(ascii_art['image']['image'])<jupyter_output><empty_output><jupyter_text>表示セクション  入力  アスキーアートに使用する文字<jupyter_code>IPython.display.display(IPython.display.HTML('<b>【{}種類】</b>'.format(len(ascii_chars))))
print('  '.join(ascii_chars))<jupyter_output><empty_output><jupyter_text>入力画像<jupyter_code>IPython.display.display(IPython.display.HTML('<b>【{} * {}】</b>'.format(*image['base'].size)))
image['base']<jupyter_output><empty_output><jupyter_text>中間表現  アスキーアート化する画像<jupyter_code>IPython.display.display(IPython.display.HTML('<b>【{} * {}】</b>'.format(*image['image'].size)))
image['image']<jupyter_output><empty_output><jupyter_text>出力  アスキーアート (文字)<jupyter_code>IPython.display.display(IPython.display.HTML('<b>【{} * {}】</b>'.format(*ascii_art_size)))
print(os.linesep.join(ascii_art['ascii']))<jupyter_output><empty_output><jupyter_text>アスキーアート (画像)<jupyter_code>IPython.display.display(IPython.display.HTML('<b>【{} * {}】</b>'.format(*ascii_art['image']['image'].size)))
ascii_art['image']['image']<jupyter_output><empty_output><jupyter_text>評価セクション  平均二乗誤差<jupyter_code>IPython.display.HTML('<b>{}</b>'.format(calc_mean_square_error(image['ascii'], ascii_art['matrix'])))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.16714353838817358, 'fraction_numerical': 0.005603242727706247, 'mean_word_length': 4.777548209366391, 'pattern_counts': {'":': 0, '<': 56, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 11, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '637112', 'license': 'MIT', 'n_tokens_mistral': 3876, 'n_tokens_neox': 3634, 'n_words': 396, 'path': 'generator.ipynb', 'repo_name': 'massongit/ascii_art_generater', 'size': '8388'}