Visual Studio 2013 C ++中的位图透明度

我对这个感到困惑。 我使用在论坛上推荐的程序来将32位PNG转换为带有Alpha通道的32位位图。 我将这些添加到资源编辑器中,并通过工具箱将它们放到一个对话框中。 我已阅读(我认为是广泛的)位图透明度和Visual Studio中的限制。
令我费解的是,我通过Visual Studio资源编辑器将图片控件添加到我的对话框中。 例如,我有两个红色的球,一个在24位的位图,一个在32位的位图。 在Visual Studio的testing模式下,当使用资源编辑器打开.rc时,透明度看起来很好。 视觉工作室的测试模式

但是,当我以编程方式调用DialogBox时,我没有获得透明度。

DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc); 

DialogBox以编程方式调用时的对话框 当我点击Visual Studio中的testingbutton时,它必须调用例程DialogBox或类似的来显示位图。 当我把资源编辑器中的位图放到对话框中时,它显示透明度。 微软是做什么的,我没做什么?
我正在开发没有MFC,故意。 是这样的问题,只有在MFC可以对话框的加载透明度(我意识到它降低到CreateWindowEX)。 我意识到,各种bitblt方法也可以使用。 那是Visual Studio在幕后做的吗? 还审查了WM_CTLCOLORSTATIC等材料。你们都觉得呢? 有没有一些简单的方法来调用DialogBox并获得对话框上的透明的BMP? 或者我们都被迫使用MFC? 或者用户必须编写例程来擦除背景/绘画等

对我来说,看起来有点奇怪,没有透明的图像,对话框很容易。 需要一个非正方形的图像? 这是一个问题。 现在软件工程师必须添加大量的代码到对话callback或程序。 似乎更多的一个bug给我。

谢谢您,对于您的评论。

  #include <windows.h> #include <winuser.h> #include "resource.h" INT_PTR CALLBACK DialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; switch (message) { case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // Parse the menu selections: switch (wmId) { case IDOK: EndDialog(hwnd, LOWORD(wParam)); break; case IDCANCEL: EndDialog(hwnd, LOWORD(wParam)); return TRUE; } case WM_PAINT: break; case WM_DESTROY: EndDialog(hwnd, LOWORD(wParam)); break; } return FALSE; } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc); return 0; } 

Solutions Collecting From Web of "Visual Studio 2013 C ++中的位图透明度"

有两种可能的解决方案,既有优点也有缺点。

解决方案1通过简单地将一个应用程序清单文件添加到资源来修复OP所陈述的原始问题。 这个解决方案不需要编码。 该解决方案所实现的透明度并不完美,但自Windows XP以来,在所有Windows版本上都受支持。

解决方案2更先进,因为它创建了一个分层的子窗口 ,通过对话背景以及任何重叠的子控件提供图像的真实透明度。 缺点是,至少Windows 8是必需的,并且必须写出相当数量的非平凡的代码(但是你是幸运的,因为我已经为你做了这些;-))。

解决方案1 ​​ – 添加应用程序清单

只有在添加指定公共控件版本6.0.0.0应用程序清单时 ,本地静态控件才支持具有Alpha透明度的位图。 从屏幕截图中控件的“老派”看,我们可以看到你还没有这样的清单。

将以下片段保存到名为“manifest.xml”的文件中,并将其放入应用程序资源文件夹中。 在Visual Studio中,右键单击项目,进入“清单工具”>“输入和输出”>“附加清单文件”>输入“manifest.xml”的相对路径,不带引号。

 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> <application> <!-- Windows 10 --> <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> <!-- Windows 8.1 --> <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> <!-- Windows 8 --> <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> <!-- Windows 7 --> <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> <!-- Windows Vista --> <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> </application> </compatibility> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" /> </dependentAssembly> </dependency> </assembly> 

不需要进一步编码,只需在资源编辑器中为图片控件(实际上是一个静态控件)选择位图资源。

要使用的位图应该是32 bpp位图,自下而上的行顺序,非预乘alpha。 如果使用PixelFormer从PNG转换,请使用格式A8:R8:G8:B8(32 bpp),而不要在导出对话框中选中其他复选框。 如果使用XnView进行转换,只需保存为BMP,则默认情况下将使用此格式。

结果:

在这里输入图像说明

正如我们所看到的,我们只能得到“假”的透明度。 图像下方的任何其他控件将被裁剪在静态控件的边界处。

解决方案2 – 使用分层的子窗口

使用分层子窗口 (WS_EX_LAYERED扩展样式)可以实现真正的透明度。 这是从Windows 8以来的支持,但它需要一些编码。

我将所需的代码封装到可能从对话框的WM_INITDIALOG处理程序调用的函数SetLayeredWindowFromBitmapResource()中。 该函数以std::system_error异常抛出任何错误,所以你必须添加一个try / catch块来处理错误(这在下面的“使用”示例中进一步显示)。

 #include <system_error> /// Turn given window into a layered window and load a bitmap from given resource ID /// into it. /// The window will be resized to fit the bitmap. /// Bitmap must be 32bpp, top-down row order, premultiplied alpha. /// /// \note For child windows, this requires Win 8 or newer OS /// (and "supportedOS" element for Win 8 in application manifest) /// /// \exception Throws std::system_error in case of any error. void SetLayeredWindowFromBitmapResource( HWND hwnd, UINT bitmapResourceId, HINSTANCE hInstance = nullptr ) { // Enable "layered" mode for the child window. This enables full alpha channel // transparency. // GetWindowLong() won't reset the last error in case of success. // As we can't judge from the return value of GetWindowLong() alone if // the function was successful (0 may be returned even in case of // success), we must reset the last error to reliably detect errors. ::SetLastError( 0 ); DWORD exStyle = ::GetWindowLong( hwnd, GWL_EXSTYLE ); if( !exStyle ) { // NOTE: Call GetLastError() IMMEDIATELY when a function's return value // indicates failure and it is documented that said function supports // GetLastError(). // ANY other code (be it your own or library code) before the next line // must be avoided as it may invalidate the last error value. if( DWORD err = ::GetLastError() ) throw std::system_error( static_cast<int>(err), std::system_category(), "SetLayeredWindowFromBitmapResource: Could not get extended window style" ); } // SetWindowLong() won't reset the last error in case of success. // As we can't judge from the return value of GetWindowLong() alone if // the function was successful (0 may be returned even in case of // success), we must reset the last error to reliably detect errors. ::SetLastError( 0 ); if( !::SetWindowLong( hwnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED ) ) { if( DWORD err = ::GetLastError() ) throw std::system_error( static_cast<int>(err), std::system_category(), "SetLayeredWindowFromBitmapResource: Could not set extended window style" ); } // Use RAII ( https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization ) // to cleanup resources even in case of exceptions. // This greatly simplifies the code because now we don't have to manually cleanup the // resources at every location in the code where we throw an exception. struct Resources { HBITMAP hImage = nullptr; HGDIOBJ hOldImage = nullptr; HDC hMemDC = nullptr; // This destructor will be automatically called before the function // SetLayeredWindowFromBitmapResource() returns aswell as any locations // in the code where the "throw" keyword is used to throw an exception. ~Resources() { if( hMemDC ) { if( hOldImage ) ::SelectObject( hMemDC, hOldImage ); ::DeleteDC( hMemDC ); } if( hImage ) ::DeleteObject( hImage ); } } res; // Make it possible to use nullptr as an argument for the hInstance parameter of // this function. This means we will load the resources from the current executable // (instead of another DLL). if( ! hInstance ) hInstance = ::GetmoduleeHandle( nullptr ); // Load bitmap with alpha channel from resource. // Flag LR_CREATEDIBSECTION is required to create a device-independent bitmap that // preserves the alpha channel. res.hImage = reinterpret_cast<HBITMAP>(::LoadImage( hInstance, MAKEINTRESOURCE( bitmapResourceId ), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION )); if( !res.hImage ) { DWORD err = ::GetLastError(); throw std::system_error( static_cast<int>(err), std::system_category(), "SetLayeredWindowFromBitmapResource: Could not load bitmap resource" ); } // Get bitmap information (width, height, etc.) BITMAP imgInfo{ 0 }; if( !::GetObject( res.hImage, sizeof( imgInfo ), &imgInfo ) ) { DWORD err = ::GetLastError(); throw std::system_error( static_cast<int>(err), std::system_category(), "SetLayeredWindowFromBitmapResource: Could not get bitmap information" ); } if( imgInfo.bmBitsPixel != 32 || imgInfo.bmPlanes != 1 ) { // Use a constant error value here because this is our own error condition. // Of course GetLastError() wouldn't return anything useful in this case. DWORD err = ERROR_INVALID_DATA; throw std::system_error( err, std::system_category(), "SetLayeredWindowFromBitmapResource: bitmap must be 32 bpp, single plane" ); } // Create a memory DC that will be associated with the image. // UpdateLayeredWindow() can't use image directly, it must be in a memory DC. res.hMemDC = ::CreateCompatibleDC( nullptr ); if( !res.hMemDC ) { DWORD err = ::GetLastError(); throw std::system_error( static_cast<int>(err), std::system_category(), "SetLayeredWindowFromBitmapResource: Could not create memory DC" ); } res.hOldImage = ::SelectObject( res.hMemDC, res.hImage ); if( !res.hOldImage ) { DWORD err = ::GetLastError(); throw std::system_error( static_cast<int>(err), std::system_category(), "SetLayeredWindowFromBitmapResource: Could not select bitmap into memory DC" ); } // Assign the image to the child window, making it transparent. SIZE size{ imgInfo.bmWidth, imgInfo.bmHeight }; POINT ptSrc{ 0, 0 }; BLENDFUNCTION blend{ AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; if( !::UpdateLayeredWindow( hwnd, nullptr, nullptr, &size, res.hMemDC, &ptSrc, 0, &blend, ULW_ALPHA ) ) { DWORD err = ::GetLastError(); throw std::system_error( static_cast<int>(err), std::system_category(), "SetLayeredWindowFromBitmapResource: Could not update layered window" ); } // Destructor of res object will cleanup resources here! } 

用法:

该函数可能会在对话框过程的WM_INITDIALOG处理程序中调用,请参阅下面的示例。 该示例还显示了如何处理错误。

注意:我在这里调用MessageBoxA(),因为std::exception::what()返回一个const char* ,它显然是一个多字节(ANSI)编码的字符串,包含来自操作系统的本地化错误消息(使用VS2015或更新版本) 。

 #include <sstream> /// Dialog box procedure. INT_PTR CALLBACK TestDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) { UNREFERENCED_PARAMETER( lParam ); switch( message ) { case WM_INITDIALOG: { // This is the child window where we want to show the image (eg a static). if( HWND hwndImage = ::GetDlgItem( hDlg, IDC_IMAGE ) ){ try{ SetLayeredWindowFromBitmapResource( hwndImage, IDB_BITMAP1 ); } catch( std::system_error& e ){ std::ostringstream msg; msg << e.what() << std::endl << "Error code: " << e.code(); ::MessageBoxA( hDlg, msg.str().c_str(), L"Error", MB_ICONERROR ); } } return TRUE; } case WM_COMMAND: { if( LOWORD( wParam ) == IDOK || LOWORD( wParam ) == IDCANCEL ){ EndDialog( hDlg, LOWORD( wParam ) ); return TRUE; } break; } } return FALSE; } 

结果:

截图

陷阱:

应用程序必须具有至少指定Win 8兼容性的清单资源:

 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> <application> <!-- Windows 10 --> <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> <!-- Windows 8.1 --> <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> <!-- Windows 8 --> <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> </application> </compatibility> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" /> </dependentAssembly> </dependency> </assembly> 

要加载的图像必须是带预乘alpha通道的32 bpp自顶向下位图。

例如,一个普通的PNG可以使用PixelFormer转换成这种格式。 打开图像,然后选择文件>导出。 选择位图,格式A8:R8:G8:B8(32 bpp),预乘alpha,自顶向下行顺序。

建立在@ Zett42的答案,一个简单的编译指示,也可以指示编译器使用6 0 0 0控件,而不是使用清单文件。

 #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' " "version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")