使用GDI+(WIC)加载32位的位图或者PNG图片(具有透明通道)
#include <windows.h> #include <gdiplus.h> HBITMAP LoadBitmapFromResource(DWORD ResourceID, bool transparent = true) { HANDLE hGlobal = NULL; ULONG_PTR GDIToken = 0; Gdiplus::Image* Img = NULL; Gdiplus::GdiplusStartupInput GDIStartInput = NULL; Gdiplus::GdiplusStartup(&GDIToken, &GDIStartInput, NULL); HRSRC hResource = FindResource(NULL, MAKEINTRESOURCE(ResourceID), "BINARY"); if (!hResource) {return NULL;} HGLOBAL hFileResource = LoadResource(NULL, hResource); if (!hFileResource) {return NULL;} LPVOID lpFile = LockResource(hFileResource); if (!lpFile) {return NULL;} DWORD dwSize = SizeofResource(NULL, hResource); if (!dwSize) {return NULL;} void* data = LockResource(hFileResource); if (!data) {return NULL;} IStream* pStream = NULL; hGlobal = GlobalAlloc(GMEM_FIXED, dwSize); memcpy(hGlobal, data, dwSize); UnlockResource(hFileResource); FreeResource(hFileResource); if (CreateStreamOnHGlobal(hGlobal, true, &pStream) == S_OK) { Img = new Gdiplus::Image(pStream, false); pStream->Release(); GlobalFree(hGlobal); hGlobal = NULL; HBITMAP hBitmap = NULL; static_cast<Gdiplus::Bitmap*>(Img)->GetHBITMAP(transparent ? Gdiplus::Color::Transparent : Gdiplus::Color(0, 0, 0), &hBitmap); delete Img; Gdiplus::GdiplusShutdown(GdiImage::GDIToken); GDIStartInput = NULL; GDIToken = 0; return hBitmap; } GlobalFree(hGlobal); hGlobal = NULL; Gdiplus::GdiplusShutdown(GdiImage::GDIToken); GDIStartInput = NULL; GDIToken = 0; return NULL; }
最后会返回可操作的hBitmap句柄
另附上异曲同工的代码:
Gdiplus::Bitmap *loadimage(HINSTANCE hinst, const wchar_t* name) { auto hres = FindResource(hinst, name, RT_RCDATA); if(!hres) return nullptr;//resource not found if(auto size = SizeofResource(hinst, hres)) if(auto data = LockResource(LoadResource(hinst, hres))) if(auto stream = SHCreateMemStream((const BYTE*)data, size)) { auto *bmp = new Gdiplus::Bitmap(stream); if(!bmp) { //wrong input, or gdiplus was not initialized } stream->Release(); return bmp; } return nullptr; } ... auto gimg = loadimage(hinst, MAKEINTRESOURCE(ID_PNG1)); Gdiplus::Color clr = Gdiplus::Color::Transparent; gimg->GetHBITMAP(Gdiplus::Color::Transparent, &bitmap); ImageList_AddMasked(himage, bitmap, 0)); DeleteObject(bitmap); //资源定义应如下所示: ID_PNG1 RCDATA "file.png" //确保初始化Gdiplus: struct gdiplus_init { Gdiplus::GdiplusStartupInput tmp; ULONG_PTR token; gdiplus_init(){ Gdiplus::GdiplusStartup(&token, &tmp, NULL); } ~gdiplus_init(){ Gdiplus::GdiplusShutdown(token); } } gdipls_init;
附上代码链接:https://stackoverflow.com/questions/58751417/how-can-i-add-a-transparent-png-as-a-toolbar-icon
补充:也可以使用WIC来加载PNG文件,参考:用C ++显示启动画面
因为是外网资源,所以我将摘取部分内容以防吞链接。
为了在屏幕上显示图像,我们需要将其作为HBITMAP提供。在Windows图像处理组件允许我们PNG图像解码成32位,每像素的位图(带alpha通道),并提取其像素到一个DIB。
在此代码中,我将假定图像嵌入在初始屏幕EXE的资源中(使用与.RC文件类似的语句):
IDI_SPLASHIMAGE PNG splash.png
第一步是在资源数据上创建一个IStream。这涉及加载资源,将其数据复制到内存缓冲区中,然后在该缓冲区上创建流。(请注意,以下代码已出于教学目的进行了更改;可以通过对COM接口和Windows句柄使用智能指针以及使用异常处理错误条件来改进生产代码。)COM应该已经初始化(通过调用 CoInitialize或 CoInitializeEx),然后再调用此方法。
// Creates a stream object initialized with the data from an executable resource. IStream * CreateStreamOnResource(LPCTSTR lpName, LPCTSTR lpType) { // initialize return value IStream * ipStream = NULL; // find the resource HRSRC hrsrc = FindResource(NULL, lpName, lpType); if (hrsrc == NULL) goto Return; // load the resource DWORD dwResourceSize = SizeofResource(NULL, hrsrc); HGLOBAL hglbImage = LoadResource(NULL, hrsrc); if (hglbImage == NULL) goto Return; // lock the resource, getting a pointer to its data LPVOID pvSourceResourceData = LockResource(hglbImage); if (pvSourceResourceData == NULL) goto Return; // allocate memory to hold the resource data HGLOBAL hgblResourceData = GlobalAlloc(GMEM_MOVEABLE, dwResourceSize); if (hgblResourceData == NULL) goto Return; // get a pointer to the allocated memory LPVOID pvResourceData = GlobalLock(hgblResourceData); if (pvResourceData == NULL) goto FreeData; // copy the data from the resource to the new memory block CopyMemory(pvResourceData, pvSourceResourceData, dwResourceSize); GlobalUnlock(hgblResourceData); // create a stream on the HGLOBAL containing the data if (SUCCEEDED(CreateStreamOnHGlobal(hgblResourceData, TRUE, &ipStream))) goto Return; FreeData: // couldn't create stream; free the memory GlobalFree(hgblResourceData); Return: // no need to unlock or free the resource return ipStream; }
现在我们有了一个指向图像数据的IStream指针,我们可以使用WIC加载该图像了。此过程中的一个重要步骤是使用 WICConvertBitmapSource来确保图像采用32bpp格式,适合直接转换为DIB。此方法假定输入图像为PNG格式;对于启动屏幕,这是一个绝佳的选择,因为它允许使用alpha通道以及对源图像进行无损压缩。(为使初始屏幕图像尽可能小,我强烈建议使用PNGOUT压缩实用程序。)
// Loads a PNG image from the specified stream (using Windows Imaging Component). IWICBitmapSource * LoadBitmapFromStream(IStream * ipImageStream) { // initialize return value IWICBitmapSource * ipBitmap = NULL; // load WIC's PNG decoder IWICBitmapDecoder * ipDecoder = NULL; if (FAILED(CoCreateInstance(CLSID_WICPngDecoder, NULL, CLSCTX_INPROC_SERVER, __uuidof(ipDecoder), reinterpret_cast<void**>(&ipDecoder)))) goto Return; // load the PNG if (FAILED(ipDecoder->Initialize(ipImageStream, WICDecodeMetadataCacheOnLoad))) goto ReleaseDecoder; // check for the presence of the first frame in the bitmap UINT nFrameCount = 0; if (FAILED(ipDecoder->GetFrameCount(&nFrameCount)) || nFrameCount != 1) goto ReleaseDecoder; // load the first frame (i.e., the image) IWICBitmapFrameDecode * ipFrame = NULL; if (FAILED(ipDecoder->GetFrame(0, &ipFrame))) goto ReleaseDecoder; // convert the image to 32bpp BGRA format with pre-multiplied alpha // (it may not be stored in that format natively in the PNG resource, // but we need this format to create the DIB to use on-screen) WICConvertBitmapSource(GUID_WICPixelFormat32bppPBGRA, ipFrame, &ipBitmap); ipFrame->Release(); ReleaseDecoder: ipDecoder->Release(); Return: return ipBitmap; }
接下来是使用CreateDIBSection分配可直接写入的DIB。通过使用正确的值设置BITMAPINFO结构,DIB的格式将与WIC加载的32bpp BGRA图像相同,并且可以将像素直接从WIC位图复制到DIB。
//Creates a 32-bit DIB from the specified WIC bitmap. HBITMAP CreateHBITMAP(IWICBitmapSource * ipBitmap) { // initialize return value HBITMAP hbmp = NULL; // get image attributes and check for valid image UINT width = 0; UINT height = 0; if (FAILED(ipBitmap->GetSize(&width, &height)) || width == 0 || height == 0) goto Return; // prepare structure giving bitmap information (negative height indicates a top-down DIB) 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; // create a DIB section that can hold the image void * pvImageBits = NULL; HDC hdcScreen = GetDC(NULL); hbmp = CreateDIBSection(hdcScreen, &bminfo, DIB_RGB_COLORS, &pvImageBits, NULL, 0); ReleaseDC(NULL, hdcScreen); if (hbmp == NULL) goto Return; // extract the image into the HBITMAP const UINT cbStride = width * 4; const UINT cbImage = cbStride * height; if (FAILED(ipBitmap->CopyPixels(NULL, cbStride, cbImage, static_cast<BYTE *>(pvImageBits)))) { // couldn't extract image; delete HBITMAP DeleteObject(hbmp); hbmp = NULL; } Return: return hbmp; }
最后,可以将这三个函数放在一起以从EXE的资源加载PNG图像并将其转换为HBITMAP:
// Loads the PNG containing the splash image into a HBITMAP. HBITMAP LoadSplashImage() { HBITMAP hbmpSplash = NULL; // load the PNG image data into a stream IStream * ipImageStream = CreateStreamOnResource(MAKEINTRESOURCE(IDI_SPLASHIMAGE), _T("PNG")); if (ipImageStream == NULL) goto Return; // load the bitmap with WIC IWICBitmapSource * ipBitmap = LoadBitmapFromStream(ipImageStream); if (ipBitmap == NULL) goto ReleaseStream; // create a HBITMAP containing the image hbmpSplash = CreateHBITMAP(ipBitmap); ipBitmap->Release(); ReleaseStream: ipImageStream->Release(); Return: return hbmpSplash; }