美文网首页
SVGA 图形编程教程

SVGA 图形编程教程

作者: invisprints | 来源:发表于2017-10-08 19:10 被阅读0次

    作为华中科技大学自动化学院的学生,相信许多人在大二的时候会对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;
    }
    

    相关文章

      网友评论

          本文标题:SVGA 图形编程教程

          本文链接:https://www.haomeiwen.com/subject/slryyxtx.html