在13号模式下读取像素时出现错误数据(386汇编 - DOS)

3

我正在为DOSBOX编写一个汇编绘画程序,用于学校项目。在我的程序中,用户使用左键以特定颜色绘制像素。我在13号模式下直接写入。用户可以通过右键更改颜色,这将读取鼠标指向的像素的颜色。

这很好地工作,直到用户启动某个旨在清除屏幕并显示可供选择的调色板颜色的子例程(具有相同的右键功能)。然后,用户在调色板上右键单击,并使用int 10h,0dh读取颜色。无论用户在调色板屏幕上右键单击哪里,稍后由用户写入屏幕的颜色始终是白色。

简而言之,右键单击任何位置都会正确更改画笔颜色,但如果在调色板模式下进行,则随后的颜色始终为白色。 以下是我的代码:

GRAPHICS equ 13h
H_HOTSPOT equ 7
V_HOTSPOT equ 7
H_RES equ 320
V_RES equ 200
PIXELCOUNT equ H_RES*V_RES
DISPLAY_SEG equ 0A000h

EXIT_KEY equ 1071h          ; q key
COLORPICKER_KEY equ 1177h   ; w key

PALETTE_SIZE equ 16
SQUARE_SIZE equ 10
SQUARE_PADDING equ 2
SQUARE_ROW_JUMP equ H_RES - SQUARE_SIZE
PALETTE_ROW_JUMP equ H_RES - (SQUARE_PADDING + SQUARE_SIZE)*PALETTE_SIZE

sseg SEGMENT
    db 256 dup(?)
sseg ENDS

dseg SEGMENT

stdBrushMask    dw 1111111011111111b
                dw 1111111011111111b
                dw 1111111011111111b
                dw 1111111011111111b
                dw 1111111011111111b
                dw 1111111011111111b
                dw 1111111011111111b
                dw 0000000100000000b
                dw 1111111011111111b
                dw 1111111011111111b
                dw 1111111011111111b
                dw 1111111011111111b
                dw 1111111011111111b
                dw 1111111011111111b
                dw 1111111011111111b
                dw 1111111011111111b

                dw 0000000100000000b
                dw 0000000100000000b
                dw 0000000100000000b
                dw 0000000100000000b
                dw 0000000100000000b
                dw 0000000100000000b
                dw 0000000100000000b
                dw 1111111011111111b
                dw 0000000100000000b
                dw 0000000100000000b
                dw 0000000100000000b
                dw 0000000100000000b
                dw 0000000100000000b
                dw 0000000100000000b
                dw 0000000100000000b
                dw 0000000100000000b

stdBrushHotSpots dw 7
                 dw 7

pickerToolMask  dw 1111100001000001b
                dw 1111100000000000b
                dw 1111100000000000b
                dw 1111100000000000b
                dw 1111100000000000b
                dw 1111000000000000b
                dw 1110000000000000b
                dw 1100000000000000b
                dw 1000000000000000b
                dw 1000000000000000b
                dw 1000000000000000b
                dw 1000000000011111b
                dw 0000000000111111b
                dw 0000000001111111b
                dw 0000000011111111b
                dw 0000111111111111b

                dw 0000000000000000b
                dw 0000001100011100b
                dw 0000001111111110b
                dw 0000000111111110b
                dw 0000000111111110b
                dw 0000001111111100b
                dw 0000011111111100b
                dw 0000111111111100b
                dw 0001111111111110b
                dw 0011111111100110b
                dw 0001111111000000b
                dw 0001111110000000b
                dw 0011111100000000b
                dw 0111001000000000b
                dw 0110000000000000b
                dw 0000000000000000b

pickerToolHotSpots dw 1
                   dw 14

foreground_color dw 000fh
background_color dw 0000h
colorpicker_flag db 00

prev_position    dw H_RES   ;   Cache of the last drawn pixel's coordinates, to avoid duplicate writes.
                 dw V_RES

pos_backup       dw H_RES/2
                 dw V_RES/2 ;   Cache of the mouse position before switching to palette mode or back.

video_mode_not_available db "Mode 13h is not supported on this computer. To use this program, get a VGA or MCGA graphics card / monitor.$"

display_backup db PIXELCOUNT dup(?)
dseg ENDS

cseg SEGMENT
assume cs:cseg, ds:dseg, ss:sseg

;---------------Mouse Procs-------------
stdBrush PROC
    push bx cx ax dx
    mov ax, dseg
    mov es, ax
    mov bx, stdBrushHotSpots
    mov cx, stdBrushHotSpots + 2
    mov ax, 9
    mov dx, offset stdBrushMask
    int 33h
    mov ax, DISPLAY_SEG
    mov es, ax
    pop dx ax cx bx
    ret
stdBrush ENDP

pickerTool PROC
    push bx cx ax dx
    mov ax, dseg
    mov es, ax
    mov bx, pickerToolHotSpots
    mov cx, pickerToolHotSpots + 2
    mov ax, 9
    mov dx, offset pickerToolMask
    int 33h
    mov ax, DISPLAY_SEG
    mov es, ax
    pop dx ax cx bx
    ret
pickerTool ENDP

mouseReset PROC
    push ax
    mov ax, 0
    int 33h
    pop ax
    ret
mouseReset ENDP

showCursor PROC
    push ax
    mov ax, 1
    int 33h
    pop ax
    ret
showCursor ENDP

hideCursor PROC
    push ax
    mov ax, 2
    int 33h
    pop ax
    ret
hideCursor ENDP

getCursorStat PROC
    push ax
    mov ax, 3
    int 33h
    pop ax
    ret
getCursorStat ENDP

initCursor PROC
    mov ax, dseg
    mov es, ax
    call mouseReset
    call stdBrush
    call showCursor
    mov ax, DISPLAY_SEG
    mov es, ax
    ret
initCursor ENDP

;--------------------Graphics Procs-------------
graphicsCompat PROC
    ; Checks if mode 13h is supported on this machine.
    mov ax, 1a00h
    int 10h ; Get display combination
    cmp al, 1ah
    je mode_13_supported
    mov ah, 9
    mov dx, offset video_mode_not_available
    int 21h ; Display error message
    inc sp  ; Discard the return address
    inc sp
    push offset exit    ; Replace return address with exit procedure address
    ret                 ; Return to exit procedure.

    mode_13_supported:
        ;No error, mode 13 is supported
    ret
graphicsCompat ENDP

graphicsMode PROC
    mov ah, 0
    mov al, GRAPHICS
    int 10h
    ret
graphicsMode ENDP

pixel PROC
    push bp
    mov bp, sp
    push ax bx cx dx
    xor bx, bx
    mov cx, ss:[bp+6]   ; x coord
    mov dx, ss:[bp+4]   ; y coord
    cmp cx, H_RES
    jnc pixel_outofbounds
    cmp dx, V_RES
    jnc pixel_outofbounds
    mov ax, H_RES
    mul dx
    add ax, cx
    mov di, ax      ; Puts the pixel's offset in di
    mov ax, ss:[bp+8]   ; read color argument
    stosb
  pixel_outofbounds:
    pop dx cx bx ax bp
    ret 6
pixel ENDP

backupScreen PROC
    push ax di si
    mov ax, DISPLAY_SEG
    mov ds, ax
    mov ax, dseg
    mov es, ax
    mov cx, PIXELCOUNT
    mov si, 0
    mov di, offset display_backup
    call hideCursor
    rep movsb
    call showCursor
    mov ds, ax
    mov ax, DISPLAY_SEG
    mov es, ax
    pop si di ax
    ret
backupScreen ENDP

restoreScreen PROC
    push di si
    mov cx, PIXELCOUNT
    mov si, offset display_backup
    mov di, 0
    rep movsb
    pop si di
    ret
restoreScreen ENDP

;-------------------Colorpicker Procs----------
paletteModeToggle PROC
    push ax
    call hideCursor
    mov pos_backup, cx
    mov pos_backup+2, dx
    mov al, colorpicker_flag
    not al
    mov colorpicker_flag, al
    test al, al
    jz palette_mode_off
    palette_mode_on:
        call backupScreen
        call graphicsMode
        call paletteDraw
        call showCursor
        call pickerTool
        pop ax
        jmp input_loop
    palette_mode_off:
        call graphicsMode
        call restoreScreen
        call showCursor
        call stdBrush
    pop ax
    jmp input_loop
paletteModeToggle ENDP

paletteSquareDraw PROC
    push bp
    mov bp, sp
    push ax cx dx

    mov dx, ss:[bp+4]   ; retrieves ypos
    mov cx, ss:[bp+6]   ; retrieves xpos
    mov ax, H_RES
    mul dx
    add ax, cx
    mov di, ax  ; Puts the real pixel address in di.
    mov ax, ss:[bp+8]   ;retrieves color

    xor ch, ch
    square_loop:
        xor cl, cl
        square_row_loop:
            stosb
            inc cl  ; increment horizontal counter
            cmp cl, SQUARE_SIZE
            jb square_row_loop
        inc ch                      ; increment the vertical counter
        add di, SQUARE_ROW_JUMP     ; move di to the start of the next row
        cmp ch, SQUARE_SIZE
        jb square_loop

    pop dx cx ax bp
    ret 6
paletteSquareDraw ENDP

paletteDraw PROC

    xor cx, cx  ; xpos = 0
    xor dx, dx  ; ypos = 0
    xor ax, ax  ; color = 0
    xor bx, bx  ; squarecounter = 0

    palette_loop:   ; for i in paletterows:
        xor bl, bl
        add dx, SQUARE_PADDING
        palette_row_loop:   ;   for j in palettecols:
            add cx, SQUARE_PADDING  ; xpos += padding
            push ax
            push cx
            push dx
            call paletteSquareDraw  ; DrawSquare()

            add cx, SQUARE_SIZE
            inc ax  ; next color
            inc bl  ; squarecounter++
            cmp bl, PALETTE_SIZE
            jb palette_row_loop
        ; prep for new row
        xor bl, bl
        xor cx, cx
        add dx, SQUARE_SIZE
        inc bh
        cmp bh, PALETTE_SIZE
        jb palette_loop
        ret

paletteDraw ENDP

pickColor PROC
    push bp
    mov bp, sp
    push ax dx si
    mov dx, ss:[bp+4]
    mov cx, ss:[bp+6]
    xor bh, bh
    mov ah, 0dh
    int 10h
    mov foreground_color, ax
    pop si dx ax bp
    ret 4
pickColor ENDP

;--------------------------Program Entry-------------------------
main:
    mov ax, dseg
    mov ds, ax
    mov ax, sseg
    mov ss, ax
    mov ax, DISPLAY_SEG
    mov es, ax

    call graphicsCompat
    call graphicsMode
    call mouseReset
    call stdBrush
    call showCursor
    mov ax, DISPLAY_SEG
    mov es, ax

input_loop:

    ;------------Mouse Input Detection------------------------------------
    call getCursorStat
    shr bx, 1
    jnc no_left_click
    ; Left click detected
    mov al, colorpicker_flag
    test al, al
    jnz no_left_click   ; no left clicks in palette mode.
    shr cx, 1
    cmp cx, prev_position
    jnz draw_pixel
    cmp dx, prev_position + 2
    jz no_left_click
  draw_pixel:
    call hideCursor
    mov ax, foreground_color
    mov prev_position, cx
    mov prev_position + 2, dx
    push ax cx dx
    call pixel
    call showCursor


  no_left_click:
    mov bx, 1
    mov ax, 6
    int 33h
    test bx, bx
    jz keyboard_detection
    ; Right click detected
    shr cx, 1
    push cx dx
    call pickColor
  keyboard_detection:
    ;------------Keyboard Input Detection----------------------------------
    mov ah, 1
    int 16h
    jz no_keystroke
    xor ah, ah
    int 16h
    cmp ax, EXIT_KEY
    je exit
    cmp ax, COLORPICKER_KEY
    jne no_palette_toggle
    jmp paletteModeToggle
  no_palette_toggle:
  no_keystroke:
    jmp input_loop

exit:
    mov al, 3
    mov ah, 0
    int 10h
    mov ax, 4c00h
    int 21h

cseg ENDS
end main

清晰的截图: 在绘图模式下使用蓝色画笔。 在绘图模式下使用蓝色画笔。 切换到调色板模式后,我右键单击了亮绿色的正方形。 切换到调色板模式后,我右键单击了亮绿色的正方形。 切换回绘图模式后,画笔开始绘制白色像素。 切换回绘图模式后,画笔开始绘制白色像素。 在绘图模式下右键单击蓝色像素。 在绘图模式下右键单击蓝色像素。 画笔颜色正确地转换为蓝色。 画笔颜色正确地转换为蓝色。

在此版本中,我使用int 10h,0dh来读取像素,但是当我尝试直接从A000段读取时(在调试期间,当屏幕明显不为空时,该段似乎完全填充为零),出现了相同的问题。我切换到使用int 10h,以查看直接读取是否存在问题。

我确实进行了直接写入操作。

最重要的问题是:我在读取这些像素时做错了什么?为什么我的程序最初能够正确读取像素颜色,但在基本清除屏幕、更改光标并绘制大量彩色正方形后,它只能读取白色?为什么在清除这些正方形并将屏幕恢复到先前状态之后它又可以工作了?


哦,我现在明白了ret #n在过程中释放堆栈... 嗯...还在寻找任何东西。(抱歉) - Ped7g
我从源代码中没有发现任何错误。但是我也不知道VRAM如何包含所有零,而调色板却在那里绘制。我认为在地址654(?2 * 320 + 2 + 10 + 2)处应该首先有“1”。所描述的行为对我来说没多大意义,而且我没有TASM来编译它。我正在寻找ds / es的问题,但看起来您已经在所有地方正确设置了它们(尽管我有点想知道为什么您不会在过程中使用push / pop保存/恢复它们,在需要以不同方式设置它们的地方,就像您已经在其他寄存器中所做的那样。但这不是bug。 - Ped7g
@Ped7g 关于VRAM中的零点问题真的让我很困扰。在绘图模式下检查VRAM时也会出现这种情况。我读到了一些关于必须切换到某种读取模式才能直接从VRAM中读取数据的内容,所以可能是这个原因。不过我不确定。感谢您指出程序过程中保存寄存器堆栈的差异。我忘记了我可以推送和弹出段寄存器,因为我一直以为只能通过将数据从其他寄存器(如AX)移动来更改它们。我可以使用XCHG指令交换段寄存器吗? - Itamar
我认为xchg操作数中没有段寄存器,即使在单边也是如此,但为什么不直接写出来让TASM告诉你呢?我记得在256B intros中经常使用push 0A000h pop es,所以这可能是初始化es的最短(只是稍微慢一点)方法(除非你已经意外地将0xA000存储在某个寄存器中,那么mov es,reg当然更好)。我认为VGA VRAM应该在调试期间完全可读,没有任何问题,所以这很令人不安。你使用什么调试器,以及如何使用?也许它会引起其他人的共鸣(我不再使用DOS工具)。 - Ped7g
是的,我正在使用td.exe。dosbox内置调试器是什么,如何访问它? - Itamar
显示剩余3条评论
1个回答

6

您的问题在于鼠标驱动程序使用软件光标。VGA卡不支持硬件光标,这意味着为了显示光标,鼠标驱动程序需要将其绘制到VGA帧缓冲器中。这意味着当选择器光标显示并且您读取当前鼠标位置处的像素时,实际上是读取选择器光标热点的颜色,即白色。当使用笔刷光标时,该问题不会发生,因为其热点的像素是透明的。


DOSBOX还可以模拟其他的设备吗?如果可以,那么我需要采取哪些步骤才能不将光标存储在帧缓冲区中? - Itamar
@Itamar:你可以自己绘制光标,以避免使用显卡功能(最终可能是最好的选择)。Dosbox模拟这些“machine = hercules | cga | tandy | cga | tandy | pcjr | ega | vgaonly | svga_s3 | svga_et3000 | svga_et4000 | svga_paradise | vesa_nolfb | vesa_oldvbe”(如果我没记错,早期的这些设备都没有硬件支持图形精灵/鼠标光标,也许svga有一些,但我不会打赌)。 - Ped7g
2
@Itamar 简单的解决方案是在读取帧缓冲区之前暂时隐藏鼠标指针。 - Ross Ridge
@Itamar 或许将热点设置在 (0, 15) 也可以。 - Margaret Bloom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接