これを見ていたら手がうずうずしてきたので描いてみた。
Cool Ascii Animation using an Image Sprite, Canvas, and Javascript
リンク先はカラー画像(JavaマスコットキャラのDukeくん)をCanvasで
- グレイスケール化し、
- 輝度にあわせてアスキーアートに変換している。
本記事ではPython/PILによって見よう見まねでグレイスケールとアスキーアート画像を作成する。
画像のグレイスケール化
PILを使えば簡単に行うことができる。
from PIL import Image from PIL import ImageOps if __name__ == "__main__": input_image = Image.open("lena.png") output_image = ImageOps.grayscale(input_image) output_image.save("gray_by_pil.png")
単にグレイスケール画像を作りたいならこれで事足りるが、アスキーアートを作成する際はピクセル単位で処理を行う必要がある。
よって練習のためにgrayscale関数を実装する。
from itertools import product def grayscale(input_image): w, h = input_image.size input_pix = input_image.load() output_image = Image.new("L", (w, h)) output_pix = output_image.load() for x, y in product(range(w), range(h)): r, g, b = input_pix[x, y] output_pix[x, y] = (r + g + b)/3 return output_image if __name__ == "__main__": input_image = Image.open("lena.png") output_image = grayscale(input_image) output_image.save("gray.png")
元の画像の大きさは512x512である。
Original | Grayscale |
---|---|
ここでは単に平均をとっているが、グレイスケール変換には輝度を抽出するように係数を調整するやり方が存在する。
Three algorithms for converting color to grayscale ― The Endeavour
More on colors and grayscale ― The Endeavour
係数で重み付けした計算式は次のようなものだ。
r*0.2126 + g*0.7152 + b*0.0722
よって計算式を適用した関数は次のようになる。
def luminosity_grayscale(input_image): w, h = input_image.size input_pix = input_image.load() output_image = Image.new("L", (w, h)) output_pix = output_image.load() for x, y in product(range(w), range(h)): r, g, b = input_pix[x, y] output_pix[x, y] = r*0.2126 + g*0.7152 + b*0.0722 return output_image
grayscaleとluminosity_grayscale関数を見比べてみると、色の計算部以外はすべて同じ処理であることがわかる。
そこで共通の処理をまとめてprocessImageというデコレータにしてしまおう(いい名前が思いつかなかった)。
def processImage(func): def wrapper(input_image): w, h = input_image.size input_pix = input_image.load() output_image = Image.new("L", (w, h)) output_pix = output_image.load() for x, y in product(range(w), range(h)): output_pix[x, y] = func(input_pix[x, y]) return output_image return wrapper @processImage def grayscale(rgb): r, g, b = rgb return (r + g + b)/3 @processImage def luminosity_grayscale(rgb): r, g, b = rgb return r*0.2126 + g*0.7152 + b*0.0722
画像のアスキーアート化
グレイスケールができたら次はいよいよ画像のアスキーアート化に取り掛かる。
画像のグレイスケールに応じて明るいところではまばらな文字、暗いところでは詰まった文字を割り当てる。
いまいち勝手が掴めずまったく汎用性のないコードになってしまった。
def image2ascii(input_image): from PIL import ImageDraw, ImageFont w, h = input_image.size character, line = "", [] DIV = 64 fontsize = w/DIV font = ImageFont.truetype("C:/Windows/Fonts/msgothic.ttc", fontsize, encoding="utf-8") input_pix = input_image.load() output_image = Image.new("RGBA", (w, h)) draw = ImageDraw.Draw(output_image) for y in range(0, h, fontsize): line = [] for x in range(0, w, fontsize): r, g, b = input_pix[x, y] gray = r*0.2126 + g*0.7152 + b*0.0722 if gray > 250: character = " " elif gray > 230: character = "`" elif gray > 200: character = ":" elif gray > 175: character = "*" elif gray > 150: character = "+" elif gray > 125: character = "#" elif gray > 50: character = "W" line.append(character) draw.text((0, y), "".join(line), font = font, fill="#000000") return output_image if __name__ == "__main__": input_image = Image.open("lena.png") output_image = image2ascii(input_image) output_image.save("ascii.png")
余談
今回のコードはWindows/Python2.6/PIL1.1.7の環境で動かした。
フォントを扱う段階でPILがコケた際は、下記記事のStep5が参考になった。
blockdiag を WindowsXP で動かす « Stop Making Sense
あとLenaさんの画像は
http://optipng.sourceforge.net/pngtech/img/lena.html
から拾ってきたのだが公式に配布しているところとかあるのだろうか?