作为华中科技大学自动化学院的学生,相信许多人在大二的时候会对C课设留下深刻的印象,而在 C课设中,SVGA 是大多数学生的第一道门槛,本文仅从 C 课设的需求角度对 SVGA 作简要讲解。
更新时间:2017-10-8。
预备知识
王士元的那本什么书是肯定要刷完的,刷完差不多就可以看这篇文章了。
SVGA初始化
类似 VGA 的initgraph()
,不过初始化 SVGA 需要手动初始化,大家一般看到的代码如下
unsigned char SetSVGAMode(unsigned int vmode)
{
union REGS in,out;
in.x.ax=0x4f02; //进入设置SVGA模式
in.x.bx=vmode; //SVGA 模式参数
int86(0x10,&in,&out); //输入到中断函数中
if(out.h.ah==0x01) //功能调用失败
{
ReturnMode(); //退出当前模式
printf("Set SVGA error!"); //中断
getch();
exit(1);
}
return(out.h.ah);
}
vmode 模式参数可取以下模式号,不同模式号调用的窗口大小不同,这里只显示常用的几个
15位模式号 | 分辨率 | 颜色数 |
---|---|---|
111H | 640*480 | 64K |
114H | 800*600 | 64K |
建议窗口不要太大,不然刷屏时会有明显的延迟。 ReturnMode()
为调用失败后返回原模式的函数,它需要自己手写
int ReturnMode()
{
union REGS in;
in.h.ah=0;
in.h.al=(char)0x03; //返回文本模式
int86(0x10,&in,&in); /*中断*/
return 0;
}
画点函数
作为 SVGA 下画图的最基本功能,必须要搞懂每一行代码的意思
void Putpixel(int x, int y, long colorTable)
{
long pos;
short far *VideoBuffer=(short far *)0xA0000000L;
register int NewPage = 0;
long color=CalColor(colorTable);
/*计算该点的在VRAM的位置*/
/*这里假设设置的VRAM逻辑宽度为1600*/
/*什么是逻辑宽度后面会讲到*/
pos = y *1600l+ x;
NewPage = pos >> 15; //计算显示页面号
SelectPage(NewPage); //选择页
VideoBuffer[pos%65536] = color;
}
大多数 SVGA 卡采用类似于扩页内存 LIM/EMS 规范所使用的方法,把 VRAM 分成小块(称为页),分别映射到电脑提供的地址空间(称为窗口)上,并用单个可读写窗口显示图像。此窗口可同时读写,一般情况下窗口的位置在以 0xA0000000L 为起点的地址上,尺寸为 64KB。超过64k 的 VRAM 的地址空间用页映射机制分块映射到电脑提供的地址上。 SVGA 的颜色值比较奇葩,在256色情况下跟颜色的索引号不同,我们需要计算 在 64K 高彩色模式下,颜色值共占一个字。B占0~4位,G占5~10位,R占11~15位。将各颜色分量移位组合后便可得到其颜色值:
long CalColor(long colorTable)
{
unsigned long r,g,b;
r=colorTable>>16;
g=(colorTable-(r<<16))>>8;
b=colorTable-(r<<16)-(g<<8);
r>>=3;
g>>=2;
b>>=3;
return (r<<11)+(g<<5)+b;
}
确定了要显示的点的位置后需要将页面切换的相应位置
unsigned int SelectPage(unsigned char index)
{
union REGS in,out;
in.x.ax=0x4f05;
in.x.bx=0;
in.x.dx=index;
int86(0x10,&in,&out); //中断
return 0;
}
05H 为调用页面控制功能 index 为当前窗口的页面号 VRAM 只能装65536那么大,所以要求下余数,然后再在该位置赋值为相应颜色值
设置逻辑扫描线长度
int SetScreenWidth(unsigned long pixels)
{
static union REGS r;
r.h.bl=0;
r.x.ax=0x4f06;
r.x.cx=pixels;
int86(0x10,&r,&r);/*中断*/
return 0;
}
pixels在本文中设置为1600 逻辑扫描线就是你显示图像时逻辑上一行显示像素的多少,这跟实际屏幕上一行显示像素多少不同,它可以更大,但不能无限大。这个一般跟你屏幕宽度保持相同即可。在涉及到画面整体移动时这个需要设置的更大,以便容纳更多的图像。作用如下图所示
灰色部分为逻辑上的显示屏,红色部分为实际上的显示屏。比如在地图过大的情况下,用较大的逻辑屏存储图像,然后移动实际上的显示屏就可以实现生活中查看地图的效果。
画直线
/*************************
* 函数名称:Line
* 函数功能:画直线
* 参数:
* long x1, long y1:直线左端点
* long x2, long y2:直线右端点
* int width:直线宽度
* long colorTable:256色颜色表
* 返回值:
* 0:成功
************************/
int Line( double x1, double y1, double x2, double y2, int width, long colorTable )
{
int pointX;
int pointY;
int k,i,NewPage,OldPage;
short far *VideoBuffer=(short far *)0xA0000000L;
double r,tcos,tsin,DX,DY;
long pos;
long color=CalColor(colorTable);
for(i=0;i>15;
SelectPage(NewPage);
DX = x1 - x2;
DY = y1 - y2;
r = sqrt(DX * DX + DY * DY);
tcos = DX / r;
tsin = DY / r;
for ( k = 0; k <= r; k ++)
{
pointX = (int)(x2 + tcos * k);
pointY = (int)(y2 + tsin * k);
pos =(long)( pointY * 1600l+ pointX);
NewPage =pos >>15;
if (NewPage != OldPage)
{
SelectPage(NewPage);
OldPage= NewPage;
}
VideoBuffer[pos] = color;//设置颜色,可根据需要更改
}
if(tsin==0)
{
y1++;
y2++;
}
else
{
x1++;
x2++;
}
}
return 0;
}
由于 SelectPage() 函数十分耗时间,因此我们要尽可能少用它,不能画一个点就调用它一下。我们建立两个变量 NewPage 和 OldPage 前者保存当前要画的点所在的 Page,后者保存之前的点所在的 Page,如果两者一样,则不需要切换页,直接画点即可,直到两者不一样再切换画面。 其他的就比较简单,主要是对直线上点的定位问题,稍稍想一下就清楚了。
显示图片
/*************************
* 函数名称:ShowPic
* 函数功能:读取bmp文件到显存
* 参数:
* int x, int y:图片左上角坐标
* char * FileName:文件地址
* 返回值:
* 0:成功
* -1:失败
************************/
int ShowPic(int x,int y,char *FileName)
{
int i,j,k=0;
FILE *fp;
char OldPage=0,NewPage=0;
unsigned int DataOffset;
long Width,Height,BitCount;
unsigned long pos;
short *buffer;
short far *VedioBuffer=(short far *)0xA0000000L;//显存初始地址指针
BITMAPINFOHEADER BmpInfoHeader;
BITMAPFILEHEADER BmpFileHeader;
if((fp=fopen(FileName,"rb"))==NULL)
{
ReturnMode();
printf("Cannot read the picture\n\t\t%s",FileName);
getch();
return -1;
}
/* 读取文件头和信息头 */
fread(&BmpFileHeader, sizeof(BmpFileHeader),1,fp);
fread(&BmpInfoHeader, sizeof(BmpInfoHeader),1,fp);
Width = BmpInfoHeader.Width;
Height = BmpInfoHeader.Height;
DataOffset = BmpFileHeader.OffBits;
BitCount = BmpInfoHeader.BitCount / 8;
/*RAM start*/
buffer=(short *)malloc(Width*sizeof(short));//申请内存
if(buffer==NULL)
{
ReturnMode();
printf("SVAGA.c_Malloc error! in function ShowPic!");
getch();
return -1;
}
/* 图像的宽度(以字节为单位)必须是4的倍数,倘若不到4的倍数则必须要用0补足。k来满足字节对齐 */
k=(Width*BitCount%4)?(4-Width*BitCount%4):0;
OldPage=((Height-1+y)*1600l+x)>>15;//一定要转成long
NewPage=OldPage;
SelectPage(OldPage);
fseek(fp,DataOffset,SEEK_SET);
for(i=Height-1;i>=0;i--)
{
fread(buffer,Width*BitCount+k,1,fp); //读取一行像素点的信息
for(j=0;j>15;//计算第几页
if(NewPage!=OldPage)
{
SelectPage(NewPage);
OldPage=NewPage;
}
VedioBuffer[pos&0x00007fff]=buffer[j];//写内存
}
}
/* 关闭文件 */
fclose(fp);
free(buffer);
return 0;
}
按照往年惯例,显示图片一般用 256 色的 bmp 图片,因为可以有往届的代码参考,如果你认为你很牛逼想在课设验收老师面前炫技的话,尽情用你喜欢的图片格式。
关于bmp怎么存放图像的,建议大家上网找找答案,这里不再赘述。这里提供我当年阅读的文章bmp文件(此网页已失效,大家自己去找吧)
剩下的注释已经写的很详细了,有不懂可以在下面留言。
其他有用函数
C课设已经验收完了,这里给出一些可能有用的源代码,如果你认真看了上文,下面的代码你应该看得懂。
/*************************
* 函数名称:GetBackground
* 函数功能:动画背景抠图
* 参数:
* int left,right:左右边界
* int top,bottom:上下边界
* short * buffer:图片存储地址
* 返回值:
* 0:成功
************************/
int GetBackground(int left,int top,int right,int bottom,short *buffer)
{
int i,j;
unsigned long pos,Width,Height;
char OldPage,NewPage;
short far *VideoBuffer=(short far *)0xA0000000L;
Width=right-left;
Height=bottom-top;
OldPage=(long)(top*1600l+left)/32768;
NewPage=OldPage;
SelectPage(NewPage);
for(i=0;i0 ? CalColor(colorTable) : colorTable;
Width=right-left;
Height=bottom-top;
OldPage=(top*1600l+left)/32768;
NewPage=OldPage;
SelectPage(NewPage);
for(i=0;i0)
{
i--;
str[i]='\0';
PutBackground(x+i*8,y,x+i*8+8,y+16,buffer[i],-1);
}
else
{
i=0;
str[i]='\0';
}
continue;
}
if(i<=10&&ch!=-1) //max=10
{
str[i]=(char)(ch&0xff);
GetBackground(x+i*8,y,x+i*8+8,y+16,buffer[i]);
printASC(x+i*8,y,str+i,SVGA_RED,1,1);
i++;
str[i]='\0';
}
}
return 0;
}