Itamar
Itamar

Reputation: 137

Graphics Cursor in Assembly

I'm trying to make a small paint-like program in Assembly for DOSBox. I'm not sure what CPU type DOSBox emulates though, but from what I've found it might be 386.

I did my research and figured out how to use the mouse interrupt, 33h. I also managed to use the screen masks and cursor masks to define my own cursor. However, I want to be able to change my cursor later in the program when the user switches to, for example, the color sampler tool. When I tried to do this, the cursor turned into a black square. This is because I do not know exactly the steps required to perform such a change.

Am I supposed to hide the cursor, then reset it, then change the cursor mask, then show it again? Do I change the mask and only THEN reset? Do I not need to reset the mouse at all? Here is all my mouse-related code.

P.S. I know that I can optimize a lot of this by turning a bunch of procs into macros, and I might do that in the future once I have the bugs sorted out.

stdBrush PROC
    push bx cx ax dx
    mov bx, stdBrushHotSpots
    mov cx, stdBrushHotSpots + 2
    mov ax, 9
    mov dx, offset stdBrushMask
    int 33h
    pop dx ax cx bx
    ret
stdBrush ENDP

pickerTool PROC
    push bx cx ax dx
    mov bx, pickerToolHotSpots
    mov cx, pickerToolHotSpots + 2
    mov ax, 9
    mov dx, offset pickerToolMask
    int 33h
    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

Those are small procedures that act as shortcuts for using the different functions of int 33h. The exceptions are initCursor, which combines those shortcuts to initialize the cursor at the start of the program, and stdBrush & pickerTool which both set the graphics cursor to a specific cursor (as you can guess, stdBrush is the standard brush cursor, and pickerTool sets the cursor for the color sampler tool).

Below are the masks for my two cursors.

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

There are some indentation discreprencies that are caused by the copying of the code to stackoverflow, I couldn't bother to go line-by-line and fix them.

Here is the part of the program that causes me trouble:

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 pickerTool
        call backupScreen
        call graphicsMode
        call paletteDraw
        call mouseReset
        call showCursor
        pop ax
        jmp input_loop
    palette_mode_off:
        call stdBrush
        call graphicsMode
        call restoreScreen
        call mouseReset
        call showCursor
    pop ax
    jmp input_loop
paletteModeToggle ENDP

It shows a color palette, and it's supposed to change the cursor into the color sampler tool cursor. Instead, the cursor turns into a black square and stays that way even when palette mode is toggled off. I suspect that I did not take the correct steps while changing the cursor. It works just fine at the start of the program, when the cursor hasn't been shown yet.

In this procedure, I hide the cursor, then I change the cursor mask, then I reset the mouse to default driver values (not even sure this is necessary) and then make it visible again. Am I doing this wrong?

BTW in case you haven't noticed yet, I'm using TASM.

If you need to see any more parts of my code, please let me know.

Upvotes: 2

Views: 3779

Answers (2)

Margaret Bloom
Margaret Bloom

Reputation: 44046

You don't need to hide/show the cursor in order to change its shape.

If you see a black box, double check

  • The ES segment. If you are building a COM it should be equal to CS; if you are building an EXE it should be equal to DS if the cursors are in the data segment or CS if they are in the code segment.
  • The bitmaps of the cursors. Test with a full inverting square dw 32 DUP(0ffffh) (TIMES 32 dw 0ffffh in NASM syntax) that is visible over every color and easy to generate.

Debugging other people code is uninteresting for this site, I'll provide instead a MCWE (Minimal Complete Working Example) of how to change the cursor shape.

Press any key to change the cursor. Press again to exit.

.286
.MODEL TINY

_CODE SEGMENT PARA PUBLIC 'CODE' USE16
    ASSUME CS:_CODE, DS:_CODE 
    ORG 100h

 __START__:

    call initGraphics                   ;Get into graphic mode and show cursor 

    push 08h 
    push 08h 
    push OFFSET barCursor
    call setCursor 

    xor ah, ah
    int 16h

    push 08h 
    push 08h 
    push OFFSET checkerCursor
    call setCursor 

    xor ah, ah
    int 16h

    call finalizeGraphics

    mov ax, 4c00h
    int 21h 

    ;
    ; PROCEDURES
    ;

    ;Set graphic mode, reset mouse and show cursor
 initGraphics:
    push es

    mov ax, 13h
    int 10h

    push 0a000h
    pop es 
    xor di, di 
    mov ax, 0909h
    mov cx, 320*200/2 
    rep stosw 

    xor ax, ax 
    int 33h 

    mov ax, 01h 
    int 33h

    pop es
    ret

 ;Hide cursor and set text mode
 finalizeGraphics:
    mov ax, 02h
    int 33h 

    mov ax, 03h 
    int 10h 

    ret 

 ;Set cursor
 ;Hotspot X
 ;Hotspot Y
 ;Ptr to cursor bitmaps
 setCursor:
    push bp 
    mov bp, sp

    pusha
    push es

    mov ax, 09h
    mov bx, WORD PTR [bp+08h]
    mov cx, WORD PTR [bp+06h]
    mov dx, WORD PTR [bp+04h]
    push ds                         ;Setting ES = DS is not necessary in COM
    pop es                          ;files unless somebody changed ES
    int 33h 

    pop es
    popa

    pop bp  
    ret 06h     


    ;
    ; CURSORS
    ;

    barCursor       dw  16 DUP(0fe7fh)
                    dw  16 DUP(0180h)

    checkerCursor   dd  8 DUP(5555aaaah)
                    dd  8 DUP(0aaaa5555h)

_CODE ENDS 

END __START__

For other readers, the format of the cursor bitmaps is1:

OFFSET     SIZE       DESCRIPTION
 00h        32         16x16 pixel AND mask
 20h        32         16x16 pixel XOR mask

The 16x16 pixel means that each pixel under the cursor is mapped to a bit in that matrix.
The cursor size is 16x16 so each WORD (16 bits) define a row.
The leftmost pixel in a row is mapped to the LSb of the WORD.

For example the WORD 4807h (0100 1000 0000 0111) has a 1 for the 1st, 2nd, 3rd, twelfth and fifteenth pixel.

The AND mask is used to clear the pixels under the cursors, a 1 means to leave the pixel unaffected, a 0 set it to black.

The XOR mask is used to invert the pixels under the cursors, a 1 means to invert the pixel value (in mode 13h just the lower nibble), a 0 means to leave it unaffected.

This comes from the properties of AND and XOR.


1 Ralf Brown Interrupt entry is a little sloppy here.

Upvotes: 2

Ped7g
Ped7g

Reputation: 16596

According to this page:

ax =0: resets mouse to default driver values:

  • mouse is positioned to screen center
  • mouse cursor is reset and hidden
  • no interrupts are enabled (mask = 0)
  • double speed threshold set to 64 mickeys per second
  • horizontal mickey to pixel ratio (8 to 8)
  • vertical mickey to pixel ratio (16 to 8)
  • max width and height are set to maximum for video mode

So your stdBrush + graphicsMode + restoreScreen + resetMouse + showCursor is very likely a problem.

Also what is graphicsMode? If it is setting up the gfx mode, it will very likely destroy cursor graphics too.

So if you want to call all that, I would at first try this order:

  • graphics mode
  • reset mouse
  • restore screen
  • stdBrush
  • showCursor

Or maybe try to minimize things like set/reset, if you are staying all the time in the same graphics mode, the "restore screen" can't redraw it all? With mouse I see even less benefit of resetting it each time (although may be safe after gfx mode change).


Dosbox emulates what you set it to.

cputype = auto | 386 | 386_slow | 486_slow | pentium_slow | 386_prefetch


About how to change cursor gfx .. you don't need to call any hide/reset/show/etc. Just call set "INT 33,9" again with new gfx data. It will replace it immediately (it's just setting up two addresses in gfx driver, to let it know where it should fetch the mask+ink data, and the gfx driver is using that every display frame ... IIRC how that one worked).

I recall for sure, when I was doing my "sprite editor" in 13h DOS mode, that I went instead for my own cursor draw routine, so I could use the 256color sprites (their final versions drawn in the editor itself). But I can't recall any technical details, it's ~25y back. :)

Upvotes: 0

Related Questions