9.1.8 用putimage 函数实现动画的技巧 9.2 菜单设计技术 9.2.1 下拉式菜单的设计 9.2.2 选择式菜单的设计 9.2.3 实现阴影窗口的技巧 9.3 音响技巧 9.3.1 音乐程序设计 9.3.2 自动识谱音乐程序 9.3.3 实现后台演奏音乐的技巧
9.1.7 用随机函数实现动画的技巧 在一些特殊的C语言动画技术中,可以利用随机函数int random(int num ) 取一个0~num范围内的随机数,经过某种运算后,再利用C 语言的作图语句产生各种大小不同的图形,也能产生很强的移动感。 程序dh1.c就是利用随机函数来产生动画应用。该程序运行后,屏幕中间绘出一台微型计算机,微机下方同时显示“computer”的放大字形,在画出微机的小屏幕内,产生各种大小不同、颜色各异的矩形,这些矩形互相覆盖,给人以极强的动画感。 程序中改变x1、x2、y1、y2的值,能将图形移动屏幕的任何位置,改变x、y的值,能将图形放大或缩小。 [例9-7] 动画显示程序DH1.C #include #inclu]de #include #include #include #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),360,"COMPUTER"); rbars( ); /* 主程序*/ closegraph( )/*关闭图形系统*/ return 1; } void initialize(void) { gdrive=DETECT; initgraph (&gdrive,&gmode," "); ecode=graphresult( ); if(ecode!=0) { printf("Graphice Error: %d\n",grapherrormsg(ecode)); eXit(1); } getpalette(&palette); mcolor=getmaXcolor( )+1; } void rbars(void) { int color; /* 画计算机图形*/ setcolor(DARKGRAY); 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() 函数用来确定存储图形所需的字节数,所定义的字节数根据实际需要可以定义得多一些。 putimage( )函数可以把getimage( )存储的图形重写在屏幕上。利用putimage( )函数中的COPY_PUT项,在下一个要显示的位置上于屏幕中重写图像,如此重复、交替地显示下去,即可达到覆盖刷新的目的,从而实现动画显示。由于图形是一次性覆盖到显示区的,并在瞬间完成,其动态特性十分平滑,动画效果较好。
程序dh2.c就是根据上述思路而实现的。程序运行时,将在屏幕上出现一个跳动的红色小球。 [例9-8] 动画显示程序dh2.c #include #include #include #include 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 { 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) { 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( ),就可以完成下拉式菜单的设计。 程序menu1.c是一个简单拉式菜单。运行时在屏幕上一行显示主菜单的内容,当按ALT+F则进入File子菜单,然后可用光标键移动色棒选择操作,用回车确认。用Esc键退出主菜单,并可用ALT+X退出菜单系统。
[例9-9] 下拉式菜单menu1.c /*下拉式菜单menu1.c*/ #include #include #include #include void main(void) { 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); 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; } } else /*按E S C 键返回主菜单*/ { 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 #include #include 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"); for (i=0;i<600;i++); { ; } } } while(1); }
9.2.3 实现阴影窗口的技巧 目前,许多应用软件都采用了输出窗口技术,有的甚至使用了带有阴影的输出窗口。这种技术给人们以新鲜醒目的感觉,可达到事半功倍的作用。 程序menu3.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;参数t c决定窗口内字符的颜色,其范围为0 ~ 15; 参数head为窗口名称。 在文本状态下,一个字符在屏幕缓冲区内要占用2个字节来存储。且字符内容在前属性在后,顺序存储。所谓属性,就是字符的背景颜色和字符颜色,我们可以通过改变其属性字节,来实现窗口的阴影。
[例9 - 11] 阴影窗口程序menu3.c #include #include #include #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 X1,int y1,int X2,int y2,int b,int bc,int tc,char *head); void main(void ) { set_bkwin(1,2,25,18,2,2,1,"window"); getch(); } 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 { for(j=y2;j screen[j][i].attr=8; } for(i=X2+1;i { for(j=y1;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, 0Xd9 }, {0Xd5, 0Xcd, 0Xb8,0Xb3, 0Xd4, 0Xbe }, {0Xc9, 0Xcd, 0Xbb,0Xba, 0Xc8, 0Xbc }, {0Xd6, 0Xc4, 0Xb7,0Xba, 0Xb3, 0Xbd }, }; 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 putch(c[b-1][1]); putch(c[b-1][2]); for(i=y1+1;i { gotoxy(X1,i); putch(c[b-1][3]); gotoxy(X2,i); putch(c[b-1][3]); } gotoxy(X1,y2); putch(c[b-1][4]); for(i=X1+1;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语言提供的三个函数sound( )、nosound( )和clock( )可以很方便地解决上述的问题。sound( )函数可以用指定频率打开PC机扬声器直到用nosound( )函数来关闭它; clock( )函数正好用来控制发声时间,而且它不受PC机主频高低的影响。下面这段程序可使微机发出c调1的声音。
表9-2 音符与频率关系表
音符 |
c |
d |
e |
f |
g |
a |
b |
|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
频率 |
262 |
294 |
330 |
349 |
392 |
440 |
494 |
音符 |
c |
d |
e |
f |
g |
a |
b |
|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
频率 |
523 |
587 |
659 |
698 |
784 |
880 |
988 |
音符 |
c |
d |
e |
f |
g |
a |
b |
|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
频率 |
1047 |
1175 |
1319 |
1397 |
2568 |
1760 |
1976 |
[例9-12] 音乐程序music1.c #include #include void pause(int); void sound1(int,int); void main(void) { 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': 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; 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
曲谱文件首行是8节拍基数, 5 0是演奏速度,从第二行开始至文件尾,均为曲谱正文。需要说明的是在曲谱文件中,每个音符应跟上其音符的节拍,文中空格‘ ’符表示其音符是全音符。 程序中设置两个整型数组S a和S b用于存放音符的频率及节拍,其内容是一一对应的,若Sa[i]存放音符,则Sb[i]是该音符的演奏节拍。 发声的原理是利用C的标准库函数Sound( )发声,若要发音中音“ 1”音符,其音频为“262”,则函数调用Sound(262)则可通过PC机扬声器发音控制音符发声的时间由标准函数delay( )决定,nosound( )函数为关闭扬声器发音。
源程序如下: #include #include #include #include #include #include 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) { int k; for(k=0;k if ((str127[k]>=‘0’)& & ( s t r 1 2 7 [ 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 for(k=0;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) { j=0; while ((j<=len)&&(bioskey(1)==0)) { sound(2*sa[j]); /*发声*/ delay(2*rate*sb[j]); /* 延迟*/ nosound( ); /*关闭发声*/ j++; } }
9.3.3 实现后台演奏音乐的技巧 BASIC语言有一个前后台演奏音乐的语句play,该语句有很强的音乐功能。而C语言虽有sound( )函数,但不能进行后台演奏,并且必须指明音乐频率,才能使它发声。为此可编制一个与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,因此,就可以通过它来计算,实现后台演奏。 程序PLAY.C只是一个简单的后台演奏音乐的例子,将其编译后,就可在DOS提示符后直接执行,演奏过程中,按任一键都将停止演奏。
[例9-13] 后台演奏程序PLAY.C #include #include #include #include #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,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( ); } }
|