像素绘制与鼠标编程全解析
像素绘制基础
在进行像素绘制时,默认情况下,汇编器假定
DI
是相对于
DS
段地址的偏移量。而段覆盖
ES:[DI]
则会告知 CPU 使用
ES
中的段地址。当前,
ES
指向视频随机访问存储器(VRAM)。以下是一段绘制像素的代码示例:
DPi:
mov BYTE PTR es:[di],COLOR_INDEX
add di,5 ; move 5 pixels to the right
loop DPi
ret
Draw_Some_Pixels ENDP
END main
此程序实现起来较为简单,原因在于这些像素恰好位于同一屏幕行。若要绘制垂直线,可给
DI
的每个值加上 320,从而移动到下一行像素;若要绘制斜率为 1 的对角线,则给
DI
加上 321 即可。而绘制任意两点间的直线,最佳方法是使用布雷森汉姆算法(Bresenham’s Algorithm),该算法在众多网站上都有详细解释。
视频模式 13h 相关知识
视频模式 13h 有诸多特性,以下通过几个问题来深入了解:
1.
判断题
:视频模式 13h 将屏幕像素映射为二维字节数组,每个字节对应两个像素。(答案:错误)
2.
判断题
:在视频模式 13h 中,每个屏幕行使用 320 字节的存储空间。(答案:正确)
3.
简答题
:请用一句话解释视频模式 13h 是如何设置像素颜色的。
- 视频模式 13h 通过将颜色位映射到视频属性字节,利用原色的加性合成来产生颜色。
4.
简答题
:在视频模式 13h 中,颜色索引是如何使用的?
- 颜色索引用于从调色板中选择特定颜色,以确定像素的显示颜色。
5.
简答题
:在视频模式 13h 中,调色板的每个元素包含什么?
- 调色板的每个元素包含对应颜色的 RGB 值。
6.
简答题
:深灰色的三个 RGB 值是什么?
- 深灰色的 RGB 值通常为 (128, 128, 128)。
7.
简答题
:白色的三个 RGB 值是什么?
- 白色的 RGB 值为 (255, 255, 255)。
8.
简答题
:亮红色的三个 RGB 值是什么?
- 亮红色的 RGB 值为 (255, 0, 0)。
9.
挑战题
:展示如何在视频模式 13h 中将屏幕背景颜色设置为绿色。
- 可通过设置调色板中对应颜色索引的 RGB 值为绿色(如 (0, 255, 0)),并将背景像素的颜色索引设置为该索引来实现。
10.
挑战题
:展示如何在视频模式 13h 中将屏幕背景颜色设置为白色。
- 同样设置调色板中对应颜色索引的 RGB 值为白色((255, 255, 255)),并将背景像素的颜色索引设置为该索引。
鼠标编程基础
鼠标通常通过 PS - 2 鼠标端口、RS - 232 串行端口、USB 端口或无线连接与计算机主板相连。在检测鼠标之前,MS - DOS 需要安装设备驱动程序。MS - Windows 虽有内置鼠标驱动程序,但这里主要关注 MS - DOS 提供的功能。
鼠标移动以“米基”(mickeys)为单位进行跟踪,1 米基大约代表鼠标实际移动 1/200 英寸的距离。鼠标的米基与像素的比率可以设置,默认情况下,每 8 个水平像素对应 8 米基,每 8 个垂直像素对应 16 米基。此外,还有一个双速阈值,默认值为每秒 64 米基。
INT 33h 鼠标功能
INT 33h 可提供有关鼠标的各种信息,如当前位置、上次点击的按钮、速度等,还能用于显示或隐藏鼠标光标。以下是一些重要的 INT 33h 功能:
| 功能编号 | 功能描述 | 接收参数 | 返回参数 | 示例调用 | 注意事项 |
| ---- | ---- | ---- | ---- | ---- | ---- |
| 0 | 重置鼠标并获取状态 | AX = 0 | 若鼠标支持可用,AX = FFFFh 且 BX = 鼠标按钮数量;否则,AX = 0 |
mov ax,0; int 33h; cmp ax,0; je MouseNotAvailable; mov numberofButtons, bx
| 若调用前鼠标可见,此功能会将其隐藏 |
| 1 | 显示鼠标指针 | AX = 1 | 无 |
mov ax,1; int 33h
| 鼠标驱动程序会记录此功能的调用次数,将其内部显示/隐藏计数器加 1 |
| 2 | 隐藏鼠标指针 | AX = 2 | 无 |
mov ax,2; int 33h
| 鼠标驱动程序会继续跟踪鼠标位置,将其内部显示/隐藏计数器减 1 |
| 3 | 获取鼠标位置和状态 | AX = 3 | BX = 鼠标按钮状态,CX = X 坐标(像素),DX = Y 坐标(像素) |
mov ax, 3; int 33h; test bx,1; jne Left_Button_Down; test bx,2; jne Right_Button_Down; test bx,4; jne Center_Button_Down; mov Xcoord,cx; mov yCoord,dx
| 鼠标按钮状态在 BX 中返回:若位 0 置位,左键按下;若位 1 置位,右键按下;若位 2 置位,中键按下 |
| 4 | 设置鼠标位置 | AX = 4,CX = X 坐标(像素),DX = Y 坐标(像素) | 无 |
mov ax, 4; mov cx,200; mov dx,100; int 33h
| 若位置处于排除区域,鼠标将不显示 |
| 5 | 获取按钮按下信息 | AX = 5,BX = 按钮 ID(0 = 左键,1 = 右键,2 = 中键) | AX = 按钮状态,BX = 按钮按下计数器,CX = 上次按钮按下的 X 坐标,DX = 上次按钮按下的 Y 坐标 |
mov ax, 5; mov bx,0; int 33h; test ax,1; jz skip; mov Xcoord,cx; mov Y_coord,dx
| 鼠标按钮状态在 AX 中返回:若位 0 置位,左键按下;若位 1 置位,右键按下;若位 2 置位,中键按下 |
| 6 | 获取按钮释放信息 | AX = 6,BX = 按钮 ID(0 = 左键,1 = 右键,2 = 中键) | AX = 按钮状态,BX = 按钮释放计数器,CX = 上次按钮释放的 X 坐标,DX = 上次按钮释放的 Y 坐标 |
mov ax, 6; mov bx,0; int 33h; test ax,1; jz skip; mov Xcoord,cx; mov Y_coord,dx
| 鼠标按钮状态在 AX 中返回:若位 0 置位,左键释放;若位 1 置位,右键释放;若位 2 置位,中键释放 |
| 7 | 设置水平限制 | AX = 7,CX = 最小 X 坐标(像素),DX = 最大 X 坐标(像素) | 无 |
mov ax, 7; mov cx,100; mov dx,700; int 33h
| 无 |
| 8 | 设置垂直限制 | AX = 8,CX = 最小 Y 坐标(像素),DX = 最大 Y 坐标(像素) | 无 |
mov ax, 8; mov cx,100; mov dx,500; int 33h
| 无 |
坐标转换
在 MS - DOS 中,标准文本字体宽 8 像素、高 16 像素,因此可通过将像素坐标除以字符大小来将其转换为字符坐标。公式如下:
-
像素坐标转字符坐标
:$C = int(P / D)$,其中 $C$ 为字符坐标,$P$ 为像素坐标,$D$ 为字符维度。例如,若字符宽 8 像素,INT 33 功能 3 返回的 X 坐标为 100 像素,则该坐标位于字符位置 12:$C = int(100 / 8)$。
-
字符坐标转像素坐标
:$P = C \times D$,在水平方向上,$P$ 是字符单元格左侧的像素坐标;在垂直方向上,$P$ 是字符单元格顶部的像素坐标。例如,若字符宽 8 像素,要将鼠标置于字符单元格 12 中,该单元格最左侧像素的 X 坐标为 96。
其他鼠标功能
除上述功能外,INT 33h 还有许多其他有用的功能,用于配置鼠标和控制其行为,具体如下表所示:
| 功能编号 | 功能描述 | 输入/输出参数 |
| ---- | ---- | ---- |
| 0Ah | 设置水平和垂直鼠标移动每 8 像素的米基数 | 接收:CX = 水平米基数,DX = 垂直米基数。默认值为 CX = 8,DX = 16 |
| 10h | 设置鼠标排除区域(防止鼠标进入矩形区域) | 接收:CX, DX = 左上角的 X, Y 坐标;SI, DI = 右下角的 X, Y 坐标 |
| 13h | 设置双速阈值 | 接收:DX = 以米基/秒为单位的阈值速度(默认值为 64) |
| 1Ah | 设置鼠标灵敏度和阈值 | 接收:BX = 水平速度(米基/秒),CX = 垂直速度(米基/秒),DX = 双速阈值(米基/秒) |
| 1Bh | 获取鼠标灵敏度和阈值 | 返回:BX = 水平速度,CX = 垂直速度,DX = 双速阈值 |
| 1Eh | 禁用鼠标驱动程序 | 返回:若失败,AX = FFFFh |
| 20h | 启用鼠标驱动程序 | 无 |
| 24h | 获取鼠标信息 | 出错时返回 FFFFh;否则返回:BH = 主版本号,BL = 次版本号,CH = 鼠标类型(1 = 总线,2 = 串行,3 = InPort,4 = PS/2,5 = HP);CL = IRQ 号(PS/2 鼠标为 0) |
鼠标跟踪程序
下面是一个简单的鼠标跟踪程序,用于跟踪文本鼠标光标的移动。X 和 Y 坐标会在屏幕右下角不断更新,当用户按下左键时,鼠标的位置会显示在屏幕左下角。
TITLE Tracking the Mouse (mouse.asm)
; Demonstrates basic mouse functions available via INT 33h.
; In Standard DOS mode, each character position in the DOS window equals 8 mouse units.
INCLUDE Irvine16.inc
GET_MOUSE_STATUS = 0
SHOW_MOUSE_POINTER = 1
HIDE_MOUSE_POINTER = 2
GET_CURSOR_SIZE = 3
GET_BUTTON_PRESS_INFO = 5
GET_MOUSE_POSITION_AND_STATUS = 3
ESCkey = 1Bh
.data
greeting BYTE "[Mouse.exe] Press Esc to quit", 0
statusLine BYTE "Left button: ", 0
mousePositionLine BYTE "Mouse position: ", 0
blanks BYTE " ", 0
xCoord WORD 0 ; current X-coordinate
yCoord WORD 0 ; current Y-coordinate
xPress WORD 0 ; X-coord of last button press
yPress WORD 0 ; Y-coord of last button press
statusRow BYTE 7
statusCol BYTE 15
buttonPressCol BYTE 20
statusCol2 BYTE 60
coordCol BYTE 65
.code
main PROC
mov ax, @data
mov ds, ax
call Clrscr
; Get the screen X/Y coordinates.
call GetMaxXY ; OH = rows, DL = columns
dec dh ; calculate status row value
mov statusRow, dh
; Hide the text cursor and display the mouse.
call HideCursor
mov dx, OFFSET greeting
call WriteString
call ShowMousePointer
; Display status information on the bottom screen line.
mov dh, statusRow
mov dl, 0
call Gotoxy
mov dx, OFFSET statusLine
call WriteString
Loop:
; show mouse coordinates, check for left mouse button press or keypress (Esc key).
Li:
call ShowMousePosition
call LeftButtonPress ; check for button press
mov ah, 11h ; key pressed already?
int 16h
jz L2 ; no, continue the loop
mov ah, 10h ; remove key from buffer
int 16h
cmp al, ESCkey ; yes. Is it the ESC key?
je quit ; yes, quit the program
L2:
jmp Li ; no, continue the loop
quit:
call HideMousePointer
call ShowCursor
call Clrscr
call WaitMsg
exit
main ENDP
GetMousePosition PROC USES ax
; Gets the current mouse position and button status.
; Receives: nothing
; Returns: BX = button status (0 = left button down, 1 = right button down, 2 = center button down)
; CX = X-coordinate
; DX = Y-coordinate
mov ax, GET_MOUSE_POSITION_AND_STATUS
int 33h
ret
GetMousePosition ENDP
HideCursor PROC USES ax cx
; Hide the text cursor by setting its top line value to an illegal value.
; Receives: nothing.
; Returns: nothing
mov ah, GET_CURSOR_SIZE
int 10h
or ch, 30h ; set upper row to illegal value
mov ah, 1 ; set cursor size
int 10h
ret
HideCursor ENDP
ShowCursor PROC USES ax cx
; Show the text cursor by setting size to default.
; Receives: nothing.
; Returns: nothing
mov ah, GET_CURSOR_SIZE
int 10h
mov ah, 1 ; set cursor size
mov cx, 0607h ; default size
int 10h
ret
ShowCursor ENDP
HideMousePointer PROC USES ax
; Hides the mouse pointer.
; Receives: nothing.
; Returns: nothing
mov ax, HIDE_MOUSE_POINTER
int 33h
ret
HideMousePointer ENDP
ShowMousePointer PROC USES ax
; Makes the mouse pointer visible.
; Receives: nothing.
; Returns: nothing
mov ax, SHOW_MOUSE_POINTER
int 33h
ret
ShowMousePointer ENDP
LeftButtonPress PROC
; Checks for the most recent left mouse button press and displays the mouse location.
; Receives: nothing.
; Returns: nothing
pusha
mov ax, GET_BUTTON_PRESS_INFO
mov bx, 0 ; specify the left button
int 33h
; Exit proc if the coordinates have not changed.
cmp cx, xPress ; same X coordinate?
jne Li ; no: continue
cmp dx, yPress ; same Y coordinate?
je L2 ; yes: exit
; Coordinates have changed, so save them.
Li:
mov xPress, cx
mov yPress, dx
; Position the cursor, clear the old numbers.
mov dh, statusRow ; screen row
mov dl, statusCol ; screen column
call Gotoxy
push dx
mov dx, OFFSET blanks
call WriteString
pop dx
; Show coordinates where mouse button was pressed.
call Gotoxy
mov ax, xCoord
call WriteDec
mov dl, buttonPressCol
call Gotoxy
mov ax, yCoord
call WriteDec
L2:
popa
ret
LeftButtonPress ENDP
SetMousePosition PROC
; Set the mouse's position on the screen.
; Receives: CX = X-coordinate
; DX = Y-coordinate
; Returns: nothing
mov ax, 4
int 33h
ret
SetMousePosition ENDP
ShowMousePosition PROC
; Get and show the mouse coordinates at the bottom of the screen.
; Receives: nothing
; Returns: nothing
pusha
call GetMousePosition
; Exit proc if the coordinates have not changed.
cmp cx, xCoord ; same X coordinate?
jne Li ; no: continue
cmp dx, yCoord ; same Y coordinate?
je L2 ; yes: exit
; Save the new X and Y coordinates.
Li:
mov xCoord, cx
mov yCoord, dx
; Position the cursor, clear the old numbers.
mov dh, statusRow ; screen row
mov dl, statusCol2 ; screen column
call Gotoxy
push dx
mov dx, OFFSET blanks
call WriteString
pop dx
; Show the mouse coordinates.
call Gotoxy
mov ax, xCoord
call WriteDec
mov dl, coordCol ; screen column
call Gotoxy
mov ax, yCoord
call WriteDec
L2:
popa
ret
ShowMousePosition ENDP
END main
该程序的行为会因两个因素而有所不同:一是所运行的 MS - Windows 版本,二是运行模式(控制台窗口或全屏模式)。例如,在 Windows XP 中,控制台窗口默认有 50 条垂直文本行。在全屏模式下,鼠标光标是一个实心块,其坐标似乎每次变化一个像素;而在控制台窗口模式下,鼠标光标是一个指针,其坐标水平方向每次变化 8 像素,垂直方向每次变化 16 像素。
通过上述内容,我们全面了解了像素绘制和鼠标编程的相关知识,包括像素绘制方法、视频模式 13h 的特性、INT 33h 鼠标功能以及坐标转换等内容,同时还给出了一个简单的鼠标跟踪程序示例。这些知识和代码对于深入理解计算机底层编程和控制输入输出设备具有重要意义。
像素绘制与鼠标编程全解析(续)
常见问题解答
为了帮助大家更好地理解和运用上述知识,下面对一些常见问题进行解答:
1.
哪个 INT 33h 功能可以重置鼠标并获取鼠标状态?
- 功能编号为 0 的 INT 33h 功能可以实现此操作。
2.
请写出重置鼠标并获取鼠标状态的 ASM 语句。
mov ax, 0
int 33h
cmp ax, 0
je MouseNotAvailable
mov numberofButtons, bx
-
哪个 INT 33h 功能可以显示和隐藏鼠标指针?
- 功能编号为 1 的 INT 33h 功能用于显示鼠标指针,功能编号为 2 的 INT 33h 功能用于隐藏鼠标指针。
- 请写出隐藏鼠标指针的 ASM 语句。
mov ax, 2
int 33h
-
哪个 INT 33h 功能可以获取鼠标位置和状态?
- 功能编号为 3 的 INT 33h 功能可以获取鼠标位置和状态。
- 请写出获取鼠标位置并将其存储在变量 mouseX 和 mouseY 中的 ASM 语句。
mov ax, 3
int 33h
mov mouseX, cx
mov mouseY, dx
-
哪个 INT 33h 功能可以设置鼠标位置?
- 功能编号为 4 的 INT 33h 功能可以设置鼠标位置。
- 请写出将鼠标指针设置为 X = 100 和 Y = 400 的 ASM 语句。
mov ax, 4
mov cx, 100
mov dx, 400
int 33h
-
哪个 INT 33h 功能可以获取鼠标按钮按下信息?
- 功能编号为 5 的 INT 33h 功能可以获取鼠标按钮按下信息。
- 请写出当左鼠标按钮被按下时跳转到标签 Button1 的 ASM 语句。
mov ax, 5
mov bx, 0
int 33h
test ax, 1
jne Button1
-
哪个 INT 33h 功能可以获取鼠标按钮释放信息?
- 功能编号为 6 的 INT 33h 功能可以获取鼠标按钮释放信息。
- 请写出获取鼠标在右键释放时的位置并将其存储在变量 mouseX 和 mouseY 中的 ASM 语句。
mov ax, 6
mov bx, 1
int 33h
mov mouseX, cx
mov mouseY, dx
- 请写出将鼠标的垂直限制设置为 200 和 400 的 ASM 语句。
mov ax, 8
mov cx, 200
mov dx, 400
int 33h
- 请写出将鼠标的水平限制设置为 300 和 600 的 ASM 语句。
mov ax, 7
mov cx, 300
mov dx, 600
int 33h
-
挑战:假设要让鼠标指针指向文本模式下位于第 10 行、第 20 列的字符单元格的左上角,在假设每个字符有 8 个水平像素和 16 个垂直像素的情况下,需要传递给 INT 33h 功能 4 的 X 和 Y 值是多少?
- 根据字符坐标转像素坐标的公式 $P = C \times D$,水平方向上,$X = 20 \times 8 = 160$;垂直方向上,$Y = 10 \times 16 = 160$。所以需要传递的 X 值为 160,Y 值为 160。
-
挑战:假设要让鼠标指针指向文本模式下位于第 15 行、第 22 列的字符单元格的中间,在假设每个字符有 8 个水平像素和 16 个垂直像素的情况下,需要传递给 INT 33h 功能 4 的 X 和 Y 值是多少?
- 水平方向上,字符单元格左侧像素坐标为 $22 \times 8 = 176$,中间位置则为 $176 + 4 = 180$;垂直方向上,字符单元格顶部像素坐标为 $15 \times 16 = 240$,中间位置为 $240 + 8 = 248$。所以需要传递的 X 值为 180,Y 值为 248。
-
挑战:计算机鼠标是谁在什么时候、什么地方发明的?
- 计算机鼠标是由道格拉斯·恩格尔巴特(Douglas Engelbart)在 1964 年于斯坦福研究院(Stanford Research Institute)发明的。
总结与展望
在计算机编程中,BIOS 级别的编程为我们提供了对计算机输入输出设备更精细的控制。通过 INT 16h 可以对键盘进行编程,读取扩展键盘键,如功能键和光标箭头键;利用 INT 10h 能够对视频显示进行控制,包括设置颜色、绘制图形等;而 INT 33h 则为鼠标的操作和控制提供了丰富的功能。
下面是一个简单的流程图,展示了 BIOS 级编程中各部分的关系:
graph LR
A[BIOS 级编程] --> B[键盘编程 - INT 16h]
A --> C[视频显示编程 - INT 10h]
A --> D[鼠标编程 - INT 33h]
B --> E[读取扩展键盘键]
C --> F[设置颜色]
C --> G[绘制图形]
D --> H[获取鼠标位置]
D --> I[控制鼠标按钮]
在键盘编程方面,键盘硬件与 INT 9h、INT 16h 和 INT 21h 处理程序协同工作,使程序能够获取键盘输入。例如,通过轮询键盘并在按下 Esc 键时跳出循环的程序,展示了如何利用 INT 16h 实现键盘输入的检测。
视频显示编程中,利用原色的加性合成来产生颜色,将颜色位映射到视频属性字节。通过 INT 10h 提供的各种功能,可以实现滚动彩色窗口、在窗口中间写入文本以及绘制彩色图形等操作。同时,还可以通过直接写入视频内存来实现高速彩色图形的绘制。
鼠标编程则通过 INT 33h 提供的众多功能,实现了对鼠标的各种操作,如重置鼠标、显示或隐藏鼠标光标、获取鼠标位置和按钮状态等。通过坐标转换公式,还可以在像素坐标和字符坐标之间进行转换,方便在不同场景下使用。
虽然 BIOS 级编程提供了强大的功能,但获取相关信息并不容易,许多优秀的参考书籍已经绝版。不过,我们仍然可以通过一些经典的资料,如《PC Interrupts. A Programmer’s Reference to BIOS, DOS, and Third - Party Calls》来深入学习。
未来,随着计算机技术的不断发展,BIOS 级编程可能会面临新的挑战和机遇。一方面,硬件的不断更新换代可能会导致一些旧的 BIOS 功能不再适用;另一方面,新的需求也可能促使 BIOS 级编程不断发展和创新。例如,随着高分辨率显示器的普及,对视频显示编程的要求可能会更高;随着无线鼠标和触摸板等新型输入设备的广泛应用,鼠标编程也需要不断适应新的技术。
总之,BIOS 级编程是计算机编程中一个重要的领域,掌握相关知识和技能对于深入理解计算机系统和实现更高效的编程具有重要意义。希望本文能够帮助大家更好地理解和运用 BIOS 级编程的相关知识,在实际编程中取得更好的效果。
通过以上内容,我们对像素绘制和鼠标编程有了更深入的了解,从基本的概念和原理,到具体的编程实现和常见问题解答,再到对整个 BIOS 级编程领域的总结和展望,形成了一个较为完整的知识体系。无论是初学者还是有一定经验的程序员,都可以从中获取有价值的信息,进一步提升自己的编程能力。
超级会员免费看
815

被折叠的 条评论
为什么被折叠?



