none
UpdateLayeredWindow not respecting alpha channel with certain colored pixels

    שאלה

  • I seem to have uncovered another bug when using layered windows. I can load a PNG with a per-pixel alpha channel and display it without any issues. I can then iterate through that bitmap and adjust the red, green, or blue channel without adjusting the alpha channel's intensity and display the image with per-pixel transparency just fine. However, if I set the red, green, and blue channel intensities to 255, the per-pixel alpha channel fails to be acknowledged and the layered window renders as an opaque white rectangle. The color key, which should be unused, is set to red, so I am lost as to what would be causing this behavior.

    Thank you,

    Jason Stern

    #include <Objidl.h>
    #include <tchar.h>
    #include <Wincodec.h>
    #include <Windows.h>
    #include <WinUser.h>
    #include <xutility>
    
    #include "Resource.h"
    
    const TCHAR CLASS_NAME [] = TEXT ("Test");
    HWND hwnd = 0;
    BLENDFUNCTION blend_function;
    HBITMAP transparent_png_handle;
    RECT window_size = { 0, 0, 320, 240 };
    HGLOBAL resource_data = NULL;
    
    IWICBitmapSource* LoadImageFromDecoder (IWICBitmapDecoder* image_decoder)
    {
       IWICBitmapSource* bitmap_source (NULL);
       IWICBitmapFrameDecode* frame_pointer (NULL);
       image_decoder->GetFrame (0, &frame_pointer);
       WICConvertBitmapSource (GUID_WICPixelFormat32bppPBGRA, frame_pointer, &bitmap_source);
       frame_pointer->Release();
       return bitmap_source;
    }
    
    IWICBitmapSource* LoadBitmapFromStream (IStream* image_stream)
    {
       bool release_decoder (false);
       IWICImagingFactory* wic_imaging_factory (NULL);
       IWICBitmapDecoder* wic_bitmap_decoder (NULL);
       IWICBitmapSource* wic_bitmap_source (NULL);
       CoCreateInstance (CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, 
                         IID_IWICImagingFactory, (LPVOID*) &wic_imaging_factory);
       wic_imaging_factory->CreateDecoderFromStream (image_stream, NULL, WICDecodeMetadataCacheOnDemand,
                                                     &wic_bitmap_decoder);
       wic_bitmap_source = LoadImageFromDecoder (wic_bitmap_decoder);
       wic_bitmap_decoder->Release();
       wic_imaging_factory->Release();
       return wic_bitmap_source;
    }
    
    HBITMAP CreateHBitmap (IWICBitmapSource* bitmap_source)
    {
       UINT width (0);
       UINT height (0);
       bitmap_source->GetSize (&width, &height);
       BITMAPINFO bitmap_info;
       ZeroMemory (&bitmap_info, sizeof (bitmap_info));
       bitmap_info.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
       bitmap_info.bmiHeader.biWidth = width;
       bitmap_info.bmiHeader.biHeight = -((LONG)height);
       bitmap_info.bmiHeader.biPlanes = 1;
       bitmap_info.bmiHeader.biBitCount = 32;
       bitmap_info.bmiHeader.biCompression = BI_RGB;
       void* image_bits (NULL);
       HDC screen_device_context (GetDC (NULL));
       HBITMAP bitmap_handle (CreateDIBSection (screen_device_context, &bitmap_info, DIB_RGB_COLORS,
                                                &image_bits, NULL, 0));
       ReleaseDC (NULL, screen_device_context);
       const UINT stride (width << 2);
       const UINT buffer_size (stride * height);
       bitmap_source->CopyPixels (NULL, stride, buffer_size, static_cast<BYTE *> (image_bits));
       window_size.right = (LONG)width;
       window_size.bottom = (LONG)height;
       return bitmap_handle;
    }
    
    IStream* CreateStreamOnResource (LPCTSTR resource_name, LPCTSTR resource_type)
    {
       IStream* image_stream (NULL);
       HRSRC resource_handle (FindResource (NULL, resource_name, resource_type));
       DWORD resource_size (SizeofResource (NULL, resource_handle));
       HGLOBAL image_handle (LoadResource (NULL, resource_handle));
       LPVOID source_resource_data (LockResource (image_handle));
       resource_data = GlobalAlloc (GMEM_MOVEABLE, resource_size);
       LPVOID locked_resource_data = GlobalLock (resource_data);
       CopyMemory (locked_resource_data, source_resource_data, resource_size);
       GlobalUnlock (resource_data);
       CreateStreamOnHGlobal (resource_data, TRUE, &image_stream);
       return image_stream;
    }
    
    IWICBitmapSource* LoadImageFromResource (LPCTSTR resource_name, LPCTSTR resource_type)
    {
       IStream* image_stream (CreateStreamOnResource (resource_name, resource_type));
       IWICBitmapSource* image_source (LoadBitmapFromStream (image_stream));
       image_stream->Release();
       return image_source;
    }
    
    void loadImage (void)
    {
       HINSTANCE instance_handle (GetModuleHandle (0));  
       CoInitialize (0);
       LPCSTR resource_name (MAKEINTRESOURCE (IDB_TRANSPARENT_PNG));
       IWICBitmapSource* source (LoadImageFromResource (resource_name, TEXT ("PNG"))); 
       transparent_png_handle = CreateHBitmap (source);
       source->Release ();
       CoUninitialize ();
    }
    
    LRESULT CALLBACK WindowProc (HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param)
    {
       LRESULT result (0);
    
       switch (message)
       {
          case WM_DESTROY:
             PostQuitMessage (0);
             if (resource_data != NULL)
             {
                GlobalFree (resource_data);
             }
             break;
    
          default: 
             result = DefWindowProc (window_handle, message, w_param, l_param);
             break;
       }
       return result;
    }
    
    void adjustRGB (HDC& target_device_context, HBITMAP& autowhite_texture, HBITMAP& texture,
                    int mask)
    {
       if (texture != NULL)
       {
          BITMAP bitmap;
          ZeroMemory (&bitmap, sizeof (BITMAP));
          LPBITMAPINFO bitmap_info (NULL);       
          WORD bitmap_width (0), bitmap_height (0); 
          GetObject (texture, sizeof (bitmap), &bitmap);
          bitmap_info = (LPBITMAPINFO)&bitmap; 
          bitmap_width = (WORD)bitmap_info->bmiHeader.biWidth;
          bitmap_width -= (bitmap_width % 4);                       
          bitmap_height = (WORD)bitmap_info->bmiHeader.biHeight; 
          int* bitmap_data = new int[bitmap_width * bitmap_height];
          HDC window_device_context = GetWindowDC (NULL);
          BITMAPINFO requested_bitmap_info;
          ZeroMemory (&requested_bitmap_info, sizeof (BITMAPINFO));
          requested_bitmap_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
          requested_bitmap_info.bmiHeader.biWidth = bitmap_width;
          requested_bitmap_info.bmiHeader.biHeight = -bitmap_height;
          requested_bitmap_info.bmiHeader.biPlanes = 1;
          requested_bitmap_info.bmiHeader.biBitCount = 32;
          requested_bitmap_info.bmiHeader.biCompression = BI_RGB;
          requested_bitmap_info.bmiHeader.biSizeImage = bitmap_width * bitmap_height * 4;
          requested_bitmap_info.bmiHeader.biXPelsPerMeter = 0;
          requested_bitmap_info.bmiHeader.biYPelsPerMeter = 0;
          requested_bitmap_info.bmiHeader.biClrUsed = 0;
          requested_bitmap_info.bmiHeader.biClrImportant = 0; 
          GetDIBits (window_device_context, texture, 0, bitmap_height, 
                     (LPVOID)bitmap_data, &requested_bitmap_info, DIB_RGB_COLORS);
          for (int index (0);
               index < (bitmap.bmWidth * bitmap.bmHeight) - 1;
               ++index)
          {
             bitmap_data [index] |= mask;
          }      
          SetDIBits (target_device_context, autowhite_texture, 0, bitmap_height, 
                     (LPVOID)bitmap_data, &requested_bitmap_info, DIB_RGB_COLORS);
          delete[] bitmap_data;
          ReleaseDC (NULL, window_device_context);
       }
    }
    
    int CALLBACK WinMain (__in  HINSTANCE hInstance, __in  HINSTANCE hPrevInstance,
                          __in  LPSTR lpCmdLine, __in  int nCmdShow)
    {
       HINSTANCE h_instance = GetModuleHandle (0);
    
       blend_function.BlendOp = AC_SRC_OVER;
       blend_function.BlendFlags = 0;
       blend_function.SourceConstantAlpha = 0xFF;
       blend_function.AlphaFormat = AC_SRC_ALPHA;
    
       WNDCLASSEX wcex;
       wcex.cbSize = sizeof (WNDCLASSEX);
       wcex.style         = CS_HREDRAW | CS_VREDRAW;
       wcex.lpfnWndProc   = WindowProc;
       wcex.cbClsExtra    = 0;
       wcex.cbWndExtra    = 0;
       wcex.hInstance     = h_instance;
       wcex.hIcon         = LoadIcon (h_instance, MAKEINTRESOURCE (IDI_APPLICATION));
       wcex.hCursor       = LoadCursor (NULL, IDC_ARROW);
       wcex.hbrBackground = CreateSolidBrush (RGB (255, 0, 0));
       wcex.lpszMenuName  = 0;
       wcex.lpszClassName = CLASS_NAME;
       wcex.hIconSm       = LoadIcon (wcex.hInstance, MAKEINTRESOURCE (IDI_APPLICATION));
       RegisterClassEx (&wcex);
    
       loadImage ();
       SIZE destination_size = { window_size.right - window_size.left, window_size.bottom - window_size.top };
    
       hwnd = CreateWindowEx (WS_EX_LAYERED | WS_EX_APPWINDOW, CLASS_NAME, CLASS_NAME, 
                              WS_VISIBLE | WS_CAPTION | WS_SIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU, 
                              window_size.left, window_size.top, destination_size.cx, destination_size.cy,
                              NULL, NULL, GetModuleHandle (0), NULL);
    
       if (hwnd)
       {
          ShowWindow (hwnd, SW_SHOW);
    
          POINT source_coordinates = { 0 };
          POINT destination_coordinates = { window_size.left, window_size.top };
          HDC screen_device_context (GetDC (NULL));
          HDC memory_device_context (CreateCompatibleDC (screen_device_context));
    
          // displaying the original image works:
          //    HBITMAP texture (transparent_png_handle);
          //    HBITMAP previously_selected_object = (HBITMAP)SelectObject (memory_device_context, texture);
          // however, switching to this:
                HBITMAP texture (CreateCompatibleBitmap (screen_device_context, destination_size.cx, destination_size.cy));
                HBITMAP previously_selected_object = (HBITMAP)SelectObject (memory_device_context, texture);
                adjustRGB (memory_device_context, texture, transparent_png_handle, 0x00FFFFFF);
          // the alpha channel is lost. but if we adjust a single channel (in this case green):
          //    HBITMAP texture (CreateCompatibleBitmap (screen_device_context, destination_size.cx, destination_size.cy));
          //    HBITMAP previously_selected_object = (HBITMAP)SelectObject (memory_device_context, texture);
          //    adjustRGB (memory_device_context, texture, transparent_png_handle, 0x0000FF00);
          // ...the alpha channel is not lost.
    
          UpdateLayeredWindow (hwnd, screen_device_context, &destination_coordinates, &destination_size, 
                               memory_device_context, &source_coordinates, RGB (255, 0, 0), &blend_function, ULW_ALPHA);     
          SelectObject (memory_device_context, previously_selected_object);
          DeleteDC (memory_device_context);
          ReleaseDC (NULL, screen_device_context);
    
          while (true)
          {
             MSG msg; 
             BOOL ret (GetMessage (&msg, 0, 0, 0));
             if (ret > 0)
             {
                TranslateMessage (&msg);
                DispatchMessage (&msg);
             }
             else
                break;
          }
       }
    }
    

    יום שלישי 06 מרץ 2012 21:07

כל התגובות

  • Hi,

    Thank you for your post.

    This is a quick note to let you know that we are performing
    research on this issue. If we have any result about this issue, we will reply
    you as soon as possible.

    Best Regards,
    Rob



    Rob Pan [MSFT]
    MSDN Community Support | Feedback to us

    יום שישי 09 מרץ 2012 07:13
  • Do you mean when Red, Green and Blue channel are not 255 and Alpha is 0, it works fine. However, if all of Red, Green and Blue are 255, even Alpha is 0, it looks like an opaque rectange.

    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.

    Regards,
    Jacky Wu
    Microsoft Online Community Support

    יום שלישי 13 מרץ 2012 03:01
  • Do you mean when Red, Green and Blue channel are not 255 and Alpha is 0, it works fine. However, if all of Red, Green and Blue are 255, even Alpha is 0, it looks like an opaque rectange.

    Here are some sample screen shots of a transparent PNG with three different backgrounds to illustrate the problem. When the pixel data is not iterated through and modified, the per-pixel alpha channel is respected. Notice specifically the inside of the circle where an alpha gradient is present:

    However, when I set the color channel to white, the alpha channel is discarded. There are roughly six edge pixels that appear to have block transparency; however, the source image has a completely transparent border:

    So, to answer your question, it does not work. A few pixels do have block transparency; however, that is not deterministic of the pixel's alpha value.

    These screen shots came from alt-print screening the application built from the source code included in my first post.

    Hope this clarifies!

    Thank you,

    Jason Stern



    • נערך על-ידי JasonStern יום שלישי 13 מרץ 2012 16:43
    יום שלישי 13 מרץ 2012 16:42
  • Seems that you have set window background to red, if you try mask as 0x00FFFFFF, can you see the background red? if you try mask as 0x0000FF00, can you see the background red?

    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.

    Regards,
    Jacky Wu
    Microsoft Online Community Support

    יום רביעי 14 מרץ 2012 03:05
  • Seems that you have set window background to red, if you try mask as 0x00FFFFFF, can you see the background red? if you try mask as 0x0000FF00, can you see the background red?



    Why would the background look red? As the call to UpdateLayeredWindow shows, dwFlags is set to ULW_ALPHA and not ULW_COLORKEY. I am doing this because I want per-pixel alpha and not a single 1-bit opaque or transparent mask. If using ULW_ALPHA works the exact same as using ULW_COLORKEY, then why have both values and explain different purposes for each on the MSDN page for UpdateLayeredWindow?

    If you look at the post above, I included a screenshot of what a mask of 0x00FFFFFF looks like when the layered window is placed over a black, gray, and white background. Here it is again, though:

    As the code snippet above shows, adjustRGB logical OR's in the mask, so OR'ing in 0x00FFFFFF into an ARGB pixel format should max out the red, green, and blue channels - not just the alpha and red channels.

    Using the key of 0x0000FF00 yields:

    ...when the layered window is placed over a black, gray, and white background. The fact it is green makes sense. An ARGB mask of 0x0000FF00 equates to alpha = 0, red = 0, green = 255, and blue = 0. I have no idea why you were expecting it to look red. But, the problem where the alpha value is incorrect despite the bitmask for the alpha channel not being modified still persists. Again, look at what happens when UpdateLayeredWindow is called on the source image before adjustRGB is invoked (again, on a black, gray, and white background):

    Notice how the alpha value can be modified per-pixel. Since I am not modifying the alpha value of the bitmap, why is the alpha channel being discarded but the color channels retained? Nowhere in the code is the pixel format changing. The bit count is explicitly set to 32 bits per pixel. When I iterate through the pixel data in adjustRGB, the alpha channel is correct. Where does it get discarded/corrupted and why?

    Hope this clarifies!

    Thank you,

    Jason Stern


    • נערך על-ידי JasonStern יום חמישי 15 מרץ 2012 21:35
    יום חמישי 15 מרץ 2012 21:29
  • Your question falls into the paid support category which requires a more in-depth level of support. Please visit the below link to see the various paid support options that are available to better meet your needs.

    http://support.microsoft.com/default.aspx?id=fh;en-us;offerprophone


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.

    Regards,
    Jacky Wu
    Microsoft Online Community Support

    יום שישי 11 מאי 2012 06:06
  • For those others that stop by here to find answers to their issue:

    The NEW PIX (N) is calculated from the OLD PIX (O) , the DRAW PIX (D) and DRAW ALPHA (A) with the following formula:

    N=ClampTo255(Round(O*(1-A/255)+D))

    or:

    Nr=Round(Or*(1-A/255)+Dr)
    Ng=Round(Og*(1-A/255)+Dg)
    Nb=Round(Ob*(1-A/255)+Db)

    Yes, it adds D, or, your image. If R=255 (G and B are calculated the same way, but separately), then the new R will be 255, because it'll be ClampTo255(Round(O*(1-A/255)+D))=ClampTo255(Round(Something+255))=ClampTo255(Round(255+X))=ClampTo255(255+X)=255

    Yeah, it's always going to be 255, even with alpha=0. The reason is, the colors provided to ULW are supposed to be premultiplied. If you don't premultiply them, then they'll just be additive.

    So, take your color values for R,G and B and multiply them by the A (and /255 of course) before sending them to ULW. 

    • הוצע כתשובה על-ידי Andrey Svc יום שני 30 ספטמבר 2013 11:46
    שבת 09 מרץ 2013 22:43
  • After this:

    		// So, take your color values for R,G and B and multiply them by the A (and /255 of course) before sending them to ULW.
    		BYTE *pBits = (BYTE*)m_Img.GetBits();
    		ENSURE (pBits);
    		int nPitch = m_Img.GetPitch();
    		for (int i = 0; i < m_Img.GetHeight(); i++) 
    		{
    			DWORD *pLineBits = (DWORD*)pBits;
    			for (int l = 0; l < (nPitch < 0 ? -nPitch : nPitch); l += sizeof(DWORD))
    			{
    				switch (m_Img.GetBPP ())
    				{
    				case 16:
    					break;
    				case 24:
    					break;
    				case 32:
    					{
    						DWORD color = *(DWORD*)pLineBits;
    						BYTE r = GetRValue  (color);
    						BYTE g = GetGValue  (color);
    						BYTE b = GetBValue  (color);
    						BYTE a = (LOBYTE((color)>>24));
    
    #define MULTPLICATION(chanel,alpha) (((double)chanel * alpha)/255)
    						r = MULTPLICATION(r,a);
    						g = MULTPLICATION(g,a);
    						b = MULTPLICATION(b,a);
    						color = ((DWORD)a << 24) + RGB(r,g,b);
    						*(DWORD*)pLineBits = color;
    					}
    					break;
    				}
    				pLineBits ++;
    			}
    			pBits += nPitch;
    		}
    all works fine. Thanks firebird_38.

    יום שני 30 ספטמבר 2013 11:45