I did it with GDI + WIC, maybe it could be simplified with GDI+ or Direct2D (Blend effect, Using Color in Direct2D)
=>
#include <windows.h>
#include <tchar.h>
#include <Urlmon.h> // URLDownloadToCacheFile
#pragma comment (lib, "Urlmon")
#include <shlwapi.h> // SHCreateStreamOnFile
#pragma comment (lib, "Shlwapi")
#include <Wincodec.h> // IWICBitmapSource
#pragma comment (lib, "windowscodecs")
#include <thumbcache.h> // WTS_ALPHATYPE
#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
HINSTANCE hInst;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int nWidth = 680, nHeight = 600;
HRESULT WICCreate32BitsPerPixelHBITMAP(IStream* pstm, UINT /* cx */, HBITMAP* phbmp, WTS_ALPHATYPE* pdwAlpha);
void DrawBitmapTransparent(HDC hDCDest, int nXDest, int nYDest, int nBitmapWidth, int nBitmapHeight, HBITMAP hBitmap, int nXSrc, int nYSrc, int nTransparentColor, bool bXOR);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
CoInitialize(NULL);
hInst = hInstance;
WNDCLASSEX wcex =
{
sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInst, LoadIcon(NULL, IDI_APPLICATION),
LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW + 1), NULL, TEXT("WindowClass"), NULL,
};
if (!RegisterClassEx(&wcex))
return MessageBox(NULL, TEXT("Cannot register class !"), TEXT("Error"), MB_ICONERROR | MB_OK);
int nX = (GetSystemMetrics(SM_CXSCREEN) - nWidth) / 2, nY = (GetSystemMetrics(SM_CYSCREEN) - nHeight) / 2;
HWND hWnd = CreateWindowEx(0, wcex.lpszClassName, TEXT("Test"), WS_OVERLAPPEDWINDOW, nX, nY, nWidth, nHeight, NULL, NULL, hInst, NULL);
if (!hWnd)
return MessageBox(NULL, TEXT("Cannot create window !"), TEXT("Error"), MB_ICONERROR | MB_OK);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
CoUninitialize();
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HBITMAP hBitmap1 = NULL, hBitmap2 = NULL, hBitmap3 = NULL;
switch (message)
{
case WM_CREATE:
{
WCHAR wszURL1[MAX_PATH] = L"https://i.ibb.co/t8dNP1c/Red-Circle.png";
WCHAR wszURL2[MAX_PATH] = L"https://i.ibb.co/4fXcqM8/Green-Circle.png";
WCHAR wszURL3[MAX_PATH] = L"https://i.ibb.co/fCL4LrT/Blue-Circle.png";
WCHAR wszFilename[MAX_PATH];
HRESULT hr = URLDownloadToCacheFile(NULL, wszURL1, wszFilename, ARRAYSIZE(wszFilename), 0x0, NULL);
IStream* pstm = NULL;
hr = SHCreateStreamOnFile(wszFilename, STGM_READ | STGM_SHARE_DENY_WRITE, &pstm);
if (SUCCEEDED(hr))
{
WTS_ALPHATYPE alphatype;
WICCreate32BitsPerPixelHBITMAP(pstm, NULL, &hBitmap1, &alphatype);
pstm->Release();
}
hr = URLDownloadToCacheFile(NULL, wszURL2, wszFilename, ARRAYSIZE(wszFilename), 0x0, NULL);
hr = SHCreateStreamOnFile(wszFilename, STGM_READ | STGM_SHARE_DENY_WRITE, &pstm);
if (SUCCEEDED(hr))
{
WTS_ALPHATYPE alphatype;
WICCreate32BitsPerPixelHBITMAP(pstm, NULL, &hBitmap2, &alphatype);
pstm->Release();
}
hr = URLDownloadToCacheFile(NULL, wszURL3, wszFilename, ARRAYSIZE(wszFilename), 0x0, NULL);
hr = SHCreateStreamOnFile(wszFilename, STGM_READ | STGM_SHARE_DENY_WRITE, &pstm);
if (SUCCEEDED(hr))
{
WTS_ALPHATYPE alphatype;
WICCreate32BitsPerPixelHBITMAP(pstm, NULL, &hBitmap3, &alphatype);
pstm->Release();
}
return 0;
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint(hWnd, &ps);
RECT rc;
GetClientRect(hWnd, &rc);
HBRUSH hBrush = CreateSolidBrush(RGB(0, 0, 0));
FillRect(hDC, &rc, hBrush);
DeleteObject(hBrush);
BITMAP bm;
GetObject(hBitmap1, sizeof(BITMAP), &bm);
DrawBitmapTransparent(hDC, bm.bmWidth / 3, 10, bm.bmWidth, bm.bmHeight, hBitmap1, 0, 0, RGB(0, 0, 0), FALSE);
GetObject(hBitmap2, sizeof(BITMAP), &bm);
DrawBitmapTransparent(hDC, bm.bmWidth / 6, 10 + bm.bmHeight/3, bm.bmWidth, bm.bmHeight, hBitmap2, 0, 0, RGB(0, 0, 0), TRUE);
GetObject(hBitmap3, sizeof(BITMAP), &bm);
DrawBitmapTransparent(hDC, bm.bmWidth / 2, 10 + bm.bmHeight/3, bm.bmWidth, bm.bmHeight, hBitmap3, 0, 0, RGB(0, 0, 0), TRUE);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
void DrawBitmapTransparent(HDC hDCDest, int nXDest, int nYDest, int nBitmapWidth, int nBitmapHeight, HBITMAP hBitmap, int nXSrc, int nYSrc, int nTransparentColor, bool bXOR)
{
HDC hDCSrc;
HBITMAP hBitmapOld;
HDC hDCMask;
HBITMAP hBitmapMask;
HBITMAP hBitmapMaskOld;
HDC hDCMem;
HBITMAP hBitmapMem;
HBITMAP hBitmapMemOld;
int nBkColorOld;
int nTextColorOld;
BITMAP bm;
GetObject(hBitmap, sizeof(BITMAP), &bm);
if (!nBitmapWidth)
nBitmapWidth = bm.bmWidth;
if (!nBitmapHeight)
nBitmapHeight = bm.bmHeight;
hDCSrc = CreateCompatibleDC(hDCDest);
hBitmapOld = (HBITMAP)SelectObject(hDCSrc, hBitmap);
hDCMask = CreateCompatibleDC(hDCDest);
hBitmapMask = CreateBitmap(nBitmapWidth, nBitmapHeight, 1, 1, 0);
hBitmapMaskOld = (HBITMAP)SelectObject(hDCMask, hBitmapMask);
hDCMem = CreateCompatibleDC(hDCDest);
hBitmapMem = CreateCompatibleBitmap(hDCDest, nBitmapWidth, nBitmapHeight);
hBitmapMemOld = (HBITMAP)SelectObject(hDCMem, hBitmapMem);
nBkColorOld = SetBkColor(hDCSrc, nTransparentColor);
BitBlt(hDCMask, 0, 0, nBitmapWidth, nBitmapHeight, hDCSrc, nXSrc, nYSrc, SRCCOPY);
SetBkColor(hDCSrc, nBkColorOld);
nBkColorOld = SetBkColor(hDCDest, RGB(255, 255, 255));
nTextColorOld = SetTextColor(hDCDest, RGB(0, 0, 0));
if (!bXOR)
{
BitBlt(hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCDest, nXDest, nYDest, SRCCOPY);
BitBlt(hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCSrc, nXSrc, nYSrc, SRCINVERT);
BitBlt(hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCMask, 0, 0, SRCAND);
BitBlt(hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCSrc, nXSrc, nYSrc, SRCINVERT);
BitBlt(hDCDest, nXDest, nYDest, nBitmapWidth, nBitmapHeight, hDCMem, 0, 0, SRCCOPY);
}
else
{
BitBlt(hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCDest, nXDest, nYDest, SRCCOPY);
BitBlt(hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCSrc, nXSrc, nYSrc, SRCCOPY);
BitBlt(hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCMask, 0, 0, SRCAND);
BitBlt(hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCSrc, nXSrc, nYSrc, SRCINVERT);
BitBlt(hDCDest, nXDest, nYDest, nBitmapWidth, nBitmapHeight, hDCMem, 0, 0, SRCINVERT);
}
SetBkColor(hDCDest, nBkColorOld);
SetTextColor(hDCDest, nTextColorOld);
SelectObject(hDCMem, hBitmapMemOld);
DeleteDC(hDCMem);
DeleteObject(hBitmapMem);
SelectObject(hDCMask, hBitmapMaskOld);
DeleteDC(hDCMask);
DeleteObject(hBitmapMask);
SelectObject(hDCSrc, hBitmapOld);
DeleteDC(hDCSrc);
}
// From MS SDK RecipeThumbnailProvider.cpp
HRESULT ConvertBitmapSourceTo32BPPHBITMAP(IWICBitmapSource* pBitmapSource, IWICImagingFactory* pImagingFactory, HBITMAP* phbmp)
{
*phbmp = NULL;
IWICBitmapSource* pBitmapSourceConverted = NULL;
WICPixelFormatGUID guidPixelFormatSource;
HRESULT hr = pBitmapSource->GetPixelFormat(&guidPixelFormatSource);
if (SUCCEEDED(hr) && (guidPixelFormatSource != GUID_WICPixelFormat32bppBGRA))
{
IWICFormatConverter* pFormatConverter;
hr = pImagingFactory->CreateFormatConverter(&pFormatConverter);
if (SUCCEEDED(hr))
{
// Create the appropriate pixel format converter
hr = pFormatConverter->Initialize(pBitmapSource, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, NULL, 0, WICBitmapPaletteTypeCustom);
if (SUCCEEDED(hr))
{
hr = pFormatConverter->QueryInterface(&pBitmapSourceConverted);
}
pFormatConverter->Release();
}
}
else
{
hr = pBitmapSource->QueryInterface(&pBitmapSourceConverted); // No conversion necessary
}
if (SUCCEEDED(hr))
{
UINT nWidth, nHeight;
hr = pBitmapSourceConverted->GetSize(&nWidth, &nHeight);
if (SUCCEEDED(hr))
{
BITMAPINFO bmi = {};
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = nWidth;
bmi.bmiHeader.biHeight = -static_cast<LONG>(nHeight);
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
BYTE* pBits;
HBITMAP hbmp = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, reinterpret_cast<void**>(&pBits), NULL, 0);
hr = hbmp ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
WICRect rect = { 0, 0, nWidth, nHeight };
// Convert the pixels and store them in the HBITMAP. Note: the name of the function is a little
// misleading - we're not doing any extraneous copying here. CopyPixels is actually converting the
// image into the given buffer.
hr = pBitmapSourceConverted->CopyPixels(&rect, nWidth * 4, nWidth * nHeight * 4, pBits);
if (SUCCEEDED(hr))
{
*phbmp = hbmp;
}
else
{
DeleteObject(hbmp);
}
}
}
pBitmapSourceConverted->Release();
}
return hr;
}
HRESULT WICCreate32BitsPerPixelHBITMAP(IStream* pstm, UINT /* cx */, HBITMAP* phbmp, WTS_ALPHATYPE* pdwAlpha)
{
*phbmp = NULL;
IWICImagingFactory* pImagingFactory;
HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pImagingFactory));
if (SUCCEEDED(hr))
{
IWICBitmapDecoder* pDecoder;
hr = pImagingFactory->CreateDecoderFromStream(pstm, &GUID_VendorMicrosoft, WICDecodeMetadataCacheOnDemand, &pDecoder);
//hr = CoCreateInstance(CLSID_WICPngDecoder, NULL, CLSCTX_INPROC_SERVER, __uuidof(pDecoder), reinterpret_cast<void**>(&pDecoder));
//hr = pDecoder->Initialize(pstm, WICDecodeMetadataCacheOnLoad);
if (SUCCEEDED(hr))
{
IWICBitmapFrameDecode* pBitmapFrameDecode;
hr = pDecoder->GetFrame(0, &pBitmapFrameDecode);
if (SUCCEEDED(hr))
{
hr = ConvertBitmapSourceTo32BPPHBITMAP(pBitmapFrameDecode, pImagingFactory, phbmp);
if (SUCCEEDED(hr))
{
*pdwAlpha = WTSAT_ARGB;
}
pBitmapFrameDecode->Release();
}
pDecoder->Release();
}
pImagingFactory->Release();
}
return hr;
}