Direct2D bitmap with Alpha channel is not drawing properly on black background

29 Views Asked by At

Using Direct2D, I am trying to draw DXGI_FORMAT_B8G8R8A8_UNORM bitmap with alpha channel. Bitmap has 10*1 size and filled from this array:

// DXGI_FORMAT_B8G8R8A8_UNORM, 1 line, 10 pixels
static unsigned char image_data[] =
{
    0, 0, 0, 0,             // black, transparent
    0, 0, 0, 0,             // black, transparent
    0, 0, 255, 100,         // red, Alpha 100
    0, 0, 255, 200,         // red, Alpha 200
    0, 0, 255, 255,         // red, opaque
    0, 0, 255, 255,
    0, 0, 255, 200,
    0, 0, 255, 100,
    0, 0, 0, 0,
    0, 0, 0, 0
};

When drawn on white background, Alpha channel is handling as expected:

good

When background is black, Alpha channel seems to be ignored:

bad

Code fragments, only bitmap-related code is shown:

#define IMAGE_WIDTH 10
#define IMAGE_HEIGHT 1

// DXGI_FORMAT_B8G8R8A8_UNORM, 1 line, 10 pixels
static unsigned char image_data[] =
{
    0, 0, 0, 0,             // black, transparent
    0, 0, 0, 0,             // black, transparent
    0, 0, 255, 100,         // red, Alpha 100
    0, 0, 255, 200,         // red, Alpha 200
    0, 0, 255, 255,         // red, opaque
    0, 0, 255, 255,
    0, 0, 255, 200,
    0, 0, 255, 100,
    0, 0, 0, 0,
    0, 0, 0, 0
};


class DemoApp
{
    // ...
    HRESULT CreateDeviceResources();
    HRESULT OnRender();

    ID2D1HwndRenderTarget* m_pRenderTarget{};
    ID2D1Bitmap* bitmap{};
};

HRESULT DemoApp::CreateDeviceResources()
{
    HRESULT hr = S_OK;
    // ...
            hr = m_pRenderTarget->CreateBitmap(
                D2D1::SizeU(IMAGE_WIDTH, IMAGE_HEIGHT),
                D2D1::BitmapProperties(
                    D2D1::PixelFormat(
                        DXGI_FORMAT_B8G8R8A8_UNORM,
                        D2D1_ALPHA_MODE_PREMULTIPLIED)),
                    &bitmap);

            if (SUCCEEDED(hr))
            {
                hr = bitmap->CopyFromMemory(nullptr, image_data, IMAGE_WIDTH * 4);
            }
    // ...
    return hr;
}


HRESULT DemoApp::OnRender()
{
    HRESULT hr;
    hr = CreateDeviceResources();

    if (SUCCEEDED(hr) && !(m_pRenderTarget->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED))
    {
        D2D1_SIZE_F renderTargetSize = m_pRenderTarget->GetSize();
        m_pRenderTarget->BeginDraw();
        m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());

        m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));        // white background - OK
        //m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::Black));          // black background - Alpha channel is ignored


        D2D_RECT_F draw_rect{ 10, 20, 10 + IMAGE_WIDTH * 32, 20 + IMAGE_HEIGHT * 32 };

        m_pRenderTarget->DrawBitmap(bitmap, &draw_rect, 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, nullptr);

        hr = m_pRenderTarget->EndDraw();
    }

    return hr;
}

How can I get a proper Alpha channel rendering on any background?

Finally, full application code. To reproduce the problem, it is enough to create desktop Win32 application with Visual Studio (in my case it is VS2019) and paste this code to main.cpp file:

#include <windows.h>
#include <d2d1.h>
#include <dwrite.h>
#include <wincodec.h>

// D2D bitmap rendering with Alpha.
// Background D2D1::ColorF::White - OK. Background D2D1::ColorF::Black - Alpha is ignored.

#pragma comment(lib, "DXGI.lib")
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwrite.lib")

#define IMAGE_WIDTH 10
#define IMAGE_HEIGHT 1

// DXGI_FORMAT_B8G8R8A8_UNORM, 1 line, 10 pixels
static unsigned char image_data[] =
{
    0, 0, 0, 0,             // black, transparent
    0, 0, 0, 0,             // black, transparent
    0, 0, 255, 100,         // red, Alpha 100
    0, 0, 255, 200,         // red, Alpha 200
    0, 0, 255, 255,         // red, opaque
    0, 0, 255, 255,
    0, 0, 255, 200,
    0, 0, 255, 100,
    0, 0, 0, 0,
    0, 0, 0, 0
};


template<class Interface>
inline void SafeRelease(Interface** ppInterfaceToRelease)
{
    if (*ppInterfaceToRelease != NULL)
    {
        (*ppInterfaceToRelease)->Release();
        
        (*ppInterfaceToRelease) = NULL;
    }
}

class DemoApp
{
public:
    ~DemoApp();

    HRESULT Initialize(HINSTANCE h);
    void RunMessageLoop();

private:
    HRESULT CreateDeviceIndependentResources();
    HRESULT CreateDeviceResources();
    void DiscardDeviceResources();

    HRESULT OnRender();
    void OnResize(UINT width, UINT height);
    static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

private:
    HWND m_hwnd{};
    ID2D1Factory* m_pD2DFactory{};
    IWICImagingFactory* m_pWICFactory{};
    IDWriteFactory* m_pDWriteFactory{};
    ID2D1HwndRenderTarget* m_pRenderTarget{};
    ID2D1Bitmap* bitmap{};
};

DemoApp::~DemoApp()
{
    SafeRelease(&m_pD2DFactory);
    SafeRelease(&m_pDWriteFactory);
    SafeRelease(&m_pRenderTarget);
    SafeRelease(&bitmap);
}

HRESULT DemoApp::Initialize(HINSTANCE h)
{
    HRESULT hr;

    hr = CreateDeviceIndependentResources();
    if (SUCCEEDED(hr))
    {
        WNDCLASSEX wcex{};
        wcex.cbSize = { sizeof(WNDCLASSEX) };
        wcex.style = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc = DemoApp::WndProc;
        wcex.cbWndExtra = sizeof(LONG_PTR);
        wcex.hInstance = h;
        wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
        wcex.lpszClassName = L"D2DDemoApp";

        RegisterClassEx(&wcex);

        m_hwnd = CreateWindow(
            L"D2DDemoApp", L"D2DDemoApp",
            WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480,
            nullptr, nullptr, h, this
        );
        
        hr = m_hwnd ? S_OK : E_FAIL;
        
        if (SUCCEEDED(hr))
        {
            ShowWindow(m_hwnd, SW_SHOWNORMAL);
            UpdateWindow(m_hwnd);
        }
    }

    return hr;
}

HRESULT DemoApp::CreateDeviceIndependentResources()
{
    HRESULT hr;

    hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory);

    if (SUCCEEDED(hr))
    {
        hr = DWriteCreateFactory(
            DWRITE_FACTORY_TYPE_SHARED,
            __uuidof(m_pDWriteFactory),
            reinterpret_cast<IUnknown**>(&m_pDWriteFactory)
        );
    }
    
    return hr;
}


HRESULT DemoApp::CreateDeviceResources()
{
    HRESULT hr = S_OK;

    if (!m_pRenderTarget)
    {
        RECT rc;
        GetClientRect(m_hwnd, &rc);

        D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);

        hr = m_pD2DFactory->CreateHwndRenderTarget(
            D2D1::RenderTargetProperties(),
            D2D1::HwndRenderTargetProperties(m_hwnd, size),
            &m_pRenderTarget);

        if (SUCCEEDED(hr))
        {
            hr = m_pRenderTarget->CreateBitmap(
                D2D1::SizeU(IMAGE_WIDTH, IMAGE_HEIGHT),
                D2D1::BitmapProperties(
                    D2D1::PixelFormat(
                        DXGI_FORMAT_B8G8R8A8_UNORM,
                        D2D1_ALPHA_MODE_PREMULTIPLIED)),
                    &bitmap);

            if (SUCCEEDED(hr))
            {
                hr = bitmap->CopyFromMemory(nullptr, image_data, IMAGE_WIDTH * 4);
            }
        }

    }

    return hr;
}

void DemoApp::DiscardDeviceResources()
{
    SafeRelease(&m_pRenderTarget);
    SafeRelease(&bitmap);
}

void DemoApp::RunMessageLoop()
{
    MSG msg;

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

HRESULT DemoApp::OnRender()
{
    HRESULT hr;
    hr = CreateDeviceResources();

    if (SUCCEEDED(hr) && !(m_pRenderTarget->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED))
    {
        D2D1_SIZE_F renderTargetSize = m_pRenderTarget->GetSize();
        m_pRenderTarget->BeginDraw();
        m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());

        m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));        // white background - OK
        //m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::Black));          // black background - Alpha channel is ignored


        D2D_RECT_F draw_rect{ 10, 20, 10 + IMAGE_WIDTH * 32, 20 + IMAGE_HEIGHT * 32 };

        m_pRenderTarget->DrawBitmap(bitmap, &draw_rect, 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, nullptr);

        hr = m_pRenderTarget->EndDraw();

        if (hr == D2DERR_RECREATE_TARGET)
        {
            hr = S_OK;
            DiscardDeviceResources();
        }
    }

    return hr;
}

void DemoApp::OnResize(UINT width, UINT height)
{
    if (m_pRenderTarget)
    {
        D2D1_SIZE_U size;
        size.width = width;
        size.height = height;
        m_pRenderTarget->Resize(size);
    }
}

LRESULT CALLBACK DemoApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    LRESULT result = 0;

    if (message == WM_CREATE)
    {
        LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
        DemoApp* pDemoApp = (DemoApp*)pcs->lpCreateParams;

        ::SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pDemoApp));
        result = 1;
    }
    else
    {
        DemoApp* pDemoApp = reinterpret_cast<DemoApp*>(
            ::GetWindowLongPtrW(hwnd, GWLP_USERDATA));

        bool wasHandled = false;

        if (pDemoApp)
        {
            switch (message)
            {
            case WM_SIZE:
            {
                UINT width = LOWORD(lParam);
                UINT height = HIWORD(lParam);
                pDemoApp->OnResize(width, height);
            }
            wasHandled = true;
            result = 0;
            break;

            case WM_PAINT:
            case WM_DISPLAYCHANGE:
            {
                PAINTSTRUCT ps;
                BeginPaint(hwnd, &ps);

                pDemoApp->OnRender();
                EndPaint(hwnd, &ps);
            }
            wasHandled = true;
            result = 0;
            break;

            case WM_DESTROY:
            {
                PostQuitMessage(0);
            }
            wasHandled = true;
            result = 1;
            break;
            }
        }

        if (!wasHandled)
        {
            result = DefWindowProc(hwnd, message, wParam, lParam);
        }
    }

    return result;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE,LPSTR, int)
{
    if (SUCCEEDED(CoInitialize(NULL)))
    {
        {
            DemoApp app;

            if (SUCCEEDED(app.Initialize(hInstance)))
            {
                app.RunMessageLoop();
            }
        }
        CoUninitialize();
    }

    return 0;
}
0

There are 0 best solutions below