The MASM Forum Archive 2004 to 2012

General Forums => The Workshop => Topic started by: RuiLoureiro on December 05, 2007, 07:55:01 PM

Title: Drawing lines
Post by: RuiLoureiro on December 05, 2007, 07:55:01 PM
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
Title: Re: Drawing lines
Post by: donkey on December 05, 2007, 08:41:06 PM
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
Title: Re: Drawing lines
Post by: ChrisLeslie on December 05, 2007, 09:04:41 PM
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
Title: Re: Drawing lines
Post by: RuiLoureiro on December 05, 2007, 09:43:03 PM
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
Title: Re: Drawing lines
Post by: hutch-- on December 05, 2007, 10:34:21 PM
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.
Title: Re: Drawing lines
Post by: RuiLoureiro on December 06, 2007, 06:53:23 PM
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
Title: Re: Drawing lines
Post by: donkey on December 07, 2007, 05:02:53 AM
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
Title: Re: Drawing lines
Post by: RuiLoureiro on December 07, 2007, 03:41:59 PM
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

Title: Re: Drawing lines
Post by: donkey on December 07, 2007, 09:06:00 PM
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
Title: Re: Drawing lines
Post by: hutch-- on December 08, 2007, 07:48:53 AM
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.
Title: Re: Drawing lines
Post by: Jimg on December 08, 2007, 02:57:51 PM
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.
Title: Re: Drawing lines
Post by: RuiLoureiro on December 08, 2007, 04:56:53 PM
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
Title: Re: Drawing lines
Post by: donkey on December 08, 2007, 05:39:18 PM
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
Title: Re: Drawing lines
Post by: RuiLoureiro on December 08, 2007, 09:32:27 PM
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
Title: Re: Drawing lines
Post by: donkey on December 08, 2007, 09:42:35 PM
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
Title: Re: Drawing lines
Post by: hutch-- on December 09, 2007, 04:10:57 AM
There are times when I feel like a voice crying in the wilderness when it comes to writing reliable code and this among oher things means understanding the limitations of having 8 general purpose registers and understanding why the operating system comforms to the Intel convention of what registers to preserve and what registers you can trash within a procedure.


Volatile = EAX ECX & EDX.
System = EBX ESP EBP ESI EDI


There IS a reason for which registers are volatile, they are the first three registers in processor order. They were historically the Accumulator, Counter and Data registers. The rest are in their order, Base address register, Stack pointer, Base pointer, Source index, Destination index.

From the Intel manual this is the operation of PUSHAD / POPAD in the above mentioned order.


PUSHAD
Temp (ESP);
Push(EAX);
Push(ECX);
Push(EDX);
Push(EBX);
Push(Temp);
Push(EBP);
Push(ESI);
Push(EDI);

POPAD
EDI Pop();
ESI Pop();
EBP Pop();
increment ESP by 4 (* skip next 4 bytes of stack *)
EBX Pop();
EDX Pop();
ECX Pop();
EAX Pop();


These two old instructions are useful in temporary code for debugging, mainly to find mistakes that someone else has made in not preserving the correct registers in the first place but also for tasks like checking the value of a register in the middle of an algo while its running but they have no place in production code as they are far slower than preserving on the basis of need.

The simple rule is to do what you need to do to write safe code but don't do more so you write slow code.

Often we have had questions like "Why does my assembler code run slower than C code" and the answer is you have written SLOW assembler code. Then you get the perennial question "Why does my app run on win9(whatever) but crashes on XP" and the answer is the same as it always was, learn your register preservation rules and the problem will magically go away.

For Rui the situation is a simple one, learn your register preservation rules and write fast reliable code including if you wish later, code with no stack frame, keep listening to bad advice and you will write slow unreliable code.
Title: Re: Drawing lines
Post by: donkey on December 09, 2007, 09:36:15 AM
Well said Steve, I can hear your voice from behind the tree in that same wilderness  :bg
Title: Re: Drawing lines
Post by: RuiLoureiro on December 09, 2007, 03:02:10 PM
donkey,
           thank you. I am going to study your code.

Hi Hutch,
           i didnt see your post before. Ok, when i need fast code i preserve only the registers that need to be preserved. Another thing is the rule (more or less) i said it is to myself. It is to myself.

           There is a problem with DrawVLine: it doesnt change the color. It draws the first set of lines in blue but when i change to red it draws in blue. I am sure the parameter is for red 000000FFh. So «invoke    SetDCPenColor, _hdc, Cor» doesnt work.

           Now, i am using the following code and it works correctly. At the end of drawing each set i use DestroiPen.


; ------------------------------------------------------------------------------
; Input:
;           _hdc    - Device context handle
;
DrawVerLine     proc    XIni:DWORD, YIni:DWORD, YEnd:DWORD, Cor:DWORD
                 ;
                cmp     _hPen, 0   
                jne     @F
                ;
                ; color: Cor, width: $PENWIDTH pixels
                ; --------------------------------------------------
                invoke    CreatePen, PS_SOLID, $PENWIDTH, Cor     
                mov       _hPen, eax
                invoke    SelectObject, _hdc, eax
                mov       _hPenOld, eax               
                ;
  @@:           invoke  MoveToEx, _hdc, XIni, YIni, NULL
                ;
                invoke  LineTo, _hdc, XIni, YEnd                   
                ;
                ret
DrawVerLine     endp
; ------------------------------------------------------------------------------
DestroiPen        proc
                  ;pushad
                  ;
                  cmp       _hPen, 0
                  je        _eDestroiPen
                  ;
                  invoke    SelectObject, _hdc, _hPenOld                                 
                  invoke    DeleteObject, eax                      ;_hPen
                  ;
                  mov       _hPen, 0                 
                  ;
_eDestroiPen:     ;popad
                  ret
DestroiPen        endp


Rui
Title: Re: Drawing lines
Post by: ChrisLeslie on December 09, 2007, 09:06:46 PM
Hi Rui

Isn't this code a little bit dangerous if you leak away too much memory by a possible loop with millions of undestroyed Pens?

Chris
Title: Re: Drawing lines
Post by: RuiLoureiro on December 09, 2007, 09:33:29 PM
Quote from: ChrisLeslie on December 09, 2007, 09:06:46 PM
Isn't this code a little bit dangerous if you leak away too much memory by a possible loop with millions of undestroyed Pens?

Hi Chris,
            Why ? I am not seeing.

1. _hPen =0  -> Create -> Draw  1ª line
2.                                    Draw  2ª line
3.                                    Draw  3ª line
4.                    Destroy -> _hPen=0

Where is the error ?   
Rui
           
Title: Re: Drawing lines
Post by: ChrisLeslie on December 09, 2007, 11:14:54 PM
Hi Rui

I think that if your program draws millions of lines with a procedure that does not clean up after each call, and you forget to invoke your DestroiPen procedure then your computer might start to argue with you.

Chris
Title: Re: Drawing lines
Post by: donkey on December 10, 2007, 04:48:23 AM
Quote from: ChrisLeslie on December 09, 2007, 11:14:54 PM
Hi Rui

I think that if your program draws millions of lines with a procedure that does not clean up after each call, and you forget to invoke your DestroiPen procedure then your computer might start to argue with you.

Chris

Actually, he can only create the pen once regardless of how many lines he draws, if _hPen contains a value the procedure will not create another until it is set to zero by the DestroiPen function. It seems like he has taken precautions though I would tend to create a pen at the start of the proggy and modify it when needed, destroying it on exit but that is my personal style, actually I think one app I did created 8 pens and only destroyed them just before ExitProcess.
Title: Re: Drawing lines
Post by: ChrisLeslie on December 10, 2007, 09:30:16 AM
You're right. I didn't see the anonymous jump. :U