News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

TransparentBlt(): two things wrong

Started by NoCforMe, December 09, 2023, 06:10:02 AM

Previous topic - Next topic

NoCforMe

I'll admit this is really picayune, unimportant stuff, but maybe somebody somewhere might find it useful. I'm using TransparentBlt() in a program that displays info for a Windows icon (.ico) file when that icon has transparency. It didn't always work, and I found out why. There's also a blatant falsehood in the Micro$oft documentation for this function. (Surprise, surprise.)

First, the transparency issue (and after all, why else would anyone use this unless they wanted to see ... transparency?). The last parameter is the color (RGB) that you want to make transparent in the icon's bitmap. It wasn't working at all for me, even though everything was valid and correctly set up.

Turns out this only works if the transparent color is the first one in the "color table" (the array of palette entries). My icon was a 16-color one and my transparent color was index #15, so it didn't work. It did work when I changed the index to #0.

This is stupid! It's like what Henry Ford was said to declare about the Model T: you can have it in any color, as long as that color is black. Since this is how the function actually works, they should eliminate the last parameter and just say that this will render the first palette entry as transparent.

Second thing: Micro$oft Learn says about this function:
QuoteIf the source and destination rectangles are not the same size, the source bitmap is stretched to match the destination rectangle.
This is simply untrue. The source and destination rectangles must be exactly the same size; otherwise the function fails. There is no stretching.
Assembly language programming should be fun. That's why I do it.

fearless

Convert the icon to a bitmap and use GdiTransparentBlt or TransparentBlt with the background color you want as transparent.

- Create a CompatibleBitmap size of the icon
- FillRect with background color that will be transparent in that bitmap
- Use DrawIconEx to draw the icon into the bitmap created previously
- GdiTransparentBlt/TransparentBlt to destination dc (another dc in memory or window dc)

Depends on complexity of colors what you choose for transparent. Maybe bright pink or green or something - as long as that color isnt already in use in the icon/bitmap, then specify that same color as the transparent color in the GdiTransparentBlt/TransparentBlt call - you can also stretch this "icon" bitmap as well, now that it is a bitmap

NoCforMe

Yes, that's exactly what I do now.

The point is that TransparentBlt() only works if the transparent color is the first one in the palette. This is not mentioned at all in the Micro$oft documentation.

And stretching does not work. Don't believe me? Try it.

Also: GdiTransparentBlt() = TransparentBlt().
And GdiTransparentBlt isn't defined anywhere in the MASM32 package.
Assembly language programming should be fun. That's why I do it.

NoCforMe

So @fearless, maybe you can help me here: since TransparentBlt() doesn't work reliably, I'm thinking of doing the icon display "by hand", by using two BitBlt()s:
1. BitBlt() AND mask--> display DC using SRCAND (this creates transparency)
2. BitBlt() XOR bitmap--> display DC using SRCINVERT

What I have when I'm in my WM_PAINT handler is:
  • An XOR bitmap in memory
  • An AND bitmap in memory
  • A palette in memory

(I won't need the palette, I'm pretty sure.)

Question is, how do I create handles to the two bitmaps to select into my compatible DC for BitBlt()?

Thanks in advance.
Assembly language programming should be fun. That's why I do it.

fearless

https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-gditransparentblt

Just the same as TransparentBlt but its in Gdi32 now since Win 2000, just copy the TransparentBlt prototype from Msimg32.inc, paste it into gdi32.inc and rename it. Or just use TransparentBlt, no real difference.

I have tried it and am using it in a custom control and seem to be able to get bitmaps to stretch using it.

Are you calling SetStretchBltMode beforehand? https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-setstretchbltmode

Also make sure to call SetBrushOrgX if using HALFTONE:
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-setbrushorgex

Think I used COLORONCOLOR in the past and it seemed to work fine (also doesnt need the call to SetBrushOrgX either)

NoCforMe

I actually don't need to stretch the bitmap; I was just pointing out how it didn't seem to work as advertised. I did try using SetStretchBltMode(COLORONCOLOR), but that didn't work either. (When I've needed to display a bitmap at a different size I've used StretchBlt() successfully. Just no transparency.)

I'd much rather have an answer to the other question about creating handles to my bitmaps.
Assembly language programming should be fun. That's why I do it.

adeyblue

#6
Works fine here. You probably have a DIB bitmap not a compatible bitmap, which the documentation says it doesn't work with.
QuoteThe TransparentBlt function works with compatible bitmaps (DDBs).

This stretches and transparents a random colour, 16 & 256 colour bitmaps
Picture here  - https://prnt.sc/ucpB9ylqYNEz - the yellow in the D is the background of the window
The bitmaps are here - https://www.airesoft.co.uk/files/temp/test.zip
Weirdly it also works passing in the LR_CREATEDIBSECTION flag to LoadImage, so I guess I have the opposite problem - it works more than it should!
#include <windows.h>
#include <windowsx.h>
#pragma comment(lib, "msimg32.lib")

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    static HDC hdcSrc;
    static HBITMAP hbmOld;
    static BITMAP bm;
    switch(msg)
    {
        case WM_CREATE:
        {
            //HBITMAP hbm = (HBITMAP)LoadImage(NULL, L"test-256.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
            HBITMAP hbm = (HBITMAP)LoadImage(NULL, L"test-16.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
            HDC hdcDest = GetDC(hwnd);
            hdcSrc = CreateCompatibleDC(hdcDest);
            ReleaseDC(hwnd, hdcDest);
            GetObject(hbm, sizeof(bm), &bm);
            hbmOld = SelectBitmap(hdcSrc, hbm);
        }
        break;
        case WM_DESTROY:
        {
            HBITMAP hbmSrc = SelectBitmap(hdcSrc, hbmOld);
            DeleteBitmap(hbmSrc);
            DeleteDC(hdcSrc);
            PostQuitMessage(0);
        }
        break;
        case WM_PAINT:
        {
            PAINTSTRUCT ps = {};
            HDC hdc = BeginPaint(hwnd, &ps);
            RECT rc;
            GetClientRect(hwnd, &rc);
            // test-256 - the RGB value is the colour of the D
            //COLORREF transColour = RGB(192, 224, 0);
            // test-16 - the RGB value of the B
            COLORREF transColour = RGB(128, 0, 0);
            TransparentBlt(hdc, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hdcSrc, 0, 0, bm.bmWidth, bm.bmHeight, transColour);
            EndPaint(hwnd, &ps);
            return 0;
        }
        break;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

int main()
{   
    HINSTANCE hMod7 = GetModuleHandle(NULL);
    HBRUSH hbrYellow = CreateSolidBrush(RGB(255, 242, 0));
    WNDCLASS wndCls = {};
    wndCls.hbrBackground = hbrYellow;
    wndCls.lpfnWndProc = &WndProc;
    wndCls.lpszClassName = L"MyClass";
    wndCls.hInstance = hMod7;
    ATOM at = RegisterClass(&wndCls);
    HWND hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, MAKEINTATOM(at), L"Title", WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0, 0, 500, 500, NULL, NULL, hMod7, NULL);
    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    DeleteBrush(hbrYellow);
    return msg.wParam;
}

QuoteQuestion is, how do I create handles to the two bitmaps to select into my compatible DC for BitBlt()?
If you have a HICON, the easiest way is to call GetIconInfo which will put them both into HBITMAPs for you.

NoCforMe

Quote from: adeyblue on December 09, 2023, 09:28:56 AM
QuoteQuestion is, how do I create handles to the two bitmaps to select into my compatible DC for BitBlt()?
If you have a HICON, the easiest way is to call GetIconInfo which will put them both into HBITMAPs for you.

Yes, I know that method, used it in the program earlier. I was hoping for some way to get handles to the already-in-memory bitmaps, but I could make that work too.
Assembly language programming should be fun. That's why I do it.

TimoVJL

Just to make compiling easier
#define UNICODE
#define WIN32_LEAN_AND_MEAN
...
    PAINTSTRUCT ps;    // = {0};
...
//int main(void)
void __cdecl WinMainCRTStartup(void)
...
    WNDCLASS wndCls = {0};
...
    //return msg.wParam;
    ExitProcess(msg.wParam);


Also:
https://masm32.com/board/index.php?topic=101.0

May the source be with you

NoCforMe

#9
I got everything working OK finally. The trick for getting transparency that could be turned on or off was to use GetIconInfo() on the icon handle to get access to the two bitmaps, AND and XOR, and then to use BitBlt() with various ROP codes to display the icon.

In pseudocode:
ii ICONINFO
icoHandle = LoadImage (iconFileName, LR_LOADFROMFILE ...)
GetIconInfo (icoHandle, &ii)
ANDbitmap = ii.hbmMask
XORbitmap = ii.hbmColor

; in WM_PAINT handler:
IF (user wants to see transparency)
   SelectObject (icoDC, ANDbitmap)
   BitBlt (hDC, ... icoDC, ... SRCAND)
   SelectObject (icoDC, XORbitmap)
   BitBlt (hDC, ... icoDC, ... SRCPAINT)
ELSE    ; no transparency
   SelectObject (icoDC, XORbitmap)
   BitBlt (hDC, ... icoDC, ... SRCCOPY)

As before, if the icon has alpha-channel info or it's a non-palettized icon, I use DrawIcon() instead to render it, which automagically takes care of transparency. (In this case the user can't turn transparency off. I could arrange that but it would be pretty complicated.)

So how do you determine if the icon has transparency or not? You look at the AND bitmap; any icon with transparency will have some bits set to 1 where the background color shows through (=transparency). For each 1 bit, look at the corresponding pixel in the XOR bitmap and see if enough 1 bits map to the same color (I use a threshold of 4 matches). If they do, then that color is (probably) the transparent color. Remember that for a palettized image the color is a palette index; to get the actual color you need to get that actual palette entry (RGB). (Since I'm no longer using TransprentBlt() to show transparency, the color is only used for reporting purposes.)

The ICONINFO structure returned by GetIconInfo() looks like this:
ICONINFO STRUCT
  fIcon     DWORD      ?
  xHotspot  DWORD      ?
  yHotspot  DWORD      ?
  hbmMask   DWORD      ?
  hbmColor  DWORD      ?
ICONINFO ENDS
where .hbmMask is a handle to the AND mask bitmap and .hbmColor a handle to the XOR bitmap.

I was surprised that the raster opcodes (ROPs, the last parameter of BitBlt()) weren't what I had expected after reading all the available documentation, which says that to show transparency one needs first to apply BitBlt() with the AND mask using SRCAND (which is true) and then to apply it with the XOR bitmap using SRCINVERT, which isn't true and doesn't work. The winning combinations are shown above.

Note: The caller (you) is responsible for getting rid of these two handles when they're no longer needed:

INVOKE DeleteObject, XORbitmap
INVOKE DeleteObject, ANDbitmap

The program still doesn't work correctly with 1BPP (2-color) icons, but this is a pretty low priority for me ...

Revised source & .exe is attached here. Let me know what you think. I'm open to suggestions.
Assembly language programming should be fun. That's why I do it.