News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Drawing lines

Started by RuiLoureiro, December 05, 2007, 07:55:01 PM

Previous topic - Next topic

RuiLoureiro

Hi all
I need to draw some vertical lines and i am using MoveToEx and LineTo to do it but the lines dont appear in the screen. It may be a problem with the color of the lines i dont know. How to specify it ? Any help about this would be appreciated. Thanks
RuiLoureiro

donkey

Be sure that you are setting the pen colour and not the brush colour when drawing lines. You can get the current pen colour as follows...

invoke GetCurrentObject,[LineDC],OBJ_PEN
mov [hp],eax
invoke GetObject,[hp],SIZEOF LOGPEN,ADDR lpen


You would generally create a pen using CreatePen or CreatePenIndirect or choose one of the stock objects, I prefer creating my own pens it allows more control over the type and colour...

invoke CreatePen,PS_SOLID,1,0FFh ; red pen width of 1 pixel
mov [hp], eax
invoke SelectObject, [LineDC], eax


Be sure to destroy the pen with DeleteObject when done with it.

Donkey
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

ChrisLeslie

I had used this macro in an old project to draw lines. It seemed to work to do some graphical drawing work.
drawLine MACRO DC,colr,xx1,yy1,xx2,yy2
    pushad
    invoke CreatePen,0,1,colr
    mov esi,eax    ;; hPen in esi
    invoke SelectObject,DC,esi
    mov edi,eax    ;; hPenOld in edi
    invoke MoveToEx,DC,xx1,yy1,NULL
    invoke LineTo,DC,xx2,yy2
    invoke SelectObject,DC,edi
    invoke DeleteObject,esi
    invoke DeleteObject,edi
    popad
ENDM

Some folk don't like macros, but then I'm not a purist! :bg

RuiLoureiro

Donkey,
            Thank you so much. Your advice is very good ! I like to prepare all kind of things at the begining of the program and then use it. Very good. Thank you

CrisLeslie,
            Thank you for your example. There is no problem about macros or not because i transform macros into procs and vice versa. It help me too. Thank you.
Now it is time to work ! When my problem was solved i will be here to say something.
Have a good night/day.
Rui

hutch--

Rui,

Just remember that if you are drawing lines on the client area of a window which is normal, you need to do it from the WM_PAINT message processing in your WndProc for your main window. This ensures that even when the window has been covered by another window that the lines you draw will still show after the window has been sized or overlapped by another.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

RuiLoureiro

Hutch,
         I didnt forget of WM_PAINT. Thank you for your help too.
My first window program has now 1Mb of code and data and it runs correctly ... till now !
At one point, when the program start, i call one proc to print on the client area. The same proc is under WM_PAINT and so, there is no problem when we maximize the window or when we move the mouse etc.

My work was this:

$PENWIDTH     equ 3

.data
_hdc             dd 0        ; Device context handle 
_hPen            dd 0        ; Pen handle
_hPenOld         dd 0        ; Pen handle
;-----------------------------------------------------------------------
.code

; Input:
;           _hdc    - Device context handle
;
CriaPen           proc      Cor:DWORD
                  pushad
                  ;
                  ; color: Cor, width: $PENWIDTH pixels
                  ; -----------------------------------
                  invoke    CreatePen, PS_SOLID, $PENWIDTH, Cor     ; eax
                  mov       _hPen, eax
                  invoke    SelectObject, _hdc, eax
                  mov       _hPenOld, eax
                  ;
                  popad
                  ret
CriaPen           endp
; ----------------------------------------------------------------------------
; Input:
;           _hdc    - Device context handle
;
SetPenColor       proc      Cor:DWORD
                  invoke    SetDCPenColor, _hdc, Cor
                  ret
SetPenColor       endp
; -----------------------------------------------------------------------------
DestroiPen        proc
                  pushad
                  cmp       _hPen, 0
                  je        _eDestroiPen
                  ;
                  invoke    SelectObject, _hdc, _hPenOld                                 
                  invoke    DeleteObject, _hPen
                  ;
_eDestroiPen:     popad
                  ret
DestroiPen        endp
;-----------------------------------------------------------------------------
; Input:
;           _hdc    - Device context handle
;
DrawVLine       proc    XIni:DWORD, YIni:DWORD, YFim:DWORD, Cor:DWORD
                pushfd
                pushad
                ;
                invoke    SetDCPenColor, _hdc, Cor               
                ;
                ; eax = X, ebx = Y
                ; ----------------
                mov     eax, XIni
                mov     ebx, YIni
                invoke  MoveToEx, _hdc, eax, ebx, NULL
                ;
                mov     eax, XIni
                mov     ebx, YFim               
                invoke  LineTo, _hdc, eax, ebx 
                ;
                popad
                popfd
                ret
DrawVLine       endp


All is running well !

Thank you Donkey, ChrisLeslie, Hutch for your help
Have a good night/day
RuiLoureiro

donkey

Hi RuiLoureiro

Just a little factoid, there is nothing really wrong with your code. When you use SelectObject to select an object into a DC it returns the handle of the object that has been replaced. Since you are only creating a pen for one-shot draws you have no need to save it's handle. You simply have to destroy the handle returned when you select in the old pen...

invoke    CreatePen, PS_SOLID, $PENWIDTH, Cor     ; eax
invoke    SelectObject, _hdc, eax
mov       _hPenOld, eax
...
invoke    SelectObject, _hdc, _hPenOld
invoke    DeleteObject, eax


And by the way why all the pushad/popad & pushfd/popfd stuff ? You are not using any registers that need preserving so it's not really necessary and pretty much a waste of cycles.

Donkey
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

RuiLoureiro

Hi donkey,
                Thank you for the reply

Quote from: donkey on December 07, 2007, 05:02:53 AM
You simply have to destroy the handle returned when you select in the old pen...

    I think i saw it in an example somewhere. But Ok. We are always learning.

Quote from: donkey on December 07, 2007, 05:02:53 AM
why all the pushad/popad & pushfd/popfd stuff ? You are not using any registers that need preserving so it's not really necessary and pretty much a waste of cycles.

    I have a lot of procs that assume values in the registers. In this particular case i need to preserve some registers. But also i have one rule to myself: all procs that print stuff preserve all registers and flags. In this way i can use/test it nearly everywhere in the program. Also, many times i make the version1, version2,... etc. of the same proc. To test or to use it, i comment one and paste another. And i learn with it.
   I have another rule to myself: i dont want to know what registers the system procedures preserve (
except ebp)
    Another version of DrawVLine may be this:


; Input:
;           _hdc    - Device context handle
;
DrawVLine       proc    XIni:DWORD, YIni:DWORD, YEnd:DWORD, Cor:DWORD
                ;pushfd
                pushad
                ;
                invoke    SetDCPenColor, _hdc, Cor               
                ;
                invoke  MoveToEx, _hdc, XIni, YIni, NULL
                ;
                invoke  LineTo, _hdc, XIni, YEnd                   
                ;
                popad
                ;popfd
                ret
DrawVLine       endp

Thanks
Rui


donkey

Quote from: RuiLoureiro on December 07, 2007, 03:41:59 PMI have another rule to myself: i dont want to know what registers the system procedures preserve (
except ebp)

Hi,

The Windows API procedures preserve ESI, EDI and EBX. Your procedures are also expected to preserve those registers by Windows. If you do not use those registers they will not be modified and therefore do not have to be explicitly preserved. EAX, ECX and EDX are considered volatile registers and your procedure is not expected to do anything to preserve them. EBP and ESP are used for the stack and should be returned unaltered. For flags you must preserve the state of the direction flag Windows also guarantees that that flag will be preserved, all others are volatile.

Donkey
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

hutch--

Rui,

> I have another rule to myself: i dont want to know what registers the system procedures preserve (except ebp)

This works against you in a number of ways. By using PUSHAD / POPAD is slower because it has to push and pop all of thew general purpose registers where in many instances you don't have to do it to ANY registers if the proc only uses EAX ECX and EDX.

The standard convention is very reliable, vary from that and you have to write dangerous unreliable code or slow code. As Donkey mentioned, freely modify EAX ECX EDX by ALWAYS preserve EBX ESI EDI and if you write stack frame free procedures ESP and EBP. If ALL of the necessary registers are not the same on procedure exit, your code will not run on all versions of Windows.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

Jimg

Just a note of support-  I regularly use pusha/popa in non-time critical code for simplicity, and you are calling enough slow api's that the extra cycles will be lost in the noise.

RuiLoureiro

Quote from: Jimg on December 08, 2007, 02:57:51 PM
Just a note of support ...

Hi Jimg
          Thank you

Hutch,
          Thanks for the advice.

Donkey,
           Ok, but  is this code better to preserve all registers ?


; ----------------------------------------------------------------------------------------------
; Input:
;           _hdc    - Device context handle
; Info:
;           Preserve all registers
;
DrawVLine       proc    XIni:DWORD, YIni:DWORD, YEnd:DWORD, Cor:DWORD
                push   eax
                push   ecx
                push   edx
                ;
                invoke    SetDCPenColor, _hdc, Cor               
                ;
                invoke  MoveToEx, _hdc, XIni, YIni, NULL
                ;
                invoke  LineTo, _hdc, XIni, YEnd                   
                ;
                pop   edx
                pop   ecx
                pop   eax
                ret
DrawVLine       endp
 
Rui

donkey

Hi RuiLoureiro,

If the procedure that calls the function expects those registers to be preserved then yes you will have to preserve them, however as a general rule I follow the Windows ABI (application binary interface) and only preserve the registers mentioned in my previous post. Is it better coding practice ? well not really in any significant way, it is just sticking to the set of rules that Microsoft created for Win32, yes it will make your application run marginally faster but as Jimg pointed out the difference is virtually unnoticeable in non-critical functions. In your case a GDI based line drawing function would not be particularly critical, if it was you would have implemented your own and stayed clear of the GDI, so there is little gained in not preserving them.

Now the argument for sticking to the ABI... If you later replace your internal function with one from the API you will be forced to modify all calls to the function because internally you expect all registers to be preserved. You may think that this will not happen but it does and in the case of line drawing may require alot of tweaking. However if you follow the ABI you know in advance that Windows API functions will react the exact same way your internal functions react. It is always better if everything reacts the same way every time, consistency reduces errors.

Also, you should be using EAX to return some sort of indication that the function was successful, for example NULL if successful/-1 if an error occurred, this facilitates error and exception handling being built into your program making it more robust. I have always believed that Microsoft/Intel made a critical error in judgment when they chose to preserve EBX over ECX, the counting register should have been preserved, the base register is more likely to be volatile. In your case preserving ECX is a good idea as this is a function that may be called in a counting loop and the x86 CPU has some loop and counting functions that use ECX. EDX however is the extended return register under the ABI and therefore there is no need to preserve it.

Donkey
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

RuiLoureiro

Hi donkey,
               I read your "document" carefully and i agree with you completely. In the future i will go to follow ABI "plus ecx case".

Quote from: donkey on December 08, 2007, 05:39:18 PM
In your case a GDI based line drawing function would not be particularly critical, if it was you would have implemented your own and stayed clear of the GDI, so there is little gained in not preserving them.

           I would like to know what to do to implement my own without suport of GDI. How to begin.

Quote
In Also, you should be using EAX to return some sort of indication that the function was successful, for example NULL if successful/-1 if an error ...

           I generally use EAX=0 meaning NO (not found/no error/...) and EAX=1 YES and/or EAX=2 (error/...)

Quote
I have always believed that Microsoft/Intel made a critical error in judgment when they chose to preserve EBX over ECX, the counting register should have been preserved, the base register is more likely to be volatile.

          I agree. But in my case i have no problems with ECX because i use variables with the length at the back and tables the same way. Something like this (it waste memory):

            dd 10       (at  -8 )                                                                                 dd DimY
            dd 7         (at  -4 )                         dd 3                                                 dd DimX
_Var1    db "Example"                      _Tbl    dd offset Var1                _ArrayXY      dd x dup (?)
            db 3 dup (?)                                  dd offset Var2
            db 0     (null terminated too)           dd offset Var3

Thank you, donkey.

Rui

donkey

Hi RuiLoureiro,

You have the ability to write directly to a bitmap so any function of the GDI can be imitated at a much greater speed. I have posted a few here from time to time and have gotten in some cases 20-30x the speed of the GDI. For myself I use 32 bit DIB images internally in my graphics projects, this helps in that the pixel depth is natural. Here is an old example of a gradient fill that I wrote some time ago (it must be old, I was still using MASM then  :P ). It extracts the handle to the bitmap from the DC passed in parameters then obtains a pointer to the actual image bits, at that point it performs a gradient fill, not sure how much faster this one is than the MSIMG one but it demonstrates the concept well enough. For lines you should look into the Bresenham line algorithm, it is a fairly fast and reliable one that is easy enough to find a suitable implementation of.

GradientFillD proc gradDC,gradTriVert,gradRect,gradSize,gradFillMethod
LOCAL gBitmap :DWORD
LOCAL gradDIBits :DWORD
LOCAL gbmp :DIBSECTION
LOCAL gradSteps :DWORD

LOCAL deltaRED :BYTE
LOCAL deltaGRN :BYTE
LOCAL deltaBLU :BYTE

;invoke GradientFill,hMemDC,ADDR vert,2,ADDR gRect,1,gFillMethod
; This algorithm is for a single TRIVERTEX structure for speed
; gradSize is ignored but included for compatibility

; Get the DIBits
invoke GetCurrentObject,gradDC,OBJ_BITMAP
mov gBitmap,eax
invoke GetObject,gBitmap,SIZEOF DIBSECTION,ADDR gbmp
mov eax,gbmp.dsBm.bmBits
mov gradDIBits,eax

; Calulate the steps for each color
; Find the number of steps

.IF gradFillMethod == GRADIENT_FILL_RECT_V
mov edi,gradRect
mov eax,[edi].RECT.bottom
mov ecx,[edi].RECT.top
sub eax,ecx
.ELSE
mov edi,gradRect
mov eax,[edi].RECT.right
mov ecx,[edi].RECT.left
sub eax,ecx
.ENDIF
mov gradSteps,eax

mov edi,gradTriVert
; Red step
movzx eax,[edi].TRIVERTEX.Red
movzx ecx,[edi+SIZEOF TRIVERTEX].TRIVERTEX.Red
sub eax,ecx
test eax,eax
.IF !SIGN?
xor edx,edx
div gradSteps
mov deltaRED,al
.ELSE
neg eax
xor edx,edx
div gradSteps
neg eax
mov deltaRED,al
.ENDIF

; Green step
movzx eax,[edi].TRIVERTEX.Green
movzx ecx,[edi+SIZEOF TRIVERTEX].TRIVERTEX.Green
sub eax,ecx
test eax,eax
.IF !SIGN?
xor edx,edx
div gradSteps
mov deltaGRN,al
.ELSE
neg eax
xor edx,edx
div gradSteps
neg eax
mov deltaGRN,al
.ENDIF

; Blue step
movzx eax,[edi].TRIVERTEX.Blue
movzx ecx,[edi+SIZEOF TRIVERTEX].TRIVERTEX.Blue
sub eax,ecx
test eax,eax
.IF !SIGN?
xor edx,edx
div gradSteps
mov deltaBLU,al
.ELSE
neg eax
xor edx,edx
div gradSteps
neg eax
mov deltaBLU,al
.ENDIF


ret

GradientFillD endp
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable