A Starter Guide to Understanding GDI

Helpful script writing tricks and HowTo's
iseahound
Posts: 1582
Joined: 13 Aug 2016, 21:04
Contact:

A Starter Guide to Understanding GDI

15 Feb 2024, 16:33

Today I'll show you the best way to work with Gdip_All.ahk. It involves the use of my custom library, ImagePut to help decipher and explain every step of the way. Finally, in the end, you're able to discard ImagePut and rely solely on GDI and GDI+.

Gdip_All: https://github.com/buliasz/AHKv2-Gdip/blob/master/Gdip_All.ahk
ImagePut: https://github.com/iseahound/ImagePut/blob/master/ImagePut.ahk
Note: You can substitute the Gdip_All functions below, I'll use the raw DllCalls as an example.

GDI
A handle to a device context is a container that holds various objects. The exact objects can be retrieved with GetCurrentObject. To set a new object, use SelectObject, returning the previous object. The previous object must then be freed with DeleteObject. To summarize, an hdc always contains a stock OBJ_BITMAP, OBJ_BRUSH, OBJ_PEN, etc when it is created.

Let's take a look at the stock bitmap. It's a 1x1 tiny black pixel.
5.png
5.png (94 Bytes) Viewed 3544 times

Code: Select all

#Include ImagePut.ahk

; Creates a handle to a device context compatible with the current screen.
hdc := DllCall("CreateCompatibleDC", "ptr", 0, "ptr")

; Scale the tiny pixel 300x so we can see it.
ImageShow({scale: 300, image: hdc}) ; Right click to close.


Next we create a custom handle to a GDI bitmap hBitmap using CreateDIBSection.

Code: Select all

#Include ImagePut.ahk

; Creates a GDI hBitmap.
width := 300, height := 300
bi := Buffer(40, 0)                    ; sizeof(bi) = 40
   NumPut(  "uint",        40, bi,  0) ; Size
   NumPut(   "int",     width, bi,  4) ; Width
   NumPut(   "int",   -height, bi,  8) ; Height - Negative so (0, 0) is top-left.
   NumPut("ushort",         1, bi, 12) ; Planes
   NumPut("ushort",        32, bi, 14) ; BitCount / BitsPerPixel
hbm := DllCall("CreateDIBSection", "ptr", 0, "ptr", bi, "uint", 0, "ptr*", &pBits:=0, "ptr", 0, "uint", 0, "ptr")

ImagePutWindow(hbm)
Notice how you're seeing a completely transparent window! That's because the pointer to the pixels pBits is full of repeated 0x00000000 where the color is defined as pre-multiplied ARGB.



Let's add a bit of color to show what I mean:
Screenshot 2024-02-14 225112.png
Screenshot 2024-02-14 225112.png (5.19 KiB) Viewed 3544 times

Code: Select all

#Include ImagePut.ahk

bi := Buffer(40, 0)                    ; sizeof(bi) = 40
   NumPut(  "uint",        40, bi,  0) ; Size
   NumPut(   "int",        10, bi,  4) ; Width
   NumPut(   "int",       -10, bi,  8) ; Height - Negative so (0, 0) is top-left.
   NumPut("ushort",         1, bi, 12) ; Planes
   NumPut("ushort",        32, bi, 14) ; BitCount / BitsPerPixel
hbm := DllCall("CreateDIBSection", "ptr", 0, "ptr", bi, "uint", 0, "ptr*", &pBits:=0, "ptr", 0, "uint", 0, "ptr")

; Add some colors: Red             Green      and      Blue
NumPut("uint", 0xFFFF0000, "uint", 0xFF00FF00, "uint", 0xFF0000FF, pBits)

; Zoom in 30x (sorry for blurry scaling)
ImagePutWindow({scale: 30, image: hbm})


Cool! Now let's finish up the typical GDI application which in order:
  1. Creates an hdc or a container that holds various GDI objects
  2. Creates a hbm or hBitmap or a handle to a GDI bitmap
  3. Selects the hbm onto the hdc
And if you're wondering what happens to that lonely 1x1 black pixel, well that's the stock bitmap! So don't worry about obm (old bitmap) as its memory can never be leaked. It's still good practice to delete it anyways.

Code: Select all

#Include ImagePut.ahk

hdc := DllCall("CreateCompatibleDC", "ptr", 0, "ptr")
bi := Buffer(40, 0)                    ; sizeof(bi) = 40
   NumPut(  "uint",        40, bi,  0) ; Size
   NumPut(   "int",        10, bi,  4) ; Width
   NumPut(   "int",       -10, bi,  8) ; Height - Negative so (0, 0) is top-left.
   NumPut("ushort",         1, bi, 12) ; Planes
   NumPut("ushort",        32, bi, 14) ; BitCount / BitsPerPixel
hbm := DllCall("CreateDIBSection", "ptr", 0, "ptr", bi, "uint", 0, "ptr*", &pBits:=0, "ptr", 0, "uint", 0, "ptr")
obm := DllCall("SelectObject", "ptr", hdc, "ptr", hbm, "ptr")

; Add some AliceBlue #9CD6E4 with some double d transparency.
loop 100
  NumPut("uint", 0xDD9CD6E4, pBits, 4 * (A_Index-1))

ImagePutWindow({scale: 30, image: hdc})


Now what about obm? Well you can ignore and forget about it. But unlike the rest of the GDI stock objects that 1x1 black pixel can't be summoned via GetStockObject. Instead. you can do:

Code: Select all

obm := DllCall("CreateBitmap", "int", 0, "int", 0, "uint", 1, "uint", 1, "ptr", 0, "ptr")
See: https://devblogs.microsoft.com/oldnewthing/20100416-00/?p=14313
Hopefully you can now understand how GDI works with the help of ImagePut as a debugging tool.

Return to “Tutorials (v2)”

Who is online

Users browsing this forum: No registered users and 2 guests