WUIsBack/shared/LoadImage.c

257 lines
7.0 KiB
C

#include <windows.h>
#include <wincodec.h>
#include <gdiplus.h>
// Adapted from https://faithlife.codes/blog/2008/09/displaying_a_splash_screen_with_c_part_i/
// 1. Get resource as an IStream
// 2. Use Windows Imaging Component to load the PNG
// 3. Convert the WIC bitmap to an HBITMAP
// Why does loading a PNG need to be so difficult?
typedef HRESULT (WINAPI *_WICConvertBitmapSource)(REFWICPixelFormatGUID dstFormat, IWICBitmapSource *pISrc, IWICBitmapSource **ppIDst);
static _WICConvertBitmapSource $WICConvertBitmapSource;
static HGLOBAL GetRawResource(HINSTANCE hInstance, LPWSTR name, LPWSTR type) {
HRSRC resource = FindResource(hInstance, name, type);
if (!resource) {
TRACE(L"FindResource failed: %d", GetLastError());
return NULL;
}
DWORD resourceSize = SizeofResource(hInstance, resource);
HGLOBAL imageHandle = LoadResource(hInstance, resource);
if (!imageHandle) {
TRACE(L"LoadResource failed: %d", GetLastError());
return NULL;
}
LPVOID sourceResourceData = LockResource(imageHandle);
if (!sourceResourceData) {
TRACE(L"LockResource failed: %d", GetLastError());
return NULL;
}
HGLOBAL resourceDataHandle = GlobalAlloc(GMEM_MOVEABLE, resourceSize);
if (!resourceDataHandle) {
TRACE(L"GlobalAlloc failed: %d", GetLastError());
return NULL;
}
LPVOID resourceData = GlobalLock(resourceDataHandle);
if (!resourceData) {
TRACE(L"GlobalLock failed: %d", GetLastError());
GlobalFree(resourceDataHandle);
return NULL;
}
CopyMemory(resourceData, sourceResourceData, resourceSize);
GlobalUnlock(resourceDataHandle);
return resourceDataHandle;
}
static IStream *GetResourceStream(HINSTANCE hInstance, LPWSTR name, LPWSTR type) {
IStream *stream;
HGLOBAL resource = GetRawResource(hInstance, name, type);
if (!resource) {
TRACE(L"GetResource failed: %d", GetLastError());
return NULL;
}
if (SUCCEEDED(CreateStreamOnHGlobal(resource, TRUE, &stream))) {
return stream;
}
GlobalFree(resource);
return NULL;
}
static IWICBitmapSource *GetWICBitmap(IStream *imageStream) {
IWICBitmapSource *bitmap;
IWICBitmapDecoder *decoder;
UINT frameCount;
IWICBitmapFrameDecode *frame;
if (!SUCCEEDED(CoCreateInstance(&CLSID_WICPngDecoder, NULL, CLSCTX_INPROC_SERVER, &IID_IWICBitmapDecoder, (LPVOID *)&decoder))) {
return NULL;
}
if (!SUCCEEDED(IWICBitmapDecoder_Initialize(decoder, imageStream, WICDecodeMetadataCacheOnLoad))) {
goto end;
}
if (!SUCCEEDED(IWICBitmapDecoder_GetFrameCount(decoder, &frameCount)) || frameCount != 1) {
goto end;
}
if (!SUCCEEDED(IWICBitmapDecoder_GetFrame(decoder, 0, &frame))) {
goto end;
}
$WICConvertBitmapSource(&GUID_WICPixelFormat32bppPBGRA, (IWICBitmapSource *)frame, &bitmap);
IWICBitmapFrameDecode_Release(frame);
end:
IWICBitmapDecoder_Release(decoder);
return bitmap;
}
static HBITMAP GetHBitmapForWICBitmap(IWICBitmapSource *bitmap) {
HBITMAP hBitmap;
UINT width, height;
if (!SUCCEEDED(IWICBitmapSource_GetSize(bitmap, &width, &height)) || width == 0 || height == 0) {
return NULL;
}
BITMAPINFO bminfo;
ZeroMemory(&bminfo, sizeof(bminfo));
bminfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bminfo.bmiHeader.biWidth = width;
bminfo.bmiHeader.biHeight = -(LONG)height;
bminfo.bmiHeader.biPlanes = 1;
bminfo.bmiHeader.biBitCount = 32;
bminfo.bmiHeader.biCompression = BI_RGB;
void *imageBits;
HDC screenDC = GetDC(NULL);
hBitmap = CreateDIBSection(screenDC, &bminfo, DIB_RGB_COLORS, &imageBits, NULL, 0);
ReleaseDC(NULL, screenDC);
if (!hBitmap) {
return NULL;
}
UINT stride = width * 4;
UINT imageSize = stride * height;
if (!SUCCEEDED(IWICBitmapSource_CopyPixels(bitmap, NULL, stride, imageSize, (BYTE*)imageBits))) {
DeleteObject(hBitmap);
return NULL;
}
return hBitmap;
}
HBITMAP LoadPNGResource(HINSTANCE hInstance, LPWSTR resourceName, LPWSTR resourceType) {
if (!$WICConvertBitmapSource) {
$WICConvertBitmapSource = (_WICConvertBitmapSource)GetProcAddress(LoadLibrary(L"windowscodecs.dll"), "WICConvertBitmapSource");
if (!$WICConvertBitmapSource) {
return NULL;
}
}
IStream *imageStream = GetResourceStream(hInstance, resourceName, resourceType);
if (!imageStream) {
TRACE(L"GetResourceStream failed: %d", GetLastError());
return NULL;
}
IWICBitmapSource *bitmap = GetWICBitmap(imageStream);
if (!bitmap) {
TRACE(L"GetWICBitmap failed: %d", GetLastError());
IStream_Release(imageStream);
return NULL;
}
HBITMAP result = GetHBitmapForWICBitmap(bitmap);
IWICBitmapSource_Release(bitmap);
IStream_Release(imageStream);
return result;
}
BOOL ScaleAndWriteToBMP(HBITMAP hBitmap, DWORD width, DWORD height, LPWSTR outputPath) {
BOOL result = FALSE;
if (!hBitmap) {
TRACE(L"Null bitmap");
return FALSE;
}
HDC hdc = GetDC(NULL);
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP scaledBitmap = CreateCompatibleBitmap(hdc, width, height);
if (!scaledBitmap) {
TRACE(L"CreateCompatibleBitmap failed: %d", GetLastError());
goto end;
}
BITMAP bmp;
if (!GetObject(hBitmap, sizeof(BITMAP), &bmp)) {
TRACE(L"GetObject failed: %d", GetLastError());
goto end;
}
HBITMAP hOld = (HBITMAP)SelectObject(hdcMem, hBitmap);
HDC hdcMemScaled = CreateCompatibleDC(hdc);
HBITMAP hOldScaled = (HBITMAP)SelectObject(hdcMemScaled, scaledBitmap);
SetStretchBltMode(hdcMemScaled, HALFTONE);
if (!StretchBlt(hdcMemScaled,
0, 0, width, height, hdcMem,
0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY)) {
TRACE(L"StretchBlt failed: %d", GetLastError());
goto end;
}
BITMAPINFOHEADER bmih = {0};
bmih.biSize = sizeof(BITMAPINFOHEADER);
bmih.biWidth = width;
bmih.biHeight = height;
bmih.biPlanes = 1;
bmih.biBitCount = bmp.bmBitsPixel;
bmih.biCompression = BI_RGB;
bmih.biSizeImage = ((width * bmp.bmBitsPixel + 31) / 32) * 4 * height;
BITMAPFILEHEADER bmfh = {0};
bmfh.bfType = 0x4D42;
bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bmfh.bfSize = bmfh.bfOffBits + bmih.biSizeImage;
HGLOBAL handle = GlobalAlloc(GMEM_MOVEABLE, bmih.biSizeImage);
if (!handle) {
TRACE(L"GlobalAlloc failed: %d", GetLastError());
goto end;
}
BYTE *bitmapData = (BYTE *)GlobalLock(handle);
if (!bitmapData) {
TRACE(L"GlobalLock failed: %d", GetLastError());
goto end;
}
if (!GetDIBits(hdcMemScaled, scaledBitmap, 0, height, bitmapData, (BITMAPINFO *)&bmih, DIB_RGB_COLORS)) {
TRACE(L"GetDIBits failed: %d", GetLastError());
goto end;
}
HANDLE file = CreateFile(outputPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == INVALID_HANDLE_VALUE) {
TRACE(L"CreateFile failed: %d", GetLastError());
goto end;
}
DWORD written;
if (!WriteFile(file, &bmfh, sizeof(BITMAPFILEHEADER), &written, NULL) ||
!WriteFile(file, &bmih, sizeof(BITMAPINFOHEADER), &written, NULL) ||
!WriteFile(file, bitmapData, bmih.biSizeImage, &written, NULL)) {
TRACE(L"WriteFile failed: %d", GetLastError());
goto end;
}
result = TRUE;
end:
if (file && file != INVALID_HANDLE_VALUE) {
CloseHandle(file);
}
if (scaledBitmap) {
DeleteObject(scaledBitmap);
}
if (handle) {
GlobalUnlock(handle);
GlobalFree(handle);
}
ReleaseDC(NULL, hdc);
DeleteDC(hdcMem);
DeleteDC(hdcMemScaled);
return result;
}