大概没有什么比解决现实世界里的问题更令一个创造者着迷的了。——Fenng
趣学Python
系列是用python
实现一系列小K觉得有意思的功能,灵感来自于网络或自己的生活,实现都比较简单,不涉及复杂的技巧,适合新手入门,希望做到有趣又有用_。
话不多说,下面进入正题:用Python
制作字符动画
不知道大家有没有见过这种动图:
pikachu-ascii.gif第一次在网上见到这种字符动画,内容有点不可描述,咳咳。总之就是惊为天人的感觉吧,第一反应就是:卧槽,还能这么玩?!后来琢磨了一下,做起来应该没啥难度,正好在学Python
,顺手实现一个。
0x00 环境
- 系统:
Ubuntu Mate
-
Python
版本:3.7,2.x太古老了,入手Python
还是直接上3.x吧 - 用到的库:
-
pillow
:著名的图片处理库 -
imageio
:从库的名字上就可以看出它的功能,主要是图像读写,用户最后将多张PNG
合成
GIF
-
argparser
:用于提供良好的cli
-
0x01 思路
- 将
GIF
图片拆分成多张图片,可以通过pillow
的Image
模块实现 - 将每一张图片处理为字符画:
- 将图片划分成小块,作为后续处理的最小单元,每个单元的颜色近似为其左上角的
RGB
数值 - 计算每个单元的灰度值,灰度值计算公式为:
0.2126 * r + 0.7152 * g + 0.0722 * b
- 根据灰度值将
ASCII
字符串映射到对应的单元,同时记录每个单元原来的RGB
- 创建创建字符画对象,将
ASCII
写入到对应单元,并用记录的RGB
数值填充
- 将图片划分成小块,作为后续处理的最小单元,每个单元的颜色近似为其左上角的
- 将生成的字符画合成
GIF
动图
0x02 具体实现
1. 拆分GIF
def gif2png(fileName, asciiChar, font, isgray, scale):
''' 将GIF拆分,并将每一帧处理成字符画
fileName: GIF文件
asciiChar: 灰度值对应的字符串
font: ImageFont对象
isgray: 是否生成黑白动图
scale: 缩放比例
'''
im = Image.open(fileName) # GIF文件打开为一个序列
path = os.getcwd()
# Cache文件用以保存拆分后的图片和生成的字符画
if(not os.path.exists(path+"/Cache")):
os.mkdir(path+"/Cache")
os.chdir(path+"/Cache")
# 清空Cache文件夹下的内容,防止多次运行时被之前的文件影响
for f in os.listdir(path+"/Cache"):
os.remove(f)
# GIF打开后的序列通过tell返回每一帧的索引,超出索引范围后抛出异常
try:
while 1:
current = im.tell()
name = fileName.split('.')[0]+'-'+str(current)+'.png'
im.save(name) # 保存每一帧图片
# 将每一帧处理为字符画
img2ascii(name, asciiChar, font, isgray, scale)
im.seek(current+1) # 继续处理下一帧
except:
os.chdir(path)
2. 将每一帧图片转换为字符画
首先需要将图片转换为RGB
模式,并根据图片大小设定转换后的图片尺寸
im = Image.open(img).convert('RGB') # 注意,此处需要先将图片转换为RGB模式
# 设定处理后的字符画大小,需要为整型
raw_width = int(im.width * scale)
raw_height = int(im.height * scale)
然后根据设定的字体大小和缩放倍数确定单元尺寸
# 获取设定的字体的尺寸,ImageFont默认的尺寸大小为6x11,其他字体会有所不同
# 此处使用的字体为truetype字体,大小为10px
font_x, font_y = font.getsize(' ')
# 确定单元的大小
block_x = int(font_x * scale)
block_y = int(font_y * scale)
之后将图片进行缩放,每一个单元对应缩放后的一个像素点
# 确定长宽各有几个单元
w = int(raw_width/block_x)
h = int(raw_height/block_y)
# 将每个单元缩小为一个像素
im = im.resize((w, h), Image.NEAREST)
对缩放后图片的每个像素点进行遍历,记录对应的字符和RGB
数值,分别保存在txts
和colors
中。getpixel
返回的是一个包含RGB
值的元组,前三项分别为R
、G
、B
的值。
# txts和colors分别存储对应块的ASCII字符和RGB值
txts = []
colors = []
for i in range(h):
line = ''
lineColor = []
for j in range(w):
pixel = im.getpixel((j, i))
lineColor.append((pixel[0], pixel[1], pixel[2]))
line += get_char(asciiChar, pixel[0], pixel[1], pixel[2])
txts.append(line)
colors.append(lineColor)
遍历txts
和colors
,写入到新的画布上,生成字符画
# 创建新画布
im_txt = Image.new("RGB", (raw_width, raw_height), (255, 255, 255))
# 创建ImageDraw对象以写入ASCII
draw_handle = ImageDraw.Draw(im_txt)
for j in range(len(txts)):
for i in range(len(txts[0])):
if isgray:
draw_handle.text((i*block_x, j*block_y), txts[j][i], (50, 50, 50))
else:
draw_handle.text((i*block_x, j*block_y), txts[j][i], colors[j][i])
im_txt.save(img)
3. 将多张图片合成为GIF
主要通过imageio
库来实现GIF
图片的合成,其中duration
为每一帧图像的持续时间,默认为0.2秒。
# 读取Cache文件夹下的文件,合成GIF动画
def png2gif(dir_name, duration):
path = os.getcwd()
os.chdir(dir_name)
dirs = os.listdir()
images = []
num = 0
for d in dirs:
images.append(imageio.imread(d))
num += 1
os.chdir(path)
imageio.mimsave(d.split('-')[0]+'-ascii.gif',images,duration = duration)
0x03 注意事项
- 传入参数的类型,如
Image.new
、Image.resize
中的尺寸均需转换为整型 - 处理拆分后的图像时需要转换为
RGB
模式 - 遍历图像时注意外层循环为高度,内层循环为宽度
源码地址: pic2ascii
欢迎关注公众号:Java和Python
qcode.jpg
网友评论