要实现这个功能,我们首先需要理解Icon的格式,我们可以看到Icon的结构如下:
typedef
struct _ICONINFO {
BOOL fIcon;
DWORD xHotspot;
DWORD yHotspot;
HBITMAP hbmMask;
HBITMAP hbmColor;
} ICONINFO;
typedef ICONINFO *PICONINFO;
BOOL fIcon;
DWORD xHotspot;
DWORD yHotspot;
HBITMAP hbmMask;
HBITMAP hbmColor;
} ICONINFO;
typedef ICONINFO *PICONINFO;
从上面我们可以看到Icon和cursor的结构基本一样,主要都包括一幅mask位图和一幅color位图
如下一个8个像素的红色小图标:
HBITMAP IconToBitmap(HICON hIcon, SIZE * pTargetSize = NULL)
{
ICONINFO info = { 0 };
if (hIcon == NULL
|| ! GetIconInfo(hIcon, & info)
|| ! info.fIcon)
{
return NULL;
}
INT nWidth = 0 ;
INT nHeight = 0 ;
if (pTargetSize != NULL)
{
nWidth = pTargetSize -> cx;
nHeight = pTargetSize -> cy;
}
else
{
if (info.hbmColor != NULL)
{
BITMAP bmp = { 0 };
GetObject(info.hbmColor, sizeof (bmp), & bmp);
nWidth = bmp.bmWidth;
nHeight = bmp.bmHeight;
}
}
if
(nWidth
<=
0
|| nHeight <= 0 )
{
return NULL;
}
INT nPixelCount = nWidth * nHeight;
HDC dc = GetDC(NULL);
INT * pData = NULL;
HDC dcMem = NULL;
HBITMAP hBmpOld = NULL;
bool * pOpaque = NULL;
HBITMAP dib = NULL;
BOOL bSuccess = FALSE;
do
{
BITMAPINFOHEADER bi = { 0 };
bi.biSize = sizeof (BITMAPINFOHEADER);
bi.biWidth = nWidth;
bi.biHeight = - nHeight;
bi.biPlanes = 1 ;
bi.biBitCount = 32 ;
bi.biCompression = BI_RGB;
dib = CreateDIBSection(dc, (BITMAPINFO * ) & bi, DIB_RGB_COLORS, (VOID ** ) & pData, NULL, 0 );
if (dib == NULL) break ;
memset(pData, 0 , nPixelCount * 4 );
dcMem = CreateCompatibleDC(dc);
if (dcMem == NULL) break ;
hBmpOld = (HBITMAP)SelectObject(dcMem, dib);
::DrawIconEx(dcMem, 0 , 0 , hIcon, nWidth, nHeight, 0 , NULL, DI_MASK);
pOpaque = new (std::nothrow) bool [nPixelCount];
if (pOpaque == NULL) break ;
for (INT i = 0 ; i < nPixelCount; ++ i)
{
pOpaque[i] = ! pData[i];
}
memset(pData, 0 , nPixelCount * 4 );
::DrawIconEx(dcMem, 0 , 0 , hIcon, nWidth, nHeight, 0 , NULL, DI_NORMAL);
BOOL bPixelHasAlpha = FALSE;
UINT * pPixel = (UINT * )pData;
for (INT i = 0 ; i < nPixelCount; ++ i, ++ pPixel)
{
if (( * pPixel & 0xff000000 ) != 0 )
{
bPixelHasAlpha = TRUE;
break ;
}
}
if ( ! bPixelHasAlpha)
{
pPixel = (UINT * )pData;
for (INT i = 0 ;i < nPixelCount; ++ i, ++ pPixel)
{
if (pOpaque[i])
{
* pPixel |= 0xFF000000 ;
}
else
{
* pPixel &= 0x00FFFFFF ;
}
}
}
bSuccess = TRUE;
} while (FALSE);
if (pOpaque != NULL)
{
delete []pOpaque;
pOpaque = NULL;
}
if (dcMem != NULL)
{
SelectObject(dcMem, hBmpOld);
DeleteDC(dcMem);
}
ReleaseDC(NULL, dc);
if ( ! bSuccess)
{
if (dib != NULL)
{
DeleteObject(dib);
dib = NULL;
}
}
return dib;
}
另外感慨Webkit是个宝库, 我们的Icon转Bitmap代码实际上可以参考这里:
它的Mask位图如下:
如果我们把Mask位图画出来,我们会看到周围1的区域都是白色的,中间0的区域是黑色的
它的color位图如下:
如果我们把color位图画出来,我们会看到除了中间区域是红色的,周围0的区域都是黑色的。
思考将Icon画到目标位图上时,他们是如何最终合成的?
实际很简单,就是先和Mask位图做与(AND)运算,然后再与Color位图做异或(XOR)运算: AND运算的结果是除了中间区域变成黑色(0),目标位图的其他区域都保持不变;XOR 运算的结果是周围区域只有和0不一样 (color位图)才会得到1 (也就是原来是1则保持), 中间区域因为前面经过mask运算后都是0,所以中间R部分XOR后也会保留。
理解了Icon格式,我们要将Icon转Bitmap就好办了, 我们只要将Color位图考出来,然后位图里将mask部分是1的部分的Alpha通道改成0就可以了。
这里要 注意的是有些icon 的color位图本身就是带Alpha通道的,这样我们就实际上用不到Mask位图了,也不用再去改Alpha通道了。
另外对于黑白单色Icon( 比如黑白光标), 我们很多时候会发现它的color位图是空的, 这种情况下所有的数据实际上都存到了Mask位图里,这时的Mask位图高度是Icon高度的2倍,上半部分是mask部分,下班部分保存了color位图部分。
最后简单贴下代码:
HBITMAP IconToBitmap(HICON hIcon, SIZE * pTargetSize = NULL)
{
ICONINFO info = { 0 };
if (hIcon == NULL
|| ! GetIconInfo(hIcon, & info)
|| ! info.fIcon)
{
return NULL;
}
INT nWidth = 0 ;
INT nHeight = 0 ;
if (pTargetSize != NULL)
{
nWidth = pTargetSize -> cx;
nHeight = pTargetSize -> cy;
}
else
{
if (info.hbmColor != NULL)
{
BITMAP bmp = { 0 };
GetObject(info.hbmColor, sizeof (bmp), & bmp);
nWidth = bmp.bmWidth;
nHeight = bmp.bmHeight;
}
}
if(info.hbmColor != NULL)
{
DeleteObject(info.hbmColor);
info.hbmColor = NULL;
}
if(info.hbmMask != NULL)
{
DeleteObject(info.hbmMask);
info.hbmMask = NULL;
}
|| nHeight <= 0 )
{
return NULL;
}
INT nPixelCount = nWidth * nHeight;
HDC dc = GetDC(NULL);
INT * pData = NULL;
HDC dcMem = NULL;
HBITMAP hBmpOld = NULL;
bool * pOpaque = NULL;
HBITMAP dib = NULL;
BOOL bSuccess = FALSE;
do
{
BITMAPINFOHEADER bi = { 0 };
bi.biSize = sizeof (BITMAPINFOHEADER);
bi.biWidth = nWidth;
bi.biHeight = - nHeight;
bi.biPlanes = 1 ;
bi.biBitCount = 32 ;
bi.biCompression = BI_RGB;
dib = CreateDIBSection(dc, (BITMAPINFO * ) & bi, DIB_RGB_COLORS, (VOID ** ) & pData, NULL, 0 );
if (dib == NULL) break ;
memset(pData, 0 , nPixelCount * 4 );
dcMem = CreateCompatibleDC(dc);
if (dcMem == NULL) break ;
hBmpOld = (HBITMAP)SelectObject(dcMem, dib);
::DrawIconEx(dcMem, 0 , 0 , hIcon, nWidth, nHeight, 0 , NULL, DI_MASK);
pOpaque = new (std::nothrow) bool [nPixelCount];
if (pOpaque == NULL) break ;
for (INT i = 0 ; i < nPixelCount; ++ i)
{
pOpaque[i] = ! pData[i];
}
memset(pData, 0 , nPixelCount * 4 );
::DrawIconEx(dcMem, 0 , 0 , hIcon, nWidth, nHeight, 0 , NULL, DI_NORMAL);
BOOL bPixelHasAlpha = FALSE;
UINT * pPixel = (UINT * )pData;
for (INT i = 0 ; i < nPixelCount; ++ i, ++ pPixel)
{
if (( * pPixel & 0xff000000 ) != 0 )
{
bPixelHasAlpha = TRUE;
break ;
}
}
if ( ! bPixelHasAlpha)
{
pPixel = (UINT * )pData;
for (INT i = 0 ;i < nPixelCount; ++ i, ++ pPixel)
{
if (pOpaque[i])
{
* pPixel |= 0xFF000000 ;
}
else
{
* pPixel &= 0x00FFFFFF ;
}
}
}
bSuccess = TRUE;
} while (FALSE);
if (pOpaque != NULL)
{
delete []pOpaque;
pOpaque = NULL;
}
if (dcMem != NULL)
{
SelectObject(dcMem, hBmpOld);
DeleteDC(dcMem);
}
ReleaseDC(NULL, dc);
if ( ! bSuccess)
{
if (dib != NULL)
{
DeleteObject(dib);
dib = NULL;
}
}
return dib;
}
另外感慨Webkit是个宝库, 我们的Icon转Bitmap代码实际上可以参考这里: