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>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 图片,因为可以有往届的代码参考,如果你认为你很牛逼想在课设验收老师面前炫技的话,尽情用你喜欢的图片格式。

SVGA 图形编程教程_第1张图片

关于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;
}

你可能感兴趣的:(SVGA 图形编程教程)