当然,程序中出现的新指令我们还是要先讨论的。
助记符:NEG(Negate)
用 途:求一个数的相反数
格 式:NEG 寄存器
NEG 存储单元
执 行:寄存器或存储单元中原有数据被其相反数代替
很多参考书上都把这条指令称为"求补"指令,这个"求补"说得有些含糊,给人的感觉好象是源操作数不是补码形式,可能是原码或反码,执行此指令后源操作数将被其补码代替。其实汇编语言中出现的数都是补码形式,根本不需要再求。所以说这条指令的实际用途是求源操作数的相反数,NEG(-1) = +1,NEG 30 = -30。
助记符:JGE (Jump if Great or Equal)
用 途:比较两个数,根据比较结果进行转移
格 式:JGE 目的地址
执 行:两个数相比较,第一个数大于或等于第二个数时此指令完成转移。
这是一个条件转移指令,它类似于指令JAE。如同指令JA与JAE的关系一样,与这个指令沾亲的一条指令就是"JG(Jump if Great)"。而和这一对指令相对立的指令对就是"JL(Jump if Less)"与"JLE"。这一组四条指令与前面见过的指令"JA/JAE"、"JB/JBE"好象如出一辙,大有重复的嫌疑,其实不然,这里面还有些小奥妙。
在讨论这组指令的区别之前,我们先编两小段程序:
code segment assume cs:code,ds:code org 100h main proc near jmp start mess1 db 'Great!',0dh,0ah,'$' mess2 db 'Less!',0dh,0ah,'$' start: mov ah,1 mov al,255 mov dx,offset mess1 cmp al,ah ja outmess mov dx,offset mess2 outmess: mov ah,09h int 21h mov ah,4ch int 21h main endp code ends end main
code segment assume cs:code,ds:code org 100h main proc near jmp start mess1 db 'Great!',0dh,0ah,'$' mess2 db 'Less!',0dh,0ah,'$' start: mov ah,1 mov al,255 mov dx,offset mess1 cmp al,ah jg outmess mov dx,offset mess2 outmess: mov ah,09h int 21h mov ah,4ch int 21h main endp code ends end main
这两小段程序比较好理解,它将AL寄存器中的数和AH寄存器相比较,如果AL>AH则输出字符串"Great!",否则输出"Less!"。我们特意在AL寄存器内装入了255,在AH寄存器内装入了1,所以这个程序会毫无疑问地输出"Great!"
程序PROG2的结构和PROG1完全相同,只是其中的指令"JA"被换成了"JG"而已。然而就是这一点点变化,却使这个程序输出的结果与PROG1完全不同。本来255是大于1的,而这个程序却输出了"Less!",难道255反而小于1吗?
实际上255的确是小于1的,因为从补码的角度来看255实际是个负数。由此我们可以推测出指令"JA/JB"与"JG/JL"的一个重要的区别:指令"JA/JB"总是将相比较的两个数当成"无符号数",而指令"JG/JL"则是将相比较的两个数当成"带符号数"。这样的一种特性就要求我们在编写程序时头脑之中要很清楚相比较的两个数是不是带符号的,以便于我们选择下面的条件转移指令。
在8086的指令集中有很多条件转移指令,这些指令所判断的标志位各不相同,而且有些指令在形式上还有变化,因此条件转移指令其实是8086汇编语言程序设计中的一个难点。关于标志寄存器与条件转移指令的有关问题我们将在下面一章加以讨论。
LOVE.ASM程序的核心部分就是LINE子过程,这个子过程用于在两个点之间画直线。与我们讨论的Bresenham算法相比这个子过程在画线之前做了好多额外的计算与判断,这主要是因为实际画线的方向并不都是从左上到右下。
假如画线方向是从右上到左下,那么X坐标的增量就不是+1,而是-1。即使所画线的方向是从左上至右下,由于直线的斜率不同,究竟是以X坐标作为循环计数而调整Y坐标,还是以Y坐标作为循环计数调整X坐标也是有区别的。所以实际的Bresenham算法在真正画线之前要对各种不同的画线方向进行一些判断来确定画线时的各项参数。
本节至此已经对BIOS提供的绘图功能做了较详细地介绍,同时给出了描绘文字以及绘制直线的算法。BIOS的功能还是很多的,它不仅提供了"画点"功能,同时还提供了"读点"功能,即判断屏幕上某个位置的点的颜色。
功能号:0DH
用 途:取得图形屏幕上指定位置点的颜色
参 数:CX=指定位置的X坐标
DX=指定位置的Y坐标
调 用:INT 10H
返 回:AL = 所读点的颜色值
这个功能好象在电子游戏程序中可以用到,比如编写一个射击游戏,在判断子弹是否击中物体时就可以应用这个功能。下面给出的程序是一个"动画"程序,它先在屏幕上画出一个方框,然后在方框内显示一个作直线运动的白点,当这个白点撞到方框边缘时就会反弹。程序中就是应用了BIOS的读点功能判断彩色点是否"撞到"边框的:
code segment assume cs:code,ds:code org 100h main proc near jmp start ;跳过数据区 mess db 'Press Any Key...',0dh,0ah,'$' start: mov ax,0004 ;设置图形显示模式4 int 10h ;画方框上下两条水平线 mov cx,10 ;上一条水平线位于屏幕第10线 mov dx,10 mov bx,300 ;水平线的长度为300点 mov al,1 ;水平线颜色为青色 mov si,1 ;定X方向增量为1 mov di,0 ;定Y方向增量为0 call line ;调用LINE子过程 mov dx,190 ;下一条水平线位于屏幕第190线 call line ;调用LINE子过程 ;画方框左右两条垂直线 mov dx,10 ;左侧垂直线起点Y坐标 mov al,2 ;垂直线的颜色为洋红 mov bx,180 ;垂直线长度为180点 xchg si,di ;交换X,Y方向的增量值 call line ;调用LINE子过程 mov cx,310 ;右侧垂直线位于X坐标310处 call line ;调用LINE子过程 mov ah,9 ;显示字符串 mov dx,offset mess int 21h mov ah,0 ;等待键盘输入 int 16h mov si,1 ;X,Y方向的增量值均设为1 mov di,1 mov cx,56 ;起始位置的(X,Y)坐标 mov dx,47 point: add cx,si ;计算新位置的(X,Y)坐标 add dx,di mov ah,0dh ;取得新位置处点的颜色 int 10h push ax ;暂存取得的颜色值 mov al,3 ;在新位置画一个白色的点 mov ah,0ch int 10h mov ah,10 ;延迟一段时间 delay: mov bx,0 delay1: dec bx jnz delay1 dec ah jnz delay pop ax ;利用原来的颜色画一个点 mov ah,0ch int 10h cmp al,2 ;这个位置上有洋红色的线通过吗? jz neg_x ;若有洋红色的线,转NEG_X继续 cmp al,1 ;这个位置上有青色的线通过吗? jz neg_y ;若有青色的线,转NEG_Y继续 jmp point ;若此位置没有线通过,转POINT继续画点 neg_x: neg si ;将X方向的增量改为-1 jmp point ;转POINT继续画点 neg_y: neg di ;将Y方向的增量改为-1 jmp point ;转POINT继续 main endp line proc near ;画线子程序 push ax ;保存寄存器 push bx push cx push dx lineloop: mov ah,0ch ;在CX-DX指定的位置画点 int 10h add cx,si ;X,Y坐标分别加上增量值 add dx,di dec bx ;线上所有点都画完了吗? jnz lineloop ;若未画完全部点,转LINELOOP继续 pop dx ;恢复寄存器原值 pop cx pop bx pop ax ret ;返回主过程 line endp code ends end main
有关这个程序还有几点需要说明的:
(1)这个程序中LINE子过程并没有使用Bresenham画线算法,原因是程序所需要画的线都是水平或垂直的,采用Bresenham算法画这样的线反而没有效率。
(2)这个程序使用两种颜色绘制边框水平线是青色,垂直线是品红色。这样做的原因是由于当运动的点遇到水平线反弹时应该变化Y坐标的增量,遇到垂直线反弹时要变化X坐标的增量,所以程序必须有方法区分水平边框和垂直边框。
(3)这个程序演示了一种生成"动画"的方法,即先在起始位置画出一幅图,延迟一段时间后将其擦除,随后在相邻的位置画出另一幅图,如此循环就能产生动画效果。
以上我们给出的绘图程序都是使用CGA提供的彩色组1中的四种色彩,如果大家想看看CGA的另一组彩色,可以使用BIOS提供的一个控制色彩的功能,这就是10H中断的0BH功能:
功能号:0BH
用 途:设置当前的彩色组
参 数:BH=0时,设置背景颜色,其颜色值在BL中定义(用于字符模式)
BH=1时,BL中的值为调色板号(用于图形模式)
调 用:INT 10H
返 回:无
利用这个功能将PINGPONG程序修改成"PINGPONG2.ASM"的样子就可以看到彩色组0中的四种 色彩了。从直观上看彩色组0中的四种色彩比彩色组1要漂亮些,而且对比修改前后程序的输出我们还可以看到字符串"Press Any Key…"的颜色总是四种颜色中的最后一种,这也可以算是个小规律吧。至于字符的颜色是否可以改变就需要大家自己去研究了。
以上所讨论的内容就是BIOS为我们提供的几个有关图形的功能调用,熟练掌握这些功能有助于我们更深入的学习8086/88汇编语言图形程序的设计方法。不过如我们在第六章中所讲的那样,BIOS功能虽然应用方便,但是它的一个很大的缺点就是调用速度比较慢,不利于编制快速的图形程序。要想编制出高速的图形程序,我们还是需要直接对视频缓冲存储器进行操作,这就是本章第二节所要讨论的内容。
code segment assume cs:code,ds:code org 100h main proc near jmp start mess db 'Press Any Key...',0dh,0ah,'$' start: mov ax,0004 int 10h mov ah,0bh mov bh,1 mov bl,0 int 10h ;以下同PINGPON G.ASM