DOS下高分辨率直接写屏的实现

刘国安,galiu@21cn.com

对大多数计算机用户来说,DOS时代已经是很遥远的事情了。有了Windows华丽、易用的界面,DOS还有什么用呢?可是,在许多工业控制或是嵌入式系统中,DOS以它的短小精悍、实时性强等优点,仍然得到广泛的应用。可是它毕竟是很久以前的系统,时至今日,在诸如图形显示、网络连接等方面,DOS让人感到种种不便。本文将介绍在DOS下利用VESA实现高分辨率直接写屏,及汉字显示。这些技术已在实际嵌入式系统中得到应用。本文例程在DOS 6.2下采用Borland C++ 3.1编译。

1. VESA简介计算机显示卡的发展经历了很多标准,包括CGA/EGA, VGA, SVGA, 以及VESA。目前最新的是VESA/VBE 3.0,现在市面上可见的主流显示卡都支持VESA 2.0标准。所以要保证软件的通用性,采用VESA标准是一个好主意。 VESA的意思是Video Electronics Standard Association,网址为www.vesa.org,该协会负责维护VESA相关标准。这些标准都是收费的,不过在网上可以找到免费的版本。VESA的编程是在标准中断0x10中加入功能号为0x40xx的中断来进行的。

2. VESA编程初步

a) 设置显示模式:在标准的int 0x10调用中,如果设置ax=3,即为标准的文本模式;如果设置ax=0x0013则为320*200*256色,95年左右编程的人对这个模式应该是相当的熟悉。为了利用VESA显示模式,需要令ax=0x4f02,bx写入显示模式。其中0x114代表800*600*64K,117h代表1024x*768*64K。下面代码段设置显示为800*600*64k: in.x.bx=0x114; in.x.ax=0x4F02; int86(0x10,&in,&out); 进入图形显示模式后,就可以做一个指针,直接指向向显存: pVideoMem = (unsigned int *)MK_FP(0xA000, 0);

b) 显存分页:VESA的显示是分页的,一般64kbyte作为一页。800*600*64k要用到15页。VESA中控制换页的功能号是0x4f05。以下例子演示换页的过程。 in.x.bx=0; in.x.dx=iPageNumber; in.x.ax=0x4F05; int86(0x10,&in,&out);

c) 颜色定义:用16bit定义RGB颜色的代码如下: #define RGB(r,g,b) (((r)/8)*2048+((g)/4)*32+(b)/8)

d) 画点:对显存中的数据进行改变就可以直接画点。注意要计算要画的点在哪一个页面。一个小技巧是,为了避免频繁换页,把当前页面记录下来,只有当点在不同页面时才进行换页。

void SetPoint(int x, int y, unsigned int iColor)
{
    unsigned long iDist, iTemp; unsigned int iPos;
    int iPN;
    iDist = 800l*y+x;
    iTemp = iDist; iPN = 0;
    while(iTemp>32768)
    {
        iTemp = iTemp - 32768;
        iPN++;
    }
    iPos = iTemp;
    if(iPN != iOldPage)
        VideoPage(iPN); *(pVideoMem+iPos) = iColor;
}

e) 进一步绘图:有了画点程序后,就可以很容易地扩展成画线、画圆的程序。有许多快速算法,考虑到目前计算机的运算速度都相当的快,我就随便找了个算法。程序代码参见附录1。

3. 英文与汉字的显示采用这种直接写屏的方式,可以做出很漂亮的图形,速度也相当的快;可是缺点是没办法使用任何原有的图形函数。上面我们已经实现了画点、画线和画圆等基本图形函数,可是一个程序不可能没有英文的显示;另外我们也常常需要显示中文。借助于各种字库,可以方便地实现英文及中文的显示。本文中所用的是UCDOS 5.0中的字库,记得在DOS时代,这个软件还有2.13是汉字显示中的翘楚。

a) 英文的显示:字库中英文是按ASC码的顺序排列,每个英文字母占用16*8bit,即16byte。把相应的数据读出,并每位判断以后就可以把该字母“画”出来:

    fseek(fpE, 16l*c, SEEK_SET);
    fread(pFontE, sizeof(char), 16, fpE);
    for(iy=0; iy<16; iy++)
    {
        for(ix=0; ix<8; ix++)
        {
            if(*(pFontE+iy)&(1<<ix))
            SetPoint(x+8-ix, iy+y, iColor);
        }
    }

b) 中文的显示:中文的显示比较复杂,我们知道,1个中文是用2个字节表示,这两个字节的内容我们称为汉字的内码。要先将内码转换成区位码,然后算出该汉字在字库中的位置,最后把汉字的点阵信息读出来进行显示。相应的代码片段如下(注意一定要随程序附带两个字库文件):

    qu=c1-161;
    wei=c2-161;
    fseek(fpC, 32l*(94*qu+wei), SEEK_SET);
    fread(pFontC, 32, 1, fpC);
    for(iy=0; iy<16; iy++)
    {
        for(ix=0; ix<8; ix++)
        {
            if(*(pFontC+iy*2)&(1<<ix))
                SetPoint(x+8-ix, iy+y, iColor);
            if(*(pFontC+iy*2+1)&(1<<ix))
                SetPoint(x+16-ix, iy+y, iColor);
        }
    }

 c) 跟据以上两个函数,可以方便地写出通用的写字符串的程序。具体请参见附录1中的相关部分。

4. 值得做的改进

a) 目前版本对显存的操作采用分页模式,对效率有一定的影响。可以采用Flat Mode,如能利用保护模式编程,对显存进行线性寻址,估计可以提高显示速度2-3倍。

b) 目前版本是将字库全部放在硬盘上,每次写一个字就要对硬盘进行一次寻址。这样效率比较低。可以考虑利用现在的大容内存,把字库放入内存中,以增加速度。另一种替代方法是,如果程序中用到的文字不多,可以把程序中用到的汉字的点阵数据取出来,另外生成一个小字库,这个小字库就可以方便地放到常规内存中。

c) 本文例程采用800*600*64K,稍作改动即可改成1024*768*64K,可以考虑做成通用的显示接口。

d) VESA还有许多特效,比如部分滚屏等,如果需要可以自行实现。

e) GetImage和PutImage的实现。这是很有用途的两个函数,在例程中已经实现。

5. 参考资料

a) VESA BIOS EXTENSION (VBE) Core Functions Standard version: 3.0

b) Interrupt List (c) by Ralf Brown

c) 一本十年前买的老书,讲CGA/EGA编程的,本程序中画线、画圆的代码摘自该书。