Gdip_CreateBitmapFromHBITMAP() with transparency.

Post your working scripts, libraries and tools for AHK v1.1 and older
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Gdip_CreateBitmapFromHBITMAP() with transparency.

03 Apr 2019, 22:53

This is a snippet from a larger Graphics library I'm working on. Most people have been annoyed that the hBitmap -> Bitmap conversion doesn't support transparency, but this function does!
Helgef wrote:
Assume that image is the variable that is a handle to the bitmap (hBitmap).

Code: Select all

            ; struct BITMAP - https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/ns-wingdi-tagbitmap
            DllCall("GetObject"
                     , "ptr", image
                     , "int", VarSetCapacity(dib, 76+2*(A_PtrSize=8?4:0)+2*A_PtrSize)
                     , "ptr", &dib) ; sizeof(DIBSECTION) = x86:84, x64:104
            width  := NumGet(dib, 4, "uint")
            height := NumGet(dib, 8, "uint")
            bpp    := NumGet(dib, 18, "ushort")

            ; Fallback to built-in method if pixels are not ARGB.
            if (bpp != 32)
               return Gdip_CreateBitmapFromHBITMAP(image)

            ; Create a handle to a device context and associate the image.
            hdc := CreateCompatibleDC()
            obm := SelectObject(hdc, image)

            ; Buffer the image with a top-down device independent bitmap via negative height.
            ; Note that a DIB is an hBitmap, pixels are formatted as pARGB, and has a pointer to the bits.
            cdc := CreateCompatibleDC(hdc)
            hbm := CreateDIBSection(width, -height, hdc, 32, pBits)
            ob2 := SelectObject(cdc, hbm)

            ; Create a new Bitmap (different from an hBitmap) which holds ARGB pixel values.
            pBitmap := Gdip_CreateBitmap(width, height)

            ; Create a Scan0 buffer pointing to pBits. The buffer has pixel format pARGB.
            VarSetCapacity(Rect, 16, 0)
               , NumPut( width, Rect,  8,  "uint")
               , NumPut(height, Rect, 12,  "uint")
            VarSetCapacity(BitmapData, 16+2*(A_PtrSize ? A_PtrSize : 4), 0)
               , NumPut(       width, BitmapData,  0,  "uint") ; Width
               , NumPut(      height, BitmapData,  4,  "uint") ; Height
               , NumPut(   4 * width, BitmapData,  8,   "int") ; Stride
               , NumPut(     0xE200B, BitmapData, 12,   "int") ; PixelFormat
               , NumPut(       pBits, BitmapData, 16,   "ptr") ; Scan0
            DllCall("gdiplus\GdipBitmapLockBits"
                     ,   "ptr", pBitmap
                     ,   "ptr", &Rect
                     ,  "uint", 7            ; ImageLockMode.UserInputBuffer | ImageLockMode.ReadWrite
                     ,   "int", 0xE200B      ; Format32bppPArgb
                     ,   "ptr", &BitmapData)

            ; Ensure that our hBitmap (image) is top-down by copying it to a top-down bitmap.
            BitBlt(cdc, 0, 0, width, height, hdc, 0, 0)

            ; Convert the pARGB pixels copied into the device independent bitmap (hbm) to ARGB.
            DllCall("gdiplus\GdipBitmapUnlockBits", "ptr",pBitmap, "ptr",&BitmapData)

            ; Cleanup the buffer and device contexts.
            SelectObject(cdc, ob2)
            DeleteObject(hbm)
            DeleteDC(cdc)
            SelectObject(hdc, obm)
            DeleteDC(hdc)

            return pBitmap
User avatar
jballi
Posts: 724
Joined: 29 Sep 2013, 17:34

Re: Gdip_CreateBitmapFromHBITMAP() with transparency.

14 Nov 2019, 20:25

Hey, I just found this. It works! :)

I was confused as why this was constructed the way it was constructed but I finally figured out that for a function, it was necessary to copy the image from DIB section bitmap instead of just pointing to DIB section bitmap image so that the DIB section bitmap can be deleted before the function returns. It's a little odd how it works but it does work. Adding additional comments to the GdipBitmapLockBits and GdipBitmapUnlockBits calls helped me understand what is going on.

Thanks for posting this. It is very useful.

Edit: Moved the question to the Help forum.
Last edited by jballi on 15 Nov 2019, 02:38, edited 2 times in total.
guest3456
Posts: 3462
Joined: 09 Oct 2013, 10:31

Re: Gdip_CreateBitmapFromHBITMAP() with transparency.

14 Nov 2019, 20:58

does anyone have some samples with transparency where the normal CreateBitmapFromHBITMAP would fail, but where this would succeed? that would be helpful to see

User avatar
jballi
Posts: 724
Joined: 29 Sep 2013, 17:34

Re: Gdip_CreateBitmapFromHBITMAP() with transparency.

15 Nov 2019, 02:32

guest3456 wrote:
14 Nov 2019, 20:58
does anyone have some samples with transparency where the normal CreateBitmapFromHBITMAP would fail, but where this would succeed? that would be helpful to see
The GDI+ API is full of idiosyncrasies and limitations. The GdipCreateBitmapFromHBITMAP function is just one of many.

The GdipCreateBitmapFromHBITMAP function doesn't fail per se, it just ignores the alpha channel when loading a 32-bit image. Worse yet, it sets the alpha channel on all pixels to 0xFF (255), i.e. completely opaque. This process/conversion works fine for most bitmap files (images from *.bmp (most but not all), *.jpg, *.gif, etc.) but it messes up the bitmaps that use the alpha channel. PNG files are a good example of files with transparency because the PNG image format uses a 32-bit color model. Many PNG images have some pixels that are partially or completely transparent.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Gdip_CreateBitmapFromHBITMAP() with transparency.

15 Nov 2019, 03:17

iseahound wrote:
03 Apr 2019, 22:53
Helgef wrote:
I have a very vague memory asking about something like this, well, thanks I guess ;)

Cheers.
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: Gdip_CreateBitmapFromHBITMAP() with transparency.

15 Nov 2019, 03:24

Can I include this function into my gdip library? Thank you.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
guest3456
Posts: 3462
Joined: 09 Oct 2013, 10:31

Re: Gdip_CreateBitmapFromHBITMAP() with transparency.

15 Nov 2019, 05:21

jballi wrote:
15 Nov 2019, 02:32
guest3456 wrote:
14 Nov 2019, 20:58
does anyone have some samples with transparency where the normal CreateBitmapFromHBITMAP would fail, but where this would succeed? that would be helpful to see
The GDI+ API is full of idiosyncrasies and limitations. The GdipCreateBitmapFromHBITMAP function is just one of many.

The GdipCreateBitmapFromHBITMAP function doesn't fail per se, it just ignores the alpha channel when loading a 32-bit image. Worse yet, it sets the alpha channel on all pixels to 0xFF (255), i.e. completely opaque. This process/conversion works fine for most bitmap files (images from *.bmp (most but not all), *.jpg, *.gif, etc.) but it messes up the bitmaps that use the alpha channel. PNG files are a good example of files with transparency because the PNG image format uses a 32-bit color model. Many PNG images have some pixels that are partially or completely transparent.
yeah i was curious if you had a .png file for testing

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Gdip_CreateBitmapFromHBITMAP() with transparency.

15 Nov 2019, 15:31

Hey I'm glad you all enjoy this code, but I don't recognize it at the moment. For people like me who are confused, the problem is that if a hBitmap has transparent pixels, they will be converted to black using the built in function.

Problem: Transparent pixels are 0x00000000. Alpha = 0x00, Red = 0x00, Green = 0x00, Blue = 0x00. In conversion, I assume the alpha channel is dropped. So the transparent pixel becomes 0x000000 which is black.
Solution: We know a Bitmap can hold 32 bits of color. So we should be able to preserve transparency.

Words:
ARGB = Alpha, Red, Green, Blue. Also known as 32-bit color.
pARGB = premultiplied ARGB. This is 24-bit data. The alpha channel has been multiplied into the color channels. Red = Alpha x Red. Green = Alpha x Green. Blue = Alpha * Blue.
Bitmap = pBitmap, pointer to a bitmap. This has two layers, one layer of struct data, and one layer of actual pixel colors. This is a flexable approach to holding information because the 1st layer can describe what information it is holding, and the second layer can be any data.
hBitmap = handle to a bitmap. This also has 2 layers, but the 1st layer contains limited information, only enough to convert a 1-dimensional array of pixels into a 2D image by storing the width, height, and some other small numbers. The reason for this is because an hBitmap is for displaying a graphical user interface, so your monitor only has red, green, and blue pixels. It does not have an "alpha" pixel. So the pixel format of all hBitmaps is pARBB, and is 24-bit data.
pBits = pointer to the bits. This is the layer of actual pixel data.
DIB = Device independent bitmap = This is a hBitmap with the pixel data stored on memory. Memory = your computer's RAM. A long time ago, memory was expensive. So your computer monitor has a buffer (memory) that it uses to hold the image briefly before drawing onto the screen. Why not use your monitor's memory and save money? That's called a device dependent bitmap or DDB for short. Today, your printer still uses DDB. And you can access the memory buffer on your printer from your computer!


1) We have a handle to our image, and we want to convert that handle to a pointer. The reason why we do this is because a pointer is global (can be transferred to more processes) while a handle is opaque, and probably has some limitations.
2) Extract some information from our handle. Call GetObject and get width, height, and bpp. (bits per pixel)
3) Now the most straightforward approach would be to find where the pixel values are in our hBitmap and memcpy the array of pixels to the pBitmap. This fails. Reasons #1 - The hBitmap could be bottom-up or top-down. #2 - hBitmap is pARGB (24-bit data) and ARGB is 32-bit data.
4) We could just code everything in C, but we are going to avoid super low level stuff and be smart. We'll use some tricks. The first trick is to determine whether a bitmap is bottom-up or top-down. The standard approach is to change the value of the first pixel value in our data to a salmon colored pink and call GetPixel. If we get a salmon colored pink, then it's top down. If not it's bottom up. We won't do this. Instead, we'll create a new DIB (Device independent bitmap) which is the same thing as an hBitmap where the pixels are stored on local memory, and copy the pixels using BitBlt. If our newly created DIB has is initialized with a negative height, then the pixels will be stored top-down. top-down is what a normal person should expect, like reading a book. Imagine reading a book from the last sentence on the page and making your way up. That's called bottom-up and how windows normally stores pixel data. That's because some mathematicians were really concerned about purity and ideology. You know how a xy graph looks like? x-axis and y-axis? Yeah, the mathematicians wanted to use that model.
5) For the second trick we'll use something special, a hidden flag in GdipBitmapLockBits that let's us determine where the return data will be buffered. You see, we get to choose where the output of the function will be, and I choose to put it where my DIB is. Importantly, I set another flag to display the output data as PARGB, the same format as a DIB.
6) Do the following in order: Create a DIB. Create a Bitmap. Lock the Bitmap, and point the output to the DIB. Use Trick #1 - copy the pixels to the DIB, erasing any top-down/bottom-up distinctions. Use Trick #2 - Unlock the bits, which copies the 24-bit pARGB data in a DIB into 32-bit ARGB pixels.
7) Clean up and return the bitmap.
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Gdip_CreateBitmapFromHBITMAP() with transparency.

15 Nov 2019, 15:36

Slightly better comments + easier to follow through.

Code: Select all

            ; struct BITMAP - https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/ns-wingdi-tagbitmap
            DllCall("GetObject"
                     ,    "ptr", image
                     ,    "int", VarSetCapacity(dib, 76+2*(A_PtrSize=8?4:0)+2*A_PtrSize)
                     ,    "ptr", &dib) ; sizeof(DIBSECTION) = 84, 104
            width  := NumGet(dib, 4, "uint")
            height := NumGet(dib, 8, "uint")
            bpp    := NumGet(dib, 18, "ushort")

            ; Fallback to built-in method if pixels are not ARGB.
            if (bpp != 32)
               return Gdip_CreateBitmapFromHBITMAP(image)

            ; Create a handle to a device context and associate the image.
            hdc := DllCall("CreateCompatibleDC", "ptr",0)
            obm := DllCall("SelectObject", "ptr",hdc, "ptr",image)

            ; Buffer the image with a top-down device independent bitmap via negative height.
            ; Note that a DIB is an hBitmap, pixels are formatted as pARGB, and has a pointer to the bits.
            cdc := DllCall("CreateCompatibleDC", "ptr",hdc)
            hbm := CreateDIBSection(width, -height, cdc, 32, pBits)
            ob2 := DllCall("SelectObject", "ptr",cdc, "ptr",hbm)

            ; Create a new Bitmap (different from an hBitmap) which holds ARGB pixel values.
            pBitmap := Gdip_CreateBitmap(width, height)

            ; Create a Scan0 buffer pointing to pBits. The buffer has pixel format pARGB.
            VarSetCapacity(Rect, 16, 0)                ; sizeof(Rect) = 16
               , NumPut(    width, Rect,  8,   "uint") ; Width
               , NumPut(   height, Rect, 12,   "uint") ; Height
            VarSetCapacity(BitmapData, 16+2*A_PtrSize, 0)       ; sizeof(BitmapData) = 24, 32
               , NumPut(       width, BitmapData,  0,   "uint") ; Width
               , NumPut(      height, BitmapData,  4,   "uint") ; Height
               , NumPut(   4 * width, BitmapData,  8,    "int") ; Stride
               , NumPut(     0xE200B, BitmapData, 12,    "int") ; PixelFormat
               , NumPut(       pBits, BitmapData, 16,    "ptr") ; Scan0
            DllCall("gdiplus\GdipBitmapLockBits"
                     ,    "ptr", pBitmap
                     ,    "ptr", &Rect
                     ,   "uint", 7            ; ImageLockMode.UserInputBuffer | ImageLockMode.ReadWrite
                     ,    "int", 0xE200B      ; Format32bppPArgb
                     ,    "ptr", &BitmapData)

            ; Ensure that our hBitmap (image) is top-down by copying it to a top-down bitmap.
            BitBlt(cdc, 0, 0, width, height, hdc, 0, 0)

            ; Convert the pARGB pixels copied into the device independent bitmap (hbm) to ARGB.
            DllCall("gdiplus\GdipBitmapUnlockBits", "ptr",pBitmap, "ptr",&BitmapData)

            ; Cleanup the buffer and device contexts.
            DllCall("SelectObject", "ptr",cdc, "ptr",ob2)
            DeleteObject(hbm)
            DeleteDC(cdc)
            DllCall("SelectObject", "ptr",hdc, "ptr",obm)
            DeleteDC(hdc)

            return pBitmap
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: Gdip_CreateBitmapFromHBITMAP() with transparency.

15 Nov 2019, 16:41

When will you publish your graphics library? I'm very interested in this.

Thank you.

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Gdip_CreateBitmapFromHBITMAP() with transparency.

15 Nov 2019, 17:18

It's over here https://github.com/iseahound/Graphics/blob/master/lib/Graphics.ahk, with no documentation and broken v2 compatibility. It's fine for v1. No documentation = hard for others to use.
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: Gdip_CreateBitmapFromHBITMAP() with transparency.

16 Nov 2019, 09:42

I've read the code line by line and I must say.... It's insane how much work you have done already....

I wonder how exactly one would use it. It seems very specific. Am I correct?

I can tell it allows for easy text and shapes rendering.... With styles and some effects. And most notable, interactive UI elements.

I dare suggest you look into the gdip extended version I made. There are lots of things that I think would be beneficial for your undertaking. Some functions are helpful for UI interactions.

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
serzh82saratov
Posts: 137
Joined: 01 Jul 2017, 03:04

Re: Gdip_CreateBitmapFromHBITMAP() with transparency.

01 May 2020, 17:31

Indicate what you need before calling.

Code: Select all

SelectObject(hDC, obm)
kyuuuri
Posts: 340
Joined: 09 Jan 2016, 19:20

Re: Gdip_CreateBitmapFromHBITMAP() with transparency.

12 May 2020, 10:51

Sorry for reviving this topic @iseahound , how would you do it for this case: https://www.autohotkey.com/boards/viewtopic.php?f=76&t=75845. I'm trying to create a simple semi-transparent black hbitmap to use it on a picture control. Not using any file.
I think what I need is to copy the pixels from the pBitmap to the DIB section but I'm not sure how.

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 68 guests