SVGA 图形编程教程

作为华中科技大学自动化学院的学生,相信许多人在大二的时候会对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<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;//设置颜色,可根据需要更改
        }
        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<Width;j++)     //把读取的一行像素点显示出来         
        {             
            pos=((i+y)*1600l+j+x);//位置偏移量              
            NewPage=pos>>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;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; 
} 
/*************************  
 * 函数名称:PutBackground  
 * 函数功能:背景重新显示 
 * 参数:  
 *      int left, right:左右边界  
 *      int top, bottom:上下边界  
 *      short * buffer:图片存储地址  
 *      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; 
}
/************************* 
 * 函数名称:InputText 
 * 函数功能:检测输入字符并显示在屏幕上 
 * 参数:
 *      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)   //回车键   
        {           
            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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容