2

Is any way can we convert HICON or HCURSOR in to Byte array, I googled in all the way I didnt found a single generic solution, below I tried to convert HICON color and mask BITMAP to byte array and sending this through socket and creating my icon using CreateIconIndirect API but instead of doing all this stuff if I can able to send a HICON directly that will be good.

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);    
            }
        }
}

I tried in below way but it doesn't support for few monochrome cursors.

                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();            
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Krish
  • 376
  • 3
  • 14

3 Answers3

2

HICON and HCURSOR are system handles, so they work only on the current machine.

Over network only the actual data can be sent (bitmap bytes). Then that machine can create its own handles for it.

Using the HBITMAP bytes is the correct approach. You can find some details here: How to convert HICON to HBITMAP in VC++?

You can get the raw HBITMAP bits using GetDIBits(). More information: C++/Win32: How to get the alpha channel from an HBITMAP?

Cosmin
  • 21,216
  • 5
  • 45
  • 60
  • I tried this way if I copy HICON in to HBITMAP it copy icon image and rest of part will be black color, We cant make mask in HBITMAP. – Krish Aug 28 '19 at 11:29
  • This is because you need to manipulate the image to extract its data. There is no masking available, you need the raw RGB bytes (the actual image). – Cosmin Aug 28 '19 at 11:43
  • Yes your right but while reading RGB values of actual image from bitmap it reads RGB(0,0,0) -> black color also, Imagine if I read RGB values of actual image from bitmap and exclude black color this is not a generic right. – Krish Aug 28 '19 at 11:51
  • Use the GetDIBits() function from my answer. You need to include the transparency information, otherwise it defaults to black. – Cosmin Aug 28 '19 at 11:59
  • Please read all the links in my answer. Your current approach gives separate color and mask which cannot be used. – Cosmin Aug 28 '19 at 12:08
  • Yes I didnt fallow this approach but I have my POC with your approach, From your answer using DrawIconEx to get icon in to bitmap added diFlags = DI_MASK and using this link "https://learn.microsoft.com/en-us/windows/win32/gdi/storing-an-image" to save BITMAP here they use GetDIBits() but getting white color now no black. .... im almost there pls what property i have to set to make transparent ? – Krish Aug 28 '19 at 12:22
  • There is no DrawIconEx in my answer and no mask. The transparency issue seems like a separate question. My answer is for converting HICON / HCURSOR to byte array. – Cosmin Aug 28 '19 at 13:12
  • Im talking about this "https://stackoverflow.com/questions/7375003/how-to-convert-hicon-to-hbitmap-in-vc" in the solution they are using DrawIconEx to take icon to bitmap – Krish Aug 28 '19 at 13:20
  • Thank you Cosmin your solution helps me a lot, I can fix this, by end I will post my answer here so that it helps to others. – Krish Aug 28 '19 at 13:23
0

Below Code works only for color cursor for monochrome cursor use to convert 16bpp bitmap to 32bpp bitmap and use same code its works.

    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;
}
Krish
  • 376
  • 3
  • 14
0

I was able to do so by calling GetDIBits() twice, once to get the actual details of the cursor images and another time to get the pixels.

You can apply this code for the color and mask, just be aware that it only returns 32x32px cursors, also only the first frame, even if the size is configured for something else.

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);
}

It's C#, but easily converted to your language of choice.

Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79