如何在透明背景上绘制HCURSOR并避免黑色背景填充?
问题
需求:在填充透明色的表面上绘制HCURSOR,仅显示光标本身,不填充黑色背景;计划通过逐像素检查颜色来跳过透明区域。
现有基础代码:
CURSORINFO cursor = { sizeof( cursor ) }; if( GetCursorInfo( &cursor ) && cursor.flags == CURSOR_SHOWING ) ...
尝试使用DrawIcon()和DrawIconEx()函数,但绘制时会出现黑色背景。具体实现代码如下:
CURSORINFO cursor = { sizeof( cursor ) }; if( GetCursorInfo( &cursor ) && cursor.flags == CURSOR_SHOWING ) { ICONINFO info = { sizeof( info ) }; if( GetIconInfo( cursor.hCursor, &info ) ) { HWND hWnd = GetDesktopWindow(); HDC hDC = GetWindowDC( hWnd ); HDC hdcMem = CreateCompatibleDC( hDC ); BITMAP bmpCursor = { 0 }; GetObject( info.hbmColor ? info.hbmColor : info.hbmMask, sizeof( bmpCursor ), &bmpCursor ); HBITMAP hBitmap = CreateCompatibleBitmap( hdcMem, bmpCursor.bmWidth, bmpCursor.bmHeight ); auto original = SelectObject( hdcMem, hBitmap ); RECT br; br.left = br.top = 0; br.right = bmpCursor.bmWidth; br.bottom = bmpCursor.bmHeight; HBRUSH hBrush = CreateSolidBrush( RGB( 1, 1, 1 ) ); FillRect( hdcMem, &br, hBrush ); DeleteObject( hBrush ); DrawIconEx( hdcMem, 0, 0, cursor.hCursor, 0, 0, 0, nullptr, DI_DEFAULTSIZE | DI_NORMAL ); QImage img( bmpCursor.bmWidth, bmpCursor.bmHeight, QImage::Format_ARGB32 ); img.fill( Qt::transparent ); std::set< COLORREF > s; for( int x = 0; x < bmpCursor.bmWidth; ++x ) { for( int y = 0; y < bmpCursor.bmHeight; ++y ) { COLORREF c = GetPixel( hdcMem, x, y ); s.insert( c ); if( c != RGB( 1, 1, 1 ) ) { auto qc = QColor( QRgba64::fromArgb32( c ) ); qc.setAlpha( 255 ); img.setPixelColor( x, y, qc ); } } } cursorPos = r.intersects( { QPoint( cursor.ptScreenPos.x - info.xHotspot, cursor.ptScreenPos.y - info.yHotspot ), QSize( bmpCursor.bmWidth, bmpCursor.bmHeight ) } ) ? QPoint( cursor.ptScreenPos.x - info.xHotspot - r.x(), cursor.ptScreenPos.y - info.yHotspot - r.y() ) : QPoint( -1, -1 ); w = cursorPos.x() != -1 ? bmpCursor.bmWidth : 0; h = cursorPos.y() != -1 ? bmpCursor.bmHeight : 0; cursorImage = img.copy(); if( info.hbmMask ) DeleteObject( info.hbmMask ); if( info.hbmColor ) DeleteObject( info.hbmColor ); SelectObject( hdcMem, original ); DeleteDC( hdcMem ); DeleteObject( hBitmap ); } if( cursor.hCursor ) DeleteObject( cursor.hCursor ); }
但发现std::set<COLORREF> s中只有0和FFFFFF值,DrawIconEx将所有RGB(1,1,1)像素替换成了0,请问如何避免这种情况?
解决方案
问题根源
DrawIconEx会利用光标自带的掩码(mask)处理透明区域,默认将掩码对应的区域绘制为黑色(RGB(0,0,0)),直接覆盖了你预先填充的RGB(1,1,1)背景,这就是集合中只有0和FFFFFF的原因。
可行解决方法
使用带Alpha通道的DIB位图
放弃CreateCompatibleBitmap,创建支持Alpha通道的设备无关位图(DIB),这样绘制光标时能直接保留透明信息,无需手动替换颜色:BITMAPINFO bmi = { 0 }; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = bmpCursor.bmWidth; bmi.bmiHeader.biHeight = -bmpCursor.bmHeight; // 以顶部为坐标原点 bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biSizeImage = bmpCursor.bmWidth * bmpCursor.bmHeight * 4; void* pBits = nullptr; HBITMAP hBitmap = CreateDIBSection(hdcMem, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0);利用光标掩码判断透明区域
从ICONINFO获取的hbmMask是单色位图,可直接用它判断透明区域,无需依赖自定义背景色:// 创建掩码位图的DC HDC hdcMask = CreateCompatibleDC(hdcMem); HBITMAP oldMask = (HBITMAP)SelectObject(hdcMask, info.hbmMask); for( int x = 0; x < bmpCursor.bmWidth; ++x ) { for( int y = 0; y < bmpCursor.bmHeight; ++y ) { COLORREF c = GetPixel(hdcMem, x, y); // 掩码中白色对应不透明区域,黑色对应透明区域 COLORREF maskColor = GetPixel(hdcMask, x, y); if (maskColor == RGB(255,255,255)) { auto qc = QColor(QRgba64::fromArgb32(c)); qc.setAlpha(255); img.setPixelColor(x, y, qc); } // 透明区域保持QImage初始的透明状态即可 } } SelectObject(hdcMask, oldMask); DeleteDC(hdcMask);修复代码错误
GetCursorInfo返回的cursor.hCursor属于系统资源,不要调用DeleteObject销毁,直接删除代码中if( cursor.hCursor ) DeleteObject( cursor.hCursor );这一行。
内容的提问来源于stack exchange,提问作者Igor Mironchik
相关产品推荐
相关产品推荐

