LoadPicture() from variable Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
Sam_
Posts: 146
Joined: 20 Mar 2014, 20:24

LoadPicture() from variable

26 Oct 2017, 18:12

I'm looking for a way to accomplish something roughly equivalent to LoadPicture(), but instead of loading a bitmap from disk, I want to load one already read into a variable (via FileRead, .RawRead(), or equivalent). I have searched around the forum and read a dozen or so MSDN docs, but haven't been able to come up with anything that works. Any ideas?

Note: When I try to read/write bytes to the variablespace HBITMAP points to after using LoadPicture, AHK crashes. Am I doing it wrong or is it pointing to memory space I'm not allowed to access this way?

TIA,
Sam.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: LoadPicture() from variable  Topic is solved

27 Oct 2017, 02:10

hBitmap is a handle to a bitmap, it doesn't give you a pointer, to put data into. Are you trying to edit the binary data after loading the image?

This script loads a file into the memory, and then creates an hBitmap based on binary data from a variable. It uses a function by SKAN, which is linked to at the bottom.

Code: Select all

q:: ;data to hBitmap
RegRead, vPath, HKEY_CURRENT_USER\Control Panel\Desktop, Wallpaper

;==============================
;from file
;hBitmap := LoadPicture(vPath, "", vType)
;==============================

;==============================
;from data
oFile := FileOpen(vPath, "r")
oFile.Pos := 0
vSize := oFile.Length
oFile.RawRead(vData, oFile.Length)
oFile.Close()

;gdip startup
if !DllCall("kernel32\GetModuleHandle", Str,"gdiplus", Ptr)
	DllCall("kernel32\LoadLibrary", Str,"gdiplus", Ptr)
VarSetCapacity(GdiplusStartupInput, 16, 0), GdiplusStartupInput := Chr(1)
DllCall("gdiplus\GdiplusStartup", UPtrP,pToken, Ptr,&GdiplusStartupInput, Ptr,0)

hBitmap := GDIPlus_hBitmapFromBuffer(vData, vSize)

;gdip shutdown
DllCall("gdiplus\GdiplusShutdown", UPtr,pToken)
if hModule := DllCall("kernel32\GetModuleHandle", Str,"gdiplus", Ptr)
	DllCall("kernel32\FreeLibrary", Ptr,hModule)
;==============================

Loop, 3
{
	;SplashImage destroys the hBitmap each time,
	;so we need to copy the hBitmap each time
	hBitmap2 := DllCall("user32\CopyImage", Ptr,hBitmap, UInt,0, Int,0, Int,0, UInt,0, Ptr)
	SplashImage, % "HBITMAP:" hBitmap2, B ;B: borderless
	Sleep, 1500
	SplashImage, Off
	Sleep, 1500
}
return

;==================================================

;GdiPlus_SaveImageToBuffer() - Scripts and Functions - AutoHotkey Community
;https://autohotkey.com/board/topic/85523-gdiplus-saveimagetobuffer/

GDIPlus_hBitmapFromBuffer( ByRef Buffer, nSize ) { ;  Last Modifed : 21-Jun-2011
; Adapted version by SKAN www.autohotkey.com/forum/viewtopic.php?p=383863#383863
; Original code   by Sean www.autohotkey.com/forum/viewtopic.php?p=147029#147029
 hData := DllCall("GlobalAlloc", UInt,2, UInt,nSize )
 pData := DllCall("GlobalLock",  UInt,hData )
 DllCall( "RtlMoveMemory", UInt,pData, UInt,&Buffer, UInt,nSize )
 DllCall( "GlobalUnlock" , UInt,hData )
 DllCall( "ole32\CreateStreamOnHGlobal", UInt,hData, Int,True, UIntP,pStream )
 DllCall( "gdiplus\GdipCreateBitmapFromStream",  UInt,pStream, UIntP,pBitmap )
 DllCall( "gdiplus\GdipCreateHBITMAPFromBitmap", UInt,pBitmap, UIntP,hBitmap, UInt
,DllCall( "ntdll\RtlUlongByteSwap",UInt
,DllCall( "GetSysColor", Int,15 ) <<8 ) | 0xFF000000 )
 DllCall( "gdiplus\GdipDisposeImage", UInt,pBitmap )
 DllCall( NumGet( NumGet(1*pStream)+8 ), UInt,pStream ) ; IStream::Release
Return hBitmap
}
Btw if anyone can help: does a pBitmap actually point to data? And has anyone written a pBitmapFromBuffer function? Cheers.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: LoadPicture() from variable

27 Oct 2017, 03:59

Hello. Disclaimer: I know very little about gdi(+).
does a pBitmap actually point to data?
It depends, I'd say. According to msdn, the BITMAP structure looks like this,

Code: Select all

typedef struct tagBITMAP {
  LONG   bmType;
  LONG   bmWidth;
  LONG   bmHeight;
  LONG   bmWidthBytes;
  WORD   bmPlanes;
  WORD   bmBitsPixel;
  LPVOID bmBits;
} BITMAP, *PBITMAP;
So I would expect a PBITMAP to point to such a stucture. Now, in AHK code, we don't need (can't rather) specify the type of the variables we use, so if you see pBitmap in AHK, don't count on it being a pointer to such a struct ;). For example, from your code,

Code: Select all

DllCall( "gdiplus\GdipCreateBitmapFromStream",  UInt,pStream, UIntP,pBitmap ) ; Maybe use "Ptr" and "PtrP" instead
Here, pBitmap is not a pointer to the above struct. Instead, it is a pointer to a Bitmap class object. It might have been more appropriate to implement a gdi+ wrapper in an object oriented way, then there might have been less confusion. :arrow: nnnik has at least started work on it, we'll see what happens.
Note: When I try to read/write bytes to the variablespace HBITMAP points to after using LoadPicture, AHK crashes. Am I doing it wrong or is it pointing to memory space I'm not allowed to access this way?
A HBITMAP is to be consider an opaque pointer, you can't use so freely, you need to use its associated functions/methods. To get the image's bit array from a hBitmap from loadPicture, I have used this function,

Code: Select all

_getBitmap(hBitmap){
	; Url:
	; 	- https://msdn.microsoft.com/en-us/library/windows/desktop/dd144904%28v=vs.85%29.aspx 	(GetObject function)
	;	- https://msdn.microsoft.com/en-us/library/windows/desktop/dd183371(v=vs.85).aspx		(BITMAP structure)
	/*
	typedef struct tagBITMAP {
	  LONG   bmType;
	  LONG   bmWidth;
	  LONG   bmHeight;
	  LONG   bmWidthBytes;
	  WORD   bmPlanes;
	  WORD   bmBitsPixel;
	  LPVOID bmBits;
	} BITMAP, *PBITMAP;
	*/
	
	static pBits:=A_PtrSize==4?20:24
	local BITMAP,cbBuffer,nBytes
	
	if !(cbBuffer:=DllCall("Gdi32.dll\GetObject", "Ptr", hBitmap, "Int", cbBuffer, "Ptr",0))	; Get the requiered size of the buffer (BITMAP)
		throw Exception("Failed to get the requiered buffer size, ErrorLevel: " ErrorLevel " Last error: " A_LastError ".",-1)
	VarSetCapacity(BITMAP,cbBuffer,0)
	if !(nBytes:=DllCall("Gdi32.dll\GetObject", "Ptr", hBitmap, "Int", cbBuffer, "Ptr", &BITMAP) == cbBuffer)	; Get the BITMAP
		throw Exception("Failed to get the bitmap object, ErrorLevel: " ErrorLevel " Last error: " A_LastError ".",-1)
	
	BITMAP	:=	{type:				NumGet(&BITMAP,		 0, 	"Int")						 ; bmType
				,w:					NumGet(&BITMAP,		 4, 	"Int")						 ; bmWidth
				,h:					NumGet(&BITMAP,		 8, 	"Int")                       ; bmHeight
				,widthBytes:		NumGet(&BITMAP,		12,		"Int")                       ; bmWidthBytes
				,planes:			NumGet(&BITMAP,		16,	 "UShort")                       ; bmPlanes
				,bitsPixel:			NumGet(&BITMAP,		18,  "UShort")                       ; bmBitsPixel
				,bits:				NumGet(&BITMAP,	 pBits, 	"Ptr")}                      ; bmBits
	
	return BITMAP
}
Source: github and AHK forum. The pointer to the bits is in BITMAP.bits.

Please remember the above disclaimer, I'd be happy to have my statments and my understanding corrected.

Cheers :wave:
Sam_
Posts: 146
Joined: 20 Mar 2014, 20:24

Re: LoadPicture() from variable

27 Oct 2017, 13:42

jeeswg wrote:This script loads a file into the memory, and then creates an hBitmap based on binary data from a variable. It uses a function by SKAN, which is linked to at the bottom.

Code: Select all

q:: ;data to hBitmap
RegRead, vPath, HKEY_CURRENT_USER\Control Panel\Desktop, Wallpaper

;==============================
;from file
;hBitmap := LoadPicture(vPath, "", vType)
;==============================

;==============================
;from data
oFile := FileOpen(vPath, "r")
oFile.Pos := 0
vSize := oFile.Length
oFile.RawRead(vData, oFile.Length)
oFile.Close()

;gdip startup
if !DllCall("kernel32\GetModuleHandle", Str,"gdiplus", Ptr)
	DllCall("kernel32\LoadLibrary", Str,"gdiplus", Ptr)
VarSetCapacity(GdiplusStartupInput, 16, 0), GdiplusStartupInput := Chr(1)
DllCall("gdiplus\GdiplusStartup", UPtrP,pToken, Ptr,&GdiplusStartupInput, Ptr,0)

hBitmap := GDIPlus_hBitmapFromBuffer(vData, vSize)

;gdip shutdown
DllCall("gdiplus\GdiplusShutdown", UPtr,pToken)
if hModule := DllCall("kernel32\GetModuleHandle", Str,"gdiplus", Ptr)
	DllCall("kernel32\FreeLibrary", Ptr,hModule)
;==============================

Loop, 3
{
	;SplashImage destroys the hBitmap each time,
	;so we need to copy the hBitmap each time
	hBitmap2 := DllCall("user32\CopyImage", Ptr,hBitmap, UInt,0, Int,0, Int,0, UInt,0, Ptr)
	SplashImage, % "HBITMAP:" hBitmap2, B ;B: borderless
	Sleep, 1500
	SplashImage, Off
	Sleep, 1500
}
return

;==================================================

;GdiPlus_SaveImageToBuffer() - Scripts and Functions - AutoHotkey Community
;https://autohotkey.com/board/topic/85523-gdiplus-saveimagetobuffer/

GDIPlus_hBitmapFromBuffer( ByRef Buffer, nSize ) { ;  Last Modifed : 21-Jun-2011
; Adapted version by SKAN www.autohotkey.com/forum/viewtopic.php?p=383863#383863
; Original code   by Sean www.autohotkey.com/forum/viewtopic.php?p=147029#147029
 hData := DllCall("GlobalAlloc", UInt,2, UInt,nSize )
 pData := DllCall("GlobalLock",  UInt,hData )
 DllCall( "RtlMoveMemory", UInt,pData, UInt,&Buffer, UInt,nSize )
 DllCall( "GlobalUnlock" , UInt,hData )
 DllCall( "ole32\CreateStreamOnHGlobal", UInt,hData, Int,True, UIntP,pStream )
 DllCall( "gdiplus\GdipCreateBitmapFromStream",  UInt,pStream, UIntP,pBitmap )
 DllCall( "gdiplus\GdipCreateHBITMAPFromBitmap", UInt,pBitmap, UIntP,hBitmap, UInt
,DllCall( "ntdll\RtlUlongByteSwap",UInt
,DllCall( "GetSysColor", Int,15 ) <<8 ) | 0xFF000000 )
 DllCall( "gdiplus\GdipDisposeImage", UInt,pBitmap )
 DllCall( NumGet( NumGet(1*pStream)+8 ), UInt,pStream ) ; IStream::Release
Return hBitmap
}
This is exactly what I was looking for, thank you! I have noticed that when wrapped in a try-catch statement, RtlUlongByteSwap in the code above throws an exception and returns A4: it needs "CDecl" at the end.


Helgef wrote:
Note: When I try to read/write bytes to the variablespace HBITMAP points to after using LoadPicture, AHK crashes. Am I doing it wrong or is it pointing to memory space I'm not allowed to access this way?
A HBITMAP is to be consider an opaque pointer, you can't use so freely, you need to use its associated functions/methods. To get the image's bit array from a hBitmap from loadPicture, I have used this function,

Code: Select all

_getBitmap(hBitmap){
	; Url:
	; 	- https://msdn.microsoft.com/en-us/library/windows/desktop/dd144904%28v=vs.85%29.aspx 	(GetObject function)
	;	- https://msdn.microsoft.com/en-us/library/windows/desktop/dd183371(v=vs.85).aspx		(BITMAP structure)
	/*
	typedef struct tagBITMAP {
	  LONG   bmType;
	  LONG   bmWidth;
	  LONG   bmHeight;
	  LONG   bmWidthBytes;
	  WORD   bmPlanes;
	  WORD   bmBitsPixel;
	  LPVOID bmBits;
	} BITMAP, *PBITMAP;
	*/
	
	static pBits:=A_PtrSize==4?20:24
	local BITMAP,cbBuffer,nBytes
	
	if !(cbBuffer:=DllCall("Gdi32.dll\GetObject", "Ptr", hBitmap, "Int", cbBuffer, "Ptr",0))	; Get the requiered size of the buffer (BITMAP)
		throw Exception("Failed to get the requiered buffer size, ErrorLevel: " ErrorLevel " Last error: " A_LastError ".",-1)
	VarSetCapacity(BITMAP,cbBuffer,0)
	if !(nBytes:=DllCall("Gdi32.dll\GetObject", "Ptr", hBitmap, "Int", cbBuffer, "Ptr", &BITMAP) == cbBuffer)	; Get the BITMAP
		throw Exception("Failed to get the bitmap object, ErrorLevel: " ErrorLevel " Last error: " A_LastError ".",-1)
	
	BITMAP	:=	{type:				NumGet(&BITMAP,		 0, 	"Int")						 ; bmType
				,w:					NumGet(&BITMAP,		 4, 	"Int")						 ; bmWidth
				,h:					NumGet(&BITMAP,		 8, 	"Int")                       ; bmHeight
				,widthBytes:		NumGet(&BITMAP,		12,		"Int")                       ; bmWidthBytes
				,planes:			NumGet(&BITMAP,		16,	 "UShort")                       ; bmPlanes
				,bitsPixel:			NumGet(&BITMAP,		18,  "UShort")                       ; bmBitsPixel
				,bits:				NumGet(&BITMAP,	 pBits, 	"Ptr")}                      ; bmBits
	
	return BITMAP
}
Source: github and AHK forum. The pointer to the bits is in BITMAP.bits.
That makes more sense. Thank you for the explanation.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: LoadPicture() from variable

27 Oct 2017, 16:45

You need to specify the calling convention, DllCall( "ntdll\RtlUlongByteSwap",UInt, DllCall( "GetSysColor", Int,15 ) <<8 , "cdecl"). See dllcall
Sam_
Posts: 146
Joined: 20 Mar 2014, 20:24

Re: LoadPicture() from variable

29 Oct 2017, 19:09

Helgef wrote:You need to specify the calling convention, DllCall( "ntdll\RtlUlongByteSwap",UInt, DllCall( "GetSysColor", Int,15 ) <<8 , "cdecl"). See dllcall
That is what I did. I meant my comment as an observation not a question.
Sam_
Posts: 146
Joined: 20 Mar 2014, 20:24

Re: LoadPicture() from variable

08 Nov 2017, 22:44

Helgef wrote:You need to specify the calling convention, DllCall( "ntdll\RtlUlongByteSwap",UInt, DllCall( "GetSysColor", Int,15 ) <<8 , "cdecl"). See dllcall
It seems RtlUlongByteSwap from ntdll in the above DllCall does not play nicely with x64 AHK. I have implemented a workaround to manually swap the bytes with a series of NumGets and NumPuts, but is there a better solution?
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: LoadPicture() from variable

09 Nov 2017, 06:00

OK, the problem seems to be that the function exists in the x32 version of the dll but not the x64 version. So a solution would be some bit wizardry using & and | to swap the bytes, classic Stack Overflow fare, and one call to NumPut, or to use a dll function, if anyone knows of one.

Code: Select all

;DllListExports() - List of Function exports of a DLL - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=6&t=4563

q:: ;list dll functions
if A_Is64bitOS
	DllCall("kernel32\Wow64DisableWow64FsRedirection", "Ptr*",0)
else
	return
vDllName := "ntdll"
vPath32 := A_Desktop "\z " vDllName " x32.txt"
vPath64 := A_Desktop "\z " vDllName " x64.txt"
vDll32 := "C:\Windows\SysWOW64\" vDllName ".dll" ;SysWOW64 *is* 32-bit
vDll64 := "C:\Windows\System32\" vDllName ".dll"
vText32 := StrReplace(DllListExports(vDll32), "`n", "`r`n")
vText64 := StrReplace(DllListExports(vDll64), "`n", "`r`n")
if !(FileExist, vPath32)
	FileAppend, % vText32 "`r`n", % "*" vPath32, UTF-8
if !(FileExist, vPath64)
	FileAppend, % vText64 "`r`n", % "*" vPath64, UTF-8
return
Last edited by jeeswg on 20 Aug 2019, 18:52, edited 2 times in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: LoadPicture() from variable

09 Nov 2017, 12:51

Hello :wave:
is there a better solution?
There is another, ahk v2

Code: Select all

RtlUlongByteSwap64(num){
	; Url:
	;	- https://msdn.microsoft.com/en-us/library/windows/hardware/ff562886(v=vs.85).aspx (RtlUlongByteSwap routine)
	;	- https://msdn.microsoft.com/en-us/library/e8cxb8tk.aspx (_swab function)
	; For example, if the Source parameter value is 0x12345678, the routine returns 0x78563412.
	; works on both 32 and 64 bit.
	static dest, i := varsetcapacity(dest,4)
	DllCall("MSVCRT.dll\_swab", "ptr", &num, "ptr", &dest+2, "int", 2, "cdecl")
	,DllCall("MSVCRT.dll\_swab", "ptr", &num+2, "ptr", &dest, "int", 2, "cdecl")
	return numget(dest,"uint")
}
; Tested only on these examples,
msgbox(format("0x{:08x}", RtlUlongByteSwap64(0x12345678)))
msgbox(format("0x{:08x}", RtlUlongByteSwap64(0x78563412)))
ahk v1,

Code: Select all

RtlUlongByteSwap64(num){
	; Url:
	;	- https://msdn.microsoft.com/en-us/library/windows/hardware/ff562886(v=vs.85).aspx (RtlUlongByteSwap routine)
	;	- https://msdn.microsoft.com/en-us/library/e8cxb8tk.aspx (_swab function)
	; For example, if the Source parameter value is 0x12345678, the routine returns 0x78563412.
	; works on both 32 and 64 bit.
	; v1 version
	static dest, src
	static i := varsetcapacity(dest,4) + varsetcapacity(src,4)
	numput(num,src,"uint")
	,DllCall("MSVCRT.dll\_swab", "ptr", &src, "ptr", &dest+2, "int", 2, "cdecl")
	,DllCall("MSVCRT.dll\_swab", "ptr", &src+2, "ptr", &dest, "int", 2, "cdecl")
	return numget(dest,"uint")
}
; Tested only on these examples,
msgbox % format("0x{:08x}", RtlUlongByteSwap64(0x12345678))
msgbox % format("0x{:08x}", RtlUlongByteSwap64(0x78563412))
Also,DllCall( "ntdll\RtlUlongByteSwap",UInt, DllCall( "GetSysColor", Int,15 ) <<8 , "cdecl") should probably be, DllCall( "ntdll\RtlUlongByteSwap",UInt, DllCall( "GetSysColor", Int,15 ) <<8 , "cdecl uint").

@ jeeswg, nice script :thumbup:

Cheers :wave:
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: LoadPicture() from variable

09 Nov 2017, 14:51

Cheers Helgef.

Is NumGet and NumPut so bad though, compared to using DllCall?

Code: Select all

q:: ;swap bytes (UInt)
vNum := 0x11223344
vNum := (0xFF000000&vNum)>>24 | (0xFF0000&vNum)>>8 | (0xFF00&vNum)<<8 | (0xFF&vNum)<<24
MsgBox, % Format("0x{:X}", vNum)
return
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
kazhafeizhale
Posts: 77
Joined: 25 Dec 2018, 10:58

Re: LoadPicture() from variable

01 Jan 2022, 05:10

[Mod edit: This message by @kazhafeizhale ended up in the reports for this post, so that nobody could see it. Now it has been transformed into a post:]
kazhafeizhale wrote:HI, you may need free memory
Dllcall("GlobalFree", "Ptr", hData)
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: LoadPicture() from variable

01 Jan 2022, 05:46

kazhafeizhale wrote:
01 Jan 2022, 05:10
[Mod edit: This message by @kazhafeizhale ended up in the reports for this post, so that nobody could see it. Now it has been transformed into a post:]
kazhafeizhale wrote:HI, you may need free memory
Dllcall("GlobalFree", "Ptr", hData)
no

DllCall( "ole32\CreateStreamOnHGlobal", UInt,hData, Int,True, UIntP,pStream )
A value that indicates whether the underlying handle for this stream object should be automatically freed when the stream object is released. If set to FALSE, the caller must free the hGlobal after the final release. If set to TRUE, the final release will automatically free the underlying handle.
DllCall( NumGet( NumGet(1*pStream)+8 ), UInt,pStream ) ; IStream::Release

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Google [Bot], Rohwedder and 142 guests