TI-BASIC 计算器游戏开发之文字、图形、音频教程:I:中文文字显示
【第一部分】
TI-BSAIC 中文文字显示教程
现在正式讨论TI-BASIC如何处理汉字的问题,计算机历来有两种汉字处理技术,一种是矢量化汉字,一种是点阵化汉字:汉字矢量化算法很精致,需要对汉字的组成结构有相当深入的了解,当然现在它已经成为一种通用技术。虽然汉字矢量化技术很成熟,但是要把它移植到TI-89T上工作量太大,暂不考虑。
汉字点阵化也是一种通用技术,不过处理思路比较简单粗暴,这种算法根本不去考虑汉字内在结构所蕴藏的逻辑性和每个组成部分的含义,只是把汉字当做一个像素图形来处理,下图是一个从网上找到的非常适合用来说明的汉字点阵像素图:
(引用图片来自 http://blog.cechina.cn/zhiy66/205022/message.aspx)
左边是“中文字模”,中间是用0和1表示的笔画,右边是把中间的二进制数字16进制化的 byte 值,这是一个16*16的点阵。
汉字点阵处理法首先把每个汉字当做一个宽度和高度固定的图形放在屏幕上,比如高16个像素点,宽16个像素点,相当于一个16*16的正方形,横着看有16行,竖着看有16列,有笔画的像素位置设置为1,没有笔画的像素位置设置为0,这样每个像素点只需要两个值就可以保存,也就是说每个像素点用一个 bit 位(1个bit位正好有0或1两个值)就可以了,每一行有16个像素点等价于每一行有16个 bit 位,我们知道8个 bit 位是一个字节 1 byte,16个 bit 位正好2个字节,也就是说每一行的数据可以保存到2个byte里,16行的数据需要16个双字节,这个汉字图形可以用32个字节来表示,这样算下来,100个汉字就是 100*32 byte,1000个汉字就是 1000*32 byte,10000个汉字就是10000*32 byte, 当然如果你用的汉字图形可以更小一些,比如高度是8像素,宽度是8像素,也就是8*8的点阵,那么一个汉字用8 byte 就可以保存下来了。
一般来说采用点阵化汉字方会搞一个几百K字节到若干M字节的汉字字库(fxesms论坛前几天有人发了一个汉字点阵程序,可以参考一下)。程序使用时会调用这个字库文件,根据汉字的索引(好像是区位码)获得某个汉字的点阵数据,这个数据就是几个16进制数,这个数据就是汉字的字模,然后在程序中用两个嵌套循环画像素点,一个按行数循环,一个按列数循环,遇到有笔画的1就在对应行数坐标和列数坐标的位置上画一个像素点,遇到没有笔画的0就不画,这样就把这个汉字显示到屏幕上了。
上面说的是C语言调用汉字点阵字库画汉字的方法,在TI-BASIC中也有对应的画屏幕像素点的函数,而且有好几个,我们先拿其中的 PxlOn 来举例,先大致说一下 PxlOn 的用法:
【小提示】建议养成经常查看TI手册的习惯,虽然是英语,不过很简单,尤其是函数说明部分。
PxlOn顾名思义就是把像素点打开,也就是在空白的屏幕上画一点,那么在哪里画呢?当然要指定行列坐标值,这里用raw,col 分别表示行和列,完整的用法就是 PxlOn raw,col ,比如你想在第10行,第15列画一个点,就执行:
PxlOn 10,15
那么我们是否直接把C语言的处理方法搬过来用呢?确实可以这么用,不过TI-BASIC本身有一个局限,就是嵌套循环时速度会比较慢,而且在最里层的循环里还要做一次判断,判断是1还是0,因此这么做效率不高,如果只显示一个汉字还凑合,但是我们肯定不会满足于只显示一个汉字,怎么办?
【小提示】TI-BASIC的多重循环效率不高,使用时不要嵌套过深。
粗暴办法:前面我们评价过汉字点阵化处理是一种简单粗暴的算法,那么我们这里要用到一种更加简单更加粗暴的算法,它更适用于我们目前的场景:用TI-BASIC在TI-89T上显示汉字,我们的处理方法是预先把每个汉字点阵中的行列坐标值算出来,分别保存到两个列表中,行坐标保存到rawlist,对应的列坐标保存到collist,然后直接把这两个列表当做参数传给 PxlOn ,这里再补充一下,PxlOn 还有一种用法,就是使用坐标列表,形式如下:
PxlOn {rawlist},{collist}
具体的例子如下:
PxlOn {raw1,raw2,raw3...raw1000...rawn},{col1,col2,col3...col1000...coln}
这种方法避免了在多重循环中计算判断,经实际测试后确认效率是可以接受的。
这里的关键就是如何通过一个汉字点阵图来获取一个汉字的坐标列表,你当然可以用WINDOWS的画笔程序创建一个16*16 大小的图形,然后写一个汉字上去,然后把这个图形放大,一个像素点一个像素点去数,遇到有黑点的就记录下它的行坐标和列坐标,然后把所有的点都数一遍,记下所有有黑点的像素的行列坐标,再把它们分别保存到两个列表 rawlist 和 collist 中就可以了。
用计算机程序表达就是:
1、先看第0行,第0列的点是不是黑色点,如果是则记录 rawlist 为{0},collist为{0},如果不是就不做记录,这里假设(0,0)是黑色点;
2、再看第0行,第1列的点是不是黑色点,如果是则记录 rawlist 为{0,0},collist为{0,1},如果不是就不做记录,这里假设(0,1)也是黑色点;
3、再看第0行第2列的点是不是黑色点,如果是则记录 rawlist 为{0,0,0},collist为{0,1,2},如果不是就不做记录,这里假设(0,1)也是黑色点;
4、一直循环做此过程,......;
5、再看第0行,第7列的点;
6、此时第0行的8个像素点已经全部检查完毕,得到的结果是两个列表 rawlist 和 collist
7、这时开始换下一行,从第1行,第0列开始检查,具体过程跟上面的一样;
8、一直循环做此过程......
9、再看第1行,第7列的点,
10、此时第1行的8个像素点已经全部检查完毕
11、换下一行,从第2行,第0列开始,...... 到第2行,第7列结束
12、重复此过程,每次列数数到7就换行...
13、一直到最后一个点,第7行,第7列,这时你就会发现,我们的检查路径是从左上角的(0,0)开始,从左到右平行检查,每次检查到第7列就换下一行继续从左到右检查,一直到右下角的(7,7);
Python 程序如下:
# v0.1 bmp2pix.py make bmp to [x],[y] and write it to a file # v0.2 bmp2pix.py binary : write to a .89p file ,the checksum is not correct,the TI-89T refused the file transfer # v0.3 bmp2pix0.3.py change to txt: write to a txt file, then use fromtxt.exe to change it to .89t # v0.4 bmp2pix0.4.py update some var name, fix the bug of the wrong use with pix[col,raw] # v0.5 bmp2pix0.5.py update only save the rawlist and the collist as {rawlist},{collist} # v1.0 ConvertBMPtoRawCol.V1.0.py change name to ConvertBMPtoRawCol.V1.0.py import Image #im = Image.open("D:\\Project\\mmd1.bmp") im = Image.open("D:\\Project\\simple1.bmp") #im = Image.open("D:\\Project\\hanzi16-20.bmp") pix = im.load() # pix = list(im.getdata()) # in python [1,2,3,4] means list , not {1,2,3,4} pRaw = [] pCol = [] # the 20*16 raw: 20, col: 16 ''' colRange = 15 rawRange = 19 ''' # the 160*100 colRange = 159 rawRange = 99 for raw in range(0,rawRange): for col in range(0,colRange): if pix[col,raw] == 0 : pRaw.append(raw) pCol.append(col) #print "px:",px #print "py:",py pStrRawl = str(pRaw) pStrColl = str(pCol) # replace the '[]' to '{}', in TI-BASIC, rowlist,collist like this {x1,x2,x3},{y1,y2,y3} pStrRawl = pStrRawl.replace('[','{') pStrRawl = pStrRawl.replace(']','}') pStrColl = pStrColl.replace('[','{') pStrColl = pStrColl.replace(']','}') print "pStrRawl=:",pStrRawl print "pStrColl=:",pStrColl # Ti-Basic code ''' [begin==> v0.5 add comment ] #lf = chr(13) lf = "\n" tifunc = "d()" + lf tiprgm = "Prgm" + lf tiprawl = pStrRawl + "\x00" +"prawl" + lf tipcoll = pStrColl + "\x00" +"pcoll" + lf tipxlon = "PxlOn prawl,pcoll" + lf tistopic = "StoPic p1,0,0" + lf tirplcpic = "RplcPic p1,0,0" + lf tiendprgm = "EndPrgm" # The whole Ti-Basic code ticode = tifunc + tiprgm + tiprawl + tipcoll + tipxlon + tistopic + tirplcpic + tiendprgm #ticode = tipxlon + tistopic + tistopic + tiendprgm #[end==> v0.5 add comment ] ''' lf = "\n" tiprawl = pStrRawl + "-" +"prawl" + lf tipcoll = pStrColl + "-" +"pcoll" ticode = tiprawl + tipcoll print "\n" print "ticode:" , ticode # Ti-Basic PRGM file struct # Order by file description #ti_head = '**TI89**' #ti_seperate = '\x01\x00' #ti_folder = 'main' ''' # Machine type 89/92/89t ti00_3f = '**TI89**\x01\x00main\x00\x00\x00\x00Single file dated Sat Jun 16 00:14:58 20\x01\x00R\x00\x00\x00' # Variable name ti40_47 = 'd\x00\x00\x00\x00\x00\x00\x00' # 12 00 00 00 separator ti48_4b = '\x12\x00\x00\x00' # File length (ex : &97 &01 &00 &00=> &(00 00 01 97) => 407) ti4c_4f = '\x21\x11\x00\x00' # A5 5A 00 00 00 00 separator ti50_55 = '\xa5Z\x00\x00\x00\x00' # Size = bytes to read example &02 &50 => &0250 = 592 ti56_57 = '\x11\x21' # Program with chr(13) as line separator ti58_5f = '()\rPrgm\r' # the code context after 'Prgm()' and till 'EndPrgm' include 'EndPrgm' # like this 'PxlOn EndPrgm' ti60_xx = ticode # 00 00 xx 19 E4 E5 00 00 08 separator tix1_x2 = '\x00\x00' tix3_x3 = '44' tix4_x9 = '\x19\xE4\xE5\x00\x00\x08' # End mark = DC tix10_x10 = '\xdc' # 13 19 Checksum from ? to ? tix11_x12 = '\xda\xff' # 0d 0a end of the file tix13_x14 = '\x0d\x0a' # TiTail: tix1_x2 ~ tix13_x14 titail = tix1_x2 + tix3_x3 + tix4_x9 + tix10_x10 + tix11_x12 + tix13_x14 tiCodeAll = ti00_3f + ti40_47 + ti48_4b + ti4c_4f + ti50_55 + ti56_57 + ti58_5f + ti60_xx + titail ''' #tifile = open('D:\\Project\\gamePIC\\main\\main.ticode.txt','w') #tifile = open('D:\\Project\\gamePIC\\main\\ColRaw1.txt','w') #tifile = open('D:\\Project\\gamePIC\\main\\mmColRaw.txt','w') tifile = open('D:\\Project\\gamePIC\\main\\smColRaw.txt','w') tifile.write(ticode) #tifile.write("\n") tifile.close()
按照上面的算法我们完成了对一个8*8的汉字点阵图的扫描,最终得到了两个坐标列表:rawlist和collist。
这种扫描算法可以在TI上用TI-BASIC实现,但是有这么几个现实问题,一是需要TI上有汉字点阵图形,不过一旦有了点阵图就不需要再分析了,直接调用图形就可以了,当然也可以手工来绘制,这就相当的繁琐了,或者要把字库文件传到TI上,不过TI-89T貌似不能接受非TI格式的外部文件(这一点我没深入研究,可能有变通的办法,知道的朋友可以分享一下)。
所以综合考虑,我们把这部分工作放到PC机上来做,我用PYTHON写了一个简单程序(上面给出逇那个程序),从各种不同分辨率的BMP点阵图中生成 rawlist 和 collist ,保存到一个文本文件中,然后再用一个名为 fromtxt.exe 的转换程序将其转换为 TI-89T能够接受的 .89t 文件,把该文件上传到 TI-89T 中,用 TextEdit 打开,拷贝其内容,再打开 PrgmEdit ,粘贴刚才拷贝的数据,就初步得到了汉字点阵图的坐标列表数据,再在程序中执行:
PxlOn {rawlist},{collist}
就可以把汉字画到TI-89T的屏幕上了。
很好,学完上述的教程我们就明白如何把汉字显示到屏幕上去了,实际上这种汉字显示技术也是基于图形处理来做的,因此我们下一节会讲授更多、更专门的图形处理方面的内容。
【失败经验】上面这些操作看起来是不是很麻烦,话说我开始时也是打算直接用PYTHON生成一个PRGM文件,上传到TI-89T以后直接运行就行了,可是在查看了.89p 的文件格式描述后才发现几个文档不太一致,这个倒也罢了,最麻烦的是大家都没说清楚 .89p 的文件校验和算法是计算那些数据得出的,就是从文件末尾倒数的2个字节不知道该怎么填,结果生成的山寨版 .89p 文件上传时直接被拒绝,提醒我校验和不对,然后用TI-89T生成了一些 89p 的文件,用vim查它们的二进制,比对了半天也没找出啥规律来,一想,这不是在搞解密嘛,不能弄这么复杂,上网搜了一下,发现没一个程序能在我的WIN7下生成正确的 .89p 文件,有几个程序连安装都存在不少问题,而且看他们也大多是用了一个ocx控件,貌似没人知道明确的校验和算法,因此在一番尝试之后明智地选择了放弃,改用上述的办法来实现。
【建议】在软件开发过程中,可能会遇到各种问题,有些问题你可以迅速解决,有些问题需要稍微多花一些时间解决,有些问题很难在短时间内解决,那么此时就要做出取舍,看看这个问题能否通过其他处理方法回避掉,如果采用其他处理方法是否会对开发带来太多效率方面的影响?具体到我们这个例子,很显然,在面对 .89p 格式文件的校验和算法不明确的情况下,换一种方式可以尽快让开发继续下去,只是稍微操作麻烦一些,所以果断放弃对校验和算法的研究,改用简单的处理方法。在这里我想说明的是开发中遇到的任何问题,都存在不止一条的解决之道,如何选择,要根据你实际的情况来决定。
......当然我还是希望有高人能把那个校验和算法推导出来......