Search code examples
winapiassemblymasmmasm32

Win32 paint application


I have to write a program like paint in Windows. I'm using MASM32. Everything come normaly but when I move my mouse to draw, it draw a dash line, not a solid line.

Please help me to fix this :( Do we have another idea for this? Thank you and sorry for my bad English

P/S: Look at an image to see my problem!

.386    ; use 80386 instruction
.model flat,stdcall ; uses flat memory addressing model
option casemap:none

include C:\masm32\include\windows.inc   ; windows.inc have structures and constants
include C:\masm32\include\user32.inc 
includelib C:\masm32\lib\user32.lib ; CreateWindowEx, RegisterClassEx,...
include C:\masm32\include\kernel32.inc 
includelib C:\masm32\lib\kernel32.lib   ; ExitProcess
include C:\masm32\include\masm32.inc
includelib C:\masm32\lib\masm32.lib
include C:\masm32\include\gdi32.inc 
includelib C:\masm32\lib\gdi32.lib 

.CONST
DRAWING equ 1
WAITING equ 0

.DATA
ClassName db 'SimpleWinClass',0
AppName db 'Paint',0

labelNoti db 'Notification',0
labelClick db 'Start draw!',0
labelRelease db 'Stop draw!',0
labelDrawing db 'Drawing...',0
labelWaiting db 'Waiting.',0

object db '.',0
fontName db 'myfont',0

StaticClassName db 'static',0
X dw 'x',0
Y dw 'y',0

state db WAITING

.DATA?
; HINSTANCE & LPSTR typedef DWORD in windows.inc
; reserve the space for future use
hInstance HINSTANCE ?
CommandLine LPSTR ?
hitpoint POINT <>

; use for create window
wc WNDCLASSEX <?>
msg MSG <?> ; handle message
hwnd HWND ? ; handle window procedure

hwndX HWND ?
hwndY HWND ?
hwndState HWND ?

hdc HDC ?
ps PAINTSTRUCT <?>

font HGDIOBJ ?
hFont HFONT ?

.CODE
start:
    ; call GetModuleHandle(null)
    ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms683199(v=vs.85).aspx
    push NULL
    call GetModuleHandle    ; module handle same as instance handle in Win32
    mov hInstance, eax  ; return an instance to handle in eax

    ; call GetCommandLine()
    ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156(v=vs.85).aspx
    call GetCommandLine ; no parameters
    mov CommandLine, eax    ; return a pointer to the command-line for current process

    ; call WinMain(hInstance, hPrevInstance, CmdLine, CmdShow)
    ; our main function
    push SW_SHOW
    push CommandLine
    push NULL
    push hInstance
    call WinMain

    ; call ExitProcess
    push eax
    call ExitProcess

    ; Define WinMain 
    WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
        ; Structure in msdn, define in windows.inc
        ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms633577(v=vs.85).aspx

        ; Load default icon
        push IDI_APPLICATION
        push NULL
        call LoadIcon
        mov wc.hIcon, eax
        mov wc.hIconSm, eax

        ; Load default cursor
        push IDC_ARROW
        push NULL
        call LoadCursor
        mov wc.hCursor, eax

        mov wc.cbSize, SIZEOF WNDCLASSEX    ; size of this structure
        mov wc.style, CS_HREDRAW or CS_VREDRAW  ; style of windows https://msdn.microsoft.com/en-us/library/windows/desktop/ff729176(v=vs.85).aspx
        mov wc.lpfnWndProc, OFFSET WndProc  ; andress of window procedure
        mov wc.cbClsExtra, NULL 
        mov wc.cbWndExtra, NULL
        push hInstance
        pop wc.hInstance
        mov wc.hbrBackground,COLOR_WINDOW+1 ; background color, require to add 1
        mov wc.lpszMenuName, NULL
        mov wc.lpszClassName, OFFSET ClassName

        ; we register our own class, named in ClassName
        push offset wc
        call RegisterClassEx

        ; after register ClassName, we use it to create windows compond
        ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms632680(v=vs.85).aspx
        push NULL
        push hInstance
        push NULL
        push NULL
        push 600
        push 600
        push CW_USEDEFAULT
        push CW_USEDEFAULT
        push WS_OVERLAPPEDWINDOW
        push offset AppName
        push offset ClassName
        push WS_EX_CLIENTEDGE
        call CreateWindowEx

        mov hwnd, eax   ; return windows handle

        ; display window
        ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
        push CmdShow
        push hwnd
        call ShowWindow

        ; update window
        ; https://msdn.microsoft.com/en-us/library/windows/desktop/dd145167(v=vs.85).aspx
        push hwnd
        call UpdateWindow

        ; Message Loop
        MESSAGE_LOOP:
            ; get message
            ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms644936(v=vs.85).aspx
            push 0
            push 0
            push NULL
            push offset msg
            call GetMessage

            ; return in eax
            ; if the function retrieves a message other than WM_QUIT, the return value is nonzero.
            ; if the function retrieves the WM_QUIT message, the return value is zero.
            cmp eax, 0
            jle END_LOOP

            ; translate virtual-key messages into character messages - ASCII in WM_CHAR
            ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms644955(v=vs.85).aspx
            push offset msg
            call TranslateMessage 

            ; sends the message data to the window procedure responsible for the specific window the message is for.
            ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms644934(v=vs.85).aspx
            push offset msg
            call DispatchMessage
        jmp MESSAGE_LOOP

        END_LOOP:
            mov eax, msg.wParam   
        ret 
    WinMain endp

    ; Handle message with switch(notification)
    ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms633573(v=vs.85).aspx
    WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
        ; close app
        cmp uMsg, WM_DESTROY
        je ON_WM_DESTROY

        cmp uMsg, WM_CREATE
        je ON_WM_CREATE

        cmp uMsg, WM_PAINT
        je ON_WM_PAINT

        cmp uMsg, WM_LBUTTONDOWN
        je ON_WM_LBUTTONDOWN

        cmp uMsg, WM_LBUTTONUP
        je ON_WM_LBUTTONUP

        cmp uMsg, WM_MOUSEMOVE
        je ON_WM_MOUSEMOVE

        jmp ON_DEFAULT

        ; user close program
        ON_WM_DESTROY:
            push NULL
            call PostQuitMessage
            jmp EXIT

        ON_WM_CREATE:
            ; create static text
            push NULL
            push hInstance
            push 1
            push hWnd
            push 25
            push 50
            push 20
            push 20
            push WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT
            push offset X
            push offset StaticClassName
            push WS_EX_CLIENTEDGE
            call CreateWindowEx
            mov hwndX, eax

            ; create static text
            push NULL
            push hInstance
            push 1
            push hWnd
            push 25
            push 50
            push 20
            push 90
            push WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT
            push offset Y
            push offset StaticClassName
            push WS_EX_CLIENTEDGE
            call CreateWindowEx
            mov hwndY, eax

            ; create static text
            push NULL
            push hInstance
            push 1
            push hWnd
            push 25
            push 80
            push 60
            push 20
            push WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT
            push offset labelWaiting
            push offset StaticClassName
            push WS_EX_CLIENTEDGE
            call CreateWindowEx
            mov hwndState, eax

            push offset fontName
            push DEFAULT_PITCH or FF_DONTCARE
            push DEFAULT_QUALITY
            push CLIP_DEFAULT_PRECIS
            push CLIP_DEFAULT_PRECIS
            push DEFAULT_CHARSET
            push FALSE
            push FALSE
            push FALSE
            push 700
            push 0
            push 0
            push 16
            push 24
            call CreateFont
            mov font, eax

            jmp EXIT

        ON_WM_LBUTTONDOWN:
            mov [state], DRAWING

            push offset labelDrawing
            push hwndState
            call SetWindowText
            jmp EXIT

        ON_WM_LBUTTONUP:
            mov [state], WAITING

            push offset labelWaiting
            push hwndState
            call SetWindowText
            jmp EXIT

        ON_WM_MOUSEMOVE:
            push lParam
            call updateXY

            cmp [state], DRAWING
            je DRAW
            jne EXIT

            DRAW:
                push FALSE
                push NULL
                push hWnd 
                call InvalidateRect

            jmp EXIT

        ON_WM_PAINT:
            push offset ps
            push hWnd
            call BeginPaint
            mov hdc, eax

            push font
            push hdc
            call SelectObject

            mov hFont, eax

            push 1
            push offset object
            push hitpoint.y
            push hitpoint.x
            push hdc
            call TextOut

            push hFont
            push hWnd
            call SelectObject

            push offset ps
            push hWnd 
            call EndPaint

            jmp EXIT

        ON_DEFAULT:
            ; handle any message that program don't handle
            ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms633572(v=vs.85).aspx
            push lParam 
            push wParam 
            push uMsg   ; message
            push hWnd   ; windows
            call DefWindowProc
            jmp EXIT

        EXIT:
            ret
    WndProc endp

    updateXY proc lParam:LPARAM
            mov eax, lParam

            xor ebx, ebx
            mov bx, ax

            mov hitpoint.x, ebx

            push offset X
            push ebx
            call dwtoa

            push offset X
            push hwndX
            call SetWindowText

            mov eax, lParam
            shr eax, 16

            mov hitpoint.y, eax

            push offset Y
            push eax
            call dwtoa

            push offset Y
            push hwndY
            call SetWindowText
        ret
    updateXY endp

end start

Paint


Solution

  • You're using the string output function TextOut to output a dot ('.') as a point. This character has more (white) background than (black) data points. So moving the mouse overwrites the points at the current position with background. You can see the effect when you cross the lines.

    You need a character with more black data. Let's choose the space (' ') and invert the background:

    object db ' ',0
    ...
    ON_WM_PAINT:
    ...
        mov hFont, eax
        invoke SetBkColor, hdc, 00FF0000h
    
        push 1
        push offset object
        push hitpoint.y
        push hitpoint.x
        push hdc
        call TextOut
    ...
    

    INVOKE is a built-in MASM macro that do the appropriate pushes, pops and calls for you according to the calling convention declared in a preceding PROTO or PROC directive or the default calling convention. For example, you can change

    push 1
    push offset object
    push hitpoint.y
    push hitpoint.x
    push hdc
    call TextOut
    

    to

    invoke TextOut, hdc, hitpoint.x, hitpoint.y, offset object, 1
    

    A dashed line is also caused by the mouse acceleration if you quickly move the mouse. I've found no easy way to turn off that acceleration. An appropriate size of the inverted space reduces the chance for a dash line if you slowly move the mouse. Change width and height of the font:

    push offset fontName
    push DEFAULT_PITCH or FF_DONTCARE
    push DEFAULT_QUALITY
    push CLIP_DEFAULT_PRECIS
    push CLIP_DEFAULT_PRECIS
    push DEFAULT_CHARSET
    push FALSE
    push FALSE
    push FALSE
    push 700
    push 0
    push 0
    push 18                         ; Width
    push 12                         ; Height
    call CreateFont
    mov font, eax
    

    Better is to use MoveToEx and LineTo to paint a line from the last mouse position to the current mouse position. To set the thickness of the line you can use CreatePen and SelectObject.

    Example for LineTo:

    .386                ; use 80386 instruction
    .model flat,stdcall ; uses flat memory addressing model % STDCALL as default calling convention
    option casemap:none
    
    include C:\masm32\include\windows.inc
    include C:\masm32\include\kernel32.inc
    include C:\masm32\include\user32.inc
    include C:\masm32\include\gdi32.inc
    include C:\masm32\include\masm32.inc
    
    includelib C:\masm32\lib\kernel32.lib   ; ExitProcess, GetCommandLineA, GetModuleHandleA
    includelib C:\masm32\lib\user32.lib     ; BeginPaint, CreateWindowExA@, DefWindowProcA, DispatchMessageA, EndPaint, GetMessageA, InvalidateRect, LoadCursorA, LoadIconA, PostQuitMessage, RegisterClassExA, SetWindowTextA, ShowWindow, TranslateMessage, UpdateWindow
    includelib C:\masm32\lib\gdi32.lib      ; CreatePen, LineTo, MoveToEx, SelectObject
    includelib C:\masm32\lib\masm32.lib     ; dwtoa
    
    .CONST
    DRAWING equ 1
    WAITING equ 0
    
    .DATA
        ClassName db 'SimpleWinClass',0
        AppName db 'Paint',0
    
        labelDrawing db 'Drawing...',0
        labelWaiting db 'Waiting.',0
    
        StaticClassName db 'static',0
        X dw 'x',0
        Y dw 'y',0
    
        state db WAITING
    
    .DATA?
        hInstance HINSTANCE ?
        CommandLine LPSTR ?
        hitpoint POINT <>
        lastpoint POINT <>
    
        wc WNDCLASSEX <?>
        msg MSG <?> ; handle message
        hwnd HWND ? ; handle window procedure
    
        hwndX HWND ?
        hwndY HWND ?
        hwndState HWND ?
    
        hdc HDC ?
        ps PAINTSTRUCT <?>
    
        hPen HPEN ?
    
    .CODE
    
    updateXY PROC lParam:LPARAM
        movzx eax, WORD PTR lParam
        mov hitpoint.x, eax
    
        invoke dwtoa, eax, offset X
        invoke SetWindowText, hwndX, offset X
    
        mov eax, lParam
        shr eax, 16
        mov hitpoint.y, eax
    
        invoke dwtoa, eax, offset Y
        invoke SetWindowText, hwndY, offset Y
        ret
    updateXY ENDP
    
    ; https://msdn.microsoft.com/library/windows/desktop/ms633573.aspx
    WndProc PROC hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    
        cmp uMsg, WM_MOUSEMOVE
        je ON_WM_MOUSEMOVE
    
        cmp uMsg, WM_PAINT
        je ON_WM_PAINT
    
        cmp uMsg, WM_CREATE
        je ON_WM_CREATE
    
        cmp uMsg, WM_LBUTTONDOWN
        je ON_WM_LBUTTONDOWN
    
        cmp uMsg, WM_LBUTTONUP
        je ON_WM_LBUTTONUP
    
        cmp uMsg, WM_DESTROY
        je ON_WM_DESTROY
    
        jmp ON_DEFAULT
    
        ON_WM_DESTROY:              ; User closes program
            invoke PostQuitMessage, NULL
            jmp EXIT
    
        ON_WM_CREATE:
            ; Create windows for text
            invoke CreateWindowEx, WS_EX_CLIENTEDGE, offset StaticClassName, offset X, WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT, 20, 20, 50, 25, hWnd, 1, hInstance, NULL
            mov hwndX, eax
            invoke CreateWindowEx, WS_EX_CLIENTEDGE, offset StaticClassName, offset Y, WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT, 90, 20, 50, 25, hWnd, 1, hInstance, NULL
            mov hwndY, eax
            invoke CreateWindowEx, WS_EX_CLIENTEDGE, offset StaticClassName, offset labelWaiting, WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT, 20, 60, 80, 23, hWnd, 1, hInstance, NULL
            mov hwndState, eax
    
            ; Create pen for LineTo
            invoke CreatePen, PS_SOLID, 10, 00FF0000h
            mov hPen, eax
    
            jmp EXIT
    
        ON_WM_LBUTTONDOWN:
    
            ; last mouse position = current mouse position
            mov eax, hitpoint.x
            mov lastpoint.x, eax
            mov eax, hitpoint.y
            mov lastpoint.y, eax
    
            mov [state], DRAWING
            invoke SetWindowText, hwndState, offset labelDrawing
            jmp EXIT
    
        ON_WM_LBUTTONUP:
            mov [state], WAITING
            invoke SetWindowText, hwndState, offset labelWaiting
            jmp EXIT
    
        ON_WM_MOUSEMOVE:
            invoke updateXY, lParam                     ; PROC above
    
            cmp [state], DRAWING
            jne EXIT
    
            invoke InvalidateRect, hWnd, NULL, FALSE    ; https://msdn.microsoft.com/library/dd145002.aspx
            jmp EXIT
    
        ON_WM_PAINT:
            invoke BeginPaint, hWnd, offset ps
            invoke MoveToEx, ps.hdc, lastpoint.x, lastpoint.y, NULL
            invoke SelectObject, ps.hdc, hPen;
            invoke LineTo, ps.hdc, hitpoint.x, hitpoint.y
            mov eax, hitpoint.x             ; last mouse position = current mouse position
            mov lastpoint.x, eax
            mov eax, hitpoint.y
            mov lastpoint.y, eax
            invoke EndPaint, hWnd, offset ps
            jmp EXIT
    
        ON_DEFAULT:     ; handle any message that program don't handle
            invoke DefWindowProc, hWnd, uMsg, wParam, lParam    ; https://msdn.microsoft.com/library/windows/desktop/ms633572.aspx
            jmp EXIT
    
        EXIT:
            ret
    WndProc ENDP
    
    WinMain PROC hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
    
        ; WNDCLASSEX structure in MSDN, declaration in windows.inc
        ; https://msdn.microsoft.com/library/windows/desktop/ms633577.aspx
        invoke LoadIcon, NULL, IDI_APPLICATION  ; Load default icon
        mov wc.hIcon, eax
        mov wc.hIconSm, eax
        invoke LoadCursor, NULL, IDC_ARROW      ; Load default cursor
        mov wc.hCursor, eax
    
        mov wc.cbSize, SIZEOF WNDCLASSEX        ; size of this structure
        mov wc.style, CS_HREDRAW or CS_VREDRAW  ; style of windows https://msdn.microsoft.com/library/windows/desktop/ff729176.aspx
        mov wc.lpfnWndProc, OFFSET WndProc      ; andress of window procedure
        mov wc.cbClsExtra, NULL
        mov wc.cbWndExtra, NULL
        push hInstance
        pop wc.hInstance
        mov wc.hbrBackground,COLOR_WINDOW+1     ; background color, require to add 1
        mov wc.lpszMenuName, NULL
        mov wc.lpszClassName, OFFSET ClassName
    
        invoke RegisterClassEx, offset wc       ; https://msdn.microsoft.com/library/windows/desktop/ms633587.aspx
    
        ; https://msdn.microsoft.com/library/windows/desktop/ms632680.aspx
        invoke CreateWindowEx, WS_EX_CLIENTEDGE, offset ClassName, offset AppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 600, 600, NULL, NULL, hInstance, NULL
        mov hwnd, eax                       ; Store windows handle
        invoke ShowWindow, hwnd, CmdShow    ; https://msdn.microsoft.com/library/windows/desktop/ms633548.aspx
        invoke UpdateWindow, hwnd           ; https://msdn.microsoft.com/library/windows/desktop/dd145167.aspx
    
        ; Message Loop
        MESSAGE_LOOP:                       ; https://msdn.microsoft.com/library/windows/desktop/ms644936.aspx
    
            invoke GetMessage, offset msg, NULL, 0, 0
            test eax, eax
            jle END_LOOP
    
            invoke TranslateMessage, offset msg
            invoke DispatchMessage, offset msg
    
            jmp MESSAGE_LOOP
    
        END_LOOP:
        mov eax, msg.wParam
        ret
    WinMain ENDP
    
    main PROC
    
        invoke GetModuleHandle, NULL    ; https://msdn.microsoft.com/library/windows/desktop/ms683199.aspx
        mov hInstance, eax              ; return an instance to handle in eax
    
        invoke GetCommandLine           ; https://msdn.microsoft.com/library/windows/desktop/ms683156.aspx
        mov CommandLine, eax            ; return a pointer to the command-line for current process
    
        invoke WinMain, hInstance, NULL, CommandLine, SW_SHOW
    
        invoke ExitProcess, eax
    main ENDP
    
    END main