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 to properly load a .png resource for use with GdipDrawImageRectI?

Started by zedd151, April 10, 2025, 01:49:24 AM

Previous topic - Next topic

zedd151

Quote from: six_L on April 10, 2025, 02:53:33 PMi didn't test the code.
Thanks, six_L  :thumbsup:

You didn't really have to though, I would have gotten around to it sooner or later.
I'll test this tomorrow. Its just past midnight here, I have to go to sleep now.

jj2007

Quote from: zedd151 on April 10, 2025, 08:33:51 AMA nice simple example with only the needed code will do

I know, but that would be hours of work - and indeed work that I've done already and don't want to repeat. I just wanted to demonstrate that transparency can be done with streaming from resources, using GdipCreateBitmapFromStream

@NoCforMe: What is streaming? Simply doing a job from a buffer, instead of reading from an open file. So instead of passing a file handle., you typically supply the address of a callback proc of yours to the streaming function, and when that API function calls you there, you hand over a bunch of bytes from a buffer. One often used example is the EM_STREAMIN message for RichEdit controls.

zedd151

Quote from: jj2007 on April 10, 2025, 10:45:34 PM@NoCforMe: What is streaming? Simply doing a job from a buffer, instead of reading from an open file.
Thats Microsoft for ya. Why not call it what it is, Microsoft? 

@jj: hours of work? For a simple no nonsense example? Surely you jest.  Have you forgotten how to write masm compatible (meaning ml.exe) assembly without MasmBasic? or uasm for that matter?
:joking:

It would take me hours, or even days (weeks, months, years - for some projects  :tongue: ), but that is because gdiplus (and some other apis) and some of its functions and methods are very new to me. And the MS docs are no great help, in many cases. It takes a lot of trial and error on my part. Then posting my test code here for verification of legitimacy, usefulness, error checking and even debugging. Now all of that takes time. And cooperation and assistance of other members here obviously, which could  take even more time.

Anyway after sleeping on it, I have come to the conclusion that loading images from a file is a hell of a lot easier and simpler than loading image resources using gdiplus. I might just leave Connect 4 to use that method.
.... thinking further....
And in case the user deletes the image files (I could have them embedded in the executable) and write them back to file if they are 'missing'. A kind of backup plan of sorts.
The user could still use their own images to replace mine, but would have to keep the same names for them; if those are inadvertently deleted, they would be replaced with my original images automatically. I kinda like that idea.
.... more thinking ....
In fact, I would not have to supply the images files separately then, just let the program 'install' them (write them to file) upon first use.... (maybe this is not Campus material here anymore? Whaddya think?) I will be testing this method in the coming days...  :biggrin:
... still more thinking ...
Not sure what AV software (or other 'security' software or settings) would think about it though. That could be an issue for some users, and scare them away from my wares.   :eusa_boohoo:

TimoVJL

May the source be with you

zedd151

Quote from: TimoVJL on April 11, 2025, 02:59:26 AMAt campus people should be calm, not a room for Retardos  :biggrin:
??? Who ???
Lemme guess...   :rolleyes:
Oh, I know.  :biggrin:
Who else would it be...    :tongue:

zedd151

Looking at a couple of files from masm32 library

bitmapfrommemory.asm
BitmapFromMemory  PROC  pMemory:DWORD, dwFileSize:DWORD
    LOCAL hResource:DWORD,  pGlobal:DWORD,      pStream:DWORD
    LOCAL hImage:DWORD,    pPicture:DWORD,    hBitmap:DWORD
  ;invoke CoInitialize, NULL
    mov pStream, NULL
    mov pPicture, NULL    ; NULL pointers for later use
    invoke CoTaskMemAlloc, dwFileSize  ; copy picture into task memory
    .if !eax
        ; oops! we didn't get the memory
        ; the last error code was set for us, and EAX is zero, so just return
        ret
    .endif
    mov pGlobal, eax
    invoke _MemCopy, pMemory, pGlobal, dwFileSize
    ; create a stream for the picture object's creator
    invoke CreateStreamOnHGlobal, pGlobal, TRUE, ADDR pStream
    or eax,eax
    jz @f
    invoke CoTaskMemFree,pGlobal
    xor eax,eax
    ret
@@:
    invoke OleLoadPicture, pStream, NULL, TRUE, ADDR IID_IPicture, ADDR pPicture
    or eax,eax
    jz @f
    mov eax, pStream
    call release_pStream
    xor eax,eax
    ret
@@:
    ; now we are ready to get the hBipmap, we farm this out for reuseability
    invoke BitmapFromPicture, pPicture
    mov hBitmap, eax
    mov eax, pStream
    call release_pStream
    mov eax, pPicture
    call release_pPicture
    mov eax, hBitmap                ; hBitmap is our return value, stuff it
    ret                            ; we're all done
BitmapFromMemory ENDP

bitmapfromresource.asm
BitmapFromResource  PROC hModule: dword, ResNumber:DWORD
    LOCAL hResource:DWORD,  dwFileSize:DWORD, hImage:DWORD

    ; get a resource handle (address) and resource length from the executable
    invoke FindResource, hModule, ResNumber, ADDR szImage
    or eax,eax
    jnz @f
    invoke SetLastError, ERROR_FILE_NOT_FOUND
    xor eax,eax
    ret
@@:
    mov hResource, eax
    invoke LoadResource, hModule, eax
    invoke LockResource, eax
    mov hImage, eax
    invoke SizeofResource, hModule, hResource
    mov dwFileSize, eax
    .IF dwFileSize      ; we use the resource size to determine if we got a
                        ; legit image file to open
        invoke BitmapFromMemory, hImage, dwFileSize
    .ELSE
        invoke SetLastError, ERROR_FILE_NOT_FOUND
        xor eax,eax
    .ENDIF

    ; everything's been done for us now, just return
    ret                    ; we're all done

BitmapFromResource  ENDP

bitmapfrompicture.asm
BitmapFromPicture PROC pPicture:DWORD
    LOCAL tempDC:DWORD,  tempBitmap:DWORD,  OldBitmap:DWORD
    LOCAL dwWidth:DWORD, dwHeight:DWORD,    compDC:DWORD
    LOCAL hmWidth:DWORD, hmHeight:DWORD,    neghmHeight:DWORD

    ; check we have an object
    .IF pPicture == 0
        ; whoops, no object passed in
        invoke SetLastError, ERROR_INVALID_PARAMETER
        xor eax,eax
        ret
    .ENDIF
   
    ; get a DC to work with
    invoke GetDC, NULL          ; screen DC
    mov compDC, eax
    invoke CreateCompatibleDC, compDC
    .IF !eax
        ; whoops, didn't get a DC
        ; but at least we had SetLastError called for us
        invoke ReleaseDC,NULL,compDC
        xor eax,eax
        ret
    .ENDIF
    mov tempDC, eax

    ; read out the width and height of the IPicture object
    ; (IPicture)pPicture::get_Width(*hmWidth)
    lea eax, hmWidth
    push eax
    mov eax, pPicture
    push eax
    mov eax, [eax]
    call [eax].IPicture.get_Width

    ; (IPicture)pPicture::get_Height(*hmHeight)
    lea eax, hmHeight
    push eax
    mov eax, pPicture
    push eax
    mov eax, [eax]
    call [eax].IPicture.get_Height

    ; convert himetric to pixels
    invoke GetDeviceCaps, compDC, LOGPIXELSX
    invoke MulDiv, hmWidth, eax, HIMETRIC_INCH
    mov dwWidth, eax

    invoke GetDeviceCaps, compDC, LOGPIXELSY
    invoke MulDiv, hmHeight, eax, HIMETRIC_INCH
    mov dwHeight, eax
    xor eax, eax
    sub eax, hmHeight
    mov neghmHeight, eax

    invoke CreateCompatibleBitmap, compDC, dwWidth, dwHeight
    .IF !eax
        ; whoops, didn't get a bitmap
        ; but at least we had SetLastError called for us\
        ; clean up the DC
        invoke ReleaseDC,NULL,compDC
        invoke DeleteDC, tempDC
        xor eax,eax
        ret
    .ENDIF
    mov tempBitmap, eax

    invoke SelectObject, tempDC, tempBitmap
    .IF !eax
        ; whoops, didn't select our bitmap
        ; but at least we had SetLastError called for us
        invoke ReleaseDC,NULL,compDC
        invoke DeleteDC, tempDC
        invoke DeleteObject, tempBitmap
        xor eax,eax
        ret
    .ENDIF
    mov OldBitmap, eax

    ; ok, now we have our bitmap mounted onto our temporary DC, let's blit to it
    ; (IPicture)pPicture::Render(hdc, x, y, cx, cy,                            \
    ;                            xpos_himetric, ypos_himetric,                \
    ;                            xsize_himetric, ysize_himetric, *rectBounds)
    push NULL  ; *rectBounds
    push neghmHeight
    push hmWidth
    push hmHeight
    push 0
    push dwHeight
    push dwWidth
    push 0
    push 0
    push tempDC
    mov eax, pPicture
    push eax
    mov eax, [eax]
    call [eax].IPicture.Render
    test eax, eax
    .IF SIGN?
        ; the call failed!
        push eax
        ; do some clean up first
        invoke ReleaseDC,NULL,compDC
        invoke DeleteDC, tempDC
        invoke DeleteObject, tempBitmap
        pop eax
        ; need to parse out the return fail value
        .IF eax == E_FAIL
        .ELSEIF eax == E_INVALIDARG
        .ELSEIF eax == E_OUTOFMEMORY
        .ELSEIF eax == E_POINTER
        .ELSE
        .ENDIF   
        invoke SetLastError, eax
        xor eax,eax
        ret
    .ENDIF

    ; we now have the bitmap blitted, let's get it off the dc and clean up.
    ; we're not going to check for errors, cause we did our importaint thing
    ; and if these fail now, other things will fall apart anyway

    invoke ReleaseDC,NULL,compDC
    invoke SelectObject, tempDC, OldBitmap
    invoke DeleteDC, tempDC
   
    mov eax, tempBitmap    ; the bitmap handle is the return value
    ret                    ; we're all done

BitmapFromPicture ENDP               

I will experiment with these next... :biggrin:  ... leaving out anything that converts the image to HBITMAP

Will OleLoadPicture load a png as 32 bit RGBA, or 24 bit RGB??? Ima gonna find out.  :cool:
I'll read up on that function in the meantime...  :biggrin:

If nothing else, it will add a couple of tools into my image toolbox.

Vortex

Hi Zedd,

You are welcome. I am glad if I can be helpful. You can also try my image library based on OLE technology :

https://masm32.com/board/index.php?msg=82060

zedd151

Quote from: Vortex on April 11, 2025, 05:04:19 AMHi Zedd,

You are welcome. I am glad if I can be helpful. You can also try my image library based on OLE technology :

https://masm32.com/board/index.php?msg=82060
Thanks, I'll take a look at it.   :thumbsup:

I have obviously been working on my game code lately. One can never have too many options for opening and displaying images.

NoCforMe

Quote from: jj2007 on April 10, 2025, 10:45:34 PM@NoCforMe: What is streaming? Simply doing a job from a buffer, instead of reading from an open file. So instead of passing a file handle., you typically supply the address of a callback proc of yours to the streaming function, and when that API function calls you there, you hand over a bunch of bytes from a buffer. One often used example is the EM_STREAMIN message for RichEdit controls.
First of all, thank you for that straightforward explanation. Much appreciated.

A little further probing, if you don't mind:
With regard to bitmap images, does that mean that you fill your buffer with the bitmap data first? And would that be the raw data (like the file bytes), or is it another type of data, acquired from some Win32 function?

Still not sure what the advantage is here, but this sounds very similar to the way I "load" bitmaps by putting the raw .BMP data in my .data section, then using my GetBMPhandle() code to "open" the bitmap and return a usable handle. Avoids the use of a resource file (I call it using a "fake resource file", which is just an include file full of DB statements).
Assembly language programming should be fun. That's why I do it.

Vortex

Hi Zedd,

Using SHCreateMemStream is simplifying the code. Plus, you don't need resources as you can embed any binary data into your executable with Hutch's file data assembler fda.exe. This tool converts any binary file to a MS COFF object module :

\masm32\fda logo.png logo.obj pPNG

pPNG is the label identifying the binary data.

fda creates an include file named logo.inc to define the size of the embedded data :

; -----------------------------------------------------
; Include the contents of this file in your source file
; to access the data as an OFFSET and use the equate as
; the byte count for the file data in the object module
; -----------------------------------------------------
EXTERNDEF pPNG:DWORD
ln_pPNG equ <4516>

The modified code :

include logo.inc

ApiFunc TYPEDEF PROTO :DWORD,:DWORD
.
.
    .IF uMsg==WM_CREATE
.
.       
        invoke  LoadLibrary,ADDR shlwapi
        mov     hDll,eax
       
        invoke  GetProcAddress,eax,ADDR func

        ASSUME  eax:PTR ApiFunc
        invoke  eax,ADDR pPNG,ln_pPNG  ; ln_pPNG is defined in logo.inc
        ASSUME  eax:NOTHING            ; created by file data assembler fda

        mov     pStream,eax

        invoke  GdipCreateBitmapFromStream,pStream,ADDR PNGimage
        invoke  GdipCreateHBITMAPFromBitmap,PNGimage,ADDR hBitmap,0

        invoke  GdipDisposeImage,PNGimage
        coinvk  pStream,IStream,Release
        invoke  FreeLibrary,hDll


zedd151

Thanks Vortex, I'll look at that shortly. I'm away from the computer at the moment.  :smiley:


Later:
Okay, I back inside briefly.
That looks very interesting.  :thumbsup:

I will have a better look at it after I finish up with some yard work...

More Later:
Kind of neat the way that you create the object file just before assembling&linking... I was looking for a call in the source code.  :joking:

NoCforMe

Quote from: zedd151 on April 11, 2025, 06:57:29 AMThanks Vortex, I'll look at that shortly. I'm away from the computer at the moment.  :smiley:
Zedd, I think we need to get you fitted with some kind of tracking device, maybe an ankle monitor. That way we can keep track of where you are at any given time.
Assembly language programming should be fun. That's why I do it.

zedd151

Quote from: NoCforMe on April 11, 2025, 07:10:03 AMThat way we can keep track of where you are at any given time.
I'll never tell.  :cool:

Oh wait. I did already.  :toothy:

Fortunately, there wasn't a lot that needed to be done in the front yard. Just a bit of weed whacking along the western fence line. All done for today.  :biggrin:  anything else can wait for tomorrow.

Now I'm back on the back porch enjoying the weather, for those keeping track of my whereabouts.   :tongue:

zedd151

After trying several different methods of loading images other than by loading the image file, I have come to the conclusion that loading images from file is the easiest way to do it.

I will no longer seek to load image resources or any other variation at this time. I have a few projects that I would like to finish very soon, so to expedite that process, I will choose the path of least resistance and load the images from file.

Thank you to everyone that has offered the various other methods, or solutions.

I will re-explore those options at some other time in the future.  :thumbsup:

TimoVJL

May the source be with you