忙里偷闲,来一篇基于DJGPP的13H图形模式切换及基本绘图实现的方法。首先介绍一下13H模式,它是VGA的标准模式之一,分辨率为320X200,色深为8位即256色。以前DOS下
许多经典游戏使用了这个图形模式,像轩辕剑,炎龙骑士团,仙剑一(不过仙剑好像用的是ModeX模式320X240X256,记不得了,呵呵:)等等。虽然现在看来13H模式已经远不够用
了,但对于初学者来说,这个模式是较容易下手进行研究的[注:此处的初学者不是指打算学GDI或DX进行图形编程的,而是对VGA/SVGA标准和底层驱动实现感兴趣的朋友,对于前
者,此文可能有些不合口味]
13H模式色深为8位,就是说一个像素占用一个字节,这样一来,显存占用总大小为320 X 200 = 64000字节,差不多是64K,而DOS近指针的寻址限制正好是64K,因此这个模式
的操作相对简单。网上流传的资料大都是以Turbo C编译器为编码平台实现对13H的切换和基本绘图操作的,而Turbo C只能产生16位的实模式代码,也许你对实模式或保护模式不甚
清楚,简单的讲因为Intel处理器的历史问题,CPU会有两种工作模式,分别是实模式和保护模式(事实上还有一种所谓的“虚拟8086模式”),因为这不是一篇专门介绍实模式与
保护模式的文章,所以你只暂时需要知道它们之间的一个区别:就是实模式只有1MB的寻址能力,也就是说不管你机器上安装了多少内存,它只能使用1MB;而保护模式下寻址可达
到4GB(4GB = 4096MB)。而进行图形编程,消耗最大的,往往就是内存——预载入、双缓冲、动画、精灵等等等等,都需要使用到大量的内存,1MB内存的概念大概就是比一幅
1024X768稍大一点的8位色深位图,一张图就可以将它几乎耗尽![注:在DOS下这1MB内存还不都是你能用的,BIOS数据区、DOS自己要用的区,显存区、保留区,留给用户的只有
640KB而已,这下连刚才所说的那幅位图都放不下了]
再来说DJGPP,它的编译内核是移植到DOS的GCC,开源、免费、对语法标准的支持更好,而且它内置DPMI服务程序,直接生成工作在保护模式下的执行程序,这样一来,你完全
不用担心你的图形程序是否有足够的内存可用了,128M够不?那256MB呢:) 显然,DJGPP更适合用来编写需要大量内存的程序,像图形程序。恩?你说不知道从哪里得到DJGPP,
可以访问我的主页http://www.ds0101.com/里面的资源共享区中的DJGPP全自动安装版,推荐下载带RHIDE的那个点,安装好之后记得重启一下。
第一步你得切换到13H模式,这一步可以通过调用BIOS中断服务实现:
#include <dpmi.h> /* __dpmi_regs, __dpmi_int() */
#include <go32.h> /* _dos_ds */
#include <conio.h> /* getch() */
void set_13h_mode()
{
__dpmi_regs r; /* 类似TC下的 REGS r; */
r.x.ax = 0x13; /* 这句和TC下的用法没区别*/
__dpmi_int(0x10, &r); /* 这句的作用相当于TC的int86()函数,但少一个参数 */
}
int main()
{
set_13h_mode(); /* 调用上面的函数进入13H模式 */
getch(); /* 等待按键,以便让用户看到效果 */
return 0;
}
好了,上面这段代码没超过20行,它所做的事很简单,就是切换到13H模式下,并暂停等待用户按任意键。拷贝下来保存为一个.c文件再从DJGPP的RHIDE中打开编译运行吧(这和TC
或BC的IDE很相似),你没有RHIDE?那就只好委屈你手动在命令行下编译连接了,如果你是在Windows下,点击“开始”->“运行”,在运行对话框的文本栏里写上cmd,这样可以
执行一个虚拟的DOS环境,现在假设你的DJGPP安装在D:盘,敲入
d:
cd djgpp\bin
gcc name.c -oname.exe
最后一行的gcc是djgpp的编译内核,name.c是你的源文件名,这里假设是name.c,你得修改成你手头上的c文件名,同样,参数-o之后的name.exe也由你决定。如果什么最后一行回
车后什么都没显示,那么你编译成功了,否则GCC会把错误打印出来,并给出行号,方便你回头查错。好了,直接敲入生成的执行文件名(如name.exe)回车就可以运行编译好的程
序了,当然你也可以直接从Windows资源管理器去D盘找到这个文件,鼠标双击执行。你会看到一个黑屏,是的,因为我们切换进去之后什么都没做,随便敲个键让程序结束退出吧
,也许你会发现敲键之后花屏了,程序似乎没正常退出来,问题不大,这是由于我们没有在程序执行完成后将显示模式切换到文本模式的原因,在windows下的话你可以直接按
Ctrl+Enter键切换成窗口,如果是在DOS下的话,可能需要重启~~ 呵呵,不好意思,谁让你太急着看到效果呢?接着往下说,加上下面这个函数,就可以在你的程序结束时切换回
文本模式了:
void set_text_mode()
{
__dpmi_regs r;
r.x.ax = 3; /* 切换到3号模式下 */
__dpmi_int(0x10, &r);
}
别忘了在main()函数的getch()后面调用它!现在程序结束后可以直接回到文本模式了。不过屏幕上黑黑的一片的确很无聊,让我们继续加入代码,完善这个小程序吧。
接下来是画点函数:
void draw_pixel(int x, int y, int color)
{
_farpokeb(_dos_ds, 0xA0000 + y * 320 + x, color);
}
很简单,是吧?我们直接用_farpokeb函数往内在地址0xA0000 + y * 320 + x处写入了一个参数color即颜色值,x和y是像素的坐标,这个函数会在(x, y)处画一个以color为颜色
的点,我们的目的达到了,你可能有点不明白,_farpokeb()是什么函数,_dos_ds是什么,0xA0000 又是什么,y * 320 + x呢?不急,我慢慢介绍给你:
_farpokeb()很像TC下的pokeb()函数,它往内存的绝对地址写一个字节,我们这个函数就是用它来向显存实现写操作的,即“画点”;
不过_farpokeb()除了地址参数和写入值外,还比pokeb()多一个参数,也就是第一个参数,它叫选择子(selector),又涉及到保护模式的概念了,我不打算给在这里给你讲清楚
selector,如果你有兴趣,可以去查阅关于保护模式的文献。而现在,你只需要知道在这里,它得传个_dos_ds进去就能工作,你可以理解为:_dos_ds选择子可以让这个函数进行
1MB以下的低端物理内存读写;
那么来说说0xA0000吧,它其实是图形模式的显存首地址,也就是屏幕上最左上方的那个点的地址,通过它加上偏移量,就可以对屏幕上所有的点进行操作了;
y * 320 + x就是计算坐标为(x, y)的像素离首地址的偏移量的,因为显存是线性的,所以计算时就是这样的,不难吧。
[注:在图形编程中,屏幕默认的原点(0,0)在左上角,水平为x,其值往右方增加,垂直为y,其值往下方增加]
也许你在编译时,GCC报告找不到_farpokeb的定义,此时你得在源文件开头再多包含一个头文件#include <sys/farptr.h>
同样的,读取指定坐标点的函数可以这样实现:
char read_pixel(int x, int y, int color)
{
return _farpeekb(_dos_ds, 0xA0000 + y * 320 + x);
}
将写内存_farpokeb改成读内存_farpeekb,再将读回来的值返回——真的就这么简单。
有了画点函数,你就可以绘制整个世界了,线,框,面,曲线都可以由一个最简单的画点函数搭建。
再给出几个辅助函数,有一点图形编程经验的用户知道这是用来干嘛的,至于刚接触的嘛,咱们先就讲到这了,有空我会继续介绍的:
void set_color(int color, int red, int green, int blue) /* 设置调色板中指定颜色 */
{
outportb(0x3C8, color);
outportb(0x3C9, red);
outportb(0x3C9, green);
outportb(0x3C9, blue);
}
void vsync() /* 等待垂直回扫 */
{
/* 等待上一次垂直回扫结束 */
do
{
/* 空循环 */
} while (inportb(0x3DA) & 8);
/* 等待新的垂直回扫准备开始 */
do
{
/* 空循环 */
} while (!(inportb(0x3DA) & 8));
}
第一个函数可以将调色板中256个颜色中指定的颜色color的R、G、B分量设置成参数要求的值。比如说,原来第15号色是白色,你可以通过下面的调用将它变成黑色(即三个分量都
为0):
set_color(15, 0, 0, 0);
第二个函数被用来等待垂直回扫,在你即将重新显示一次屏幕之前,先调用它,以同步到垂直回扫的开始时刻,这样做可以避免动画的闪烁。
以下是整个源程序示例:
#include <dpmi.h> /* __dpmi_regs, __dpmi_int() */
#include <go32.h> /* _dos_ds */
#include <pc.h> /* outportb(), inportb() */
#include <conio.h> /* getch() */
#include <sys/farptr.h>
void set_13h_mode()
{
__dpmi_regs r; /* 类似TC下的 REGS r; */
r.x.ax = 0x13; /* 这句和TC下的用法没区别*/
__dpmi_int(0x10, &r); /* 这句的作用相当于TC的int86()函数,但少一个参数 */
}
void set_text_mode()
{
__dpmi_regs r;
r.x.ax = 3; /* 切换到3号模式下 */
__dpmi_int(0x10, &r);
}
void draw_pixel(int x, int y, int color)
{
_farpokeb(_dos_ds, 0xA0000 + y * 320 + x, color); /* 直接写显存 */
}
char read_pixel(int x, int y, int color)
{
return _farpeekb(_dos_ds, 0xA0000 + y * 320 + x); /* 直接读显存 */
}
void set_color(int color, int red, int green, int blue) /* 设置调色板中指定颜色 */
{
outportb(0x3C8, color);
outportb(0x3C9, red);
outportb(0x3C9, green);
outportb(0x3C9, blue);
}
void vsync() /* 等待垂直回扫 */
{
/* 等待上一次垂直回扫结束 */
do
{
/* 空循环 */
} while (inportb(0x3DA) & 8);
/* 等待新的垂直回扫准备开始 */
do
{
/* 空循环 */
} while (!(inportb(0x3DA) & 8));
}
int main()
{
set_13h_mode(); /* 设置13H图形模式 */
draw_pixel(10, 20, 15); /* 在坐标(10, 20)处画一个颜色为15号色的点 */
draw_pixel(100, 50, 12); /* 在坐标(100, 50)处画一个颜色为12号色的点 */
getch(); /* 等待用户按键 */
set_text_mode(); /* 切换回文本模式,退出 */
return 0;
}
OK,就到这里了,更多的相关文章可以访问我的主页:http://www.ds0101.com/
的任何问题也可以发到我的邮箱:dongkai329@yahoo.com.cn
另:转载请注明出处:0101部落(http://www.ds0101.com/)
[此贴子已经被作者于2007-8-3 17:43:46编辑过]