Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
89 lines (55 sloc) 7.43 KB
title tags categories date
chardraw
Python
作品
字符画
字符终端
2015-09-28 10:42:53 -0700

我一直热衷于重复造轮子……今天又用了之前的一个轮子并且优化了一下,因此介绍一下这个项目。

chardraw项目的目标是在4x8字体的低分辨率点阵终端用字符(近似地)显示图像。目前这个项目还没完全做好,所以只能用黑白的图像来进行介绍:

上图为等量异种电荷间的电场线图案的1/4。下图为一个测试图案。chardraw绘图。

Riemann函数在x:(0,1), y:(0, 0.5)上的图像,chardraw绘图。

针对不同的设备特点,可能存在以下限制:

  1. 屏幕被分割为许多8行4列的点阵区域,每个区域内只能有两种颜色,前景色和背景色。这是绝大多数低分辨率点阵终端都有的限制。
  2. 在单色屏幕上,前景色只能是白色(或某个特定颜色),背景色只能是黑色。
  3. 在大多数终端上,前景色和背景色只能是16个特定颜色之二。
  4. 在支持修改颜色表的设备上,上述16个颜色可以被修改,但同一时间屏幕上最多只能存在16种不同的颜色。
  5. 每个4x8点阵区域显示一个字符,这意味着其中前景色和背景色所组成的图案形状(pattern)只能是128个ASCII所对应的文字之一。
  6. 有的终端把0x80~0xFF对应为一些特殊字符,这样上一条限制会被放宽到256种pattern之一。
  7. 有的终端支持类似于斜体、粗体、下划线之类的文字效果,这使得可以选用的形状更多了,但太过于复杂,目前不打算考虑这种情况。
  8. 在支持修改字体表的设备上,上述128或者256种pattern都可以被修改,但同一时间屏幕上各个4x8区域的图案最多只能有128或256种。

(可以看出,上面所展示的图片,限制是1、2、5,也就是最严格的情况)

图:非常常见的一种4x8点阵字体,前32个码位被分配给了特殊符号。可惜在我的设备上并没有这种福利,前32个码位仅仅是空白而已。

如何在这些限制之下,尽可能好地用字符显示出图片?首先就要定义“好”的标准。一开始,我的思路是:将图像分割开,将其中每一个4x8区域与字体表(如上图,含有所有字符的pattern)中所有字符进行比较。颜色相同的点越多,比较结果得分越高,然后取最高分的字符作为最相似的字符。

首先一个问题是,如果字体表和颜色表能够修改,就不能把4x8区域分别看待,而要全局统筹考虑(因为只能有16个颜色和128种pattern,肯定要有一些不重要的细节被近似化,尽量满足重要的细节正确呈现),研究怎样分配颜色和pattern这两种紧缺的资源。不过这个问题太复杂,所以我暂且假设字体表和颜色表都不能修改,这样一来每个4x8区域都是独立的了。

其次的问题是,第一次运行程序后,得到的输出是这个样子的:

chardraw第一次运行

顺便一提,开发过程中我使用的测试图案一直是一幅更早之前制作的,等量异种电荷间电场线分布图案的1/4。因为这张图含有足够多的曲线,我认为可以比较全面地观察绘图质量。

恩,结果就是由于曲线太细,在许多地方程序认为空格是最接近的字符,图像变得断断续续了。头脑简单的我加粗了一下输入的图案,又画了一遍:

chardraw第二次运行

这次,太多的区域的最符合的字符居然是反色后的字符,虽然肯定是最接近的匹配结果,但屏幕上一块一块白色方框实在很难看,我想无论从哪种角度来讲,这都不能算作好的近似图案。

总结教训,结论就是,亮度很重要。一味地追求相似,结果可能是匹配到了过于暗或者过于亮的字符,影响整体效果。因此我修改了程序,使它计算出每个4x8区域的亮度(亮点数,取值范围显然为0~32),然后按照“亮度一致-暗一点-亮一点-暗两点-亮两点”的优先顺序去检查字符(把暗放在亮的前面,是为了稍微减少一些反色字符,它们实在太难看了)。这带来了两个好处:一是减少了需要进行比较的字符数量,加快了程序速度。二是最终所选择的字符的亮度误差一定在2以内,不会使结果的色调过于差劲。而且,因为有5个亮度级别的字符被检查,也能寻找到比较相似的字符。

chardraw第三次运行

现在产生的图案终于是粗细均匀,明暗一致了,我认为可以作为V1.0了。之后,我为程序中时间消耗最大的“寻找匹配的字符”部分加上了结果缓存,按最近最少使用来更新。这使得程序不再在大片的空白中耽误时间,而且能更快绘制大片相似颜色和直线线条。

以上就是之前的工作成果了。今天中午想画一个Riemann函数图象来看(虽然我也写过draw绘图库,可以画出正常的图,但既然产生了数据,为何不送进chardraw里看看呢?)。我发现对于这种散点式的图像,之前调节亮度的方法还是不够好——对于亮度值为1的一个孤立的点,最好的匹配结果就是空格,导致这些点消失不见。因此我为程序增加了一个“数学图像”开关,打开后会对亮度进行更严格的限制:

  • 亮度为0的区域只能用空格匹配
  • 亮度为1的区域只能用亮度为1或2的字符匹配
  • 亮度为2的区域只能用亮度为1或2或3的字符匹配
  • 更高亮度的保持以前的设计不变,即允许误差正负2级亮度

这样一来,孤立的点的位置的准确性有少许牺牲,但是确保了点不会消失,适合观察散点的位置。在这个模式下,我绘制了本文开头的那张Riemann函数图象。这是数学中非常有名的一个函数,当自变量为x时,它的函数值为:

  • 1/q,如果x是有理数,可以表示为最简分数p/q
  • 0,如果x是无理数
  • 1,如果x是0

顺便一提,这个函数图像是分形的,它的任何较小部分与较大部分之间是相似的(自相似性),并且它处处极限为0,在所有有理点上不连续,在所有无理点上连续。当然这是题外话了。


补充:有人指出这个函数应该叫做Thomae's function,吓得我赶紧查了一下,幸好并没有错。以下为英文维基百科原文:

Thomae's function, named after Carl Johannes Thomae, has many names: the popcorn function, the raindrop function, the countable cloud function, the modified Dirichlet function, the ruler function,[1] the Riemann function, or the Stars over Babylon (John Horton Conway's name).[2]


欢迎回复告诉我哪里有前人对这个项目更好的实现,或者如果感兴趣的话欢迎一起继续造轮子。