LoadPicture() from variable Topic is solved

Get help with using AutoHotkey and its commands and hotkeys
Sam_
Posts: 106
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: 6648
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: 3892
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: 106
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: 3892
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: 106
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: 106
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: 6648
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: 3892
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: 6648
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

Return to “Ask For Help”

Who is online

Users browsing this forum: flyingDman, Getfree, ineuw and 155 guests