Python3で画像をコンソールに表示してみた

f:id:sakage24:20170708022825j:plain

もちろん、生の画像を出力できるわけじゃなくて、ちょっとした加工が必要ですが、なんとかそれっぽくはなったのでメモしておきます。

手順

  1. コンソールのサイズ(高さ、横幅)を取得する
  2. 画像を読み込む
  3. 画像をコンソール用にリサイズする
  4. ドットごとのRGBを取得する
  5. RGBをエスケープシーケンスに変換する

以上がコンソール出力に必要な最低限の機能と手順です。

事前準備

pip install pillow
pip install colorama

coloramaについては以下の記事でまとめています。

www.kiwi-bird.xyz

やってみよう

ソースコード


# images.py
from PIL import Image
import os
import shutil
from colorama import Back
class ConsoleOnImages(object):
@staticmethod
def get_terminal_size():
# shutil.get_terminal_size(fallback=(列, 横幅))
terminal_size = shutil.get_terminal_size()
return terminal_size.columns, terminal_size.lines
@staticmethod
def get_thumbnail(img, columns=117, lines=63):
img.thumbnail1)lines, columns), Image.ANTIALIAS)
# img.size[0], img.size[1] = (横幅, 列)
return img.size[0], img.size[1]
@staticmethod
def get_images(path):
return Image.open(os.path.join(os.path.dirname(__file__), '../img/', path
def run(self, file, gray_scale_flag=False):
path = os.path.join(os.path.dirname(__file__), '../console_imgs/')
if not os.path.exists(path):
os.mkdir(path)
img_path_cut_ext = file[::-1].index(".") + 1
lists = os.listdir(path)
if file[:-img_path_cut_ext] + ".txt" in lists:
with open(os.path.join(path, file[:-img_path_cut_ext] + ".txt"), "rt") as f:
file = f.read()
print(file)
return True
lines, columns = self.get_terminal_size()
img = self.get_images(path=file)
img_w, img_h = self.get_thumbnail(img, lines=lines, columns=columns)
file_name = file[::-1].split(".")[1]
self.printer(file_name=file_name[::-1] + ".txt", img=img, img_h=img_h, img_w=img_w, gray_scale_flag=gray_scale_flag)
def printer(self, file_name, img, img_h, img_w, gray_scale_flag):
write_flag = False
file = ""
if not os.path.exists(os.path.join(os.path.dirname(__file__), '../console_imgs/', file_name)):
file = open(os.path.join(os.path.dirname(__file__), '../console_imgs/', file_name), 'wt')
write_flag = True
def out_put_gray_new_lines(gray_scale):
print(f"\033[48;5;{gray_scale}m  ")
if write_flag:
file.write(f"\033[48;5;{gray_scale}m  \n")
def out_put_gray_current_lines(gray_scale):
print(f"\033[48;5;{gray_scale}m  ", end='')
if write_flag:
file.write(f"\033[48;5;{gray_scale}m  ")
def out_put_new_lines(r, g, b):
print(f"\033[48;2;{r};{g};{b}m  ")
if write_flag:
file.write(f"\033[48;2;{r};{g};{b}m  \n")
def out_put_current_lines(r, g, b):
print(f"\033[48;2;{r};{g};{b}m  ", end='')
if write_flag:
file.write(f"\033[48;2;{r};{g};{b}m  ")
try:
for height in range(img_h):
for width in range(img_w):
red, green, blue = img.getpixel2)width, height
if width == img_w - 1:
if gray_scale_flag:
out_put_gray_new_lines(gray_scale=self.gray_pallet(r=red, g=green, b=blue))
else:
out_put_new_lines(r=red, g=green, b=blue)
else:
if gray_scale_flag:
out_put_gray_current_lines(gray_scale=self.gray_pallet(r=red, g=green, b=blue))
else:
out_put_current_lines(r=red, g=green, b=blue)
finally:
file.close()
print(Back.RESET)
@staticmethod
def gray_pallet(r, g, b):
gray_scale = 0.299 * r + 0.587 * g + 0.114 * b
pallets = (0, 11, 22, 33, 44, 55, 66, 77, 88, 99, 110, 121, 132, 143, 154, 165, 176, 187, 198, 209, 220, 231, 242, 253)
for index, pallet in enumerate(pallets[::-1]):
if gray_scale >= pallet:
return 0xFF - index
##### テスト用
if __name__ == '__main__':
from colorama import init
if os.name == "nt":
init()
ConsoleOnImages().run(file='test.jpg', gray_scale_flag=False)
テスト用
  • ~/img/test.jpgを置いて下さい。
  • ~/sources/images.pyを直接実行して下さい。

実行結果

f:id:sakage24:20170708022825j:plain

何の画像か当ててみて下さい。

解説

get_terminal_size関数でコンソールのサイズを取得します。それを元にPILで開いた画像をコンソールサイズに変換してます。

あとはリサイズした画像の縦横の長さでforループして、1ドットずつ画像のRGB(tuple)を取得します。それをエスケープシーケンスに渡して表示してもらっています。

エスケープシーケンス的に256色が限度だそうなので、モザイクっぽくなってしまいますが、ある意味丁度いいですよねw

それと、windowsだと使える色が少ないっぽいですね。何が何だか分からないことになってしまいました。

f:id:sakage24:20170708023655j:plain

どうにかwindowsでもコンソール表示出来ないか

bash上で256色に対応しているか確かめてみました。

検証コード

from colorama import init
init()
six = 0x0
for i in range(16):
for i in range(16):
print(f"\033[48;5;{six}m  ", end="")
six += 0x1
print(f"\033[48;5;{six}m  ")
six += 0x1

bash

f:id:sakage24:20170708232210j:plain

256色に対応しています。綺麗ですね~

コマンドプロンプト(Windows10 cmd)

f:id:sakage24:20170708232251j:plain

……

結論

Windowsは無視しよう

おまけ

グレースケールにして表示したら何とかなるんじゃないかと思っていましたが錯覚でした!!

f:id:sakage24:20170708233137j:plain


# images.py
@staticmethod
def gray_pallet(r, g, b):
gray_scale = 0.299 * r + 0.587 * g + 0.114 * b
pallets = (0, 11, 22, 33, 44, 55, 66, 77, 88, 99, 110, 121, 132, 143, 154, 165, 176, 187, 198, 209, 220, 231, 242, 253)
for index, pallet in enumerate(pallets[::-1]):
if gray_scale >= pallet:
return 0xFF - index
if __name__ == '__main__':
from colorama import init
if os.name == "nt":
init()
# gray_scale_flag = False = カラー
# gray_scale_flag = True = グレースケール
# デフォルト引数はカラー
ConsoleOnImages().run(file='test.jpg', gray_scale_flag=True)

終わり

こちらのスライドが大変参考になりました。

www.slideshare.net

エスケープシーケンスを丁寧に解説してくれています。感謝

www.mm2d.net

脚注   [ + ]

1. lines, columns), Image.ANTIALIAS) # img.size[0], img.size[1] = (横幅, 列) return img.size[0], img.size[1] @staticmethod def get_images(path): return Image.open(os.path.join(os.path.dirname(__file__), '../img/', path
2. width, height

コメントを残す

メールアドレスが公開されることはありません。