终端看电影指日可待——ASCII转义序列的妙用意义序列\x1b[8D和\x1b[0K。除此之外,ASCII转义序列还有许多其他功能。例如,可以通过将转义序列中的参数38改为48来自定义输出的前景色,可以通过将打印内容改为两个空格来自定义输出的背景色,看起来像黑色画布中的本例中画了一个红色方块,只要大小合适,就可以在终端打印一张图片,只需要用每个像素点的颜色作为背景色,在行和列上输出两个空格即可对应坐标。如果可以擦除输出,在同一个位置打印不同的图片,甚至可以实现动画效果。眼见为实,让我用Python来演示一下。将GIF加载到终端如果要使用上面的思路在终端显示一张GIF图片,首先要获取GIF图片每一帧的每个像素点的颜色。使用名为Pillow的库可以在Python中轻松解析GIF文件,首先安装此库?/tmprmdirshow_gif?/tmpmkdirshow_gif?/tmpcdshow_gif?show_gifpython3-mvenv./venv?show_gif。./venv/bin/activate(venv)?show_gifpipinstallPillowCollectingPillow使用缓存的Pillow-8.1.0-cp39-cp39-macosx_10_10_x86_64.whl(2.2MB)安装收集的包:PillowSuccessfullyinstalledPillow-8.1.0version2警告:p你.3;但是,版本21.0.1可用。您应该考虑通过“/private/tmp/show_gif/venv/bin/python3-mpipinstall--upgradepip”命令进行升级。然后你可以让它读取并解析GIF图像importsysfromPILimportImage,ImageSequenceif__name__=='__main__':path=sys.argv[1]im=Image.open(path)forframeinImageSequence.Iterator(im):passandpasseach将frame转换成RGB模式然后遍历每个像素importsysfromPILimportImage,ImageSequenceif__name__=='__main__':path=sys.argv[1]im=Image.open(path)forframeinImageSequence.Iterator(im):rgb_frame=frame.convert('RGB')pixels=rgb_frame.load()foryinrange(0,rgb_frame.height):forxinrange(0,rgb_frame.width):调用Image的实例方法load得到class它是PixelAccess类的一个实例。它可以像二维数组一样,利用坐标获取每个像素点的颜色值。颜色值是一个长度为3的元组类型的值,其中像素的三基色的分量是从ANSI转义码中得到的。在条目的24位部分可知,24位可以使用带有参数48;2;的转义序列来设置背景颜色。后面是用分号分隔的三个原色分量。导入sysfromPIL导入图像,ImageSequenceif__name__=='__main__':path=sys.argv[1]im=Image.open(path)forframeinImageSequence.Iterator(im):rgb_frame=frame.convert('RGB')pixels=rgb_frame.load()foryinrange(0,rgb_frame.height):forxinrange(0,rgb_frame.width):colors=pixels[x,y]print('\x1b[48;2;{};{};{}m\x1b[0m'.format(*colors),end='')print('')必须在每次双循环遍历所有后清除输出并将光标重置到左上角像素打印,这可以通过ASCII转义序列实现。参考VT100用户指南可知,显示的字符可以用ED命令擦除,对应的转义序列为\x1b[2J;用CUP命令可以将光标位置移到左上角,对应的转义序列为\x1b[0;0H。在开始打印一帧图像之前输出这两个转义序列(im):rgb_frame=frame.convert('RGB')pixels=rgb_frame.load()print('\x1b[2J\x1b[0;0H',end='')foryinrange(0,rgb_frame.高度):对于范围内的x(0,rgb_frame.width):colors=pixels[x,y]print('\x1b[48;2;{};{};{}m\x1b[0m'.format(*colors),end='')print('')最后,只需要在每一帧打印完后,根据GIF文件的要求休眠一段时间即可。每帧的显示时长可以从info属性的keyduration中获取,单位是毫秒)对于ImageSequence.Iterator(im)中的帧:rgb_frame=frame.convert('RGB')pixels=rgb_frame.load()print('\x1b[2J\x1b[0;0H',end='')fory在范围(0,rgb_frame.height)中:对于x在范围(0,rgb_frame.width)中:颜色=像素[x,y]打印('\x1b[48;2;{};{};{}m\x1b[0m'.format(*colors),end='')print('')time.sleep(rgb_frame.info['duration']/1000)现在可以看到效果了。我准备了一张GIF图片进行测试,宽高都是47像素,一共34帧,让它在终端显示。一个小改进你可能已经注意到之前的演示效果中有明显的闪烁,这是由于没有足够快地打印ASCII转义序列造成的。在这种情况下,可以先生成一整行转义序列,然后一次性输出到终端。变化并不复杂importsysimporttimefromPILimportImage,ImageSequenceif__name__=='__main__':path=sys.argv[1]im=Image.open(path)forframeinImageSequence.Iterator(im):rgb_frame=frame.convert('RGB')pixels=rgb_frame.load()print('\x1b[2J\x1b[0;0H',end='')foryinrange(0,rgb_frame.height):last_colors=None线=''forxinrange(0,rgb_frame.width):colors=pixels[x,y]ifcolors!=last_colors:line+='\x1b[0m\x1b[48;2;{};{};{}m'.format(*colors)else:line+=''last_colors=colors打印('{}\x1b[0m'.format(line))time.sleep(rgb_frame.info['duration']/1000)但是效果但是看原文是很明显的
