在VGA卡上实现宽行图形平滑水平流动
实际生活中,一幅画往往要比一个屏幕大得多(如图崐1),为了在屏幕上显示这幅画,我们便要将屏幕当作镜崐头,不停地上下左右移动,才能将画流鉴尽,这种显示图崐形的方法就是图形的摇动技术。如果让镜头保持水平,然后再均匀向左或向右移动,就形成了图形的平滑平移动。崐利用Turbo C 2.0提供的标准函数很难实现这种功能的。本文便向你介绍一种基于VGA控制器来实现这种特殊功能,如果将其用于应用程序,将收到非常不错的效果。
一、VGA显示图形原理
在显示图形时,VGA控制寄存器内部,已经设置实际图形的宽度和高度(确切地说,它们是由位移/逻辑屏幕宽度寄存器和最大行比较寄存器的值决定,通常情况下它们和当前屏幕的水平分辨率和垂直分辨率相同),另外还有一个起始显存偏移地址寄存器,它决定着屏幕上第0行图形从哪一个地址开始读取数据。在不致于混淆情况下,我们不防设显存起始偏移地址为start ,每行有w个字节,崐共有h行,则显示第i行图形时,应在start+i*w偏移地址读取数据。 当显示的行数达到h(最大行比较寄存器的值)时,重新显示第0行,再显示第1行、第2行………。
实际中,并不是每一个图形高度都达到了物理屏幕的崐高度(即垂直分辨率),这时屏幕上超过该高度的图形将崐从A000: 0000的显存中取数据,再送到屏幕上,这样屏幕崐被分成了两个互不干扰的部分:上部分从start开始读取崐数据,下部分从0偏移地址读取数据(如图2)。根据这崐一原理,如果我们改变start的值,将使上部分的图形发崐生变化而下部分图形保持静止。如:若start值加1,则图崐形将向右平移8个像素,但这样一次就移动了8个像素,达崐不到我们平滑移动图形的目的。因此,有必要对相关的寄存器作一下说明。

二、VGA控制寄存器
VGA的寄存器可分为外部寄存器、定序寄存器、CRTC寄存器、图形控制寄存器、属性控制寄存器五个组。除了崐外部寄存器占用主机I/O端口地址外,其余均用两个主机I/O端口:一个为索引地址寄存器,另一个为数据寄存器。每一个寄存器具有一个索引值,如果访问某一个寄存器,则先将索引值送入该组中的索引寄存器中,再将数据送入数据寄存器中。
这几组寄存器索引和数据地址如下: 寄存器组 索引寄存器地址 数据寄存器地址 定序寄存器 3c4 3c5 CRTC控制寄存器 3D4 3D5 图形控制寄存器 3CE 3CF 属性控制寄存器 3C0 3C0
a:显存偏移地址寄存器 高8位寄存器索引号为c,低8位寄存器索引号为d,共16位。它决定屏幕第0行的图形将从哪个偏移地址读数据,它们属CRTC寄存器。
b:行比较寄存器 索引号为18,属于CRTC控制寄存器。对于VGA来说,共有10位。此寄存器装入的是低8位,第9位由溢出寄存器(索引号为7)D4位来决定,第 10位由最大扫描行寄存器(索引号为9)的D6位定。这10 位值决定了屏幕上部分的行数,改变其值,可使屏幕上半部分实行滚动或平移。
c:位移/逻辑屏幕宽度寄存器 索引号为13,属于CRTC控制寄存器。该寄存器的D7到D0位定义图形的宽度,即图形行与行间的差别。它确定屏幕的显示宽度并用于计算下一个显示行的起始地址,实际崐写入时,应将宽度除以2。
d:水平像素平移寄存器 索引号为13,属于属性控制寄存器。其中d7-d4位保留,D3-D0 为水平平移像素的个数。此寄存器是决定屏幕水平平滑移动的关键,它允许显示一次水平移动n个(由低崐4位决定),所以若显存起始地址+1,相当于此寄存器值为8。这两个寄存器一起使用, 图形便可平滑任意移动。
E:属性方式控制寄存器 索引号为10,属于属性控制寄存器。若要使屏幕下部分保持静止,则用到该寄存器,因为图形平移时是对整个屏幕进行的。
该寄存器的D5位置为1时,当扫描行达到最大行比较寄存器的值时,不再对下部分的图形进行平移。此外,D0位也应置为1,表示当前工作在图形方式下。
当我们要访问属性方式寄存器时,由于它的索引寄存器和数据寄存器均为3C0,因此对VGA属性控制寄存器编程时,则先读外部寄存器3DA一次,使触发器复位,然后才对3C0进行访问。若将data写入索引号为n的寄存器中,可用下列语句实现:
inp(0x3da);/*读端口3da,使触发性复位*/ outp(0x3c0,0x20|n);/*访问n号寄存器*/ outp(0x3c0,data); /*写入数据*/
属性控制寄存器的地址寄存器(端口为3C0)说明如下:D7-D6保留;D5为1时,表示屏幕被启动,调色板不可修改;D4-D0:寄存器的索引号。
三、程序实现
以下附录的Turbo C 2.0程序,可实现上半部图形平滑平移,而下半部分不变。程序中采用非压缩SPT作为图形文件。直得说明的是:若图形文件太大,则可能出现一些空白或断开象现,这一点是由于VGA显示时仅将a000:000─a000:ffff作为当前显示页数据区域。(本文发表于《电脑编程技巧与维护》1996年第9期)
#include <dos.h>
#include <stdio.h>
#include <mem.h>
#define START 70 /*流动的图形部分在整个图形的起始行数*/
#define LINE 375 /*上部分图形的高度*/
void typespt(FILE *fp);
void SetPanRep(int n);
void SetVGALogicalWidth(unsigned int width);
void SetShowMode(int mode);
void SetLineComp(unsigned int data);
void SetVgaShowOffset(unsigned int offset);
int main(int argn,char *argv[])
{
FILE *fp;
if (argn!=2)
{ printf("\7Formart:%s \n",
argv[0]);
return(1);}
if((fp=fopen(argv[1],"rb"))==NULL)/*SPT件不能打开,退出*/
{ printf("\07\nError:%s can't open!\n",agrv[1]);
exit(1);}
typespt(fp);/*显示SPT文件*/
fclose(fp); /*并闭SPT文件*/
SetShowMode(0x03);/*置为文本模式*/
exit(0);
}
void typespt(FILE *fp)
{ unsigned int w,h,i,j;
char *buf;
fseek(fp,34L,SEEK_SET);
w=getw(fp)/8;/*读图像宽度,并化为字节*/
h=getw(fp); /*取图像的高度,若大于479,则仅显示前479行*/
if (h>479) h=479;
if ((buf=(char *)malloc(w))==NULL)
{printf("\7"); return;}
fseek(fp,64L,SEEK_SET);
SetShowMode(0x12); /*设置显示模式为640*480*16*/
SetVGALogicalWidth(w); /*设置屏幕的逻辑宽度*/
SetLineComp(LINE);
SetVgaShowOffset(START*w);
outp(0x3c4,2);/*允许4个位面都写入图像数据*/
outp(0x3c5,0x0f);
for(i=0;i=w+START*w) j=START*w;
/*若图像已到宽度,则从指定的显存开始*/
}
SetPanRep(i%8+1); /*水平移动1-7个像素*/
if (bioskey(1))
return; /*若有键按下,则返回*/
delay(20);
}
}
void SetShowMode(int mode) /*设置显示模式*/
{ union REGS r;
r.h.al=mode;
r.h.ah=0;
int86(0x10,&r,&r);
}
void SetPanRep(int n) /*设置水平移动像*/
{ inp(0x3da);/*读状态寄存器,使触发器复位*/
outp(0x3c0,0x20|0x13);/*设置写0x13号索引器*/
outp(0x3c0,n); /* 移动N个水平像素*/
}
void SetVGALogicalWidth(unsigned int width)
{ /*设置屏幕的逻辑宽度*/
outp(0x3d4,0x13); /*索引号13为水平像素平移寄存器*/
outp(0x3d5,width/2);
}
void SetVgaShowOffset(unsigned int offset)
/*设置VGA显示存的偏移地址*/
{ outp(0x3d4,0xc); /*设置显存的高8位*/
outp(0x3d5,(offset>>8)&0x00ff);
outp(0x3d4,0xd);
outp(0x3d5,offset&0x00ff);/*设置显存的低8位*/
}
void SetLineComp(unsigned int data)
{ /*设置VGA最大行比较寄存器的值*/
unsigned char Hic,LCReg;
outp(0x3d4,0x18); /*将data低8位写入18号行
比较寄存器*/
outp(0x3d5,(unsigned char)(data%256));
Hic=(unsigned char ) (data/256); /*高8位送于Hic保留*/
outp(0x3d4,0x07); /*读7号溢出寄存器的值*/
LCReg=inp(0x3d5);
if (Hic&1) LCReg|=0x10;
/*若Hic的b0位为1 ,则将溢出寄存器的b4置1*/
else LCReg&=0xef;
outp(0x3d4,0x07);
outp(0x3d5,LCReg);
outp(0x3d4,0x09); /*读9号最大扫描行寄存器*/
LCReg=inp(0x3d5);
if (Hic&2) LCReg|=0x40;
/* 若Hic的b1位为1 ,则将9号寄存器的D6位置1*/
else LCReg&=0xbf;
outp(0x3d4,0x09);
outp(0x3d5,LCReg);
}
本文Word版下载地址为:http://www.i0713.net/Download/Prog/Dragon/Doc/dHZ.doc