ITEEDU

当然,程序中出现的新指令我们还是要先讨论的。

助记符: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"好象如出一辙,大有重复的嫌疑,其实不然,这里面还有些小奥妙。

在讨论这组指令的区别之前,我们先编两小段程序:

PROG1.ASM
        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
PROG2.ASM
        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的读点功能判断彩色点是否"撞到"边框的:

PINGPONG.ASM
        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功能虽然应用方便,但是它的一个很大的缺点就是调用速度比较慢,不利于编制快速的图形程序。要想编制出高速的图形程序,我们还是需要直接对视频缓冲存储器进行操作,这就是本章第二节所要讨论的内容。

PINGPONG2.ASM
        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