字元畫是一系列字元的組合,我們可以把字元看作是比較大塊的像素,一個字元能表現一種顔色(暫且這麼了解吧),字元的種類越多,可以表現的顔色也越多,圖檔也會更有層次感。
問題來了,像素是有顔色深淺的,我們如何将帶有不同顔色的像素編碼為對應的字元呢?我們是要轉換一張彩色的圖檔,這麼多的顔色,要怎麼對應到單色的字元畫上去?
轉化方法
轉化的整體思路就是:RGB->灰階->字元,詳細來說就是:
将彩色圖檔轉化為灰階圖,根據顔色深淺的 RGB 值映射為灰階值;
根據字元集順序及字元集長度,将灰階值映射到字元。
這裡就要介紹灰階值和 RGB 的概念了。
RGB
RGB 色彩模式是通過對紅(R)、綠(G)、藍(B) 三個顔色通道的變化以及它們互相之間的疊加來得到各式各樣的顔色的,RGB 即是代表紅、綠、藍三個通道的顔色,這個标準幾乎包括了人類視力所能感覺的所有顔色。
通常情況下,RGB 各有 256 級亮度,用數字表示為從 0、1、2... 到 255。
灰階圖
灰階值:指黑白圖像中點的顔色深度,範圍一般從 0 到 255,白色為 255,黑色為 0,故黑白圖檔也稱灰階圖像
那麼如何将彩色圖檔轉為灰階呢?
常用的公式如下,可以将像素的 RGB 值映射到灰階值:
gray = 0.2126 * r + 0.7152 * g + 0.0722 * b
得到灰階值後,就可以将灰階值和字元進行映射了。可以建立一個不重複的字元清單,灰階值小(暗)的用清單開頭的符号,灰階值大(亮)的用清單末尾的符号。
下面是我們的字元畫所使用的字元集,一共有 70 個字元,從左住右, 表示的顔色深度依次遞減。字元的種類與數量可以自己根據字元畫的效果反複調試。
ascii_char = list("[email protected]%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")
實作
字元集容量為 70,一個字元對應的值:區間寬度= 256/字元集長度,即區間寬度為 256/70 = 3.6。
gray 區間和字元的對應關系
[0.0, 3.6) --> $
[3.6, 7.2) --> @
[7.2, 10.8) --> B
...
[244.2, 247.8] --> '
[247.8, 251.4] --> .
[251.4, 255.0] -->
RGB 轉字元的函數
# 将256 灰階映射到 70 個字元上
def get_char(r, g, b, alpha=256):
if alpha == 0:
return " "
length = len(ascii_char)
gray = int(0.2126*r + 0.7152*g + 0.0722*b)
# 每個字元對應的 gray 值區間寬度
unit = (256.0+1)/length
# gray值對應到 char_string 中的位置(索引值)
index = int(gray/unit)
return ascii_char[index]
完整代碼
import argparse
from PIL import Image
# 指令行輸入參數處理
parser = argparse.ArgumentParser()
parser.add_argument('file') # 輸入圖檔
parser.add_argument('-o', '--output') # 輸出檔案
parser.add_argument('--width', type=int, default=40) # 輸出字元畫寬度
parser.add_argument('--height', type=int, default=40) # 輸出字元畫高度
# 擷取參數
args = parser.parse_args()
IMG = args.file
WIDTH = args.width
HEIGHT = args.height
OUTPUT = args.output
ascii_char = list(
"[email protected]%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")
# 将256 灰階映射到 70 個字元上
def get_char(r, g, b, alpha=256):
if alpha == 0:
return " "
length = len(ascii_char)
gray = int(0.2126*r + 0.7152*g + 0.0722*b)
# 每個字元對應的 gray 值區間寬度
unit = (256.0+1)/length
# gray值對應到 char_string 中的位置(索引值)
index = int(gray/unit)
return ascii_char[index]
if __name__ == "__main__":
im = Image.open(IMG)
im = im.resize((WIDTH, HEIGHT), Image.NEAREST)
txt = ""
for i in range(HEIGHT):
for j in range(WIDTH):
txt += get_char(*im.getpixel((j, i)))
txt += '\n'
print(txt)
if OUTPUT:
with open(OUTPUT, 'w') as f:
f.write(txt)
else:
with open('output.txt', 'w') as f:
f.write(txt)
運作結果
原圖
dora
$ python ascii.py ascii_dora.png --width=80 --height=40
$$$#kqqq#$$
$kxxxxxx( Ox "Lq$
@xxxxxxxxmf z dxx0$
@xxxxxxxxxxx# n I}z ( (xxxx0
$xxxxxxxxxxc#(Ip ( COC( h pu$
@xxxxxxxxxW # C *j :kM "Opn-p
kxxxxxxxu> "n#n- Mkkkkk f- $
8xxxxxxxh }B&C "-(nnnn- p
$xxxxxxmn I-}nnnOpphpz} h $
oxxxxxu }n }OpnI f
Jxxxxx# "kkkkkkka8%*aW%pn- -n }CI$
Yxxxxx- nkkkkkkkkkkkkkkkkkkkkkkkkkkk* $
mxxxxx kkkkkkkkkkkkkkkkkkkkkkkkkko >
$xxxxx zkk8bYfcOMMkkkkkkkkkkkkkk8 >$
cxxxxC nfffffffffffc#kkkkkkkkkn $
$xxxxx" Qfffffffffffukkkkkk8 $
@xxxxk} hfffffffffffpkkka> $
$xxxxx( "nQffffffff8} -$
$uxxxxW -Cpp$%%%%%%%pn$
$xxxxc#8akkkkkkn >b_ub0o&W$
$8kkkWdxxxxxx# pIII[I0 $
xxxxxxxxxxxY 0IIIXIf n
qxxxxxxxxxxxd h aJua"
$xxxxxxxx#k} (hC-" Ih$ $*(-C$
xx0qcxxxxq n n$- $
qxxxxxxxx# ( # p
$xxxxxxxxc "- (I p
$kkkk%$xxxxxxxxxJ -npnn- fh $
$kkkk8$xxxxxxxxxxxC "dxn C
#xxxxxxxxxxxxxxdhnCW0uxxxx# O
xxxxxxxxxxxxxxxxxxxxxuqB$$ 8 I$
> xxxxxxxxxxxxxxxL$
( pxxxxxxxxxxY$
Cxxxxxxuk$
>OO$
$ $
$C
$$Onn*$
:stuck_out_tongue_winking_eye:
知識點
指令行解析子產品 argparse
看下樣例就可以:
# 指令行輸入參數處理
parser = argparse.ArgumentParser()
parser.add_argument('file', help='the input file') # 輸入圖檔
parser.add_argument('-o', '--output', help='the output text file') # 輸出檔案
parser.add_argument('-w', '--width', type=int, default=40,
help='the width of the output, default is 40') # 輸出字元畫寬度
parser.add_argument('--height', type=int, default=40,
help='the height of the output, default is 40') # 輸出字元畫高度
# 擷取參數
args = parser.parse_args()
IMG = args.file
WIDTH = args.width
HEIGHT = args.height
OUTPUT = args.output
使用:
$ python ascii.py --help
usage: ascii.py [-h] [-o OUTPUT] [-w WIDTH] [--height HEIGHT] file
positional arguments:
file the input file
optional arguments:
-h, --help show this help message and exit
-o OUTPUT, --output OUTPUT
the output text file
-w WIDTH, --width WIDTH
the width of the output, default is 40
--height HEIGHT the height of the output, default is 40
如果覺得有用,歡迎關注我的微信,有問題可以直接交流:
你的關注是對我最大的鼓勵!