009 - 下载 第9章 实用编程技巧 9.1...

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: 下载 第9章 实用编程技巧 9.1 图形应用技巧 9.1.1 显示适配器类型的自动测试 目前 P C机及兼容机的显示器及其适配器的类型非常多,有单色的,也有彩色的。这些显 示器及适配器的模式对应用程序来说是非常重要的。如何在程序中自动识别显示器的模式, 以便更好地使用当前的显示模式是每个微机应用程序开发者的一个重要课题。下面程序可以 方便测出当前显示器适配器的模式(有关具体知识,请参见其它相关的技术书籍) 。 [例9-1] 测试显示适配器类型。 #include #include #define #define #define #define <stdio.h> <graphics.h> P(note) PV(format value) , PM PD printf(note) printf(formatvalue) , printf("mode is ") printf("\n\tdetected graphics drive is") void main( ) { int gdrive ,gerror ,gmode ; detectgraph(&gdrive ,&gmode) ; /* 标准测试函数 */ if(gdrive<0) {P("No graphics hardware detected !\n") ; return ; } switch (gdrive) {case 1: PD ; P("CGA") ; switch(gmode) { case 0 : PM ; P("CGAC0 320 200") ; × break ; case 1 : PM ; P("CGAC1 320 200") ; × break ; case 2 : PM ; 下载 第9章 实用编程技巧 P("CGAC2 320 200") ; × break ; case 3 : PM P("CGAC3 640 200") ; break ; ; × case 4 : PM P("CGAh4 320 200") ; break ; ; × } break ; case 2: PD ; P("MCGA") ; switch(gmode) { case 0 : PM ; P("MCGAC0 320 200") × case 1 : PM ; P("MCGAC1 320 200") × case 2 : PM ; P("MCGAC2 320 200") × case 3 : PM ; P("MCGAC3 320 200") × case 4 : PM ; P("MCGAC4 620 200") × case 5 : PM ; P("MCGAC5 620 480") × } break ; case 3 : PD ; P("EGA") ; switch(gmode) { case 0 :PM ; P("EGALO 640 200") ; × break ; case 1 :PM ; P("EGALO 640 350") ; × break ; } break ; case 4 :PD ; P("EGA64") ; switch(gmode) {case 0 :PM ; P("EGA64LO 640 200") ; × break ; case 1 : PM ; P("EGA64HI 640 350") ; × break ; } break ; case 5 :PD ; P("EGAMONO") ; PM ; P("EGAMONO 640 350") ; × break ; case 6 :PD ; P("IMB8614") ; switch(gmode) {case 0 : PM ; P("IMB8514LO 640 480") ; × ; ; ; ; ; ; break break break break break break ; ; ; ; ; ; 171 172 case case case case C 语言程序设计 break ; case 1 : PM ; P("IMB8514HI 1024 768") ; × break ; } break ; 7 :PD ; P("HERCMONO") ; PM ; P("HERCMONO 720 348") ; × break ; 8 :PD ; P("ATT400") ; switch(gmode) {case 0 : PM ; P("ATT400C0 320 200") ; × break ; case 1 : PM ; P("ATT400C1 320 200") ; × break ; case 2 : PM ; P("ATT400C2 320 200") ; × break ; case 3 : PM ; P("ATT400C3 320 200") ; × break ; case 4 : PM ; P("ATT400CMCD 640 400") ; × break ; } break ; 9 :PD ; P("VGA") ; switch(gmode) {case 0 : PM ; P("VGALO 640 400") ; × break ; case 1 : PM ; P("VGALO 640 350") ; × break ; case 2 : PM ; P("VGALO 640 480") ; × break ; } break ; 10 :PD ; P("PC3270") ; PM ; P("PC3270HI 720 350") ; × 下载 173 第9章 实用编程技巧 下载 break ; } P("\n\n\t\t\t THANK YOU !") ; } 开发图形软件的基本方法 大家都知道, Turbo C 具有汇编语言那样直接控制系统硬件以及调用操作系统资源的功能, 同时又具有一般高级语言完成复杂运算的能力。因此, C语言已成为开发图形软件最理想的程 序语言。下面主要介绍几个生成基本图形的函数,它们是开发复杂图形软件的基础。 显示方式与色调函数 若要在屏幕上显示图形,首先要把屏幕设置为彩色图形显示方式,常用的方式是: mode 4 320 200 × 4色 在这种显示方式下,可以使用两种不同的配色器来配置色调,见表 9-1 。 表9-1 屏幕色调 配色器号 颜色0 颜色1 颜色2 颜色 3 0 1 同底色 同底色 绿 青 红 淡红 黄 白 利用 C 语言标准库函数 int86( ) ,可以方便地完成显示与色调的设置。这两个函数 s e t m o d e ( ) 和palet( ) 见程序 TX.c。例如,要求设 4号显示模式和使用 0号配色器时,调用形式如下: setmode(4); palet(0); 画点函数和画线函数 在设置屏幕显示和色调后,就可使用各种绘图函数绘制不同颜色不同形状的图形。任何 图形都有是由点组成的,所以画点函数是其它函数的基础。 在屏幕上画一个点有两种方法:一是调用 D O S 功能,二是直接存取显示缓冲区(视频 RAM ) 。由于后者有更高的执行速度,所以一般人都使用第二种方法。画点函数 point()中有三 个参数: x 、 y 、 c o l o r ,其中 x 、 y 分别是显示点的行和列坐标, c o l o r 是点的颜色,取值 0 ~ 3 , 对应的颜色如表 9-1所示。该函数根据彩色图形显示的原理,直接控制视频 RAM区。它通过指 针ptr访问该内存区域。指针初始化时, char far ptr=(char far *)×b8000000 0 视频 RAM的首地址赋予了指针 ptr, 从而通过该指针就可以直接存取视频 RAM的任何单元。 ( 针对不同的显示模式,此地址可能不同 ) 。 使用 p o i n t ( ) 函数可以在屏幕的任意位置上显示指定颜色的一个点。例如在 2 0 行 1 5 列上显 示一个红色点时,使用: point(20 ,15 ,2) ; 使用画点函数可以编写画直线的函数 l i n e ( ) ,其原理是已知直线的两个端点坐标时,用迭 代过程确定组成直线各点的位置。函数 line()的参数为 x1,y1 ,x2,y2 ,color。其中 x1、y1和 x 2 、y 2 分别是直线的起点和终点坐标。 C o l o r 是直线的颜色,取值 0 ~ 3 。例如在屏幕的右斜对 角上画一条绿色直线,使用的格式为: 174 C语言程序设计 下载 line(0 ,0,119 ,319 ,1) ; 矩形与填充函数 矩形是由四条直线组成的。使用直线函数可以矩形。绘制时,只需知道它左上角和右下 角的坐标,就可以用直线函数绘出它的四条边,矩形函数 b o x ( ) 的参数为 x 1 、 y 1 、 x 2 、 y 2 、 color。其中 x1、y1和x2、y2分别是矩形左上角和右下角的坐标, color 是四条边的颜色。 矩形填充块,实际是在指定位置上画出具有相同长度和颜色的直线,其填充函数 f i l l b o x ( ) 中的参数与 box() 中的参数相同。 绘制图形 使用上述几个基本图形的函数,可以编写出在屏幕上绘制任意图形的程序。它使用键盘 上的箭头等功能键,实现显示位置和颜色的控制。为了获取键盘扫描代码和显示当前绘图位 置,要使用十字函数 xhair()和获取键盘扫描代码函数 getkey()。 下面给出一个简单的绘图程序 t x . c 。它相当于用画笔在屏幕上绘制图形,画笔的位置用十 字光标显示。画笔的移动由↓、→、↑、←四个键控制。用 H o m e、 P g U p 、 P g D n 和E n d 键分 别控制十字光标向 4 5 °方向移动。画笔的抬起落下由字母键 O控制,颜色由数字键 0 ~3 控制。 功能键 F1 用于设定单步前进, F2用于设定 5步前进。 该程序还可以画出矩形、填充矩形和直线。这时需要设置它们的坐标位置,直线需要两 个端点坐标,矩形需要两个对角的坐标。例如,在抬笔状态下,把十字光标移至第一个位置 后按回车键,然后再移至第二个位置按回车键。之后按下 L 键时,则在设定的两个端点的位置 上画出一条直线;如果按 B键,则以两个位置为对角画出矩形;如果按 F键,则画出填充矩形。 另外,用 P 键可改变色调,按 Q键结束运行,返回到 DOS状态。 [例9-2] 绘图程序 tx.c #include<stdio.h> #include<dos.h> #include<ctype.h> #include<conio.h> #include<math.h> void setmode(int) ; void palet(int) ; void point(int int ,int) ; , void line(int int ,int ,int ,int) ; , void box(int int ,int ,int ,int) ; , void fillboX(int int ,int ,int ,int) ; , void Xhair(int int) ; , int getkey(void) ; void main() { union{ char c[2] ; int i; }key ; int X=10 y=10 ,cc=2 ,onflag=1 ,palnum=1 ; , 下载 第9章 实用编程技巧 intX1=0 ,y1=0 ,X2=0 ,y2=0 ,firstpoint=1 ; int d=1 ; setmode(4) ; palet(0) ; Xhair(X ,y) ; do { key.i=getkey() ; Xhair(X ,y) ; if(!key.c[0]) switch(key.c[1]){ case 75: if(onflag) /*left*/ line(X ,y,X,y-d ,cc) ; y-=d ; break ; case 77: if(onflag) /* right */ line(X ,y,X,y+d ,cc) ; y+=d ; break ; case 72:if(onflag) /* up */ line(X ,y,X-d ,y,cc) ; X-=d ; break ; case 80: if(onflag) /* down */ line(X ,y,X+d ,y,cc) ; X+=d ; break ; case 71:if(onflag) /* Home-up left */ line(X ,y,X-d ,y+d ,cc) ; X-=d ; X-=d ; break ; case 73:if(onflag) /*PgUp-up right */ line(X ,y,X-d ,y+d ,cc) ; X-=d ; y+=d ; break ; case 79:if(onflag) /*End-down left */ line(X ,y,X+d ,y-d ,cc) ; X+=d ; y-=d ; break ; case 81:if(onflag) /* PgUp-down right */ line(X ,y,X+d ,y+d ,cc) ; X+=d ; y+=d ; break ; case 59: /*F1*/ d=1 ; break ; 175 176 C语言程序设计 case 60: /*F2*/ d=5 ; break ; } else switch(tolower(key.c[0])){ case 'o': /* brush on-off */ onflag=!onflag ; break ; case '1': /* color 1*/ cc=1 ; break ; case '2': /* color2 */ cc=2 ; break ; case '3': /* color 3*/ cc=3 ; break ; case '0': /* color 0*/ cc=0 ; break ; case 'b': /* set boX */ boX(X1 ,y1 ,X2 ,y2 ,cc) ; break ; case 'f': /*set fill boX */ fillboX(X1 ,y1 ,X2 ,y2 ,cc) ; break ; case 'l': /* set line */ line(X1 ,y1 ,X2 ,y2 ,cc) ; break ; case 'r': /*set endpoint */ if(firstpoint){ X1=X ; y1=y ; } else { X2=X ; y2=y ; } firstpoint = !firstpoint ; break ; case 'p': /* set color */ palnum = palnum==1 ? 2 :; 1 palet(palnum) ; break ; } Xhair(X ,y) ; }while(key.c[0]!='q') ; getch() ; 下载 下载 setmode(2) ; } /* 设置显示方式 */ void setmode(mode) int mode ; { union REGS regs ; regs.h.al=mode ; regs.h.ah=0 ; int86(0X10 ,&regs ,&regs) ; } /* 设置色调 */ void palet(pn) int pn; { union REGS regs ; regs.h.bh=1 ; regs.h.bl=pn ; regs.h.ah=11 ; int86(0X10 ,&regs ,&regs) ; } /* 画点函数 */ void point(X y,color) , int X,y,color ; { union { char cc[2] ; int i; }mask ; int i,indeX ,posit ; unsigned char t ; char Xor ; char far *ptr=(char far *)0Xb8000000 ; mask.i=0Xff3f ; if(X<0||X>199||y<0||y>319) return ; Xor=color&128 ; color=color&127 ; posit=y%4 ; color<<=2*(3-posit) ; mask.i>>=2*posit ; indeX=X*40+(y/4) ; if(X%2)indeX+=8152 ; if(! Xor){ t=*(ptr+indeX)&mask.cc[0] ; *(ptr+indeX)=t|color ; } else{ t=*(ptr+indeX)|(char)0 ; 第9章 实用编程技巧 177 178 C语言程序设计 *(ptr+indeX)=t^color } 下载 ; } /* 直线函数 */ void line (X1 y1 ,X2 ,y2 ,color) , int X1,y1 ,X2 ,y2 ,color ; { register int t dis ; , int Xerr=0 yerr=0 ,dX ,dy ; , int incX incy ; , dX=X2-X1 ; dy=y2-y1 ; if (dX>0)incX=1 ; else if(dX==0)incX=0 ; else incX=-1 ; if(dy>0)incy=1 ; else if(dy==0)incy=0 ; else incy=-1 ; dX=abs(dy) ; dy=abs(dy) ; for (t=0 t<=dis+1 ;t++){ ; point(X1 ,y1 ,color) ; Xerr+=dX ; yerr+=dy ; if(Xerr>dis){ Xerr-=dis ; X1+=incX ; } if(yerr>dis){ yerr-=dis ; y1+=incy ; } } } /* 矩形函数 */ void boX(X1 y1 ,X2 ,y2 ,color) , int X1,y1 ,X2 ,y2 ,color ; { line (X1 y1 ,X2 ,y1 ,color) ; , line(X1 ,y1 ,X2 ,y2 ,color) ; line(X1 ,y2 ,X2 ,y2 ,color) ; line(X2 ,y1 ,X2 ,y2 ,color) ; } /* 矩形填充函数 */ void fillboX(X1 y1 ,X2 ,y2 ,color) , int X1,y1 ,X2 ,y2 ,color ; { register int i begin ,end ; , begin=X1<X2? X1:X2 ; 下载 第9章 实用编程技巧 179 end=X1>X2? X1:X2 ; for (i=begin i<=end ;i++) ; line(i ,y1 ,i,y2 ,color) ; } /* 十字光标定位函数 */ void Xhair(X y) , int X,y; { line(X-4 ,y,X+3 ,y,1|128) ; line(X ,y+4 ,X,y-3 ,1|128) ; } /* 获取键盘扫描码函数 */ int getkey() { union REGS regs ; regs.h.ah=0 ; return int86(0X16 &regs ,&regs) ; , } 9.1.2 屏幕图像的存取技巧 Turbo C 提供了丰富的图形操作函数,利用这些函数可以很容易编写图形和图像处理程序, 但是 Turbo C 没有提供屏幕图像存储和恢复的函数,而在许多情况下需要将屏幕上的图像全部 或部分的以文件的形式保存在磁盘上,在需要时快速地从磁盘调入内存并重现在屏幕上。下 面介绍的程序就是用来对屏幕上任一块矩形区域进行存取的程序 t x 1 . c。saveimage( ) 函数首先 将屏幕上的一块矩形区域图像的数据写入内存某地址,然后创建一个二进制文件,并把该地 址的数据写入此文件中。这样屏幕上的图像就以文件的形式存储在磁盘上了。 loadimage( ) 函 数首先将磁盘上的图像数据文件打开并读入到内存某地址,然后从该地址将这些数据读到视 屏缓冲区。这样,原先保存的图像就重现在屏幕上了。 t x 1 . c 程序首先测试硬件的图形适配卡类型,并根据其值进入相应的图形方式,然后在屏 幕上画一个彩色饼形统计图,并将该图像存入文件 graph.dat中,最后打开 graph.dat文件并读入 内存,将这幅图像重现在屏幕上。 [例9-3] 存取任意屏幕图像程序 tx1.c /* 存取任意屏幕图像头文件 tX1.h */ #include<stdio.h> #include<graphics.h> #include<conio.h> #include<fcntl.h> #include<alloc.h> #include<stdlib.h> #include<io.h> , void saveimage(X1 y1 ,X2 ,y2 ,f) int X1,y1 ,X2 ,y2 ; char *f ; 180 C语言程序设计 { ; char *ptr unsigned size ; FILE *fn ; size=imagesize(X1 ,y1 ,X2 ,y2) ; ptr=malloc(size) ; getimage(X1 ,y1 ,X2 ,y2 ,ptr) ; if ((fn=fopen(f "wb"))==NULL) { , restorecrtmode() ; printf("Cannot create file %s\n") ; ,f eXit(1) ; } fwrite(ptr ,size ,1,fn) ; fclose(fn) ; free(ptr) ; } , void loadimage(X1 y1 ,f) int X1,y1 ; char *f; { ; char *ptr unsigned size ; FILE *fn ; if((fn=fopen(f ,"rb"))==NULL){ restorecrtmode() ; printf("Cannot open file %s\n") ; ,f eXit(1) ; } size = 0 ; while(fgetc(fn) != EOF) size++ ; ptr=malloc(size) ; rewind(fn) ; fread(ptr ,size ,1,fn) ; fclose(fn) ; putimage(X1 ,y1 ,ptr ,COPY_PUT) ; free(ptr) ; } /* 图像存取示例 */ void initialize(void) ; void quit(void) ; int X,y; char *f="graph.dat" ; void main(void) { initialize() ; outteXtXy(X/2 ,-10 ,"Saving Image") ; 下载 下载 第9章 实用编程技巧 181 saveimage(0 ,0,X,y,f) ; outteXtXy(X+50 ,y+y/2+10 ,"Loading Image") ; loadimage(X ,y/2 ,f) ; quit( ) ; } void initialize( ) { , int gdrive=DETECT gmode ,errorcode ; int angle=360/MAXCOLORS color ; , initgraph(&gdrive ,&gmode , "") ; errorcode=graphresult( ; ) if (errorcode!=grOk) { ,g ; printf("graphics error:%s\n" rapherrormsg(errorcode)) printf("press any key to halt:") ; getch( ) ; eXit(1) ; } ; X=getmaXX() *2/5 y=getmaXy( ) *2/5 ; setviewport(X/2 ,y/2 ,getmaXX( )-X/2 getmaXy( )-y/2 0) ; , , setteXtjustify( CENTER_TEXT ENTER_TEXT) ; ,C rectangle(0 ,0,X,y) ; for (color=0 color<MAXCOLORS ;color++){ ; ,color) ; setfillstyle(SOLID_FILL pieslice(X/2 ,y/2 ,color*angle ,(color+1)*angle ,y/3) ; } } void quit( ) { ; getch( ) closegraph( ) ; } 9.1.3 屏幕显示格式的控制方法 一个良好的屏幕格式能给操作者提供很大的方便,也给人们一种赏心悦目的感觉。 为了控制屏幕显示格式,需要编写两个屏幕控制。我们可以借助这两个函数,设计出用 户所需要的屏幕显示格式。 下面是一个简单的演示程序。程序中借助这两个函数,在屏幕中间显示变动的数字。 [例9-4] 控制显示格式 tx3.c # i nclude<stdio.h> # include<bios.h> #include<dos.h> 182 C 语言程序设计 下载 void cls(int) ; void gotoXy(int int) ; , void cls(line) int line ; { union REGS in out ; , in.X.aX=0600 ; in.X.cX=0000 ; in.h.dh=line-1 ; in.h.dl=79 ; in.h.bh=07 ; int86(0X10 ,&in ,&out) ; } void gotoXy (X y) , int X,y; { union REGS in out ; , in.h.dh=X ; in.h.dl=y ; in.h.ah=02 ; in.h.bh=0 ; int86 (0X10 &in ,&out) ; , } void main( ) { int i; gotoXy(0 ,0) ; cls(11) ; gotoXy(4 ,20) ; printf("------------------------") ; gotoXy(5 ,20) ; printf("|proceeding record No: |") ; gotoXy(6 ,20) ; printf("------------------------") ; for (i=1 i<=1000 ;i++) ; { gotoXy(5 ,45) ; printf("%4d" ,i) ; } } 9.1.4 使图形软件脱离BGI的方法 大家知道,用 Tu r b o 编译的图形软件,在运行时,当前目录下必须要有相应的 B G I文件。 例如 C G A . B G I 、 EGAVGA.BGI等。这对于应用程序是不太方便的。为了解决这个问题,可使 用以下方法。 1) 用Turbo C 提供的 BTIOBJ.EXE把*.BGI编译成目标文件 *.OBJ。例如: 第9章 实用编程技巧 下载 C> BGIOBJ 183 CGA 这样就产生了一个 C G A . O B J。同样,将 EGAVGA.BGI进行编译,再把这些 * . O B J 拷入 T C 目录下的 LIB 子目录中。 使用时,先编译一个 p r o j e c t 文件,把需要的 O B J 文件列入 p r o j e c t 文件中。例如对 BGIDEMO .C ,相应 BGIDEMO.PRJ可写为: BGIDEMO.C EGAVGA.OBJ 这样在集成环境下调试 BGIDEMO.C时,当前目录下不需要有 EGAVGA.BGI,最后生成的 EXE 文件,运行时也不需要 EGAVGA.BGI。 2) 对命令行编译 TCC.EXE只要在编译时列入相应的 EGAVGA.BGI,最后生成的EXE文件。 运行时也不需要 EGAVGA.BGI 的EXE文件。 3) 对于经常使用 TCC.EXE的用户,可用 Turbo C 提供的 TLIB.EXE将上述 *.obj扩充进图形 库GRAPHICS.LIB。方法为: C> TLIB GRAPHICS.LIB +EGAVGA.OBJ 这样,以后在编译时,只要联贯新的 GRAPHICS.LIB就可编译出不需要 BGI 的图形软件。 9.1.5 拷贝屏幕图形的方法 在图形方式下,有时需将屏幕信息在打印机上输出。为了输出屏幕图形,要有一个内存 驻留程序。这时要考虑内存驻留程序的激活问题,激活时机的控制以及 TSR的初始化问题。 所有 T S R 程序都靠键来激活,因此需用自己的键盘中断程序代替 D O S 的键盘中断程序, 激活 T S R程序是在 D O S不忙时进行的。当 D O S正在使用时,有一个字节被置 1,当它未被使用 时,该字节为 0,这个地址可用 3 4 H号中断获取,该中断返回后, E S 寄存器存放段地址, B X 寄存器放位移,因而,当该字节为 0时, TSR 程序才允许激活。 实现时,使用了 interrupt类型说明、寄存器伪变量和程序终止并驻留的技术。 首先由 main( ) 函数完成初始化工作,它先取得中断 5子程序的地址,然后设置新的 5 号中 断程序,这由 g e t v e c t 和 s e t v e c t 来实现的。最后用 k e e p ( 0, s i z e ) 使程序驻留,并保存 1 6 * s i z e 大 小的数据空间。 i n t e r r u p t类型说明符允许我们编写中断处理程序,说明该类型的函数进入时,会自动保存 各寄存器的值,退出时恢复各寄存器的值,新的中断处理程序先判断当前的显示方式,若是 文本方式,则执行老中断程序,若是图形方式,就执行新的图形拷贝程序。 每当发生中断调用时, D O S 转移到一个内部很小的数据栈上工作,为了确保程序能正常 工作,必须建立起自己的数据栈,用寄存器伪变量 S P 和S S 实现。打印完屏幕后,恢复原环境 ( 此程序针对 EPSON LQ 系列打印机 )。 [例9-5] 屏幕图形硬拷贝程序 tx4.c #include <dos.h> #include<stdio.h> #include<graphics.h> #include<bios.h> #define STK_SIZE 0 1000 × 184 C语言程序设计 #define print(ch) biosprint(0h ,0) ,c void set_graphics(int cols) ; void print_scr(int X1 int y1,int X2,int y2); , char video_mode(void) ; void interrupt new_int5(void) ; void interrupt (*old_int5)() ; unsigned char stack [STK_SIZE] ; unsigned sp ss ; , main() { union REGS r ; struct SREGS s ; old_int5=getvect(5) ; keep(0 ,2000) ; return 0 ; } void interrupt new_int(void) { char vmode ; vmode=video_mode() ; if((vmode==2)|(vmode==3)|(vmode==7)) setvect(5 ,old_int5) ; else{ disable() ; ss=_SS ; sp=_SP ; _SS=_DS ; _SP=(unsigned)&stack[STK_SIZE-2] ; enable( ) ; print_scr(0 ,0,639 ,349) ; disable() ; _SP=sp ; _SS=ss ; enable( ) } ; } void print_scr(int X1 int y1,int X2,int y2) , { register int i X,y,pX ; , int cols color ,sum ; , X2++ ; y2++ ; cols=X2-X1 ; for(y=y1 ;y<y2 ;y+=8){ set_graphics(cols) ; for(X=X1 ;X<=X2 ;X++){ sum=0 ; for(i=0 ;i<8 ;i++){ 下载 第9章 实用编程技巧 下载 185 if(y+i<y2){ color=getpiXel(X ,y+i) ; if(color)sum+=1<<(7-i) ; } } print(sum) ; } printf("\n") ; } } void set_graphics(int cols) { char den_code ; union aa{ unsigned char c[2] ; unsigned int i ; }u ; u.i=cols ; print(27) ; print(65) ; print(8) ; print(27) ; print(76) ; print(u.c[0]) ; print(u.c[1]) ; } char video_mode(void) { union REGS r ; r.h.ah=15 ; return int86(0 10 ,&r ,&r)&255 ; × } 9.1.6 随意改变VGA显示器显示颜色的技巧 V G A 显示适配器是一种使用很普遍的高性能图形适配器,最多有 2 6 1 2 4 4( 6 4 × 6 4 × 6 4) 种颜色,可同时使用其中的任意种。但是目前来说,普遍使用的仍是非曲直 6种颜色的显示模 式。 若仅仅用现有的 1 6 种颜色编制图形软件或窗口软件,画面则显得单调,能否根据需要自 由设置这 1 6 种颜色呢?答案是肯定的。在显示器上某一色号所显示的颜色仅由显示卡上的 D A C 颜色寄存器中的值决定。 D A C 颜色寄存器是一个 1 8位的寄存器,红、绿、蓝各占六位, 卡上共有 2 5 6 个这样的寄存器,分别对应于 2 5 6个色号。开机时的 1 6种颜色(即 0 ~ 1 5 号)被设 置成如下寄存器及比色: 色号 0 对应寄存器号码 红 绿 蓝 0 0 0 0 186 C语言程序设计 下载 1 1 0 0 42 2 2 0 42 0 3 3 0 42 42 4 4 42 0 0 5 5 42 0 42 6 20 42 21 0 7 7 42 42 42 8 56 21 21 21 9 57 21 21 63 10 58 21 63 21 11 59 21 63 63 12 60 63 21 21 13 61 63 21 63 14 62 63 63 21 15 63 63 63 63 如果改变这 1 6个寄存器中的值,即可改变在屏幕上显示的 1 6 种颜色,而对程序运行没有 任何其它影响。 具体实现可以通过调用 VGA BIOS 中断进行,也可以通过 V G A 寄存器编程实现。在西文 方式下以上两种方法都可以使用,但在中文系统下,由于中文系统修改了视频中断 10H,因此 只能通过 VGA 寄存器编程实现。 下面两个程序,一个用于设置颜色,另一个用于检查设置。设置的颜色可从 2 6 1 2 4 4 种颜 色中任意设定 0 ~ 1 5 号颜色。在程序中: r e d , g r e e n,blue 取值为 0 ~ 6 3; c o l o r n u m 为要改变颜 色所对应的寄存器号。使用的格式为: setcolor getcolor < 寄存器中 > < 寄存器号 > <红> <绿> <蓝> 例如,对亮绿色( 10号)进行改色,可在 DOS提示符下,键入: setcolor 58 35 25 15 回车后, 1 0 号码色将成为由红色 3 5 、绿色 2 5 、蓝色 1 5、调成的新颜色。若要检查 1 0 号的 设置,可在 DOS提示符下,键入: getcolor 58 回车后,屏幕上将出现寄存器号、红、绿、蓝的颜色值。 [例9-6] 设置新色彩 setcolor.c 。 /* 格式: setcolor< 寄存器中 >< 红 > < 绿 > < 蓝 >*/ #include<dos.h> #include<stdlib.h> #include<stdio.h> void main(int argc char *argv) , { int colornum read0 ,green0 ,blue0 ; , union REGS r ; 第9章 实用编程技巧 下载 187 if(argc<5) { printf("input error!\n") ; eXit(1) ; } colornum=atoi(argv[1]) ; read0=atoi(argv[2]) ; green0=atoi(argv[3]) ; blue0=atoi(argv[4]) ; outportb(0 ×3c8 ,colornum) ; outportb(0 ×3c9 ,read0) ; outportb(0 ×3c9 ,green0) ; outportb(0 ×3c9 ,blue0) ; } /* 检查颜色设置 getcolor.c*/ /* 格式: getcolor< 寄存器号 >*/ #include<dos.h> #include<stdio.h> #include<stdlib.h> , main(int argc char *argv[ ] ) { , int colornum read0 ,green0 ,blue0 ; union REGS r ; if(argc<2) { ; printf("input error!\n") eXit(1) ; } colornum=atoi(argv[1]) ; outportb (0 3c7 ,colornum) ; × read0=inportb(0 ×3c9) ; green0=inportb(0 ×3c9) ; blue0=inportb(0 ×3c9) ; printf("No=%d , read=%d ,green=%d , blue=%d\n" ,colornum ,read0 ,green0 , blue0) ; } 9.1.7 用随机函数实现动画的技巧 在一些特殊的 C语言动画技术中,可以利用随机函数 int random(int num ) 取一个 0~num范 围内的随机数,经过某种运算后,再利用 C 语 言的作图语句产生各种大小不同的图形,也能 产生很强的移动感。 程序 d h 1 . c就是利用随机函数来产生动画应用。该程序运行后,屏幕中间绘出一台微型计 算机,微机下方同时显示“ c o m p u t e r ”的放大字形,在画出微机的小屏幕内,产生各种大小 不同、颜色各异的矩形,这些矩形互相覆盖,给人以极强的动画感。 188 C语言程序设计 下载 程序中改变 x 1 、x 2 、y 1 、y 2 的值,能将图形移动屏幕的任何位置,改变 x、 y 的值,能将 图形放大或缩小。 [例9-7] 动画显示程序 DH1.C #include<conio.h> #inclu]de<stdio.h> #include<stdlib.h> #include<graphics.h> #include<time.h> #define X1 260 #define X2 320 #define y1 140 #define y2 180 #define Xy 16 int gdrive gmode ,mcolor ,ecode ; , struct palettetype palette ; void initialize(void) ; void rbars(void) ; int main( ) { initialize( ) ; /* 初始化图形系统 */ /* 显示放大字体 */ setcolor(YELLOW) ; settextstyle(TRIPLEX_FONT ,HORIZ_DIR ,4) ; settextjustify(CENTER_TEXT ,CENTER_TEXT) ; outtextxy((getmaXX( )/2-17) 60 ,"COMPUTER") ; ,3 rbars( ) ; /* 主程序*/ closegraph( ) /* 关闭图形系统 */ ; return 1 ; } void initialize(void) { gdrive=DETECT ; initgraph (&gdrive &gmode ,"") ; , ecode=graphresult( ) ; if (ecode!=0) { printf("Graphice Error : %d\n " rapherrormsg(ecode)) ,g eXit(1) ; } getpalette(&palette) ; mcolor=getmaXcolor( )+1 ; } void rbars(void) { int color ; /* 画计算机图形 */ setcolor(DARKGRAY) ; ; 第9章 实用编程技巧 下载 189 bar3d(X1-20 ,y1-20 ,X2+56 ,y2+70 ,0,3) ; setfillstyle(CLOSE_DOT_FILL ,BLUE) ; setfillstyle(SOLID_FILL ,RED) ; circle(X2+28 ,y2+60 ,4) ; bar(X1+4 ,y1+78 ,X1+20 ,y1+83) ; setcolor(MAGENTA) ; circle(X2+28 ,y2+60 ,4) ; circle(X2+16 ,y2+60 ,4) ; circle(X2+4 ,y2+60 ,4) ; setcolor(WHITE) ; setfillstyle(SOLID_FILL ,DARKGRAY) ; bar3d(X1-60 ,y1+120 ,X1+154 ,y1+170 ,0,2) ; bar3d(X1+120 ,y1+126 ,X1+100 ,y1+164 ,0,2) ; line (X1+20 y1+145 ,X1+100 ,y1+145) ; , setfillstyle(SOLID_FILL ,GREEN) ; bar(X1+26 ,y1+130 ,X1+34 ,y1+132) ; bar(X1+26 ,y1+150 ,X1+34 ,y1+152) ; setfillstyle(WIDE_DOT_FILL ,RED) ; bar(X1-24 ,y1+128 ,X1-44 ,y1+142) ; /* 利用随机函数实现矩形画面互相覆盖,产生动感 */ while(!kbhit( )) { color=random(mcolor-1)+1 ; setcolor(color) ; setfillstyle(random(11)+1 ,color) ; bar3d(X1+random(getmaXX( )/Xy) ,y1+random(getmaXy( )/Xy) , X2+getmaXX( )/Xy y2+ getmaXy( )/Xy 0,5) ; , , } } 9.1.8 用putimage 函数实现动画的技巧 计算机图形动画显示的是由一系列静止图像在不同位置上的重现。计算机图形动画技术 一般分为画擦法和覆盖刷新法两大类。画擦法是先画 T时刻的图形,然后在 T + △T时刻把它擦 掉,改画新时刻的图形是由点、线、圆等基本图元组成。这种一画一擦的方法对于实现简单 图形的动态显示是比较有效的。而当需要显示比较复杂的图形时,由于画擦图形时间相对较 长,致使画面在移动时出现局闪烁现象,使得动画视觉效果变差。所以,为提高图形的动 态显示效果,在显示比较复杂的图形时多采用覆盖刷新的方法。 在Turbo C 的图形函数中,有几个函数可完成动画的显示: getimage(int left , int top ,int right ,int bottom ,void far*buf) 函数把屏幕图形部分拷贝 到由 buf所指向的内存区域。 imagesize() 函 数用来确定存储图形所需的字节数,所定义的字节数根据实际需要可以定 义得多一些。 p u t i m a g e ( ) 函数可以把 g e t i m a g e ( ) 存储的图形重写在屏幕上。利用 p u t i m a g e ( ) 函数中的 C O P Y _ P U T 项,在下一个要显示的位置上于屏幕中重写图像,如此重复、交替地显示下去, 190 C语言程序设计 下载 即可达到覆盖刷新的目的,从而实现动画显示。由于图形是一次性覆盖到显示区的,并在瞬 间完成,其动态特性十分平滑,动画效果较好。 程序 d h 2 . c 就是根据上述思路而实现的。程序运行时,将在屏幕上出现一个跳动的红色小 球。 [例9-8] 动画显示程序 dh2.c #include <stdio.h> #include<graphics.h> #include<alloc.h> #include<conio.h> void main(void) { int driver=DETECT mode ; , int k=0 i,m,m1 ; , int maXX mayy ,size ; , char *buf ; initgraph(&driver ,&mode ," "); maXX=getmaXX() ; mayy=getmaXy() ; setfillstyle(SOLID_FILL ,LIGHTGRAY) ; bar(1 ,1,maXX ,mayy) ; setcolor(RED) ; for(i=0 ;i<=10 ;i++) circle(150 ,150 ,i) ; size=imagesize(100 ,100 ,250 ,200) ; if(size != -1) buf=malloc(size) ; if(buf) { getimage(100 ,100 ,250 ,200 ,buf) ; m=120 ;m1=m ; do{ k=k+1 ; if ((m1+100)>mayy) { for(m=m+30 ;m<maXX ;m=m+30) { m1=m1-20 ; putimage(m ,m1 ,buf ,COPY_PUT) ; } } if((m+100)>maXX) { m=m-100 ; for(m1=m1+100 ;m1>=1 ;m1=m1-10) { 第9章 实用编程技巧 下载 191 m1=m1-19 ; putimage(m ,m1 ,buf ,COPY_PUT) ; } for(m=m ;m>1 ;m=m-30) { m1=m1-17 ; putimage(m ,m1 ,buf ,COPY_PUT) ; } } m1=m1+20 ; m=m+20 ; putimage(m ,m1 ,buf ,COPY_PUT) ; }while(k!=1000) ; getch() ; } restorecrtmode() ; } 9.2 菜单设计技术 菜单在用户编写的程序中占据相当一部分内容。设计一个高质量的菜单,不仅能使系统 美观,更主要的是能够使操作者使用方便,避免一些误操作带来的严重后果。 9.2.1 下拉式菜单的设计 下拉式菜单是一个窗口菜单,它具有一个主菜单,其中包括几个选择项,主菜单的每一 项又可以分为下一级菜单,这样逐级下分,用一个个窗口的形式弹出在屏幕上,一旦操作完 毕又可以从屏幕上消失,并恢复原来的屏幕状态。 设计下拉式菜单的关键就是在下级菜单窗口弹出之前,要将被该窗口占用的屏幕区域保 存起来,然后产生这一级菜单窗口,并可用光标键选择菜单中各项,用回车键来确认。如果 某选择项还有下级菜单,则按同样的方法再产生下一级菜单窗口。 用 Turbo C 在 文本方式时提供的函数 gettext( ) 来放屏幕规定区域的内容,当需要时用 puttext( )函数释放出来,再加上键盘管理函数 bioskey( ),就可以完成下拉式菜单的设计。 程序 m e n u 1 . c 是一个简单拉式菜单。运行时在屏幕上一行显示主菜单的内容,当按 ALT+F 则进入 F i l e子菜单,然后可用光标键移动色棒选择操作,用回车确认。用 E s c 键退出主菜单, 并可用 ALT+X退出菜单系统。 [例9-9] 下拉式菜单 menu1.c /* 下拉式菜单 menu1.c*/ #include<conio.h> #include<stdio.h> #include<stdlib.h> #include<bios.h> void main(void) { 192 C语言程序设计 下载 int i,key ,key0 ,key1 ,y,test ; char *m[ ]={"File " "Edit " "Run " "Compile " "Projsct " , , , , , "Options " "Debug " "Break/watch "} , , ; /* 定义主菜单的内容 */ char *f[ ]={"Load F3" , /* 定义FILE 子菜单的内容 */ "Pick ALT+F3" , "New ", "Save F2" , "Write to " , "Directory " , "Change dir " , "Os shell " , "Quit ALT+X"} ; char buf[16*10*2] buf1[16*2] ; /* 定义保存屏幕区域的数组变量 */ , textbackground(BLUE) ; /* 设置文本屏幕背景色 */ clrscr( ) ; /* 屏幕背径着色 */ window(1 ,1,80 ,1) ; /* 定义一个文本窗口 */ textbackground(WHITE) ; /* 设置窗口背景色 */ textcolor(BLACK) ; clrscr( ) ; window(1 ,1,80 ,2) ; for (i=0 i<8 ;i++) ; cprintf("%s" ,m[i]) ; /* 显示主菜单的内容 */ while(1) { key=0 ; while(bioskey(1) == 0) ; /* 等待键盘输入 */ key = bioskey(0) ; /* 取键盘输入码 */ key = key&0Xff? 0:key>>8 /* 只取扩充键码 */ ; if(key == 45) eXit (0) ; /* 如果按 ALT+X 键则退出 */ if(key == 33) /* 如果按ALT+F 则显示子菜单 */ { textbackground(BLACK) ; textcolor (WHITE) ; gotoxy(4 ,1) ; cprintf("%s" ,m[0]) ; gettext(4 ,2,19 ,11 ,buf) ;/* 保存窗口区域的在原有内容 */ window(4 ,2,19 ,11) ; textbackground(WHITE) ; textcolor(BLACK) ; clrscr( ) ; window(4 ,2,19 ,12) ; gotoxy(1 ,1) ; /* 作一个单线形边框 */ putch(0xff) ; for (i=2 i<10 ;i++) ; { gotoxy(1 ,i) ; putch(0 ×b3) ; gotoxy(16 ,i) ; putch(0 ×b3) ; } gotoxy(1 ,10) ; 第9章 实用编程技巧 下载 193 putch(0Xc0) ; for (i=2 i<16 ;i++) ; putch(0Xc4) ; putch(0Xd9) ; for (i=2 i<10 ;i++) ; { gotoxy(2 ,i) ; cprintf("%s" ,f[i-1]) ; } gettext(2 ,2,18 ,3,buf1) ; textbackground(BLACK) ; textcolor(WHITE) ; gotoxy(2 ,2) ; cprintf("%s" ,f[0]) ; y=2 ; key1=0 ; while((key0!=27)&&(key1!=45)&&(key0!=13)) {/* 输入为ALT+X ,回车或 ESC 键退出循环 */ while(bioskey(1)==0) ; /* 等待键盘输入 */ key0=key1=bioskey(0) ; /* 取键盘输入码 */ key0=key0&0Xff ; /* 只取扩充码 */ key1=key1&0Xff? 0:key1>>8 ; if (key1==72||key1==80) /* 如果为上下箭头键 */ { puttext(2 ,y,18 ,y+1 ,buf1) ; /* 恢复原来的信息 */ if (key1==72) y= y==2? 9:y-1 /* 上箭头处理 */ ; if (key1==80) y= y==9? 2:y+1 /* 下箭头处理 */ ; getteXt(2 ,y,18 ,y+1 ,buf1) ; /* 保存新色棒前产生这一位置屏幕内容 */ textbackground(BLACK) ; /* 产生新色棒 */ textcolor(WHITE) ; gotoxy(2 ,y) ; cprintf("%s" ,f[y-1]) ; } } if(key1 == 45) eXit(0) ; /* 按ALT+X 退出*/ if(key0 == 13) /* 回车按所选菜单项进行处理 */ { switch(y) { case 1: break ; case 2: break ; case 9: eXit(0) ; default: break ; } } 194 C 语言程序设计 下载 else /* ESC 键返回主菜单 */ 按 { window(1 ,1,80 ,2) ; puttext(4 ,2,19 ,11 ,buf) ; /* 释放子菜单窗口占据的屏幕原来内容 */ textbackground(WHITE) ; textcolor(BLACK) ; gotoxy(4 ,1) ; cprintf("%s" ,m[0]) ; } } } } 9.2.2 选择式菜单的设计 所谓选择式菜单,就是在屏幕上出现一个菜单,操作者可根据菜单上所提供的数字或字 母按相应的键去执行特定的程序,当程序执行完后又回到主菜单上。 这种菜单编制简单,操作方便,使用灵活,尤其适用于大型管理程序。如果在自动批处 理文件上加入这种菜单后,操作者可根据菜单上的提示,进行相应的操作,这样可以简化许 多步骤,对一般微机用户来说是比较适合的。 [例9-10] 选择式菜单程序 menu2.c # include<stdio.h> #include<stdlib.h> #include<conio.h> main( ) { char ch ; int i; do { system("cls") ; printf("\n\t1. into Turbo C ; ") printf("\n\t2. into Windows " ) ; printf("\n\t3. into Wps ; ") printf("\n\t4. into Dbase ; ") printf("\n\t0. Quit \n\n ; ") printf("\t Please select:") ; ch=getch( ) ; switch(ch) { case '1' : system("tc") break ; ; case '2' : system("win") break ; ; case '3' : system("wps") break ; ; case '4' : system("dbase")break ; ; case '0' : system("cls") eXit(1) ; ; default: printf("\n\t*** wrong !!! ***\n") ; 第9章 实用编程技巧 下载 195 for (i=0 i<600 ;i++) ; ; {;} } } while (1) ; } 9.2.3 实现阴影窗口的技巧 目前,许多应用软件都采用了输出窗口技术,有的甚至使用了带有阴影的输出窗口。这 种技术给人们以新鲜醒目的感觉,可达到事半功倍的作用。 程序 m e n u 3 . c 是一个阴影窗口的例子。其中用到两个自编函数,一个是建立窗口函数 set_win( ) ,另一个是建立带有阴影部分的窗口函数 set_bkwin( ) 。这两个函数需要传递以下几 个参数: int X1 y1 ,X2 ,y2 ,b,bc ,tc , char *head 其中: x1、y1、 x2、y2决定了窗口边框大小, b用来选择窗口的类型;当 b=0时,无边框, 当 b =1 时,单线边框,当 b = 2 时,上下边框为双线,左右边框为单线,当 b = 3 时,双线边框, 当 b = 4 时,上下边框为单线,左右边框为双线;参数 b c 用来决定窗口的背景颜色,其范围为 0~7;参数 tc决定窗口内字符的颜色,其范围为 0~15; 参数 head为窗口名称。 在文本状态下,一个字符在屏幕缓冲区内要占用2个字节来存储。且字符内容在前属性 在后,顺序存储。所谓属性,就是字符的背景颜色和字符颜色,我们可以通过改变其属性字 节,来实现窗口的阴影。 [例9-11] 阴影窗口程序 menu3.c #include<stdio.h> #include<string.h> #include<conio.h> #define screen (*screen_ptr) typedef struct texel_struct { ; unsigned char attr }teXel ; typedef texel screen_array[25][80] ; screen_array far *screen_ptr = (screen_array far *) 0Xb800 ; void set_win( int X1,int y1, int X2,int y2,int b ,int bc, int tc, char *head) ; void set_bkwin(int ,i n t y ,i n t X ,i n t y,i n t ,i n t b ,i n t t,char X1 1 2 2 b c c *head) ; void main(void ) { set_bkwin(1 ,2,25 ,18 ,2,2,1,"window") ; getch() ; 196 C语言程序设计 下载 } void set_bkwin(X1 y1 ,X2 ,y2 ,b,bc ,tc ,head) , int X1, y1 , X2 , y2 , b, bc , tc ; char *head ; { int i, j; for (i=X1+1 i<X2+2 ;i++) ; { for (j=y2 j<y2+1 ;j++) ; screen[j][i].attr=8 ; } for (i=X2+1 i<X2+2 ;i++) ; { for (j=y1 j<y2+1 ;j++) ; screen[j][i].attr=8 ; } set_win(X1 ,y1 ,X2 ,y2 ,b,bc ,tc ,head) ; } void set_win(X1 y1 ,X2 ,y2 ,b,bc ,tc ,head) , int X1,y1 ,X2 ,y2 ,b,bc ,tc ; char *head ; { int i,j ; int c[4][6] ={ {0Xda , 0Xc4 , 0Xbf ,0Xb3 , 0Xc0 , {0Xd5 , 0Xcd , 0Xb8 ,0Xb3 , 0Xd4 , {0Xc9 , 0Xcd , 0Xbb ,0Xba , 0Xc8 , {0Xd6 , 0Xc4 , 0Xb7 ,0Xba , 0Xb3 , }; j=(X2-X1)/2-strlen(head)/2+X1+1 ; textbackground(bc) ; textcolor(tc) ; if(b!=0) { window(1 ,1,80 ,25) ; gotoxy(X1 ,y1) ; putch(c[b-1][0]) ; for (i=X1+1 i<X2 ;i++) ; putch(c[b-1][1]) ; putch(c[b-1][2]) ; for (i=y1+1 i<y2 ;i++) ; { gotoxy(X1 ,i) ; putch(c[b-1][3]) ; gotoxy(X2 ,i) ; putch(c[b-1][3]) ; } 0Xd9 0Xbe 0Xbc 0Xbd }, }, }, }, 第9章 实用编程技巧 下载 197 gotoxy(X1 ,y2) ; putch(c[b-1][4]) ; for (i=X1+1 i<X2 ;i++) ; putch(c[b-1][1]) ; } if (head[0]!=NULL) { gotoxy(j ,y1) ; textcolor(WHITE) ; textbackground(BLUE) ; cprintf("%s" ,head) ; } textcolor(tc) ; textbackground(tc) ; window(X1+1 ,y1+1 ,X2-1 ,y2-1) ; clrscr( ) ; } 9.3 音响技巧 9.3.1 音乐程序设计 我们知道,音乐是音高和音长的有序组合,设计微机音乐最重要的就是如何定义音高和 音长,以及如何让扬声器发出指定的音符。下面给出音符与频率的关系表。 C 语言提供的三个 函数 s o u n d ( )、n o s o u n d ( ) 和c l o c k ( )可以很方便地解决上述的问题。 s o u n d ( )函数可以用指定频率 打开 P C 机扬声器直到用 n o s o u n d ( )函数来关闭它; c l o c k ( )函数正好用来控制发声时间,而且它 不受 PC机主频高低的影响。下面这段程序可使微机发出 c 调1的声音。 表9-2 音符与频率关系表 音符 频率 单符 频率 音符 频率 c 1 262 c 1 523 c 1 1047 d 2 294 d 2 587 d 2 1175 [例9-12] 音乐程序 music1.c #include<stdio.h> #include<dos.h> void pause(int) ; void sound1(int int) ; , void main(void) e 3 330 e 3 659 e 3 1319 f 4 349 f 4 698 f 4 1397 g 5 392 g 5 784 g 5 2568 a 6 440 a 6 880 a 6 1760 b 7 494 b 7 988 b 7 1976 198 C语言程序设计 { int i,freq ,speed=5 ; int time=4*speed ; char *qm="iddgwwwqqgfff dddfghhhggg ddgwwwqqgfff\ ddffhjqqqqq wpggjhgddgqq hhqwwqjjjggg\ ddgwwwqqqgfff ddffhjqqqqqq" * 定义歌曲 */ ;/ while (*qm++ !='\0'){ i=1 ; switch(*qm){ case 'k': time=1*speed ; i=0 ; break ; case 'i': time=6*speed ; i=0 ; break ; case 'o': time=10*speed ; i=0 ; break ; case 'p': pause(time) ; i=0 ; break ; case 'a': freq=523 ; break ; case 's': freq=587 ; break ; case 'd': freq=659 ; break ; case 'f': freq=698 ; break ; case 'g': freq=784 ; break ; case 'h': freq=880 ; break ; case 'j': freq=988 ; break ; case 'z': freq=262 ; break ; case 'X': freq=294 ; break ; case 'c': 下载 下载 第9章 实用编程技巧 freq=330 ; break ; case 'v': freq=349 ; break ; case 'b': freq=392 ; break ; case 'n': freq=440 ; break ; case 'm': freq=494 ; break ; case 'q': freq=1047 ; break ; case 'w': freq=1175 ; break ; case 'e': freq=1319 ; break ; case 'r': freq=1397 ; break ; case 't': freq=2568 ; break ; case 'y': freq=1760 ; break ; case 'u': freq=1976 ; break ; default: i =0; break ; } if(i) sound1(freq ,time) ; } } void sound1(int freq int time) /*freq , 为频率, time为持续时间 */ { union { ; long divisor unsigned char c[2] ; } count ; 199 200 C语言程序设计 下载 unsigned char ch ; count.divisor=1193280/freq ; /* 1193280 是系统时钟速率 */ outp(67 ,182) ; outp(66 ,count.c[0]) ; outp(66 ,count.c[1]) ; ch=inp(97) ; outp(97 ,ch|3) ; pause(time) ; outp(97 ,ch) ; } void pause(int time) { int t1,t2 ; union REGS in out ; , in.h.ah=0X2c ; int86(0X21 ,&in ,&out) ; /* 取当前时间 */ t1=t2=100*out.h.dh+out.h.dl ; /*out.h.dh 为秒值, out.h.dl 为1/100 秒值 */ while(t2-t1<time) { int86(0X21 ,&in ,&out) ; t2=100*out.h.dh+out.h.dl ; if (t2<t1)t2+=6000 ; /* 增加一分钟 */ } } 9.3.2 自动识谱音乐程序 音乐的简谱是由各种音符构成的,将这些音符按不同的频率、持续时间连续发出声音, 就形成了旋律。因此音乐演奏的关键是曲调的定义与识别及发音时间的控制。 为了实现计算机自动识谱,可定义一套曲调的编码,其中“ 1 2 3 4 5 6 7 ”表示中音的 1 、 2、 3 、 5 、 6 、 7 ;高音可在中音之后加“ * ”;低音在中音之后加“;”号;减号“ - ”表示两 拍;“ .”表示一拍半;“ =”表示四分之一拍;下画线“ _”表示 1/2拍。 我们可用文本编辑软件(如 E D I T )按上述编码将一首曲子的乐谱输到计算机中,得到乐 谱文件供程序调用。其中乐谱文件的第一节的数字分别为:节拍基数和速度,输入时用空格 分开。乐谱文件的第二行到最后一行为歌曲的内容,每小节之间用“ /”分开。 下面根据“世上只有妈妈好”编制曲谱文件如下Ma.txt: 8 50 6.5_3 5 /1* 3_2-/2_3_5 6_5_6-/3 5_6_/3 2 5_6_5 1-/5.3 3 /1_6;5_ 2_/_/6.;/_5-/ 曲谱文件首行是 8节拍基数, 50是演奏速度,从第二行开始至文件尾,均为曲谱正文。 需要说明的是在曲谱文件中,每个音符应跟上其音符的节拍,文中空格‘ ’符表示其 音符是全音符。 程序中设置两个整型数组 S a 和S b 用于存放音符的频率及节拍,其内容是一一对应的,若 下载 第9章 实用编程技巧 201 Sa[i]存放音符,则 Sb[i]是该音符的演奏节拍。 发声的原理是利用 C 的标准库函数 S o u n d ( ) 发声,若要发音中音“ 1 ”音符,其音频为 “262” ,则函数调用 S o u n d ( 2 6 2 ) 则可通过 P C 机扬声器发音控制音符发声的时间由标准函数 delay( ) 决定, nosound( ) 函数为关闭扬声器发音。 源程序如下: #include <stdio.h> #include<dos.h> #include<math.h> #include<stdlib. h> #include<string.h> #include<bios.h> int sa[1000],sb[1000]; int j,step,rate,len,lenl,half; chat, strl27[127]; int getmusic(); void chang(void); void music(void); /************************/ int main() { len=0; len1=0; if(getmusic()!=0) exit (1); delay(500); music ( ); return 0; } /******************/ int getmusic() { FILE * fp; if ((fp=fopen("ma.txt","r"))==NULL) 打开曲谱文件 * /* { printf("file not open\n") ; return 1; } fscanf(fp, "%d %d\n", &step,&rate); while(! fgets(str127, 127, fp)==NULL) { chang ( ); } fclose (fp); return 0; } /*******************/ void chang(void) { 202 C 语言程序设计 int k; ; /* 组合音符频率 */ for (k=0;k<strlen(strl27) k++) if ((str127[k]>=‘0’ &&(str127[k]<’7’)) ) { sa[len]=str127[k]-48; Switch(sa[len]) { case 1:sa[len]=262;break; case 2:sa[len]=294;break; case 3:sa[len]=330;break; case 4:sa[len]=349;break; case 5:sa[len]=392;break; case 6:sa[len]=440;break; case 7:sa[len]=494;break; case 0:sa[len]=0; } len++; if(len>999) exit(0); half=0; } for (k=0;k<strlen(str127);k++)sb[k]=step; for (k=0;k<strlen(str127);k++) { 组合音符节拍 */ switch(str127[k]) /* { case '_':len1++; break; case'.': sb[len1]=sb[len]*3/4; lenl++; break; case '=': sb[len1]=ceil(sb[len1]/4); lenl++; break; case'': sb[len1]=sb[len1]/2; lenl++; break; case'_': sb[len1]=ceil(sb[len1]/2); lenl++; break; case';':if(sa[len1]>0) sa[len1]=sa[len1]/2; break; case'*':if(sa[len1]>0) sa[len1]=sa[len1]*2; break; } } } /******************/ void music(void) { 下载 下载 第9章 实用编程技巧 203 j=0; while ((j<=len)&&(bioskey(1)==0)) { sound(2*sa[j]); /* 声*/ 发 delay(2*rate*sb[j]); 延迟*/ /* nosound( ); /* 关闭发声 */ j++; } } 9.3.3 实现后台演奏音乐的技巧 B A S I C语言有一个前后台演奏音乐的语句 p l a y,该语句有很强的音乐功能。而 C 语言虽有 s o u n d ( ) 函数,但不能进行后台演奏,并且必须指明音乐频率,才能使它发声。为此可编制一 个与 play语句相同的后台演奏音乐函数。 若要奏乐,每一个音符必须有一个频率用 sound 去发声,且必须有适当的时间延时,形成 拍子,这样才能演奏音乐。我们可用指定 1拍的时间来推出其它节拍。例如: #define L1 1000 #define L2 L1/2 #define l4 L1/4 即L1为1拍, L2为1/2 拍, L4为1/4拍。 后台演奏,可通过修改 1 C 向量来实现。计算机每秒发出 1 8 . 2 次中断调用 1 C ,因此,就可 以通过它来计算,实现后台演奏。 程序 P L AY. C只是一个简单的后台演奏音乐的例子,将其编译后,就可在 D O S 提示符后直 接执行,演奏过程中,按任一键都将停止演奏。 [例9-13] 后台演奏程序 PLAY.C #include<stdio.h> #include<dos.h> #include<bios.h> #include<conio.h> #define L1 1000 #define L2 L2/2 #define L4 L1/4 void play(int *) ; void interrupt new_int9(void) ; void interrupt (*old_int9)(void) ; int HZ[4][7]={ {131 ,147 ,165 ,175 ,196 ,220 ,247} , {262 ,294 ,330 ,349 ,392 ,440 ,494} , {523 ,587 ,659 ,698 ,784 ,880 ,980} }; int *s ; int buf[100]={11 12 ,12 ,12 ,13 ,12 ,14 ,12 ,16 ,12 ,17 ,12 ,21 ,11 ,22 ,12 , , 204 C语言程序设计 23 ,12 ,24 ,12 ,25 ,12 ,26 ,12 ,27 ,12 ,31 ,12 ,0,0,0} ; void main(void) { play(buf) ; while (*s && ! bioskey(0)) /* 判断结束条件 */ ; nosound() ; setvect(0X1c ,old_int9) ; } void play(int *ms) { s=ms ; old_int9=getvect(0X1c) ; setvect(0X1c ,new_int9) ; } void interrupt new_int9(void) { static int count=0 tt=0 ; , count++ ; if (*s!=0) {if (count>=tt) {sound(HZ[*s/10][*s%10]) ;s++ ; tt=*s*18.2/1000 ; s++ ; count=0 ; } else nosound() ; old_int9() ; } } 下载 ...
View Full Document

Ask a homework question - tutors are online