将 HICON / HCURSOR 复制到字节数组中

Posted

技术标签:

【中文标题】将 HICON / HCURSOR 复制到字节数组中【英文标题】:Copy HICON / HCURSOR in to Byte Array 【发布时间】:2019-08-28 09:07:39 【问题描述】:

有什么方法可以将HICONHCURSOR 转换为字节数组,我一直在谷歌上搜索我没有找到一个通用的解决方案,下面我尝试将HICON 颜色和掩码BITMAP 转换为字节数组并通过套接字发送它并使用CreateIconIndirect API 创建我的图标,但如果我能够直接发送HICON,而不是做所有这些事情会很好。

int ProcessMouse()


   BYTE m_hbmMaskBits[70000];
   BYTE m_hbmColorBits[70000];

   CURSORINFO CursorInfo;
   CursorInfo.cbSize = sizeof(CursorInfo);
   GetCursorInfo(&CursorInfo);

   ICONINFO iconInfo;
   if (!GetIconInfo(CursorInfo.hCursor, &iconInfo)) 
     
       MessageBox(NULL, _T("CreateCursor Failed"),_T("message"),MB_OK|MB_SYSTEMMODAL);          
   
            bool isColorShape   = (iconInfo.hbmColor != NULL);
            bool isMaskShape    = (iconInfo.hbmMask != NULL);

            LONG cbSize = 0; int nWidth = 0; int nHeight = 0; int actualHeight = 0; int bmPlanes = 0;
            int bmBitsPixel = 0; int xHotspot = 0; int yHotspot = 0; int widthBytes = 0;
            // Return width,height,actualheight,bmplanes,bmbitspixel,hotsopt of cursor.
            if(!CopyIconInfo(   CursorInfo.hCursor,
                                        nWidth,
                                        nHeight,
                                        actualHeight,
                                        bmPlanes,
                                        bmBitsPixel,
                                        xHotspot,
                                        yHotspot,
                                        widthBytes  ))
                                           
                return 0;
                   

            std::vector<BYTE> bColor;
            std::vector<BYTE> bMask;

            int sz_hbmColor         = 0;
            int sz_hbmMask          = 0;        
            _tempWidth              = nWidth;
            _tempHeight             = nHeight;

            //If HCURSOR have both color and mask go with regular approach.
            if(isColorShape) 
            
                                   //Convert iconInfo.hbmColor HBITMAP to Byte array.
                bColor              = HBIMAPtoBYTE(iconInfo.hbmColor,sz_hbmColor);  
                                   //Convert iconInfo.hbmMask HBITMAP to Byte array.            
                bMask               = HBIMAPtoBYTE(iconInfo.hbmMask,sz_hbmMask);
            
            // If HCURSOR have only mask data go with new approach(split mask bitmap to color and mask).
            else if(isMaskShape) 
            
                std::vector<BYTE> bSrcBitmap;
                int sz_hbmBitmap    = 0;    
                                   //Convert iconInfo.hbmMask HBITMAP to Byte array.        
                bSrcBitmap          = HBIMAPtoBYTE(iconInfo.hbmMask,sz_hbmBitmap);
                sz_hbmColor         = sz_hbmBitmap/2;
                sz_hbmMask          = sz_hbmBitmap/2;

                bMask.resize(bMask.size() + sz_hbmBitmap/2);
                memcpy(&bMask[bSrcBitmap.size() - sz_hbmBitmap], &bSrcBitmap[0], sz_hbmBitmap/2 * sizeof(BYTE));

                bColor.resize(bColor.size() + sz_hbmBitmap/2);
                memcpy(&bColor[bSrcBitmap.size() - sz_hbmBitmap], &bSrcBitmap[sz_hbmBitmap/2], sz_hbmBitmap/2 * sizeof(BYTE));

                //Clear at end.
                bSrcBitmap.clear();

            

            try
            err = memcpy_s((m_hbmMaskBits), sz_hbmMask, &(bMask[0]), sz_hbmMask );
            err = memcpy_s((m_hbmColorBits),sz_hbmColor,&(bColor[0]),sz_hbmColor);

            //Clear at end.
            bMask.clear();
            bColor.clear();

            return 1;

        catch(...) 
            if(err)                    
                MessageBox(NULL, _T("memcopy failed at mask or color copy"),_T("message"),MB_OK|MB_SYSTEMMODAL);    
            
        

我尝试了以下方式,但它不支持少数单色光标。

                PICTDESC pd = sizeof(pd), PICTYPE_ICON;
                pd.icon.hicon = CursorInfo.hCursor;
                CComPtr<IPicture> pPict = NULL;
                CComPtr<IStream>  pStrm = NULL;
                BOOL res = FALSE;

                res = SUCCEEDED( ::CreateStreamOnHGlobal(NULL, TRUE, &pStrm) );
                res = SUCCEEDED( ::OleCreatePictureIndirect(&pd, IID_IPicture, TRUE, (void**)&pPict) );
                res = SUCCEEDED( pPict->SaveAsFile( pStrm, TRUE, &cbSize ) );

                if( res )
                
                    // rewind stream to the beginning
                    LARGE_INTEGER li = 0;
                    pStrm->Seek(li, STREAM_SEEK_SET, NULL);

                    // write to file
                    DWORD dwWritten = 0, dwRead = 0, dwDone = 0;
                    while( dwDone < cbSize )
                    
                        if( SUCCEEDED(pStrm->Read(bCursorBuff, sizeof(bCursorBuff), &dwRead)) )
                        
                            dwDone += dwRead;
                        
                    
                    _ASSERTE(dwDone == cbSize);
                
                //End of Cursor image
                pStrm.Release();
                pPict.Release();            

【问题讨论】:

【参考方案1】:

HICON 和 HCURSOR 是系统句柄,因此它们只能在当前机器上工作。

通过网络只能发送实际数据(位图字节)。然后该机器可以为其创建自己的句柄。

使用 HBITMAP 字节是正确的方法。你可以在这里找到一些细节: How to convert HICON to HBITMAP in VC++?

您可以使用GetDIBits() 获取原始 HBITMAP 位。更多信息:C++/Win32: How to get the alpha channel from an HBITMAP?

【讨论】:

我尝试过这种方式,如果我将 HICON 复制到 HBITMAP,它会复制图标图像,其余部分将是黑色,我们无法在 HBITMAP 中制作蒙版。 这是因为您需要对图像进行操作以提取其数据。没有可用的掩码,您需要原始 RGB 字节(实际图像)。 是的,你是对的,但是在从位图中读取实际图像的 RGB 值时,它也会读取 RGB(0,0,0) -> 黑色,想象一下,如果我从位图中读取实际图像的 RGB 值并排除黑色这不是一般的权利。 使用我回答中的 GetDIBits() 函数。需要包含透明度信息,否则默认为黑色。 请阅读我回答中的所有链接。您当前的方法提供了无法使用的单独颜色和遮罩。【参考方案2】:

以下代码仅适用于彩色光标,单色光标用于 将 16bpp 位图转换为 32bpp 位图并使用相同的代码。

    bool saveToMemory(HICON hIcon, BYTE* buffer, DWORD& nSize)
    
        if (hIcon == 0)
            return FALSE;

        int * pImageOffset;
        int nNumIcons = 1;
        nSize = 0;
        // copy iconheader first of all
        ICONHEADER iconheader;

        // Setup the icon header
        iconheader.idReserved = 0; // Must be 0
        iconheader.idType = 1; // Type 1 = ICON (type 2 = CURSOR)
        iconheader.idCount = nNumIcons; // number of ICONDIRs

        // save to memory
        memcpy(buffer, &iconheader, sizeof(iconheader));
        nSize += sizeof(iconheader); // update

        //
        // Leave space for the IconDir entries
        //
        nSize += sizeof(ICONDIR);

        pImageOffset = (int *)malloc(nNumIcons * sizeof(int));

        ICONINFO iconInfo;
        BITMAP bmpColor, bmpMask;

        GetIconBitmapInfo(hIcon, &iconInfo, &bmpColor, &bmpMask);

        // record the file-offset of the icon image for when we write the icon directories
        pImageOffset[0] = nSize;

        // bitmapinfoheader + colortable
        //WriteIconImageHeader(hFile, &bmpColor, &bmpMask);
        BITMAPINFOHEADER biHeader;
        UINT nImageBytes;

        // calculate how much space the COLOR and MASK bitmaps take
        nImageBytes = NumBitmapBytes(&bmpColor) + NumBitmapBytes(&bmpMask);

        // write the ICONIMAGE to disk (first the BITMAPINFOHEADER)
        ZeroMemory(&biHeader, sizeof(biHeader));

        // Fill in only those fields that are necessary
        biHeader.biSize = sizeof(biHeader);
        biHeader.biWidth = bmpColor.bmWidth;
        biHeader.biHeight = bmpColor.bmHeight * 2; // height of color+mono  
        biHeader.biPlanes = bmpColor.bmPlanes;
        biHeader.biBitCount = bmpColor.bmBitsPixel;
        biHeader.biSizeImage = nImageBytes;

        // write the BITMAPINFOHEADER
        //WriteFile(hFile, &biHeader, sizeof(biHeader), &nWritten, 0);
        memcpy(&buffer[nSize], &biHeader, sizeof(biHeader));
        nSize += sizeof(biHeader);

        // save color and mask bitmaps
        saveIconData(buffer, nSize, iconInfo.hbmColor);
        saveIconData(buffer, nSize, iconInfo.hbmMask);

        DeleteObject(iconInfo.hbmColor);
        DeleteObject(iconInfo.hbmMask);

        //
        // Lastly, save the icon directories.
        //

        DWORD size = saveIconDirectoryEntry(buffer, sizeof(ICONHEADER), pImageOffset[0], hIcon);

        free(pImageOffset);


        return TRUE;
    


    //
// Return the number of BYTES the bitmap will take ON DISK
//
static UINT NumBitmapBytes(BITMAP *pBitmap)

    int nWidthBytes = pBitmap->bmWidthBytes;

    // bitmap scanlines MUST be a multiple of 4 bytes when stored
    // inside a bitmap resource, so round up if necessary
    if (nWidthBytes & 3)
        nWidthBytes = (nWidthBytes + 4) & ~3;

    return nWidthBytes * pBitmap->bmHeight;


    // same as WriteIconData but save to memory
static UINT saveIconData(BYTE* buffer, DWORD& nSize, HBITMAP hBitmap)

    BITMAP bmp;
    int i;
    BYTE * pIconData;

    UINT nBitmapBytes;
    DWORD nWritten = 0;

    GetObject(hBitmap, sizeof(BITMAP), &bmp);

    nBitmapBytes = NumBitmapBytes(&bmp);

    pIconData = (BYTE *)malloc(nBitmapBytes);

    GetBitmapBits(hBitmap, nBitmapBytes, pIconData);

    // bitmaps are stored inverted (vertically) when on disk..
    // so write out each line in turn, starting at the bottom + working
    // towards the top of the bitmap. Also, the bitmaps are stored in packed
    // in memory - scanlines are NOT 32bit aligned, just 1-after-the-other
    for (i = bmp.bmHeight - 1; i >= 0; i--) 
    
        // Write the bitmap scanline
        // save to memory
        memcpy(&buffer[nSize], pIconData + (i * bmp.bmWidthBytes), bmp.bmWidthBytes);
        nSize += bmp.bmWidthBytes;
        nWritten += bmp.bmWidthBytes;       
    

    free(pIconData);

    return nWritten;



    //
// same as WriteIconDirectoryEntry but save to memory
//
static UINT saveIconDirectoryEntry(BYTE* buffer, DWORD pos, int imageOffset, HICON hIcon)

    ICONINFO iconInfo;
    ICONDIR iconDir;

    BITMAP bmpColor;
    BITMAP bmpMask;

    DWORD nWritten = 0;
    UINT nColorCount;
    UINT nImageBytes;

    GetIconBitmapInfo(hIcon, &iconInfo, &bmpColor, &bmpMask);

    nImageBytes = NumBitmapBytes(&bmpColor) + NumBitmapBytes(&bmpMask);

    if (bmpColor.bmBitsPixel >= 8)
        nColorCount = 0;
    else
        nColorCount = 1 << (bmpColor.bmBitsPixel * bmpColor.bmPlanes);

    // Create the ICONDIR structure
    iconDir.bWidth = (BYTE)bmpColor.bmWidth;
    iconDir.bHeight = (BYTE)bmpColor.bmHeight;
    iconDir.bColorCount = nColorCount;
    iconDir.bReserved = 0;
    iconDir.wPlanes = bmpColor.bmPlanes;
    iconDir.wBitCount = bmpColor.bmBitsPixel;
    iconDir.dwBytesInRes = sizeof(BITMAPINFOHEADER) + nImageBytes;
    iconDir.dwImageOffset = imageOffset;

    // save to memory
    memcpy(&buffer[pos], &iconDir, sizeof(iconDir));
    nWritten += sizeof(iconDir);

    // Free resources
    DeleteObject(iconInfo.hbmColor);
    DeleteObject(iconInfo.hbmMask);

    return nWritten;

【讨论】:

【参考方案3】:

我可以通过两次调用GetDIBits() 来做到这一点,一次是获取光标图像的实际细节,另一次是获取像素。

您可以将此代码应用于颜色和蒙版,但请注意,它仅返回 32x32px 光标,也仅返回第一帧,即使为其他内容配置了大小。

var windowDeviceContext = User32.GetWindowDC(IntPtr.Zero);

//Initialize the bitmap header and calculate its size.
var maskHeader = new BitmapInfoHeader();
maskHeader.Size = (uint) Marshal.SizeOf(maskHeader);

//Gets the image details.
Gdi32.GetDIBits(windowDeviceContext, iconInfo.Mask, 0, 0, null, ref maskHeader, DibColorModes.RgbColors);

//If there's any data, get it.
if (maskHeader.Height != 0)

    //To prevent the cursor image from being inverted.
    maskHeader.Height *= -1;

    var maskBuffer = new byte[maskHeader.SizeImage];

    Gdi32.GetDIBits(windowDeviceContext, iconInfo.Mask, 0, (uint) maskHeader.Height, maskBuffer, ref maskHeader, DibColorModes.RgbColors);

它是 C#,但很容易转换为您选择的语言。

【讨论】:

以上是关于将 HICON / HCURSOR 复制到字节数组中的主要内容,如果未能解决你的问题,请参考以下文章

从 C++ 中的字节数组创建 HICON?

如何透明地将 HICON 从 GDI 复制到 GDI+?

03. 资源

MFC一些必要的名词

字节数组字符串到字节数组? [复制]

将整个位图数据复制到字节数组