With some pain, I managed to get it right.
I give below the core of the code, you can download the whole script: ShowScriptImage.ahk
It uses some complementary files: DllCallStruct.ahk, BinaryEncodingDecoding.ahk and Pebwa.ahk.
imageNb := 3 currentImageNb := 0 STM_SETIMAGE = 0x0172 STM_GETIMAGE = 0x0173 IMAGE_BITMAP = 0 BITMAPFILEHEADER_SIZE := 2 + 4 + 2 + 2 + 4 BITMAPINFOHEADER_SIZE := 4 + 4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4 + 4 BI_BITFIELDS = 3 DIB_RGB_COLORS = 0 DIB_PAL_COLORS = 1 guiWidth = 400 guiHeight = 300 titleBarHeight = 24 clientHeight := guiHeight - titleBarHeight buttonSize := titleBarHeight - 4 buttonPosX := guiWidth - buttonSize - 2 buttonPosY := guiHeight - buttonSize - 2 Gui -Caption +Border ; Annoying thing: we have to give a valid _bitmap_ image here, or it won't work! Loop %A_WinDir%\*.bmp { imageFile := A_LoopFileFullPath Break } Gui Add, Picture, x0 y%titleBarHeight% w%guiWidth% h%clientHeight% vimage, %imageFile% Gui Font, s9 Bold, Tahoma Gui Margin, 0, 0 Gui Add, Text, x0 y0 w%GuiWidth% h%titleBarHeight% +0x4 ; SS_BLACKRECT Gui Add, Text, x0 y0 w%GuiWidth% h%titleBarHeight% cFFFFFF Backgroundtrans +0x200 gGuiMove , %A_Space%%A_Space%%appTitle% ; SS_CENTERIMAGE? Gui Add, Button, x%buttonPosX% y%buttonPosY% w%buttonSize% h%buttonSize% gChangeImage, > Gui Show, w%guiWidth% h%guiHeight%, %appTitle% Gui +LastFound guiID := WinExist() Return GuiMove: PostMessage 0xA1, 2, , , A ; WM_NCLBUTTONDOWN Return ChangeImage: currentImageNb := ++currentImageNb > imageNb ? 1 : currentImageNb hBitmap := CreateBitmapInMemory(image%currentImageNb%, guiID) ReplacePictureImage("Static1", hBitmap) Return GuiEscape: If (hBitmap != 0) DllCall("DeleteObject", "UInt", hBitmap) ExitApp Pause::ListVars GetBinaryData(ByRef @binaryData, _encodedData, _decodeMethod="") { local len ; Remove separators StringReplace _encodedData, _encodedData, %A_Space%, , All If _decodeMethod not in hex,pebwa ;,base64,ascii85 { If (SubStr(_encodedData, 1, 2) = "Ÿœ") _decodeMethod = pebwa Else _decodeMethod = hex } If _decodeMethod = hex { len := Hex2Bin(@binaryData, _encodedData) } Else If _decodeMethod = pebwa { len := Pebwa2Bin(@binaryData, _encodedData) } Return len } CreateBitmapInMemory(_imageHexData, _guiID) { local len, imageData local bmiHeaderAddr, dataAddr local hdc, hBitmap ; Transform hex data to binary data len := GetBinaryData(imageData, _imageHexData) ; Transform this binary data to a bitmap handle ; http://www.codeguru.com/Cpp/G-M/bitmap/article.php/c1681/ ; Get useful info from the headers bfOffBits := GetInteger(imageData, 10) bmihOffset := BITMAPFILEHEADER_SIZE biSize := GetInteger(imageData, bmihOffset) biWidth := GetInteger(imageData, bmihOffset + 4) biHeight := GetInteger(imageData, bmihOffset + 8) biBitCount := GetInteger(imageData, bmihOffset + 14, 2) biCompression := GetInteger(imageData, bmihOffset + 16) biClrUsed := GetInteger(imageData, bmihOffset + 32) bmiColors := GetInteger(imageData, bmihOffset + 36) colorNb := biClrUsed > 0 ? biClrUsed : 1 << biBitCount bmiHeaderAddr := &imageData + bmihOffset dataAddr := &imageData + bfOffBits ; More efficient and reliable than method below given in article above... ;~ dataAddr := bmiHeaderAddr + biSize ;~ If (biBitCount > 8) ;~ dataAddr += biClrUsed + (biCompression = BI_BITFIELDS ? 3 : 0) ;~ Else ;~ dataAddr += colorNb ; Skip palette ; I suppose here bitmap is in real colors mode, I don't manage a palette yet! hdc := DllCall("GetDC", "UInt", _guiID, "UInt") hBitmap := DllCall("CreateDIBitmap" , "UInt", hdc , "UInt", bmiHeaderAddr ; Bitmap data , "UInt", 4 ; Init. option: CBM_INIT , "UInt", dataAddr ; Init. data , "UInt", bmiHeaderAddr ; Color format data , "UInt" ; Color data usage , biBitCount <= 8 ? DIB_PAL_COLORS : DIB_RGB_COLORS , "UInt") If (ErrorLevel != 0 or (A_LastError > 0 and A_LastError != 127)) ; I get 127, I don't know why... { MsgBox Error: %ErrorLevel% %A_LastError% (%hBitmap%) ExitApp } DllCall("ReleaseDC", "UInt", _guiID, "UInt", hdc) Return hBitmap } ; Given a Picture control (_pictureTitle), ; and a bitmap handle (from clipboard, from GDI operations, etc.), ; tell the Picture to change its image. ; Better than BitBlt, because we don't have to manage WM_PAINT... ; Note that the given _hBitmap no longer belongs to the caller, ; either the Picture owns it, or it is destroyed, for consistent behavior. ReplacePictureImage(_pictureTitle, _hBitmap) { local hOldBitmap, hCurrentBitmap ; From info taken from http://www.autohotkey.com/forum/viewtopic.php?t=10091 ; and from the source (in script_gui.cpp). ; Reset the image of the control before deleting it SendMessage STM_SETIMAGE, IMAGE_BITMAP, 0, %_pictureTitle% ; Handle on the previous bitmamp hOldBitmap := ErrorLevel If (hOldBitmap != "FAIL" and hOldBitmap > 0) ; Destroy it DllCall("DeleteObject", "UInt", hOldBitmap) ; Set new image SendMessage STM_SETIMAGE, IMAGE_BITMAP, _hBitmap, %_pictureTitle% ; Get the handle on the bitmap stored by the control SendMessage STM_GETIMAGE, IMAGE_BITMAP, 0, %_pictureTitle% hCurrentBitmap := ErrorLevel ; If it is different than the sent one, XP made a copy because image has alpha transparency If (hCurrentBitmap != "FAIL" and hCurrentBitmap != _hBitmap) ; So delete the sent image, to avoid a memory leak DllCall("DeleteObject", "UInt", _hBitmap) }I reused the code I made to display a clipboard image, that allow to change the image in a Picture control.
I also use my Pebwa proprietary format of encoding binary to Ansi text. The image thus encoded is OK, the hexa format was above the continuation section capacity!
This shows one limitation of this format: don't use it to encode a full screen splash image! (Well, unless you stretch it or tile it...)
LIMITATION:
- I think that compressed bitmaps are not handled (and won't be).
- Images must be small! Bigger images must use several continuation sections.
TODO:
- Manage bitmaps with palette.
- Optionally stretch image to requested size, eg. to make gradient background.
- Ideally, handle more image formats, like PNG, to benefit of compression. This would need GDI+, so lot more code...
- Better packaging, outside the demo code, for easy reusing.