作為華中科技大學(xué)自動(dòng)化學(xué)院的學(xué)生,相信許多人在大二的時(shí)候會(huì)對(duì)C課設(shè)留下深刻的印象,而在 C課設(shè)中,SVGA 是大多數(shù)學(xué)生的第一道門(mén)檻,本文僅從 C 課設(shè)的需求角度對(duì) SVGA 作簡(jiǎn)要講解。
更新時(shí)間:2017-10-8。
預(yù)備知識(shí)
王士元的那本什么書(shū)是肯定要刷完的,刷完差不多就可以看這篇文章了。
SVGA初始化
類(lèi)似 VGA 的initgraph(),不過(guò)初始化 SVGA 需要手動(dòng)初始化,大家一般看到的代碼如下
unsigned char SetSVGAMode(unsigned int vmode)
{
union REGS in,out;
in.x.ax=0x4f02; //進(jìn)入設(shè)置SVGA模式
in.x.bx=vmode; //SVGA 模式參數(shù)
int86(0x10,&in,&out); //輸入到中斷函數(shù)中
if(out.h.ah==0x01) //功能調(diào)用失敗
{
ReturnMode(); //退出當(dāng)前模式
printf("Set SVGA error!"); //中斷
getch();
exit(1);
}
return(out.h.ah);
}
vmode 模式參數(shù)可取以下模式號(hào),不同模式號(hào)調(diào)用的窗口大小不同,這里只顯示常用的幾個(gè)
| 15位模式號(hào) | 分辨率 | 顏色數(shù) |
|---|---|---|
| 111H | 640*480 | 64K |
| 114H | 800*600 | 64K |
建議窗口不要太大,不然刷屏?xí)r會(huì)有明顯的延遲。 ReturnMode()為調(diào)用失敗后返回原模式的函數(shù),它需要自己手寫(xiě)
int ReturnMode()
{
union REGS in;
in.h.ah=0;
in.h.al=(char)0x03; //返回文本模式
int86(0x10,&in,&in); /*中斷*/
return 0;
}
畫(huà)點(diǎn)函數(shù)
作為 SVGA 下畫(huà)圖的最基本功能,必須要搞懂每一行代碼的意思
void Putpixel(int x, int y, long colorTable)
{
long pos;
short far *VideoBuffer=(short far *)0xA0000000L;
register int NewPage = 0;
long color=CalColor(colorTable);
/*計(jì)算該點(diǎn)的在VRAM的位置*/
/*這里假設(shè)設(shè)置的VRAM邏輯寬度為1600*/
/*什么是邏輯寬度后面會(huì)講到*/
pos = y *1600l+ x;
NewPage = pos >> 15; //計(jì)算顯示頁(yè)面號(hào)
SelectPage(NewPage); //選擇頁(yè)
VideoBuffer[pos%65536] = color;
}
大多數(shù) SVGA 卡采用類(lèi)似于擴(kuò)頁(yè)內(nèi)存 LIM/EMS 規(guī)范所使用的方法,把 VRAM 分成小塊(稱(chēng)為頁(yè)),分別映射到電腦提供的地址空間(稱(chēng)為窗口)上,并用單個(gè)可讀寫(xiě)窗口顯示圖像。此窗口可同時(shí)讀寫(xiě),一般情況下窗口的位置在以 0xA0000000L 為起點(diǎn)的地址上,尺寸為 64KB。超過(guò)64k 的 VRAM 的地址空間用頁(yè)映射機(jī)制分塊映射到電腦提供的地址上。 SVGA 的顏色值比較奇葩,在256色情況下跟顏色的索引號(hào)不同,我們需要計(jì)算 在 64K 高彩色模式下,顏色值共占一個(gè)字。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;
}
確定了要顯示的點(diǎn)的位置后需要將頁(yè)面切換的相應(yīng)位置
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 為調(diào)用頁(yè)面控制功能 index 為當(dāng)前窗口的頁(yè)面號(hào) VRAM 只能裝65536那么大,所以要求下余數(shù),然后再在該位置賦值為相應(yīng)顏色值
設(shè)置邏輯掃描線長(zhǎng)度
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在本文中設(shè)置為1600 邏輯掃描線就是你顯示圖像時(shí)邏輯上一行顯示像素的多少,這跟實(shí)際屏幕上一行顯示像素多少不同,它可以更大,但不能無(wú)限大。這個(gè)一般跟你屏幕寬度保持相同即可。在涉及到畫(huà)面整體移動(dòng)時(shí)這個(gè)需要設(shè)置的更大,以便容納更多的圖像。作用如下圖所示
灰色部分為邏輯上的顯示屏,紅色部分為實(shí)際上的顯示屏。比如在地圖過(guò)大的情況下,用較大的邏輯屏存儲(chǔ)圖像,然后移動(dòng)實(shí)際上的顯示屏就可以實(shí)現(xiàn)生活中查看地圖的效果。
畫(huà)直線
/*************************
* 函數(shù)名稱(chēng):Line
* 函數(shù)功能:畫(huà)直線
* 參數(shù):
* long x1, long y1:直線左端點(diǎn)
* long x2, long y2:直線右端點(diǎn)
* 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<width;i++)
{
NewPage = OldPage=0;
pos=(y2*1600l+x2);
NewPage=OldPage=pos>>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;//設(shè)置顏色,可根據(jù)需要更改
}
if(tsin==0)
{
y1++;
y2++;
}
else
{
x1++;
x2++;
}
}
return 0;
}
由于 SelectPage() 函數(shù)十分耗時(shí)間,因此我們要盡可能少用它,不能畫(huà)一個(gè)點(diǎn)就調(diào)用它一下。我們建立兩個(gè)變量 NewPage 和 OldPage 前者保存當(dāng)前要畫(huà)的點(diǎn)所在的 Page,后者保存之前的點(diǎn)所在的 Page,如果兩者一樣,則不需要切換頁(yè),直接畫(huà)點(diǎn)即可,直到兩者不一樣再切換畫(huà)面。 其他的就比較簡(jiǎn)單,主要是對(duì)直線上點(diǎn)的定位問(wèn)題,稍稍想一下就清楚了。
顯示圖片
/*************************
* 函數(shù)名稱(chēng):ShowPic
* 函數(shù)功能:讀取bmp文件到顯存
* 參數(shù):
* int x, int y:圖片左上角坐標(biāo)
* 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));//申請(qǐng)內(nèi)存
if(buffer==NULL)
{
ReturnMode();
printf("SVAGA.c_Malloc error! in function ShowPic!");
getch();
return -1;
}
/* 圖像的寬度(以字節(jié)為單位)必須是4的倍數(shù),倘若不到4的倍數(shù)則必須要用0補(bǔ)足。k來(lái)滿(mǎn)足字節(jié)對(duì)齊 */
k=(Width*BitCount%4)?(4-Width*BitCount%4):0;
OldPage=((Height-1+y)*1600l+x)>>15;//一定要轉(zhuǎn)成long
NewPage=OldPage;
SelectPage(OldPage);
fseek(fp,DataOffset,SEEK_SET);
for(i=Height-1;i>=0;i--)
{
fread(buffer,Width*BitCount+k,1,fp); //讀取一行像素點(diǎn)的信息
for(j=0;j<Width;j++) //把讀取的一行像素點(diǎn)顯示出來(lái)
{
pos=((i+y)*1600l+j+x);//位置偏移量
NewPage=pos>>15;//計(jì)算第幾頁(yè)
if(NewPage!=OldPage)
{
SelectPage(NewPage);
OldPage=NewPage;
}
VedioBuffer[pos&0x00007fff]=buffer[j];//寫(xiě)內(nèi)存
}
}
/* 關(guān)閉文件 */
fclose(fp);
free(buffer);
return 0;
}
按照往年慣例,顯示圖片一般用 256 色的 bmp 圖片,因?yàn)榭梢杂型鶎玫拇a參考,如果你認(rèn)為你很牛逼想在課設(shè)驗(yàn)收老師面前炫技的話,盡情用你喜歡的圖片格式。
關(guān)于bmp怎么存放圖像的,建議大家上網(wǎng)找找答案,這里不再贅述。這里提供我當(dāng)年閱讀的文章bmp文件(此網(wǎng)頁(yè)已失效,大家自己去找吧)
剩下的注釋已經(jīng)寫(xiě)的很詳細(xì)了,有不懂可以在下面留言。
其他有用函數(shù)
C課設(shè)已經(jīng)驗(yàn)收完了,這里給出一些可能有用的源代碼,如果你認(rèn)真看了上文,下面的代碼你應(yīng)該看得懂。
/*************************
* 函數(shù)名稱(chēng):GetBackground
* 函數(shù)功能:動(dòng)畫(huà)背景摳圖
* 參數(shù):
* int left,right:左右邊界
* int top,bottom:上下邊界
* short * buffer:圖片存儲(chǔ)地址
* 返回值:
* 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;i<Height;i++)
{
for(j=0;j<Width;j++)
{
pos=(long)((i+top)*1600l+j+left);
NewPage=pos/32768;
if(OldPage!=NewPage)
{
SelectPage(NewPage);
OldPage=NewPage;
}
buffer[i*Width+j]=VideoBuffer[pos%32768];
}
}
return 0;
}
/*************************
* 函數(shù)名稱(chēng):PutBackground
* 函數(shù)功能:背景重新顯示
* 參數(shù):
* int left, right:左右邊界
* int top, bottom:上下邊界
* short * buffer:圖片存儲(chǔ)地址
* long colorTable:不顯示的顏色
* 返回值:
* 0:成功
************************/
int PutBackground(int left,int top,int right,int bottom,short *buffer, long colorTable)
{
int i,j;
unsigned long pos,Width,Height;
char OldPage,NewPage;
short far *VideoBuffer=(short far *)0xA0000000L;
long color=colorTable>0 ? CalColor(colorTable) : colorTable;
Width=right-left;
Height=bottom-top;
OldPage=(top*1600l+left)/32768;
NewPage=OldPage;
SelectPage(NewPage);
for(i=0;i<Height;i++)
{
for(j=0;j<Width;j++)
{
pos=((i+top)*1600l+j+left);
NewPage=pos/32768;
if(OldPage!=NewPage)
{
SelectPage(NewPage);
OldPage=NewPage;
}
//if (color!=buffer[i*Width+j])
VideoBuffer[pos%32768]=buffer[i*Width+j];
}
}
return 0;
}
/*************************
* 函數(shù)名稱(chēng):InputText
* 函數(shù)功能:檢測(cè)輸入字符并顯示在屏幕上
* 參數(shù):
* int x, y:起始位置
* char * str:返回輸出字符串
* 0:成功
* 作者:lxr
************************/
int InputText(int x,int y,char *str)
{
int i=0,j,ch;
int buffer[MAX][100];
while(1)
{
ch=-1;
if(bioskey(1)!=0)
{
ch=bioskey(0);
}
else
{
GetBackground(x+i*8,y,x+i*8+8,y+16,buffer[i]);
VerticalLine(x+i*8,y,x+i*8,y+15,2,SVGA_WHITE);
delay(500);
PutBackground(x+i*8,y,x+i*8+8,y+16,buffer[i],-1);
delay(500);
}
if(ch==0x1c0d) //回車(chē)鍵
{
break;
}
if(ch==0x0e08) //退格鍵
{
if(i>0)
{
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;
}