笔画组字的编程问题

大概五年前,我为实现笔画组字而着手编研无字库程序时,首先编研的是笔画轮廓线内部的填充子程序。因为VB有画线语句,却没有填充语句,而我又不会用API。直到不久前,才由网友指教,解决了这个问题。我编程主要用VB,但大概也只掌握其百分之五。VB的资料浩瀚,买不齐,更看不完。而我学任一种语言,几乎都是先了解个大概,再用到什么功能,才细看某些相关语句的。这样,使得一些在别人手里是很简单的事情,我却摸索了五六年。应用最近学到的东西,应该会使字形质量提高。但我想先回顾一下五年来的屈折历程,总结经验,以明确今后的努力方向。这可能对别人也有启发,还可能让高手了解我的需要和困难,早日帮助我并得到解决。

很早就想重推“无字库”,2000年,儿子大专毕业后,更把它当作我的主要目标。2001年时,汉字笔画已基本考虑成熟,填充子程序也编出来了。我原想,只要把1984年在袖珍机PC-1500上编的的无字库软件搬到微机上来,就可以组出汉字。谁知事情并不那么简单。填充是从连通区域内部一个点(种子)开始的,碰到边界就应该停止。试着组合笔画时就发现,两笔画一相交,原来是两个连通区,就分割成五个了,至少有三个没办法填充。另外,试验也表明,填充速度太慢,不可能在组字中使用。这一难题困扰我一年多,还是没法解决。我的网站已经开张,牛已吹出去了,却拿不出东西。最后只得用单纯的画线语句,真的等于把1500那一套程序搬到微机上,这就是2003年4月公布的“PC1500无字库汉字演示软件”。由于微机内存没有限制,虽然单纯的画线语句,笔画粗细无变化,但已能模仿宋体,字形比PC-1500打印出来的要好得多。

以后想出了这样的快速填充方法:从笔画的外层开始,向内画6次闭合的轮廓线,一次比一次向里缩,笔画粗细却次次加倍,就把轮廓线内部填充了。这一办法,从2004年春节公布的 无字库汉字演示和设计软件1.0版开始沿用至今。但它不能精细地体现笔画轮廓线,尤其不能显示笔画尖。第二层缩得多了,要露白,缩得少了,要突破轮廓。后来设计轮廓线时用上了贝塞尔曲线,由于使用这样的填充程序,字形几乎没有改进。曾在书本上找到一个填充程序源江科技编著的《VB编程技巧280例》第283例:“大面积不规则区域的填充”),但运行速度也太慢。想在该基础上改进:先用大方块粗填,最后才一个象素一个象素的细填。试验表明能加快,但程序难调好,易出问题。于是到论坛上求助,网友tjestar等告诉我用API函数ExtFloodFillFillRgn等。现在我用简单的FloodFill,速度比我原来用的画线填充还快一点点。使用这个API函数的方法是,先点菜单“工程”—“添加模块”,在添加的模块里加上函数声明:

Declare Function FloodFill Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal crColor As Long) As Long

第一个参数"hDc",是"设备上下文句柄""PictureBox"控件就有"hDc"属性,可直接拿来用;后两个参数xy就是填充的“种子”,即指定从那点开始填充,末尾的crColor是填充的颜色。不过目前还有点小问题没解决,字体小时填得不很好。

至于笔画交叉的填充问题,用先填充后交叉的办法,即各个笔画都按其应有的尺寸在备用的"PictureBox"控件Pic0里画好,填充好,再叠加到控件Pic里,可使用VB语句:

Pic.PaintPicture Pic0.Image, x1, y1, siz, siz, 0, 0, siz, siz, vbSrcAnd

这里,x1, y1是目标Pic中的位置,0, 0是源图Pic0中的起始点,siz, 是汉字大小。最后的参数可取13种,只有 vbSrcAnd 符合要求。

以上的问题,纯属VB编程问题,下面的问题就不单纯是编程问题了。我们知道,笔画在具体的汉字内所占的空间大小是不同的,也就是说要受到不同程度的压缩。而图形受到纵向压缩,横画变薄,受到横向压缩,竖笔要变细。不同字体的笔画,在转折和首尾都有特别的形态,经过压缩也要变形。按汉字的特性,不管一个汉字笔画多少和疏密,横竖的粗细、转角的形态是不变的。通常用的电脑造字功能,使用字根的局部拷贝,如果有缩小放大,造出的汉字与原字库的字形就不协调。这也是采用合成字形的字库,常为笔画单调的明线体,其它字体就很困难。如何使笔画组字时,让任一笔画,虽经大小高宽的变化,而总能保持其特点和粗细不变?

我的办法是:在笔画轮廓线的中轴,设一折线作为骨干,骨干线在笔画首尾和转折处都有一点。这一骨干线,可随笔画在一个汉字中应占的大小而压缩变化,但笔画轮廓线的点相对于各自(最近)的骨干点的距离却保持变,或者按字形大小的比例统一改变。这样就可达到上面的要求。使用时会产生的一些具体问题,后面再分析。

轮廓线的点比骨干线的点要密得多。但不管有多密,字形放大后就要露出棱角。这要一种平滑化的算法,据说有一种专门的语言,叫PostScript,但是就是找不到有关的书和资料。我只得想出如下简单的圆角化处理:在两折线上,距折点一定距离处增加两个新点,连接两个原来的点与两新点的中点,再取这个连线的中点代替原来的点(原点被3个新点代替)。在改用贝塞尔曲线前,一直用这样的方法,效果不好,更糟糕的是,应保留的撇捺的尖角也被圆角化了。

台湾叶健欣君早已告诉我,贝塞尔公式和程序。开始时我没有理解和使用,后来使用了,由于填充问题没解决,显示不出应有的效果,已如前述。TypeScript字形用二次贝塞尔曲线,PostScript字形用三次贝塞尔曲线。三次曲线要在任两点(x0,y0)(x3,y3)之间增加两个控制点(x1,y1)(x2,y2)(二次只要一个控制点),移动这两个控制点,可以形成各种三次曲线,选取合适者。该曲线上的所有点,用下面的公式算得:

Xi = (k-i)^3*X0 + 3i*(k-i)^2*X1 + 3i^2*(k-i)*X2 + i^3*X3/k^3

Yi = (k-i)^3*Y0 + 3i*(k-i)^2*Y1 + 3i^2*(k-i)*Y2 + i^3*Y3/k^3

这里k可取2的任意次方,按要求的精度而定,我现在用k=82n=3次方)。子程序如下(曲线数据在数组d()中,x i =d(0, i)y i = d(1, i)i必须是3的倍数):

For  i = 0  To  ncc0(i) = 1Next i

 For  i = 2  To  n

    For  j = 1  To  i - 1

        bb0(j) = cc0(j - 1) + cc0(j)

    Next  j

    For  j = 1  To  i - 1

        cc0(j) = bb0(j)

    Next  j

 Next  i

pic.PSet (d(0, 1), d(1, 1)), vbBlack              '设开始点

For  g = 1  To  41 - 2 * n  Step  n           '开始画轮廓线

    If d(0, g + 1) = 0 Or d(1, g + 2) = 0 Or d(1, g + 3) = 0 Then Exit For

    For  i = 0  To  n * 2

        te = i / (n * 2)

        pp0(0) = 1: qq0(0) = 1

        For j = 1 To n

            pp0(j) = pp0(j - 1) * te

            qq0(j) = qq0(j - 1) * (1 - te)

        Next j

        For  j = 0  To  n

            bb0(j) = pp0(j) * qq0(n - j)

        Next  j

        xe = 0: ye = 0

        For  j = 0  To  n

            xe = xe + cc0(j) * bb0(j) * d(0, g + j)

            ye = ye + cc0(j) * bb0(j) * d(1, g + j)

        Next  j

        pic.Line -(xe, ye), vbBlack

    Next  i

 Next  g

附图是笔画“乙”的轮廓线。红色的是骨干线,黑色的是用贝塞尔公式算得的实际轮廓,绿色的折线中,没有数字标注的是控制点,轮廓线不通过它们。右边的小表,后两列是各点的xy坐标。前7个是放控制点用的,现在6个有值。后面依次是绿线上的点,它们右边的数字0-6指明它应该相对于那一个骨干点定位置。

后一张附图,是填充后的四个笔画。左上右下的两个,仅字形大小不同,笔画粗细分别为0.10.5。右上是上下压缩、左下是左右压缩,笔画粗细都为0.3。用这个办法试验上面的设计行不行。有时不压缩,字形可以,但一压缩,尤其两个方向压缩程度不相等时,就出问题。不是笔形不对,就是填充不全,因为这时轮廓线变了形。如何才不会出问题?首先,轮廓线的点,包括控制点,尽可能不远离各自的骨干点。否则,骨干点压缩靠近后,而轮廓点离它们的距离不变,就会交叉。可以想见,轮廓点如果在骨干点的垂直线上,左右压缩应没问题;同样,轮廓点如果在骨干点的水平线上,上下压缩也应没问题。如果骨干点是斜笔的中间,轮廓点应在笔画方向的两边。对此还摸不出更可靠的规律,特别是控制点,有时要远离骨干线,只能靠试验决定了。

     20051227