C语言图形化编程(WIN)

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);
stangleendangle参数采用角度制(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_LINE4,则该参数取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)的指针) = (原点指针) + (跳过的元素总数) * (每个元素占用的字节)

文本模式

每个元素占用两个字节——

  1. 第一个字节为文本内容
  2. 第二个内容为文本样式
    高四位为背景色,低四位为前景色

每个指针指向一个一字节(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);    //表示将背景页设置为当前输出页面