TC图形函数库:#include <graphics.h>
平台:watcom
参考资料:
本文多数例子来自上述材料,自己额外添加注释,方便理解
初始化
void far initgraph(int far *gdriver, int far *gmode, char *path);
far类型
这是早期的dos编程中使用的内存分段模式
far类型表示代码段采用远跳转
near类型表示代码段采用近跳转
但现在的win都采用平坦内存模式,不存在近跳转和远跳转,所以可以不用考虑
参数
- gdriver为驱动器
可以采用常量或数值- CGA(1)
- EGA(3)
- VGA(9)
- DETECT(0)
- gmode为模式
不同的驱动器提供的不同的模式
可以采用常量或数值 - path为图形驱动程序所在的目录
一般传入空字符串 一般自动检测即可
即gdriver = 0
此时gmode
的参数将被自动检测,但还是得传递一个变量,可以不赋值
如:int gdriver = 0, gmode; initgraph(&gdriver, &gmode, ""); //此时gmode并没有赋值
注意:
函数的形参都是指针,不要直接传递整型数据
错误捕捉示例
通过graphresult()
函数返回图形模式初始化结果
与常量grOk
进行比较
注意:graphresult()
必须在initgraph()
之后调用
int gdriver = 0, gmode;
initgraph(&gdriver, &gmode, "");
errorcode = graphresult();
if(errorcode != grOk)
{
printf("Graphics error: %s\n", grapherrormsg(errorcode));
printf("Press any key to halt:");
getchar();
exit(1);
}
图形的坐标
文本模式
屏幕一般分为25或40或50行,80或40列
以左上角为原点(1,1),向右为x正方向,向下为y正方向
图形模式
屏幕被划分为像素,每个像素为一个点,像素的个数与视频适配器的类型和适配器的工作方式有关
系统默认以左上角为原点(0,0),向右为x正方向,向下为y正方向
注意:文本模式跟图形模式的原点坐标不同
获取坐标最大值
getmaxx()
函数返回当前图形模式下的最大x坐标(整型)getmaxy()
函数返回当前图形模式下的最大y坐标(整型)
坐标轴变换
选择一个原点,建立以右为x正方向,以上为y正方向的坐标系(符合我们的习惯)
假设选定新的坐标原点(origin_x, origin_y)
原先坐标系的点(before_x, before_y)与新坐标系上的对应点(later_x, later_y)存在以下关系
before_x = origin_x + later_x
before_y = origin_y - later_y
文本模式与图形模式的切换
模式间切换不需要重复关闭、初始化图形模式
获取当前图形模式
int far getgraphmode(voide);
注意:前提是已经成功调用过initgraph()
函数
恢复为图形初始化前的模式(也就是文本模式)
void far restorecrtmode(void);
切换到文本模式(bhh)
closegraph();
text_mode();
textmode(C80);
将系统设置为指定的图形模式并清屏
void far setgraphmode(int mode);
示例:
//......省略图形初始化的代码
gmode = getgraphmode(); //获取当前图形模式
restorecrtmode(); //恢复为文本模式
setgraphmode(gmode); //设置(恢复)为之前的图形模式并清屏
关闭图形系统
void far closegraph(void);
所有的有关图形的程序段必须在initgraph()
和closegraph()
之间(类型于文件操作)
背景色和作图色设置
背景色: setbkcolor(int color);
作图色: setcolor(int color);
参数color
可以使用常量或数值表示
- BLACK(0)
- BLUE(1)
- GREEN(2)
- CYAN(3) —>青色
- RED(4)
- MAGENTA(5)—>洋红
- BROWN(6)
- LIGHTGRAY(7)
- DARKGRAY(8)
- LIGHTBLUE(9)
- LIGHTGREEN(10)
- LIGHTCYAN(11)
- LIGHTRED(12)
- LIGHTMAGENTA(13)
- YELLOW(14)
- WHITE(15)
9-13为1-5对应的浅色
基本图形函数
画点函数
void far putpixel(int x, int y, int pixelcolor);
坐标位置函数
获取最大x坐标:int far getmaxx(void);
获取最大y坐标:int far getmaxy(void);
获取当前x坐标:int far getx(void);
获取当前y坐标:int far gety(void);
移动画笔到绝对坐标:void far moveto(int x, int y);
移动画笔到相对坐标:void far moverel(int dx, int dy);
图形模式下的文本输出函数
只支持英文输出,且文字大小固定,为8*16点阵
在当前位置输出文本:void far outtext(char far *textstring);
在指定位置输出文本:void far outtextxy(int x, int y, char *textstring);
画线函数
指定直线起止点:void far line(int x0, int y0, int x1, int x2);
以当前位置为直线起点,指定终点(绝对坐标),并移动画笔:void far lineto(int x, int y);
以当前位置为直线起点,指定终点(相对坐标),并移动画笔:void far linerel(int dx, int dy);
画圆函数,画圆弧函数
画圆:void far circle(int x, int y, int radius);
画圆弧:void far arc(int x, int y, int stangle, int endangle, int radius);
stangle
和endangle
参数采用角度制(0-360),以x轴正方向为0度,以逆时针为正方向
画椭圆(弧)函数
void far ellipse(int x, int y, int stangle, int endangle, int xradius, int yradius);
画矩形函数
void far rectangle(int left, int top, int right, int bottom);
指定左上角的坐标为(left
, top
),右下角的坐标为(right
, bottom
)
线型设定函数
void far setlinestyle(int linestype, unsigned upattern, int thick);
- linestype为直线类型(常量或数值)
- 实线(缺省):SOLID_LINE(0)
- 点线:DOTTED_LINE(1)
- 中心线:CENTER_LINE(2)
- 虚线:DASHED_LINE(3)
- 用户自定义线型:USERBIT_LINE(4)
- upattern为自定义线型(十六进制数)
若linestype不为USERBIT_LINE
或4
,则该参数取0即可 - thick为直线粗细(单位:px)
相关常量:
NORM_WIDTH(1),缺省
THICK_WIDTH(3)
填充函数
设置填充模式和颜色
void far setfillsytle(int pattern, int color);
pattern可以采用常量或数值:
- 用背景色:EMPTY_FILL(0)
- 用指定颜色:SOLID_FILL(1)
- 用线:LINE_FILL(2)
- 用斜线:LTSLASH_FILL(3)
- 用粗斜线:SLASH_FILL(4)
- 用粗反斜线:BKLASH_FILL(5)
- 用反斜线:LTKLASH_FILL(6)
- 用网格线:HATCH_FILL(7)
- 用斜交线:XHATCH_FILL(8)
- 用间隔线:INTERLEAVE_FILL(9)
- 用稀疏点:WIDE_DOT_FILL(10)
- 用密集点:CLOSE_DOT_FILL(11)
- 用用户自定义模式:USER_FILL(12)
有界区域填充
void far floodfill(int x, int y, int bcolor);
(x, y)为所要填充的有界区域的任意点
bcolor不是填充色,而是边框颜色
椭圆填充
void far fillellipse(int x, int y, int xradius, int yradius);
椭圆扇区填充
void far sector(int x, int y, int stangle, int endangle, int xradius, int yradius);
多边形填充
void far fillpoly(int numpoints, int far *polypoints);
polypoints参数为各个点的坐标,存放规则如下:(假设一个poly数组)
(poly[0]
, poly[1]
) 为第一个顶点
(poly[2]
, poly[3]
) 为第二个顶点
…………以此类推
图片输出
8bit图片输出
void load_8bit_bmp(int x, int y, char *path);
#include <graphics.h>
#include <stdio.h>
main()
{
int x, y;
int driver=0, mode=VESA_1024x768x8bit;
initgraph(&driver, &mode, "");
load_8bit_bmp(0, 0, "pic256.bmp");
load_8bit_bmp(200, 200, "hello.bmp");
getchar();
closegraph();
}
24bit图片输出(系统没有提供函数)
通过读取bmp文件的二进制数据输出图片
#include <graphics.h>
#include <stdio.h>
#include <mem.h>
int load_24bit_bmp(int x, int y, char *filename)
{
FILE *fp = NULL;
byte *p = NULL; /* pointer to a line of bmp data */
byte *vp = _vp + (y*_width + x) * (_color_bits/8);
dword width, height, bmp_data_offset, bytes_per_line, offset;
int i;
p = malloc(1024L * 3); /* memory for holding a line of bmp data */
if(p == NULL) /* cannot allocate enough memory for drawing 1 line */
goto display_bmp_error;
fp = fopen(filename, "rb");
if(fp == NULL) /* cannot open bmp file */
goto display_bmp_error;
fread(p, 1, 0x36, fp); /* read BMP head */
if(*(word *)p != 0x4D42) /* check BMP signature */
goto display_bmp_error; /* not a BMP file */
if(*(word *)(p+0x1C) != 24)
goto display_bmp_error; /* not a 24-bit-color BMP file */
width = *(dword *)(p+0x12);
height = *(dword *)(p+0x16);
bmp_data_offset = *(dword *)(p+0x0A);
fseek(fp, bmp_data_offset, SEEK_SET); /* skip BMP head */
bytes_per_line = (width * 3 + 3) / 4 * 4; /* must be multiple of 4 */
for(i=height-1; i>=0; i--) /* draw from bottom to top */
{
fread(p, 1, bytes_per_line, fp); /* read a line of bmp data */
offset = i * 1024 * 3;
memcpy(vp+offset, p, width*3);
}
free(p);
fclose(fp);
return 1;
display_bmp_error:
if(p != NULL)
free(p);
if(fp != NULL)
fclose(fp);
return 0;
}
main()
{
int driver=0, mode=VESA_1024x768x24bit;
initgraph(&driver, &mode, "");
load_24bit_bmp(0, 0, "pic.bmp");
getchar();
closegraph();
}
中文输出
点阵字库
字库有固定的字体大小
读取字库文件中对应文字的16*16点阵共32字节的信息,若为1则在对应的位置上输出一个点,否则跳过
示例:(读取16*16的字库,并将其放大四倍打印)
#include <graphics.h>
void draw_hz(byte buf[16][2]);
main()
{
int driver=0, mode=VESA_640x480x8bit;
FILE *fp;
byte hz[3]="我", buf[16][2]; //每个汉字占用两个字节
long offset;
int x, y, button;
initgraph(&driver, &mode, "");
fp = fopen("hzk16", "rb"); // 打开16x16点阵字库文件
if(fp == NULL)
{
puts("Cannot open file hzk16.");
exit(0);
}
// 计算"我"字点阵的偏移位置
offset = ((hz[0] - 0xA1) * 94 + (hz[1] - 0xA1)) * 0x20; //呃?这是啥?莫非是固定的计算公式?
fseek(fp, offset, SEEK_SET); // 移动文件指针到该偏移位置
fread(buf, 1, 2*16, fp); // 读取32字节点阵数据
fclose(fp);
draw_hz(buf); // 画"我"字
getchar();
closegraph();
}
void draw_hz(byte buf[16][2])
{
word sixteen_dots;
int x0=_width/2-32, y0=_height/2-32; //打印的起始位置
//居中显示,注意我们把字库的字体放大了四倍,也就是实际上打印出来是64*64的点阵汉字
int x, y; //当前打印的“点”的坐标
int i, j;
x = x0;
y = y0;
for(i=0; i<16; i++) // 16行
{
sixteen_dots = buf[i][0]<<8 | buf[i][1]; // 把两个一字节的word合并为一个两字节的int,共16位,代表1行16个点
for(j=0; j<16; j++) // 16列
{
if(sixteen_dots & 1<<(15-j)) // 判断第(15-j)位是否为1, 为1表示有笔画
//假设j=14,则1<<(15-j)的结果为0000 0000 0000 0010
//假设sixteen_dots为...........0101 0101 0101 0101
//取位“与”即可判断第2位是否为1
{
setfillstyle(SOLID_FILL, RED);
bar(x, y, x+3, y+3); // 画一个4*4的红色方块代替一个前景点
}
else // 第(15-j)位为0,为0表示没笔画
{
setfillstyle(SOLID_FILL, BLACK);
bar(x, y, x+3, y+3); // 画一个4*4的黑色方块代替一个背景点
}
x += 4; //把“画笔”移动到当前行的下一个4*4的“点”的位置
}
//把“画笔”移动到下一行第一个4*4的“点”的位置
x = x0;
y += 4;
}
}
缺陷:当字体放大时,会出现明显的锯齿形
TTF曲线字库
有两种方式——
1、将多行字符串转换为多幅图片再输出,速度较快
2、逐行输出字符串
相关类型——
typedef struct _PIC // 照片结构类型
{
struct picture *img; // 照片的图像
struct picture *mask; // 照片的掩码
int width, height; // 照片的宽度及高度
} PIC;
相关函数——
字符串转换为图片
PIC * get_ttf_text_pic(char text[], char fontname[], int fontsize);
text为要转换的字符串
fontname为使用的曲线字库
fontsize为字体大小
返回指向图片(PIC)的指针绘制图片
void draw_picture(int x, int y, PIC *p);
p为指向所要绘制的图片的指针销毁图片,释放资源
void destroy_picture(PIC *p);
只有一个参数(指针),为指向所要销毁的图片的指针直接打印字符串
dword out_ttf_text_xy(int x0, int y0, char text[], char fontname[], int fontsize);
返回32位整型,高16位记录图片宽度,低16位记录图片高度
示例:
#include <stdio.h>
#include <graphics.h>
main()
{
int driver=0, mode=VESA_1024x768x24bit;
short int x=0, y=100;
int i;
PIC *p[4];
initgraph(&driver, &mode, "");
/* TTF字体输出演示1: 先把多行字符串转化成多幅照片, 再输出, 速度较快 */
setcolor(0xFF0000); // 红色
p[0] = get_ttf_text_pic("A QUICK BROWN FOX JUMPS", "courier.ttf", 40);
// 把字符串转化成照片, 其中"courier.ttf"是TTF字库名, 40是字体大小
// p[0]用来接收函数返回的照片指针
setcolor(0xFFFF00); // 黄色
p[1] = get_ttf_text_pic("a quick brown fox jumps", "courier.ttf", 36);
setcolor(0x00FF00); // 绿色
p[2] = get_ttf_text_pic("0123456789", "courier.ttf", 32);
setcolor(0x0000FF); // 蓝色
p[3] = get_ttf_text_pic("`~!@#$%^&*()-=_+[]{}\\|;:'\",.<>/?", "courier.ttf", 28);
for(i=0; i<4; i++)
{
draw_picture(x, y, p[i]); // 在(x,y)坐标输出照片p[i]
y += p[i]->height; // 计算下一行的y坐标
destroy_picture(p[i]); // 销毁已画照片占用的资源
}
getchar();
/* TTF字体输出演示2: 逐行输出字符串, 速度较慢 */
driver=0;
mode=VESA_1024x768x8bit;
initgraph(&driver, &mode, "");
y = 100;
setcolor(LIGHTRED);
y += out_ttf_text_xy(x, y, "夏天的飞鸟,飞到我窗前唱歌,又飞去了。", "simyahei.ttf", 40);
// 函数的返回值 = (照片宽度<<16) | 照片高度
// 故类型为short int的y实际仅加上照片高度值, 从而得到下行的y坐标
y += out_ttf_text_xy(x, y, "Stray birds of summer come to my window", "courier.ttf", 32);
y += out_ttf_text_xy(x, y, "to sing and fly away.", "courier.ttf", 32);
setcolor(YELLOW);
y += out_ttf_text_xy(x, y, "秋天的黄叶,它们没什么可唱,", "simyahei.ttf", 36);
y += out_ttf_text_xy(x, y, "And yellow leaves of autumn", "courier.ttf", 28);
y += out_ttf_text_xy(x, y, "which have no songs", "courier.ttf", 28);
setcolor(LIGHTGREEN);
y += out_ttf_text_xy(x, y, "只叹息一声,", "simyahei.ttf", 32);
y += out_ttf_text_xy(x, y, "Flutter and fall there", "courier.ttf", 24);
setcolor(LIGHTBLUE);
y += out_ttf_text_xy(x, y, "飞落在那里。", "simyahei.ttf", 28);
y += out_ttf_text_xy(x, y, "with a sigh.", "courier.ttf", 20);
getchar();
closegraph();
}
声音输出
扬声器输出
过于机械麻烦,这里跳过
声卡输出
只支持midi音乐文件和wav波形文件
相关类型——
知道有这两个类型即可,具体成员不必深究
typedef struct midi // MIDI file data structure
{
word format; // 0 or 1
word num_tracks; // 1 - 32
word divisions; // number ticks per quarter note
byte *track[16]; // Track data pointers
word repeat;
struct midi *chain;
void (*chainfunc)(struct midi *);
} MIDI;
typedef struct wave
{
dword id;
dword sample_rate;
dword byte_rate;
byte *data;
dword chunk_size; // size of this chunk in bytes
dword sample_size; // size of entire linked sample
struct wave *next; // points to next link.
struct wave *head; // points to top link.
} WAVE;
相关函数——
- 初始化
int initsound(void);
- 载入
WAVE * load_wave(char *filename);
MIDI * load_midi(char *filename);
- 播放
void play_wave(WAVE *p, int repeat);
void play_midi(MIDI *p, int repeat);
若repeat为-1,表示无限循环 - 释放
void free_wave(WAVE *p);
void free_midi(MIDI *p);
- 关闭
void closesound(void);
示例:
#include <graphics.h>
main()
{
MIDI *pmidi;
WAVE *pwave;
initsound(); // 初始化声卡
pmidi = load_midi("bach.mid"); // 载入midi文件, 返回MIDI指针
pwave = load_wave("sheep.wav"); // 载入wav文件, 返回WAVE指针
play_midi(pmidi, -1); // repeat forever 播放midi音乐
play_wave(pwave, 3); // repeat 3 times 播放wav声音
puts("Press a key to continue.\n");
getchar();
free_midi(pmidi); // 释放midi音乐占用的资源
free_wave(pwave); // 释放wav声音占用的资源
closesound(); // 关闭声卡
return 1;
}
详细函数——(包括一些控制函数)
int initsound(void); // 声卡初始化
void closesound(void); // 关闭声卡
WAVE * load_wave(char *filename); // 载入wav文件
void play_wave(WAVE *p, int repeat); // 播放wav, repeat为循环次数, repeat=-1为无限循环
void stop_wave(WAVE *p); // 停止播放wav
void pause_wave(WAVE *p); // 暂停播放wav
void resume_wave(WAVE *p); // 继续播放wav
void free_wave(WAVE *p); // 释放wav占用的资源
MIDI * load_midi(char *filename); // 载入midi文件
void play_midi(MIDI *p, int repeat); // 播放midi, repeat为循环次数, repeat=-1为无限循环
void stop_midi(MIDI *p); // 停止播放midi
void pause_midi(MIDI *p); // 暂停播放midi
void resume_midi(MIDI *p); // 继续播放midi
void free_midi(MIDI *p); // 释放midi占用的资源
键盘输入:bioskey()
bioskey(0)
若键盘缓存区为空,将等待用户输入
若键盘缓存区不为空,将直接读走其中的键bioskey(1)
若键盘缓存区为空,返回0
若键盘缓存区不为空,返回非零
什么是键盘缓冲区?
CPU能在运行程序时处理外部中断(interrupt)。当用户按下某个键时,键盘会向CPU发出一个中断请求,此时CPU会暂停正在运行的程序而转去处理键盘中断,处理的结果是把当前按住的键读走并存放到键盘缓冲区中。键盘缓冲区是一个队列(queue),相当于一个数组。
鼠标输入
int x, y, b;
union REGS r;
r.w.ax = 0x0003; /*调用0003号功能*/
int86(0x33, &r, &r);
x = r.w.cx; /* 当前鼠标的x坐标 */
y = r.w.dx; /* 当前鼠标的y坐标 */
b = r.w.bx; /* 当前鼠标的按键状态 */
其他常见功能:
- 0000
检测鼠标驱动是否安装 - 0004
把鼠标定位到(x,y) - 0007
设置水平方向的鼠标移动范围 - 0008
设置竖直方向的鼠标移动范围
详见中断大全
watcom平台提供的VESA图形库(bhh)
通过直接给坐标对应的内存赋值画图
一些程序可能会出现byte, word, dword类型
其实这都是自定义类型byte
对应char
word
对应short
dword
对应int
相关的全局变量
_vp
: 指向坐标(0,0)的指针_width
: 屏幕宽度_height
: 屏幕高度_color_bits
: 每个像素占用内存的位(bit)数_visual_page
: 当前显示画面(前景)对应的页号_active_page
: 当前输出画面对应的页号_back_page
: 后台画面(背景)对应的页号_page_gap
: 后台画面与显示画面的距离_mode_index
: 当前视频模式的编号_mode
: 当前视频模式的内部编号_draw_color
: 前景色_back_color
: 背景色_fill_color
: 填充前景色_fill_mask_index
: 填充掩码_x
: 当前x坐标_y
: 当前y坐标
指向(x,y)的指针p,指向char类型数据公式:p = _vp + ( y * _width + x ) * ( _color_bits / 8 )
解释:((x, y)的指针) = (原点指针) + (跳过的元素总数) * (每个元素占用的字节)
文本模式
每个元素占用两个字节——
- 第一个字节为文本内容
- 第二个内容为文本样式
高四位为背景色,低四位为前景色
每个指针指向一个一字节(8位)的数据
示例(输出一个蓝底红字的A):
char *p = _vp;
*p = 'A';
*(p+1) = ( BLUE << 4 ) + RED; //或*(p+1) = 0x14;
图形模式
gmode常量
watcom平台提供的VESA图形库中相关常量:
包含8bit,24bit, 32bit三种颜色模式
- VESA_320x200x8bit
- VESA_320x200x24bit
- VESA_320x200x32bit
- VESA_640x480x8bit
- VESA_640x480x32bit
- VESA_800x600x8bit
- VESA_800x600x24bit
- VESA_800x600x32bit
- VESA_1024x768x8bit
- VESA_1024x768x24bit
- VESA_1024x768x32bit 以上为图形模式
- TEXT_80x25x16bit 文本模式
8bit颜色模式
每个点占用显卡一个字节,每个点有2^8(256)种颜色,前十六种(0~15)颜色定义如下:
enum COLORS
{
BLACK, /* dark colors */
BLUE,
GREEN,
CYAN,
RED,
MAGENTA,
BROWN,
LIGHTGRAY,
DARKGRAY, /* light colors */
LIGHTBLUE,
LIGHTGREEN,
LIGHTCYAN,
LIGHTRED,
LIGHTMAGENTA,
YELLOW,
WHITE
};
24bit颜色模式
每个点占用显卡三个字节,分别代表RGB的蓝色(B),绿色(G),红色(R),(没错,就是逆序的),每个点有2^24种颜色。
示例:
char *p;
p = _vp;
*p = 0x56; //Blue
*(p+1) = 0x34; //Green
*(p+2) = 0x12; //Red
32bit颜色模式
每个点占用显卡四个字节,前三个字节表示RGB(同24bit),第四个字节通常为0x00
前景页和背景页
显示页面存在两个层次,前景和背景_active_page
为当前输出的页面_visual_page
为当前显示的页面,非显示页面将被隐藏_back_page
为当前的背景页setviualpage()
函数能够设置显示页面setactivepage()
函数能够设置输出页面
示例:
setvivualpage(_back_page); //表示将背景页设置为当前显示页面
stactivepage(_back_page); //表示将背景页设置为当前输出页面