How to save a icon?(create ico file)

Hi everbody,
          I have a handle of an icon(HICON) which i got using SHGetFileInfo. I want to save this handle as an Icon file(.ico). I am using following procedure:

int  SaveMyIcon( HICON hIcon)
{
  int       fh, i, iResult;
  UINT      uiSize;
  DWORD     dwSize;
  OFSTRUCT  of;
  LPSTR lpGMem;

    if (!hIcon)
      return FALSE;

    dwSize  = GlobalSize(hIcon);
    lpGMem  = (LPSTR)GlobalLock(hIcon);
    fh = OpenFile ("myicon.ico", &of, OF_WRITE | OF_CREATE);

    if (fh == -1)  // If NOT opened successfully.
    {
     MessageBox(NULL, "Unable to create file", NULL, MB_OK );
     return FALSE;
    }

    uiSize = _lwrite(fh, (LPSTR)lpGMem, (UINT)dwSize);
    _lclose(fh);

    if (uiSize == -1 || uiSize < (UINT)dwSize)
    {
       MessageBox(NULL, "Unable to read file", NULL, MB_OK );
      GlobalUnlock(hIcon);
       return FALSE;
    }
    else // Everything worked, return hGMem.
    {
      GlobalUnlock(hIcon);
      return TRUE;
    }
}


This procedure is not working. I am unable to create a ico file of the handle using the above procedure. How to save an icon file using the handle of an icon?

Thanks in advance.
nkraghuAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Daij-DjanCommented:
Use CreateFile not OpenFile
cast the handle to a byte* and write it to file?
WxWCommented:
Is an HICON a global memory handle as HGLOBAL ? Who ensures that locking the HICON into memory returns the icon bytes ? Who ensures that a HICON isn't a window handle that is used by Windows in some other way ?

Safer approach is to draw the icon in a DC using DrawIconEx and save the dc as a bitmap.
nkraghuAuthor Commented:
The GlobalSize/GlobalLock function is returning INVALID_HANDLE error code. That's why my above said proc was not working.

But when I use 'SetIcon' function with the same handle  to display the icon in a static control, it is displaying properly.

I don't know why the HICON handle returned by SHGetFileInfo is differeing in SetIcon and GlobalSize cases...

I tried Daij-Djan idea, but failed because of the reason mentioned above.
Exploring SQL Server 2016: Fundamentals

Learn the fundamentals of Microsoft SQL Server, a relational database management system that stores and retrieves data when requested by other software applications.

DanRollinsCommented:
What do you get if you call GetIconInfo?

How are you obtaining the HICON -- show the flags you use in the SHGetFileInfo  call.

I think that the technique you are using comes from old information that applies only to 32x32 icons:

     http://support.microsoft.com/default.aspx?scid=kb;EN-US;q104570

Note at the top that it applies to Win 3.1  !!!!

-- Dan
nkraghuAuthor Commented:
This is my SHGetFileInfo:

SHFILEINFO sfis;
SHGetFileInfo(m_Ext,FILE_ATTRIBUTE_NORMAL,
                       &sfis,
                       sizeof(sfis),
                       SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_SHELLICONSIZE|SHGFI_SMALLICON);

where m_Ext refers to the extension(.txt/.doc etc)
When I use SHGFI_ICONLOCATION flag, it is neither returning the filename in displayname param in sfis.

If I use GetIconInfo, I am getting mask and color bitmaps. Is that sufficient to write ico file using these raw bytes by creating ico format file? If so, how to do this?

DanRollinsCommented:
Creating an ICO file is a several step operation, including writing an ICONDIR record, and a series of ICONDIRENTRY records, and then the several ICONIMAGE structures.  It is covered here:

      Icons in Win32
      http://msdn.microsoft.com/library/en-us/dnwui/html/msdn_icons.asp

and it contains source code (see the file ICONS.C and the function WriteIconToICOFile() in that example).  I can't locate any simple Win32 API to write these files.

Maybe you don't need an icon???? You can get the HBITMAPs and there are several very simepl way to output these to a file.

-- Dan
nkraghuAuthor Commented:
Hi DanRollins,
                 I have tried that code. From GetIconInfo I just have hbmcolor bitmap and hbmmask bitmaps. I tried writing these two raw bitmap bits into the ico structure. But either visualstudio/ iconpro(msdn sample) is not displaying my icon properly.

                 I tried by writing 'one' icon image consisting of these two bitmaps(I used CreateBitmapInfoStruct). It's not displaying the icon properly.


Here's my snippet: If you could find any issues with this code kindly reply.

//The hIcon is the one which i got using SHGetFileInfo

BOOL WriteIconToICOFile( HICON hIcon, LPCTSTR szFileName )
{
    HANDLE          hFile;
    UINT        i;
    DWORD          dwBytesWritten;
      LPBYTE lpBits,lpBits2;              // memory pointer
      
      ICONINFO iconinfo;
      PBITMAPINFO pbi;
      ICONIMAGE icoim;
      int ret;

      ::GetIconInfo(hIcon,&iconinfo);


      
      PBITMAPINFO pBitmapInfo1=CreateBitmapInfoStruct(iconinfo.hbmColor);
      PBITMAPINFO pBitmapInfo2=CreateBitmapInfoStruct(iconinfo.hbmMask);


      HDC hdc=::GetDC(NULL);
      HDC memdc=CreateCompatibleDC(hdc);


      lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pBitmapInfo1->bmiHeader.biSizeImage);
      lpBits2 = (LPBYTE) GlobalAlloc(GMEM_FIXED, pBitmapInfo2->bmiHeader.biSizeImage);

      //HBITMAP hOld=(HBITMAP)::SelectObject(hdc,iconinfo.hbmColor);

      ret=GetDIBits(memdc,iconinfo.hbmColor,0,(WORD)pBitmapInfo1->bmiHeader.biHeight, lpBits, pBitmapInfo1,
                  DIB_RGB_COLORS);

      if(lpBits==NULL)
            return FALSE;


      ret=GetDIBits(memdc,iconinfo.hbmMask,0,(WORD)pBitmapInfo2->bmiHeader.biHeight, lpBits2, pBitmapInfo2,
                  DIB_RGB_COLORS);
      

      if(lpBits2==NULL)
            return FALSE;

      ///Fill IconImage
      icoim.icHeader.biBitCount=pBitmapInfo1->bmiHeader.biBitCount;
      icoim.icHeader.biSize=pBitmapInfo1->bmiHeader.biSize;
      icoim.icHeader.biWidth=pBitmapInfo1->bmiHeader.biWidth;
      icoim.icHeader.biHeight=pBitmapInfo1->bmiHeader.biHeight + pBitmapInfo2->bmiHeader.biHeight;
      icoim.icHeader.biPlanes=pBitmapInfo1->bmiHeader.biPlanes;      
      icoim.icHeader.biSizeImage=pBitmapInfo1->bmiHeader.biSizeImage;      
      
      icoim.icColors[1]=pBitmapInfo1->bmiColors[1];

      icoim.lpXOR = lpBits;
      icoim.lpAND = lpBits2;
      ///Fill IconImage

    // open the file
    if( (hFile = CreateFile( szFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL )) == INVALID_HANDLE_VALUE )
    {
        AfxMessageBox(  "Error Opening File for Writing" );
        return FALSE;
    }
    // Write the header
    if( ! WriteICOHeader( hFile, 1 ) )
    {
        AfxMessageBox(  "Error Writing ICO File" );
        CloseHandle( hFile );
        return FALSE;
    }
    // Write the ICONDIRENTRY's
    for( i=0; i<1; i++ )
    {
        ICONDIRENTRY    ide;

        // Convert internal format to ICONDIRENTRY
        ide.bWidth = 16;
        ide.bHeight = 16;
        ide.bReserved = 0;
        ide.wPlanes = pBitmapInfo1->bmiHeader.biPlanes;
        ide.wBitCount = pBitmapInfo1->bmiHeader.biBitCount;
        if( (ide.wPlanes * ide.wBitCount) >= 8 )
            ide.bColorCount = 0;
        else
            ide.bColorCount = 1 << (ide.wPlanes * ide.wBitCount);
            
        ide.dwBytesInRes = pBitmapInfo1->bmiHeader.biSize;
        ide.dwImageOffset = CalculateImageOffset( pBitmapInfo1, 0 );
        // Write the ICONDIRENTRY out to disk
        if( ! WriteFile( hFile, &ide, sizeof( ICONDIRENTRY ), &dwBytesWritten, NULL ) )
            return FALSE;
        // Did we write a full ICONDIRENTRY ?
        if( dwBytesWritten != sizeof( ICONDIRENTRY ) )
            return FALSE;
    }
    // Write the image bits for each image
      
    for( i=0; i<1; i++ )
    {
        /*DWORD dwTemp = pBitmapInfo1->bmiHeader.biSizeImage;

        // Set the sizeimage member to zero
        //pBitmapInfo1->bmiHeader.biSizeImage = 0;
        // Write the image bits to file
        if( ! WriteFile( hFile, lpBits, pBitmapInfo1->bmiHeader.biSize, &dwBytesWritten, NULL ) )
            return FALSE;
        if( dwBytesWritten != pBitmapInfo1->bmiHeader.biSize )
            return FALSE;
            //pBitmapInfo1->bmiHeader.biSizeImage = dwTemp;

            if( ! WriteFile( hFile, lpBits2, pBitmapInfo2->bmiHeader.biSize, &dwBytesWritten, NULL ) )
            return FALSE;
        if( dwBytesWritten != pBitmapInfo2->bmiHeader.biSize )
            return FALSE;
       
            // set it back
        pBitmapInfo2->bmiHeader.biSizeImage = dwTemp;*/

            if( ! WriteFile( hFile, &icoim, sizeof(ICONIMAGE), &dwBytesWritten, NULL ) )
            return FALSE;

            if( dwBytesWritten != sizeof(ICONIMAGE) )
            return FALSE;

    }

      // Free memory.
      
    CloseHandle( hFile );

      GlobalFree((HGLOBAL)lpBits);
      GlobalFree((HGLOBAL)lpBits2);

    return FALSE;
}

BOOL WriteICOHeader( HANDLE hFile, UINT nNumEntries )
{
    WORD    Output;
    DWORD      dwBytesWritten;

    // Write 'reserved' WORD
    Output = 0;
    if( ! WriteFile( hFile, &Output, sizeof( WORD ), &dwBytesWritten, NULL ) )
        return FALSE;
    // Did we write a WORD?
    if( dwBytesWritten != sizeof( WORD ) )
        return FALSE;
    // Write 'type' WORD (1)
    Output = 1;
    if( ! WriteFile( hFile, &Output, sizeof( WORD ), &dwBytesWritten, NULL ) )
        return FALSE;
    // Did we write a WORD?
    if( dwBytesWritten != sizeof( WORD ) )
        return FALSE;
    // Write Number of Entries
    Output = (WORD)nNumEntries;
    if( ! WriteFile( hFile, &Output, sizeof( WORD ), &dwBytesWritten, NULL ) )
        return FALSE;
    // Did we write a WORD?
    if( dwBytesWritten != sizeof( WORD ) )
        return FALSE;
    return TRUE;
}

DWORD CalculateImageOffset( PBITMAPINFO lpIR, UINT nIndex )
{
    DWORD      dwSize;
    UINT    i;

    // Calculate the ICO header size
    dwSize = 3 * sizeof(WORD);
    // Add the ICONDIRENTRY's
    dwSize += 1 * sizeof(ICONDIRENTRY);
    // Add the sizes of the previous images
    for(i=0;i<nIndex;i++)
        dwSize += lpIR->bmiHeader.biSize;
    // we're there - return the number
    return dwSize;
}


DanRollinsCommented:
>> It's not displaying the icon properly.

Please explain.  The above code looks like it saves an icon file.  It does not display anything.  

Can you open the resulting .ICO file with Visual Studio or can you read it with the LoadImage API function?

-- Dan
DanRollinsCommented:
I worked out a simpler technique to save an icon represented by an HICON to a .ICO file.

The basic technique is to
  1)  OleCreatePictureIndirect -- create an IPicture
  2)  CreateStreamOnHGlobal -- create a stream
  3)  IPicture::SaveAsFile        -- write the the tream in 'native' icon format
  4)  Then get the handle, and get a pointer to its data, and write it to a file.
  5)  Be sure to DestroyIcon after SHGetFileInfo to prevent a memory leak.

I verified that I get a small icon or regular one, depending upon settings in SHGetFileInfo.

I verified the .ICO file by Importing it into Visual Studio.  I assume that LoadImage would also work.

This was a good challenge.  It is surprising that I could find no complete, simple examples that used this.  Someone who knows more about IStreams could probably simplify this even further.

-- Dan
=-=--=-=-=-=-=-=-=-=-=-==-=-=-=--==-=-=-=--==-=-=--=-==-=--=
#include <afxctl.h>

void CD08Dlg::OnButton1()
{
      SHFILEINFO rSFI;
      memset(&rSFI,0, sizeof(rSFI) );
      SHGetFileInfo(".ZIP",FILE_ATTRIBUTE_NORMAL,
                  &rSFI,
                  sizeof(rSFI),
                  SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_SHELLICONSIZE|SHGFI_SMALLICON
      );

      HICON hIcon= rSFI.hIcon;

      LPPICTURE pPicture;
      PICTDESC  rPD;

      rPD.cbSizeofstruct= sizeof(PICTDESC);
      rPD.picType=        PICTYPE_ICON;
      rPD.icon.hicon=     hIcon;
      
      HRESULT  hr;
      IStream* pStream= 0;
      HGLOBAL  hMem=    0;

      hr= OleCreatePictureIndirect( &rPD, IID_IPicture, FALSE, (void**)&pPicture);

      hr= CreateStreamOnHGlobal(0, TRUE, &pStream );

      long lActual;
      hr= pPicture->SaveAsFile( pStream, TRUE, &lActual );
      hr= pPicture->Release();

      hr= GetHGlobalFromStream( pStream, &hMem );
      int nSize= GlobalSize( hMem );           // eyeball for debug (same as lActual)

      char szFileName[]="c:\\temp\\test2.ico";

      HANDLE hFile= CreateFile( szFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
      DWORD nActual= 0;

      LPSTR pMem= (LPSTR)GlobalLock( hMem );

      WriteFile( hFile, pMem, nSize, &nActual, 0 );
      CloseHandle( hFile );

      //-------------------------------- plug those leaks!
      GlobalUnlock( hMem );
      GlobalFree( hMem );
      DestroyIcon( hIcon );
}
nkraghuAuthor Commented:
HI DanRollins
           I meant to say that after saving icon with my proc, I was not able to open that icon. Anyway i tried your method and it worked :-)

           But now I am getting a icon with black background. It's not transparent. I came to saving icons because I was not able to save transparent gifs/jpgs. But with icons too,  it's the same case. Is there is any workaround to this solution (to get transparent icon). I tried to open the icon which was saved following your method. In visualstudio(VC6) it said unknown filetype. Loadimage worked and I am able open the icon in iconpro. (Anyway I will be accepting Danrollins answer).
           
          If I am able to get this transparent solution also, it will be helpful.

       
DanRollinsCommented:
This is even more complicated than I imagined.  
I think that if you extract 32x32 icons, they have the correct mask for transparent drawing, but there is something odd about the other sizes.  I've tried a number of different angles, but haven't hit upon a solution.

>> I tried to open the icon which was saved following your method. In visualstudio(VC6) it said unknown filetype.
VC6 brought up a bianry file format for me, but if i use Import, in the resource editrop, it did read in the fiel as an icon.  But it shows a black background.

-- Dan

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
DanRollinsCommented:
If you have Visual Studio .NET, there is a new ALT class named CImage (it does not use .NET, but does use msimg32.dll and gdi32.lib).  It has the ability to save files in all of the most common formats, including GIF and ICO.  It has a "SetTransparentColor"  method that might let you save transparent GIFs.  

I'll bet that this newer technology is much better at handling 256-color ICONS and icons of various sizes.

-- Dan
Daij-DjanCommented:
The problem (as I see it) is that icons can be translucent.
Not only transparent or not (100% or 0%)

Newer icons have an alpha channel. That must be accounted for.
nkraghuAuthor Commented:
Can we save icons with CImage? I guess we need encoderclass id for reading and writing. We can only READ icon but cannot write. I have tried the same with Bitmap.save(Which overloads save function of Image class) method in .NET

                    In .NET case I had tried for saving gifs. But that too had this black background issue. That's why I came to saving icons in alternate ways mentioned by Dan.
DanRollinsCommented:
CImage Class
       http://msdn.microsoft.com/library/en-us/vclib/html/vcrefCImage.asp
CImage::IsTransparencySupported()
      http://msdn.microsoft.com/library/en-us/vclib/html/vcrefcimageistransparencysupported.asp

CImage::Save()
      http://msdn.microsoft.com/library/en-us/vclib/html/vcrefcimagesave.asp

The guidFileType can be ImageFormatGIF or any of the values in
      http://msdn.microsoft.com/library/en-us/gdicpp/GDIPlus/GDIPlusReference/Constants/ImageFileFormatConstants.asp

including (apparemtly) ImageFormatIcon and ImageFormatGIF

-- Dan
Daij-DjanCommented:
DanRollinsCommented:
Daij-Djan,
We have already estatbished that the method in your first link (and the first piece of code in your second link) does not work in this situation.

The UNDOCUMENTED  OleSavePictureFile API function used in your second link does save some steps, but the result it the same... An icon with a black background for all icon formats except 32x32  and the 32x32 image ends up with reduced color resolution.

The OleCreatePictureIndirect API seems to create 16-color icons and seems to not preserve the mask.   I am continuing to check some other options.  Perhaps the HDC used in the IPicture object can be manipulated.

-- Dan
Daij-DjanCommented:
Sorry in that case...
Do you have to preserve the alpha-channel?

If not converting it to a bmp and  saving THAT is an option
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Microsoft Development

From novice to tech pro — start learning today.