The MASM Forum

General => The Workshop => Topic started by: hutch-- on October 16, 2016, 10:13:35 AM

Title: Has anyone done any work in GDI+ recently ?
Post by: hutch-- on October 16, 2016, 10:13:35 AM
I remember in the distant past that there was a technique for storing JPG or PNG files as resources which could then be loaded in BMP format for display. Storing the JPG or PNG files as resources or any of the alternatives is simple enough but what I forget is the technique to load them as bitmaps so you can use them for toolbars, background images and similar. I don't know if GDI+ handles RGB/A format images.

The main thing I am chasing is image size reduction as any reasonable sized bitmap starts to get very large and you can end up with an application that may be 50 or 60 kilobytes that blows out over a megabyte due to image data.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: jj2007 on October 16, 2016, 11:33:44 AM
RichMasm uses these for loading and displaying images, e.g. the car in the attached source:
GdipLoadImageFromFile
GdipGetImageDimension
GdipDrawImageRectI
GdipDisposeImage
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: fearless on October 16, 2016, 12:06:15 PM
Can store the resource (png/jpeg) as RT_RCDATA (RCDATA)

then use something similar to chris vega's load png from resource (had to search for this code fragment):


include gdiplus.inc

IFNDEF GDIPLUS_LOADBITMAPFROMRESOURCE_INC
GDIPLUS_LOADBITMAPFROMRESOURCE_INC equ <1>

;
; Load JPG/PNG from resource using GDI+
;   Actually, this function can load any image format supported by GDI+
;
; by: Chris Vega
;

gdiplusLoadBitmapFromResource proc hInst:HMODULE, lpName:LPSTR, lpType:LPSTR, pBitmapFromStream:DWORD

        local rcRes:HRSRC
        local hResData:HRSRC
        local pResData:HANDLE
        local sizeOfRes:DWORD
        local hbuffer:HANDLE
        local pbuffer:DWORD
        local pIStream:DWORD
        local hIStream:DWORD

        ; ------------------------------------------------------------------
        ; STEP 1: Find the resource
        ; ------------------------------------------------------------------
        invoke  FindResource, hInst, lpName, lpType
        or              eax, eax
        jnz             @f
        jmp             gdiplusLoadBitmapFromResource@Close
@@:     mov             rcRes, eax
       
        ; ------------------------------------------------------------------
        ; STEP 2: Load the resource
        ; ------------------------------------------------------------------
        invoke  LoadResource, hInst, rcRes
        or              eax, eax
        jnz             @f
        ret             ; Resource was not loaded
@@:     mov             hResData, eax

        ; ------------------------------------------------------------------
        ; STEP 3: Create a stream to contain our loaded resource
        ; ------------------------------------------------------------------
        invoke  SizeofResource, hInst, rcRes
        or              eax, eax
        jnz             @f
        jmp             gdiplusLoadBitmapFromResource@Close
@@:     mov             sizeOfRes, eax
       
        invoke  LockResource, hResData
        or              eax, eax
        jnz             @f
        jmp             gdiplusLoadBitmapFromResource@Close
@@:     mov             pResData, eax

        invoke  GlobalAlloc, GMEM_MOVEABLE, sizeOfRes
        or              eax, eax
        jnz             @f
        jmp             gdiplusLoadBitmapFromResource@Close
@@:     mov             hbuffer, eax

        invoke  GlobalLock, hbuffer
        mov             pbuffer, eax
       
        invoke  RtlMoveMemory, pbuffer, hResData, sizeOfRes
        invoke  CreateStreamOnHGlobal, pbuffer, FALSE, addr pIStream
        or              eax, eax
        jz              @f
        jmp             gdiplusLoadBitmapFromResource@Close
@@:     

        ; ------------------------------------------------------------------
        ; STEP 4: Create an image object from stream
        ; ------------------------------------------------------------------
        invoke  GdipCreateBitmapFromStream, pIStream, pBitmapFromStream
       
        ; ------------------------------------------------------------------
        ; STEP 5: Free all used locks and resources
        ; ------------------------------------------------------------------
        invoke  GetHGlobalFromStream, pIStream, addr hIStream
        ;invoke  GlobalFree, hIStream ; fearless 2016 - dont uncomment as stream has to be open for lifetime of image or corruption can occur.
        invoke  GlobalUnlock, hbuffer
        invoke  GlobalFree, hbuffer
       
gdiplusLoadBitmapFromResource@Close:
        ret
gdiplusLoadBitmapFromResource endp



GdipCreateHBITMAPFromBitmap can be used to convert to normal GDI HBITMAP bitmap format from the pBitmap GDI+ bitmap (pBitmapFromStream)

When using the above code, notice I've commented out the GlobalFree of the hIStream handle and added the comment, plus function should return hIStream in eax for later proper release.

Once finished with image handles, they can be disposed of with the DeleteObject (Gdi) and GdiDisposeImage (Gdi+) and the stream handle freed with the IStream release method:

Which requires the following structures if not already defined/included:

UNKNOWN STRUCT
   QueryInterface   DWORD ?
   AddRef           DWORD ?
   Release          DWORD ?
UNKNOWN ENDS

IStream STRUCT
IUnknown            UNKNOWN <>
Read                DWORD ?
Write               DWORD ?
Seek                DWORD ?
SetSize             DWORD ?
CopyTo              DWORD ?
Commit              DWORD ?
Revert              DWORD ?
LockRegion          DWORD ?
UnlockRegion        DWORD ?
Stat                DWORD ?
Clone               DWORD ?
IStream ENDS
ENDIF



And releasing the stream is something like this:

mov eax, hIStream
push eax
mov eax,DWORD PTR [eax]
Call IStream.IUnknown.Release[eax]; release the stream handle



Other solution is to use pnglib (http://www.madwizard.org/programming/projects/pnglib) to load pngs (Stored as RCDATA) and convert to bitmap handle - rough example:

invoke PNG_Init, addr pngInfo
; resource must be RCDATA type
invoke PNG_LoadResource, addr pngInfo, hInstance, IMG_SOMEPNG
invoke PNG_Decode, addr pngInfo
invoke PNG_CreateBitmap, addr pngInfo, hWin, PNG_OUTF_AUTO, FALSE
mov hPngBitmap, eax
invoke PNG_Cleanup, addr pngInfo
; do something with bitmap handle now:
Invoke SendDlgItemMessage, hWin, IDC_SOMEBITMAPCONTROL, STM_SETIMAGE, IMAGE_BITMAP, hPngBitmap


Title: Re: Has anyone done any work in GDI+ recently ?
Post by: Siekmanski on October 16, 2016, 04:07:51 PM
With a call to, "GdipBitmapLockBits" you get access to the picture data

BitmapData struct                         
    dwWidth      dd ?   
    dwHeight     dd ?   
    Stride       dd ?   
    PixelFormat  dd ?   
    Scan0        dd ?   Picture data
    Reserved     dd ?
BitmapData ends

I use this method to copy the picture data to a D3D9 texture surface.
In your case you could copy it to a "CreateDIBSection"

Here is the routine I use to load pictures from resource or from file, includes a function to set the transparency with a colorkey.

From resource:
   invoke  LoadTexture,501,NULL,addr g_pPictureTexture ;GOLDFONTTRANS2.png
   invoke  LoadTexture,501,D3DCOLOR_ARGB(255,255,0,255),addr g_pPictureTexture ;GOLDFONTTRANS2.png -> Pink color is transparent
From file:
   invoke  LoadTexture,TEXT_("Media/GOLDFONTTRANS2.png"),D3DCOLOR_ARGB(255,255,0,255),addr g_pPictureTexture

LoadTexture proc uses ebx esi edi PictureName:DWORD,ColorKey:DWORD,pTexture:DWORD
LOCAL GdiplusToken:dword
LOCAL PictureBuffer:dword
LOCAL RsrcSize:dword
LOCAL hRsrc:dword
LOCAL pStream:dword
LOCAL pImage:dword
LOCAL pLockedRect:D3DLOCKED_RECT
LOCAL ReturnMessage:dword

    mov     ReturnMessage,E_FAIL

    mov     eax,pTexture
    cmp     dword ptr [eax],NULL
    jnz     Exit_LoadTexture
   
    invoke  GdiplusStartup,addr GdiplusToken,addr GdiplusSInput,NULL
    test    eax,eax
    jnz     Exit_LoadTexture

    mov     PictureBuffer,NULL
    mov     pStream,NULL
   
    invoke  FindResource,NULL,PictureName,TEXT_("PICTURE")
    test    eax,eax
    jnz     Picture_In_Resource ; Picture found in the resource ?
   
    ; Try to load Picture from file.

    invoke  MultiByteToWideChar,CP_ACP,0,PictureName,-1,addr FilenameW,MAX_PATH-1
    invoke  GdipCreateBitmapFromFile,addr FilenameW,addr pImage
    test    eax,eax
    jnz     ShutdownGDIplus
    jmp     LockBitmap 

Picture_In_Resource:
    mov     hRsrc,eax
    invoke  SizeofResource,NULL,eax
    mov     RsrcSize,eax

    invoke  GlobalAlloc,GMEM_FIXED or GMEM_ZEROINIT,eax
    test    eax,eax
    jz      ShutdownGDIplus
    mov     PictureBuffer,eax
    invoke  LoadResource,NULL,hRsrc
    invoke  LockResource,eax
    invoke  RtlMoveMemory,PictureBuffer,eax,RsrcSize
    invoke  CreateStreamOnHGlobal,PictureBuffer,TRUE,addr pStream
    test    eax,eax
    jnz     Close_Gdiplus

    invoke  GdipCreateBitmapFromStream,pStream,addr pImage
    test    eax,eax
    jnz     Close_Gdiplus

LockBitmap:
    invoke  GdipBitmapLockBits,pImage,NULL,ImageLockModeRead,PixelFormat32bppARGB,addr GDIplusBitmapData
    test    eax,eax
    jnz     Close_Image

    coinvoke g_pD3DDevice,IDirect3DDevice9,CreateTexture,GDIplusBitmapData.dwWidth,GDIplusBitmapData.dwHeight,\
                                1,D3DUSAGE_DYNAMIC,D3DFMT_A8R8G8B8,D3DPOOL_DEFAULT,pTexture,NULL
    cmp     eax,D3D_OK
    jne     UnlockBitmap

    coinvoke g_pPictureTexture,IDirect3DTexture9,LockRect,0,addr pLockedRect,NULL,D3DLOCK_DISCARD
    cmp     eax,D3D_OK
    jne     UnlockBitmap
   
    mov     esi,GDIplusBitmapData.Scan0     ; pointer to the bitmap data
    mov     edi,pLockedRect.pBits           ; pointer to the texture data
   
    mov     ecx,GDIplusBitmapData.dwHeight
Height_lp:
    mov     edx,GDIplusBitmapData.dwWidth
    xor     ebx,ebx
Width_lp:
    mov     eax,dword ptr [esi+ebx]
   
    cmp     ColorKey,NULL
    jz      NoColorKey
    or      eax,D3DCOLOR_ARGB(255,0,0,0)
    cmp     ColorKey,eax
    jne     NoColorKey
    xor     eax,eax
NoColorKey:
   
    mov     dword ptr [edi+ebx],eax
    add     ebx,4
    dec     edx
    jnz     Width_lp
    add     esi,GDIplusBitmapData.Stride   
    add     edi,pLockedRect.Pitch
    dec     ecx
    jnz     Height_lp   

    coinvoke g_pPictureTexture,IDirect3DTexture9,UnlockRect,0

    mov     ReturnMessage,D3D_OK

UnlockBitmap:
    invoke  GdipBitmapUnlockBits,pImage,addr GDIplusBitmapData
Close_Image:
    invoke  GdipDisposeImage,pImage
Close_Gdiplus:
    SAFE_RELEASE  pStream
    cmp     PictureBuffer,NULL
    jz      ShutdownGDIplus
    invoke  GlobalFree,PictureBuffer
ShutdownGDIplus:
    invoke  GdiplusShutdown,GdiplusToken
Exit_LoadTexture:
    mov     eax,ReturnMessage
    ret

LoadTexture endp


Example of a GDIplus texture loader:
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: Vortex on October 16, 2016, 09:07:36 PM
Hi Hutch,

The functions from masm32.lib :

BitmapFromFile
BitmapFromMemory
BitmapFromPicture
BitmapFromResource


Attached is a similar example based on the OleLoadPicture and CopyImage API functions.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: hutch-- on October 16, 2016, 09:45:15 PM
Thanks Erol, these will be very useful, all I have to do is convert it to 64 bit.  :biggrin:
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: hutch-- on October 17, 2016, 01:08:14 AM
I don't seem to be having much luck with GDIPLUS in 64 bit.

    LOCAL nbuffer[128]:BYTE             ; buffer for unicode string
    LOCAL nPbuf :QWORD                  ; its pointer
    LOCAL token :DWORD                  ; token for GdiplusStartup
    LOCAL gdiin :GdiplusStartupInputX    ; structure for GdiplusStartup
    LOCAL pbmp  :DWORD                  ; address for bitmap

    invoke GdiplusStartup,ADDR token,ADDR gdiin,0

    fn MessageBox,0,LastError$(),"GdiplusStartup",MB_OK

  ; ----------------------------------------
  ; get buffer, convert file name to unicode
  ; ----------------------------------------
    mov nPbuf, ptr$(nbuffer)
    invoke MultiByteToWideChar,CP_ACP,MB_PRECOMPOSED,"toolbar.png",-1,nPbuf,64      ; OK

    invoke GdipCreateBitmapFromFile,nPbuf,ADDR pbmp
    mrm hBitmap, rax            ; pbmp

    fn MessageBox,0,str$(hBitmap),str$(pbmp),MB_OK

I am only getting junk returns.

Has anyone done any work in GDIPLUS in 64 bit ?

The gdiplus.dll versions I have in win10 in both system32 and wow64 are both 32 bit versions, the next question is there a 64 bit version ?
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: jcfuller on October 17, 2016, 01:15:38 AM
Hutch,
Don't know if this is relevant but  I have done some 64bit unicode c++ (VS 2015 community) work . Only enough to know it works.

James
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: qWord on October 17, 2016, 04:43:36 AM
GdipCreateBitmapFromFile (https://msdn.microsoft.com/en-us/library/windows/desktop/ms533971%28v=vs.85%29.aspx) does create objects of type GpBitmap and return a value from the GpStatus enumeration (0 == Success).The second parameter is a  pointer to a pointer to the created GpBitmap (pbmp must be a QWORD).
To get a GDI bitmap from GpBitmap see GdipCreateHBITMAPFromBitmap.
The GdiplusStartupInput structure must be filled before calling GdiplusStartup (all zero except GdiplusVersion=1).

BTW: I've done several x64-GDI+ projects successfully.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: hutch-- on October 17, 2016, 05:21:43 AM
Thanks for that, I was not having much luck with the data I could find.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: hutch-- on October 17, 2016, 05:57:51 AM
Gratsie,

Works perfectly. I did not know about the last step.

    LOCAL nbuffer[128]:BYTE             ; buffer for unicode string
    LOCAL nPbuf :QWORD                  ; pointer to unicode buffer
    LOCAL token :QWORD                  ; token for GdiplusStartup
    LOCAL gdiin :GdiplusStartupInput    ; structure for GdiplusStartup
    LOCAL pbmp  :QWORD                  ; address for bitmap
    LOCAL ptrb  :QWORD

    mov gdiin.GdiplusVersion, 1
    mov gdiin.DebugEventCallback, 0
    mov gdiin.SuppressBackgroundThread, 0
    mov gdiin.SuppressExternalCodecs, 0

    invoke GdiplusStartup,ADDR token,ADDR gdiin,0
    mov nPbuf, ptr$(nbuffer)
    invoke MultiByteToWideChar,CP_ACP,MB_PRECOMPOSED,"toolbar.png",-1,nPbuf,64      ; OK
    invoke GdipCreateBitmapFromFile,nPbuf,ADDR pbmp
    invoke GdipCreateHBITMAPFromBitmap,pbmp,ADDR hBitmap,0
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: qWord on October 17, 2016, 07:28:37 AM
Just as side note: to destroy the GDI+ Bitmap you must use GdipDisposeImage (could be done after creating the hBitmap)

regards
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: hutch-- on October 17, 2016, 02:58:08 PM
Interestingly enough it has been worth the effort, assembled size dropped from 65k to 39k by changing from a bitmap to a png file.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: mabdelouahab on October 17, 2016, 04:00:07 PM
This is just an x64bit example
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: jj2007 on October 17, 2016, 06:49:16 PM
Quote from: hutch-- on October 17, 2016, 02:58:08 PM
Interestingly enough it has been worth the effort, assembled size dropped from 65k to 39k by changing from a bitmap to a png file.

Try as jpg with a high compression; png is a great format, but Windows adds lots of colours via anti-aliasing of text, and that spoils the compression rate of png's.

@mabdelouahab: Lovely :t
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: hutch-- on October 17, 2016, 07:33:39 PM
The win with PNG is its lossless which matters with things like image strips. I produce most PNG files I use with Axialis Icon Workshop so Windows does not get a chance to modify any of the images directly. They do get fed through whatever rendering gdiplus uses but they seem to look OK. The size win is a rgb/a bitmap at 41014 bytes is replaced with a PNG at 14246 bytes with no quality loss.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: Siekmanski on October 17, 2016, 09:37:49 PM
One option to reduce png size is cutting out the crystal ball ( it becomes 420 by 420 pixels ) and reducing color count without messing up the aplha values.
I tried this in Paint Shop Pro but it messes up the alpha values.

Hutch,
Can Axialis Icon Workshop cut out pieces of the picture and reduce color count without messing up the alpha values ?
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: hutch-- on October 17, 2016, 11:41:08 PM
 I will have a look but it does not ring a bell, I mainly work in PNG in Icon Workshop and anything you load in can be converted to RGB/A but I have not seen that option. I did a test piece with a 512 square icon and even with the default compression, it ended up about 350k, without the compression it was over 1 meg. The immediate requirement was to get the toolbar bitmap size down which is working OK.

I just checked and the only option is to save the file as a PNG but with no other modifications. Just as size comparisons, I made 2 512 x 512 images, one a PNG, the other a BMP of the identical image, PNG saved at 280280 bytes, BMP saved at 1048630 bytes so the size saving is significant. You can routinely cut out rectangular parts and save them as files but no colour depth adjustments.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: Siekmanski on October 18, 2016, 12:15:33 AM
OK, maybe here is a new challenge for us.
Image compression using GDI+ and keeping the info in the file we need such as alpha channels, color reduction to an acceptable loss, cutting a portion out of the original image.

I was thinking of manipulations of the different layers and splitting up the rgb -> yuv to do color reduction on each channel seperatly and choose the best image compression for each channel or other things we can think of.
Combining those to one "small" file wich can be loaded from the resource.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: hutch-- on October 18, 2016, 08:57:55 AM
As a number of folks have helped out with the question, here is what I have used so far in loading a PNG file instead of a bitmap. The PNG data is stored as a DB sequence and is loaded by the following code.

    invoke png_toolbar                  ; get PNG data address and length
    mov ppng, rax                       ; pointer to png data
    mov lpng, rcx                       ; length of png data
    mrm npng, "tb_@_name.png"           ; temp file name


This is the entry proc where I have put all of the gdiplus code so far.

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

entry_point proc

    LOCAL gdii  :GdiplusStartupInput    ; structure for GdiplusStartup
    LOCAL pbmp  :QWORD                  ; address for bitmap
    LOCAL ppng  :QWORD                  ; pointer PNG data
    LOCAL lpng  :QWORD                  ; length PNG data
    LOCAL npng  :QWORD                  ; name for temp PNG file

    mov gdii.GdiplusVersion, 1
    mov gdii.DebugEventCallback, 0
    mov gdii.SuppressBackgroundThread, 0
    mov gdii.SuppressExternalCodecs, 0

    invoke GdiplusStartup,ADDR token,ADDR gdii,0

    mov hInstance, rv(GetModuleHandle,0)
    mov hCursor,   rv(LoadCursor,0,IDC_ARROW)
    mov sWid,      rv(GetSystemMetrics,SM_CXSCREEN)
    mov sHgt,      rv(GetSystemMetrics,SM_CYSCREEN)
    mov hIcon,     rv(LoadImage,hInstance,10,IMAGE_ICON,48,48,LR_LOADTRANSPARENT)

    invoke png_toolbar                  ; get PNG data address and length
    mov ppng, rax                       ; pointer to png data
    mov lpng, rcx                       ; length of png data
    mrm npng, "tb_@_name.png"           ; temp file name

    invoke save_file,npng,ppng,lpng     ; create the temp file

    invoke GdipCreateBitmapFromFile,L(npng),ADDR pbmp
    invoke GdipCreateHBITMAPFromBitmap,pbmp,ADDR hBitmap,0
    invoke GdipDisposeImage,pbmp

    invoke DeleteFile,npng              ; delete the temp file

  ; ----------------------------------------------------------------
  ; load the rebar background tile stretching it to the rebar height
  ; ----------------------------------------------------------------
    mov tbTile, rv(LoadImage,hInstance,30,IMAGE_BITMAP,sWid,rbht,LR_DEFAULTCOLOR)

    call main

    invoke GdiplusShutdown,token        ; cleanup on exit
    invoke ExitProcess,0
    ret

entry_point endp

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: Vortex on October 19, 2016, 05:37:04 AM
Attached is a GDI+ example assembled with Poasm 64 and HJWasm 64  The sample project loads a PNG image from file and displays it inside a window.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: jj2007 on October 19, 2016, 07:15:46 AM
Nice :t

I have one problem with building, though:
    mov    wc.lpfnWndProc,rax ; trashes return address with HJWasm; OK with AsmC/code]
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: Vortex on October 19, 2016, 07:23:27 AM
This time the image stored in memory is displayed in the window. OleLoadPicture loads the GIF image embedded into the executable and the get_Handle method of the IPicture interface retrieves the handle to the image in memory.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: Vortex on October 19, 2016, 07:34:38 AM
Hi Jochen,

Thanks. The window template is taken from the code \HJWasm\Samples\Win64_3.asm  Is your WinMain procedure constructed differently?
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: adeyblue on October 19, 2016, 07:41:33 AM
If you don't mind being adventurous with undocumented function usage, then you can easily give GDI+ a PNG from a resource without creating a temp file first, using the GdipCreateBitmapFromStream(IStream*, GpBitmap**) function.

SHCreateStreamOnModuleResourceW (http://undoc.airesoft.co.uk/shlwapi.dll/SHCreateStreamOnModuleResourceW.php) in shlwapi.dll (Vista+) copies resource bytes into a COM IStream, which you pass to GdipCreateBitmapFromStream to get your GpBitmap and go from there. They moved that function to shcore.dll from Windows 8, but they left a forwarder so GetProcAddress(shlwapi, 628) still works.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: mabdelouahab on October 19, 2016, 08:05:15 AM
from a resource

DrawResImage PROC  Graphic__:qword,IdRes:dword,X_:DWORD,Y_:DWORD,W_:DWORD,H_:DWORD
LOCAL _rcRes:qword,_hResData:qword,_sizeOfRes:qword,_hbuffer:qword,_pIStream:qword,_hBitmap:qword

.invoke  FindResource , 0, IdRes, RT_RCDATA
.if rax
mov _rcRes, rax
mov _hResData, _rv(LoadResource , 0, _rcRes)
mov _sizeOfRes, _rv(SizeofResource , 0, _rcRes)

.invoke  CreateStreamOnHGlobal, 0, TRUE, & _pIStream 
.invoke _pIStream.4,_hResData,_sizeOfRes,0
; mov rdx, _hResData
; mov r8, _sizeOfRes
; xor r9, r9
; mov rcx, _pIStream
; mov rax, qword ptr [rcx]
; add rax, 020h
; call qword ptr [rax]
.invoke  GdipCreateBitmapFromStream, _pIStream,& _hBitmap
.if W_
.invoke GdipDrawImageRectI ,Graphic__, _hBitmap,X_, Y_ ,W_,H_
.else
.invoke GdipDrawImage ,Graphic__, _hBitmap,X_, Y_
.endif
.invoke GdipDisposeImage,_hBitmap
.invoke _pIStream.2
; mov rcx, _pIStream
; mov rax, qword ptr [rcx]
; add rax, 010h
; call qword ptr [rax]
mov rax,TRUE
.else
xor rax,rax
.endif
ret
DrawResImage endp
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: jj2007 on October 19, 2016, 10:07:26 AM
Quote from: Vortex on October 19, 2016, 07:34:38 AM
Hi Jochen,

Thanks. The window template is taken from the code \HJWasm\Samples\Win64_3.asm  Is your WinMain procedure constructed differently?

No, Erol. I am actually getting different code from the same identical source, when building a) with HJWasm or b) with AsmC. Knowing how many idiosyncratic assembly options have crept into the Watcom forks, I suspect that there is one that influences the way the frame is built ::)

What actually happens in the HJWasm build (attached) is that mov wc.lpfnWndProc, rax substitutes the return address with the pointer to the WndProc. So on exit the ret lets the code continue in WndProc...

Anyway, this is not a problem with your code but rather with the assemblers.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: hutch-- on October 19, 2016, 11:18:26 PM
Has anyone had any luck getting this GDI+ function to run on a PNG resource ?

GpStatus WINGDIPAPI GdipCreateBitmapFromResource(
  HINSTANCE hInstance,
  GDIPCONST WCHAR* lpBitmapName,
  GpBitmap** bitmap
);


I can routinely get this code to load a PNG resource by using a temp file but I have been trying to get the direct "GdipCreateBitmapFromResource()" function to work with no success on a PNG RCDATA resource.



In RC file

20 RCDATA "toolbar.png"

In source file.

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

BmpFromResource proc resid:QWORD

    LOCAL hRes  :QWORD
    LOCAL pRes  :QWORD
    LOCAL lRes  :QWORD
    LOCAL pbmp  :QWORD
    LOCAL hBmp  :QWORD
    LOCAL npng  :QWORD

    mov hRes, rv(FindResource,0,resid,RT_RCDATA)
    mov pRes, rv(LoadResource,0,hRes)
    mov lRes, rv(SizeofResource,0,hRes)
    mrm npng, "@@@@.@@@"                ; temp file name

    invoke save_file,npng,pRes,lRes     ; create the temp file

    invoke GdipCreateBitmapFromFile,L(npng),ADDR pbmp
    invoke GdipCreateHBITMAPFromBitmap,pbmp,ADDR hBmp,0
    invoke GdipDisposeImage,pbmp

    invoke DeleteFile,npng              ; delete the temp file

    mov rax, hBmp

    ret

BmpFromResource endp

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

This works fine but I was trying to bypass the temp file using "GdipCreateBitmapFromResource()" but nothing will get a working return value from it.

Title: Re: Has anyone done any work in GDI+ recently ?
Post by: jj2007 on October 20, 2016, 12:19:31 AM
Quote from: hutch-- on October 19, 2016, 11:18:26 PM
Has anyone had any luck getting this GDI+ function to run on a PNG resource ?

I made some tests:       invoke SetLastError, 0
      invoke LoadBitmap, wcx.hInstance, chr$("MYPNG")
      deb 4, "LoadBitmap MYPNG", eax, $Err$()
      invoke SetLastError, 0
      invoke LoadBitmap, wcx.hInstance, chr$("MYBITMAP")
      deb 4, "LoadBitmap MYBITMAP", eax, $Err$()
      invoke SetLastError, 0
      invoke FindResource, wcx.hInstance, chr$("MYPNG"), RT_RCDATA
      deb 4, "FindRes MYPNG", eax, $Err$()
      invoke FindResource, wcx.hInstance, chr$("MYBITMAP"), RT_BITMAP
      deb 4, "FindRes MYBITMAP", eax, $Err$()
      invoke SetLastError, 0
      invoke GdipCreateBitmapFromResource, wcx.hInstance, eax, addr hBitmap
      deb 4, "Bitmap from FindResource", eax, $Err$()
      invoke SetLastError, 0
      invoke GdipCreateBitmapFromResource, wcx.hInstance, wChr$("MYBITMAP"), addr hBitmap
      deb 4, "Bitmap from wChr$()", eax, $Err$()


Output:LoadBitmap MYPNG
eax             0
$Err$()         Impossibile trovare il nome di risorsa specificato nel file immagine.


LoadBitmap MYBITMAP
eax             84219241
$Err$()         Operazione completata.


FindRes MYPNG
eax             4964648
$Err$()         Operazione completata.


FindRes MYBITMAP
eax             4964456
$Err$()         Operazione completata.


Bitmap from FindResource
eax             2
$Err$()         Impossibile trovare il nome di risorsa specificato nel file immagine.


Bitmap from wChr$()
eax             0
$Err$()         Operazione completata.


The main problem seems to be that RCDATA and BITMAP are different animals :(
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: mabdelouahab on October 20, 2016, 12:37:32 AM
Quote from: hutch-- on October 19, 2016, 11:18:26 PM
I can routinely get this code to load a PNG resource by using a temp file but I have been trying to get the direct "GdipCreateBitmapFromResource()" function to work with no success on a PNG RCDATA resource.
GdipCreateBitmapFromResource work with bitmap resource not with rcdata
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: hutch-- on October 20, 2016, 12:39:34 AM
Yes, that makes sense, thanks for the confirmation.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: mabdelouahab on October 20, 2016, 01:10:04 AM
hutch--
Full Example: Crystal ball from resource without creating a temp file:crystal-ball.zip (http://www.mediafire.com/file/rdg27w6av59k7d9/crystal-ball.zip) * It could not be included in the attachments because it is larger than 512
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: hutch-- on October 20, 2016, 01:31:49 AM
I downloaded it, its very well done.  :t
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: jj2007 on October 20, 2016, 01:18:43 PM
I took another road, work in progress (still severe memory leaks) but so far it looks OK, i.e. the exe runs on XP and Win7-64 alike. Grateful for tests with other OS versions :icon14:

It requires a hilarious workaround, though, to remain compatible with XP:
QuotePrior to Windows Vista, SHCreateMemStream was not included in the public Shlwapi.h file, nor was it exported by name from Shlwapi.dll. To use it on earlier systems, you must call it directly from the Shlwapi.dll file as ordinal 12.

The original bmp file is 922270 bytes, the exe has 270kB.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: GuruSR on October 20, 2016, 02:03:07 PM
Just a thought, though have you looked on...  SourceForge for the FreeImage library?

GuruSR.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: FORTRANS on October 21, 2016, 12:02:15 AM
Hi,

Quote from: jj2007 on October 20, 2016, 01:18:43 PM
I took another road, work in progress (still severe memory leaks) but so far it looks OK, i.e. the exe runs on XP and Win7-64 alike. Grateful for tests with other OS versions

   Windows XP and 8.1.  There was an error message in the console
output on XP.  Output attached.  Didn't like my P-III as always. <g>

Regards,

Steve N.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: HSE on October 21, 2016, 12:30:43 AM
Hi JJ!! 7-32

Not skat.zip for recompilation.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: hutch-- on October 21, 2016, 01:24:04 AM
I am actually happy enough with the technique of storing the PNG file as a resource, writing it as a file then loading the file with the GDI+ function. I have used the technique in the past and what makes it viable is the OS disk cache and the time lag between writing the file, reading it with the GDI+function then deleting the file is so short that it never reaches the disk, its a purely in memory operation that is easily fast enough.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: jj2007 on October 21, 2016, 02:10:13 AM
Quote from: GuruSR on October 20, 2016, 02:03:07 PM
Just a thought, though have you looked on...  SourceForge for the FreeImage library?

A DLL with 5.5MB :dazzled:

I feel already ashamed for the 20k overhead of a MasmBasic hello world :redface:

@HSE+FORTRANS: Thanks for testing. I have to investigate into the SelectObject failure. Does the image show properly?

@Hutch: Correct but still, if you can avoid writing to a file, it would be even better. Zipping a BMP has about the same compression as converting to PNG, sometimes more, sometimes less. So unzipping to bmp in memory is a valid option imho. However, GdipCreateBitmapFromStream as used by mabdelouahab seems even better - compliments :t

@mabdelouahab: How (or rather: why) does this line work?
.invoke GdipCreateBitmapFromScan0,rect.right,rect.bottom,0,PixelFormat32bppPARGB,0,&bitmap

According to the docs, the 0 should be a pointer to the pixels ::)
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: FORTRANS on October 21, 2016, 03:01:07 AM
Hi,

Quote from: jj2007 on October 21, 2016, 02:10:13 AM
@HSE+FORTRANS: Thanks for testing. I have to investigate into the SelectObject failure. Does the image show properly?

   I think so.  On the XP machine there was a lot of flickering when
the window was moved.  But it is a slower machine.

HTH,

Steve N.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: HSE on October 21, 2016, 03:06:47 AM
JJ : image look good. I can't see As column. It's no posible to increase window size beyond Kings.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: jj2007 on October 22, 2016, 11:15:53 AM
Thanks. The images show, so it's working in principle. I've spent some time on a weird problem:  if 1 ; ##### no leak but bmp doesn't work, and jpg fails often #####
push rv(SizeofResource, 0, esi)
push rv(LoadResource, 0, esi) ; src
mov hGlobal, rv(GlobalAlloc, GMEM_MOVEABLE, stack[4]) ; dest
invoke GlobalLock, eax
push eax
call MbCopy ; invoke MbCopy, dest, src, bytes
invoke GlobalUnlock, hGlobal
invoke CreateStreamOnHGlobal, hGlobal, 0, addr iStream
invoke GdipCreateBitmapFromStream, iStream, ebx ; addr hBitmap; GdipBitmapLockBits?
invoke GlobalFree, hGlobal
  else ; ##### bmp works, but leaks approx size of bitmaps #####
push rv(SizeofResource, 0, esi) ; invoke ShlWapi:SHCreateMemStream, pBuffer, bytes
push rv(LoadResource, 0, esi)
call GdiSI.jjMemStream ; get *IStream; use global GdiSI because ebx points to bitmap
invoke GdipCreateBitmapFromStream, eax, ebx ; addr hBitmap; GdipBitmapLockBits
  endif


The upper version works fine with png, and it doesn't leak, but it fails always with bmp and sometimes with jpg.
The lower version works great, but to prevent leaking, the stream needs to be released in the right moment (MS Social (https://social.msdn.microsoft.com/Forums/sqlserver/en-US/d603d347-53ec-4b82-be4b-7581a485d61c/gdiplus-lock-the-file-not-able-to-delete?forum=vclanguage)):
QuoteUnfortunately GDI+ does tend to hold files open for images for the duration of the image.  This is because GDI+ lazy decompresses the image (i.e. it doesn't actually decompress the image until the DrawImage call), which means it keeps your stream open.

There is a related post by Siekmanski (http://masm32.com/board/index.php?topic=3124.msg32367#msg32367) that inspired me :t
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: Siekmanski on October 22, 2016, 12:27:11 PM
iStream pointer must be empty before use.
I use some kind of a SAFE_RELEASE,

.if iStream != 0
mov   ecx,iStream
push  ecx
mov   eax,[ecx]
call  DWORD PTR [eax + 8] ; IUnknown::Release
mov   iStream,0
.endif
.
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: jj2007 on October 22, 2016, 06:29:22 PM
Yes, that did the trick. Just tested it on my XP VM, and noticed that in one occasion, SelectObject oldpen returns 0, which was flagged by my debugging routine. GetLastError reveals that there was no error, though (meaning XP has initially no pen selected). So I got curious and found this in Raymond Chen's What are the dire consequences of not selecting objects out of my DC? (https://blogs.msdn.microsoft.com/oldnewthing/20130306-00/?p=5043):
QuoteGDI will sometimes (not always) lie and say, "Sure, I deleted your object." It didn't actually delete it, because it's still selected into a DC, but it also ties a string around its finger, and when the object is finally deselected, GDI will say, "Oh, wait, I was supposed to delete this object," and perform the deletion.
::)
Title: Re: Has anyone done any work in GDI+ recently ?
Post by: Vortex on June 07, 2018, 04:40:42 AM
Here is my GdipCreateBitmapFromStream example. It displays a PNG image loaded from file into memory :

    .IF uMsg==WM_CREATE

        invoke  GlobalAlloc,GMEM_MOVEABLE,PNG_IMG_SIZE
        mov     hMem,eax

        invoke  GlobalLock,eax
        mov     pMem,eax

        invoke  ReadFileToMem,ADDR filename,eax

        mov     eax,OFFSET StartupInfo
        mov     GdiplusStartupInput.GdiplusVersion[eax],1
        mov     GdiplusStatus,eax

        invoke  GdiplusStartup,ADDR token,ADDR StartupInfo,0
        invoke  CreateStreamOnHGlobal,hMem,FALSE,ADDR pStream
        invoke  GdipCreateBitmapFromStream,pStream,ADDR BmpImage

        invoke  GdipCreateHBITMAPFromBitmap,BmpImage,ADDR hBitmap,0


Title: Re: Has anyone done any work in GDI+ recently ?
Post by: zedd151 on June 09, 2018, 07:19:17 PM
Quote from: Vortex
Here is my GdipCreateBitmapFromStream example. It displays a PNG image loaded from file into memory

Works fine Windows 10 Home Ed.