News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

How can I.....? GDI32 question...

Started by zedd151, March 03, 2025, 04:51:09 PM

Previous topic - Next topic

zedd151

This came up while converting my tic tac toe game to NOT use bitmap image resources *.bmp.
I now use gdi32 drawing functions to draw lines and flood fill the interior with a solid color. In others, I use FillRect to draw elements of the game board.
Still another draws an ellipse.

My question is, can I only do this once on program startup, and save a bitmap of each of them, to simply use BitBlt to draw them onto the DC later rather than using the drawing functions each time that WM_PAINT is called? It draws too slowly that way...

I know it is possible, but I am at a loss on how to do so properly.
I've got some ideas, but not sure if they are sound ideas. I don't need code, just point me in the right direction.  :smiley:
Well, maybe a little code would help to demonstrate the process. I have been known to screw things like this up. And badly.
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

_japheth

Quote from: zedd151 on March 03, 2025, 04:51:09 PMMy question is, can I only do this once on program startup, and save a bitmap of each of them, to simply use BitBlt to draw them onto the DC later rather than using the drawing functions each time that WM_PAINT is called?

Yes. The simplest approach is to use the technique that's also used by many tools to save the contents of the desktop to a .BMP file: create a memory context and copy the screen to this context. In your case you have to copy the client area instead of the screen, of course. A little obstacle may arize if you have to deal with palettes:

   invoke   GetSystemMetrics,SM_CXSCREEN
   mov dwWidth,eax
   invoke   GetSystemMetrics,SM_CYSCREEN
   mov dwHeight,eax
   invoke GetDC,0
   mov hDCSrc,eax   
   invoke CreateCompatibleDC,eax          ;create a memory context
   mov hDCMemory,eax
   invoke CreateCompatibleBitmap,hDCSrc,dwWidth,dwHeight   ; create the bitmap for the memory context
   mov hBitmap,eax
   invoke SelectObject,hDCMemory,eax
   mov hOldBitmap,eax
   invoke GetDeviceCaps,hDCSrc,RASTERCAPS;Raster
   and eax,RC_PALETTE
   mov HasPaletteScrn,eax   ;Palette
   invoke GetDeviceCaps,hDCSrc,SIZEPALETTE   ;Size of
   mov PaletteSizeScrn,eax
   .if HasPaletteScrn && (PaletteSizeScrn == 256)
      mov LogPal.palVersion , 300h
      mov LogPal.palNumEntries , 256
      invoke GetSystemPaletteEntries,hDCSrc,0,256,addr LogPal.palPalEntry
      invoke CreatePalette,addr LogPal
      mov hPal,eax
      invoke SelectPalette,hDCMemory,hPal,0
      mov hPalPrev,eax
      invoke RealizePalette,hDCMemory
   .endif
   invoke BitBlt,hDCMemory,0,0,dwWidth,dwHeight,hDCSrc,0,0,SRCCOPY
   invoke SelectObject,hDCMemory,hOldBitmap
   mov hBitmap,eax
   .if HasPaletteScrn && (PaletteSizeScrn == 256)
         invoke SelectPalette,hDCMemory,hPalPrev,0
;         mov hPal,eax
   .endif
   invoke ReleaseDC,0,hDCSrc



 
Dummheit, gepaart mit Dreistigkeit - eine furchtbare Macht.

zedd151

#2
Quote from: _japheth on March 03, 2025, 10:45:53 PMYes. The simplest approach is to use the technique that's also used by many tools to save the contents of the desktop to a .BMP file
Well crap. I actually have code to capture the desktop.  :greensml:  I had not thought about that.
Thanks.  :smiley:

Some of the things in that code, were some of the steps I would have *tried* already. It confirms that my thought processes were not too far off.  :smiley:
I don't think I will need device caps or pallet (my iPads auto correct keeps  correcting there), or changing bit depth, since I won't be saving it to file.

I am going to play around with this later today.  :azn:  and post the results of my experimentation, if all goes well.
Nothing ventured, nothing gained.  :cool:
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

zedd151

#3
Create our "O" piece in WM_CREATE...
      .if uMsg == WM_CREATE
        invoke DrawShapeO, hWin ;; O piece DC handle and its bmp handle are global in scope, to delete upon program close.


... By calling this procedure:
I use global variables, and will need those to delete the O_DC and O_bmp in WM_CLOSE
    DrawShapeO proc hWin:dword
    local hDC:dword, x:dword, y:dword
    local hPen:dword, hBrush:dword, hBrushOld:dword, hPenOld:dword
    .data
        O_DC        dd 0 ; DC for "O" piece
        O_bmp      dd 0 ; bmp for "O" piece
        O_bmpold    dd 0
    .code
        invoke  GetDC, hWin  ; get DC for the client area of the main window.    :P   
        mov    hDC, eax
        invoke  CreateCompatibleDC, hDC
        mov    O_DC, eax
        invoke  CreateCompatibleBitmap, hDC, 140, 140
        mov    O_bmp, eax
        invoke SelectObject, O_DC, O_bmp
        invoke CreateSolidBrush, bcolor  ;; get background colored brush
        mov hBrush, eax
        invoke SelectObject, O_DC, hBrush
        mov hBrushOld, eax
        invoke ExtFloodFill, O_DC, 2, 2, 0, FLOODFILLSURFACE ; flood fill
        invoke CreatePen, PS_SOLID, 21, blue
        mov hPen, eax
        invoke SelectObject, O_DC, hPen
        add x, 20
        mov eax, x
        add eax, 98
        add y, 20
        mov ecx, y
        add ecx, 98
        invoke Ellipse, O_DC, x, y, eax, ecx
        invoke SelectObject, O_DC, hBrushOld
        invoke DeleteObject, hBrush
        invoke SelectObject, O_DC, hPenOld
        invoke DeleteObject, hPen
        invoke ReleaseDC, hWin, hDC
        invoke SelectObject, O_DC, O_bmp
        mov O_bmpold, eax
        ret
    DrawShapeO endp

Now we can use O_DC to draw "O" to memory DC, prior to memory DC getting BitBlt'ed to the window DC
excerpt:
        ;; drawpiece "O" in these positions
        invoke BitBlt, mDC, 14, 14, 154, 154, O_DC, 0, 0, SRCCOPY
        invoke BitBlt, mDC, 174, 14, 154, 154, O_DC, 0, 0, SRCCOPY
        invoke BitBlt, mDC, 14, 174, 154, 154, O_DC, 0, 0, SRCCOPY
        invoke BitBlt, mDC, 174, 174, 154, 154, O_DC, 0, 0, SRCCOPY
       
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        ;;
        ;;  WM_PAINT ending code
        ;;
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
       
        ;; blit memory DC to window DC
        invoke BitBlt, hDC, 0, 0, rct.right, rct.bottom, mDC, 0, 0, SRCCOPY

        ;; WM_PAINT cleanup
        invoke SelectObject, mDC, hBmpOld
        invoke DeleteObject, hBmp
        invoke DeleteDC, mDC
        invoke EndPaint, hWin, addr ps
        mov eax, 0
        ret

Lastly we delete the DC and bmp associated with the "O" piece in WM_CLOSE:
      .elseif uMsg == WM_CLOSE
        invoke SelectObject, O_DC, O_bmpold
        invoke DeleteObject, O_bmp
        invoke DeleteDC, O_DC

Works fine!  Yes the colors chosen are an ugly combination. Only used for this demonstration.


Expert opinions welcome. Meaning those with good gdi32 experience. Does this code look okay?

Now if I am 100% correct, I will no longer need to call InvalidateRect in WM_SIZE anymore... to repair drawing issues upon resizing the widow, that were caused by FloodFill/ExtFloodFill.  I will test that theory later.  :azn:
Later: I tested it, and so far, so good.  :thumbsup:  it proved true, at least with only 4 drawing events...

Thanks _japheth for your assist and moral support, although I had the right idea already. I just didn't know it so didn't try it, I was afraid to royally screw things up as usual.  :biggrin:
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

zedd151

I am now converting tic tac toe, using the above methods. So far it is going well. No redrawing issues, and no gdi leakage, and no need for call to InvalidateRect in WM_SIZE.  :azn:  I think I'm onto something.
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

zedd151

#5
I have completed reworking tic-tac-toe to use the methods described above to create reusable memory bitmaps rather than resource bitmaps, or doing multiple drawing functions directly in WM_PAINT.
The memory bitmap creation code is called from WM_CREATE. And the memory bitmaps only get BitBlt'd in WM_PAINT, just as if actual resource bitmaps were used. Perfect!
No more InvalidateRect in WM_SIZE.  :biggrin:

Attachment removed,
Latest version is posted in the Game Development board..
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

NoCforMe

Here's something that may or may not be useful to you.
If I read you correctly, you want to include bitmap images in your program without having to put them in a resource file.
Here's my method for doing this:

1. I create a bitmap in memory by defining it with DB statements. (How you create the data is another question; let's assume that you have the bits at hand that define the image you want to create.)

2. I use this magical li'l routine to "get a handle" to this bitmap, one that I can use with any GDI function that takes a bitmap handle (like, say, BitBlt()):

;====================================
; GetBMPhandle (bmpPtr, hWin)
;
; "Opens" BMP "file" from memory.
;
; Returns handle to bitmap.
;
; Hat tip to "fearless" of the MASM32 forum for
; vastly simplifying this process!
;====================================

GetBMPhandle PROC bmpPtr:DWORD, hWin:HWND
LOCAL hDC:HDC

INVOKE GetDC, hWin
MOV hDC, EAX
MOV EDX, bmpPtr
LEA ECX, [EDX + SIZEOF BITMAPFILEHEADER] ;Point to BITMAPINFOHEADER header.
MOV EAX, [EDX].BITMAPFILEHEADER.bfOffBits ;Offset of bitmap bits.
ADD EDX, EAX ;Point to bitmap bits.
INVOKE CreateDIBitmap, hDC, ECX, CBM_INIT, EDX, ECX, DIB_RGB_COLORS
PUSH EAX
INVOKE DeleteDC, hDC
POP EAX
RET ;Return w/handle.

GetBMPhandle ENDP

It's neat and a treat, and makes your source code self-contained without the need for an external .rc file.
Assembly language programming should be fun. That's why I do it.

NoCforMe

Regarding how you create the in-memory bitmap, it's actually easy peasy.
I use Hutch's bin2db utility to create the DB statements from a bitmap (.BMP) file, like so:

; E:\Programming stuff\Assembly language\Windows\Bitmaps\RedX (12x12x16).bmp is 214 bytes long
db 66,77,214,0,0,0,0,0,0,0,118,0,0,0,40,0
db 0,0,12,0,0,0,12,0,0,0,1,0,4,0,0,0
db 0,0,96,0,0,0,19,11,0,0,19,11,0,0,16,0
db 0,0,16,0,0,0,0,0,0,0,0,0,128,0,0,128
db 0,0,0,128,128,0,128,0,0,0,128,0,128,0,128,128
db 0,0,192,192,192,0,128,128,128,0,0,0,255,0,143,143
db 247,0,0,255,255,0,255,0,0,0,255,0,255,0,255,255
db 0,0,255,255,255,0,153,175,255,255,250,153,114,0,153,154
db 255,255,169,153,101,0,249,153,175,250,153,159,99,0,255,153
db 154,169,153,255,32,0,255,249,153,153,159,255,88,0,255,255
db 153,153,255,255,51,0,255,250,153,153,175,255,110,0,255,169
db 153,153,154,255,100,0,250,153,159,249,153,175,0,0,169,153
db 255,255,153,154,0,0,153,159,255,255,249,153,180,236,153,255
db 255,255,255,153,255,69

This includes the bitmap header and the actual bitmap bits; the code I posted above knows how to find the bitmap bits in there.
Assembly language programming should be fun. That's why I do it.

zedd151

Thanks for your example. Maybe for another time though, it does sound kind of nifty.
But here, I went another route. Creating the memory bitmap in code, using Rects, Lines, an Ellipse, etc. and plenty of use of FloodFill or ExtFloodFill.  Not very hard to do, btw. Just open a real .bmp image in mspaint of what you want to use, get all the needed data points then figure out which gdi drawing function would be most suitable for it. Its a little code heavy, but was fun to do.  :smiley:

Maybe I can try your method on Connect 4... which I will get started on now that tictactoe is just about done.
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

NoCforMe

Well, that's a good strategy too; create the thing once in memory, then use it multiple times.
Assembly language programming should be fun. That's why I do it.

zedd151

Your method seems it would be better for multiple colors. I think I used a total of six colors, but maximum 3 colors for each bitmap.

¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

zedd151

#11
Fully Commented (* note 4) example of the methods that I ultimately used in ttt.
Function and variable names (used here) are for demonstration purposes. You may of course choose other names.  :biggrin:

  Example Code:


    Custom1 proto :dword, :dword

    .data?
   
        hDC_Custom1    dd ?                                            ;; global variable for hDC_Custom1
        hBmp_Custom1    dd ?                                            ;; global variable for hBmp_Custom1
        hBmpOld_Custom1 dd ?                                            ;; global variable for hBmpOld_Custom1

    .code

    Custom1 proc hWin:dword, bclr:dword
    local hDC:dword, hPen:dword, hBrush:dword, hBrushOld:dword, hPenOld:dword
        invoke GetDC, hWin                                        ;; get client area DC of main window
        mov hDC, eax                                                    ;; save as local variable
        invoke CreateCompatibleDC, hDC
        mov hDC_Custom1, eax                                            ;; save as global variable - important * note2
        invoke CreateCompatibleBitmap, hDC, 140, 140
        mov hBmp_Custom1, eax                                          ;; save as global variable - important * note2
        invoke SelectObject, hDC_Custom1, hBmp_Custom1
        mov hBmpOld_Custom1, eax                                        ;; save as global variable - important * note2

    ;; any brushes, pens or other gdi objects used after this point, should be kept local, and
    ;; deleted here inside the function after finished using them

        ;; this piece of code fills the 'bitmap' with the background color 'bclr'.
        ;; important if your custom drawn bitmap has 'transparent' elements to it, and may be omitted otherwise.
       
        invoke CreateSolidBrush, bclr                                  ;; create brush in background color 'bclr'
        mov hBrush, eax                                                ;; store in hBrush
        invoke GetPixel, hDC_Custom1, 1, 1                              ;; get the current color
        invoke SelectObject, hDC_Custom1, hBrush                        ;; select hBrush
        mov hBrushOld, eax
        invoke ExtFloodFill, hDC_Custom1, 1, 1, eax, FLOODFILLSURFACE  ;; flood fill everything that is current color
        invoke SelectObject, hDC_Custom1, hBrushOld                    ;; select hBrushOld
        invoke DeleteObject, hBrush                                    ;; delete hBrush

        ;; ######################################################################
        ;;
        ;;    this area is where you will draw your lines, rectangles, ellipses, etc.
        ;;    after using brushes, pens, etc., you must delete them when no longer needed.
        ;;
        ;; ######################################################################
       

        ;; do NOT delete hDC_Custom1, hBmp_Custom1, or hBmpOld_Custom1 Here!!!
        ;; delete them in WM_CLOSE... that is the reason to keep those variables global in scope.
       
        invoke ReleaseDC, hWin, hDC                                    ;; release main window DC
        ret
    Custom1 endp


      note1  Now you can call this function from WM_CREATE to create your custom drawn bitmap
      note2  You can now use the handle, hDC_Custom1 to BitBlt your custom drawn bitmap into the DC in WM_PAINT...
              ... the DC used will depend on whether double buffering is used or not.
      note3  You must delete hDC_Custom1, hBmp_Custom1 in WM_CLOSE


      typical usage:

    ;;  .elseif uMsg == WM_CREATE
    ;;    invoke Custom1, hWin, BackColor  ; BackColor is whatever background color you want to use.


    ;;  .elseif uMsg == WM_CLOSE
    ;;    invoke SelectObject, hDC_Custom1, hBmpOld_Custom1                    ;; select old bmp
    ;;    invoke DeleteObject, hBmp_Custom1                                    ;; delete bmp
    ;;    invoke DeleteDC, hDC_Custom1                                        ;; delete DC

That is it, in a nutshell.
It strikes me that since I have here the function name appended to its accociated handle names, I could probably make a qeplugin for inserting the template code into an asm source using a dialog box for choosing the desired name.  :biggrin:  More on that, in a new topic near you soon.

note 4: :tongue:  

¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

Vortex

Hello,

You don't need embedded data in the source code. Hutch's File Data Assembler ( fda ) does the the job, it converts any file to a linkable MS COFF object module :

\masm32\fda.exe
QuoteFile Data Assembler 2.0 Copyright (c) The MASM32 Project 1998-2005

PARAMETERS
      1 source file
      2 target object file
      3 data label name
      4 data alignment [optional]

NOTES
      Source file MUST be an existing file
      Target file MUST have an "obj" extension
      The optional data aligment MUST be specified in powers of 2
      Alignment range is 1 to 8192 bytes, default is 4 bytes.
      Long names are supported but names with spaces MUST be
      enclosed in quotations.
OUTPUT
      fda.exe produces two (2) files,
      a. An object module file as named in the target.
      b. An INCLUDE file of the same name as the target
         which contains an EXTERNDEF for the data label
         name and an equate that contains the bytecount
         for the data in the object module.
EXAMPLE
      fda "My Source File.Ext" target.obj MyFile 16

Here is a quick sample. The traditional method to introduce binary resource data to the source code :

.data
DlgboxRsrc  db 1,0,255,255,0,0,0,0,0,0,0,0,64,0,207,16
            db 0,0,6,0,6,0,147,0,136,0,0,0,0,0,71,0
            db 114,0,97,0,112,0,104,0,0,0,8,0,0,0,0,0
            db 77,0,83,0,32,0,83,0,65,0,78,0,83,0,32,0
            db 83,0,69,0,82,0,73,0,70,0,0,0

    xor     ecx,ecx
    invoke  DialogBoxIndirectParam,hInstance,\
            ADDR DlgboxRsrc,ecx,ADDR DlgProc,ecx

The binary resource data can be extracted from a compiled resource script ( .res file ) with a tool like Resource Hacker.

Dlgbox.res -> Resource Hacker -> Dlgbox.bin

Using Hutch's fda tool :

\masm32\fda Dlgbox.bin Dlgbox.obj DlgboxRsrc
\masm32\bin\ml /c /coff Lines.asm
\masm32\bin\polink /SUBSYSTEM:WINDOWS Lines.obj Dlgbox.obj

The source code is now simplified :

EXTERN DlgboxRsrc:BYTE
.
.
.code
.
.   
    xor     ecx,ecx
    invoke  DialogBoxIndirectParam,hInstance,\
            ADDR DlgboxRsrc,ecx,ADDR DlgProc,ecx

sinsi

Quote from: zedd151 on March 05, 2025, 04:03:59 AM        invoke GetWindowDC, hWin   ;; get main window DC

Are you sure that GetWindowDC is the one to use? That gets the DC for the entire window, including non-client areas. According to Microsoft
QuoteGetWindowDC is intended for special painting effects within a window's nonclient area. Painting in nonclient areas of any window is not recommended.

zedd151

#14
It is just used sinsi, to create the compatible memory DC and bitmap, not doing any drawing to it.
Isn't that the same way one would do it in WM_PAINT? Create the compatible DC from the DC returned by BeginPaint? Or is there a difference that I do not yet understand.
  :toothy:

Nevermind. I had it backwards, what I was thinking...  it should be  GetDC  Thanks for catching that, sinsi  :thumbsup:

Looks like I have a few minor adjustments to make.  :tongue:  :joking:
I have modified all posts and attachments using GetWindowDC, replacing it with GetDC.
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—