Saving icons in .ico files (updated to include cursors)

Share your advanced PureBasic knowledge/code with the community.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Saving icons in .ico files (updated to include cursors)

Post by srod »

**UPDATE** Now saves cursors in .cur files as well. Just pass a handle to either an icon or a cursor along with the required filename and the code does the rest. You don't need to specify icon or cursor, it's done automatically.
***************

Hi,

the following small include file allows you to save an icon in a .ico file, something which Windows will not do on our behalf!

SYNTAX: SaveIcon(hIcon, filename$)
where 'hIcon' is a handle of an icon either loaded from a resource file or with LoadImage_() etc.

This library can only be used with icons which are already stored in memory (either loaded from disk, or created with CreateIconIndirect_() etc.) and saves the icon in the same format (depth etc.) as stored internally by Windows.

However, this code can quite 'easily' :) be adapted so that icons can be created from scratch using native Purebasic commands (where images are stored in any format) and then, using the code adapted from this library, saved in .ico format. It's just a question of messing with colour tables etc.


Warning: I have only tested with 32-bit and 1-bit monochrome bitmaps so far.



Include file:

Code: Select all

;SaveIcon.
;By Stephen Rodriguez 2006 (Updated 2010).
;Purebasic 4.5

;This small 'include' file allows for the saving of a single icon/cursor in a .ico/.cur file.
;Extending to include multiple icons/cursors in one file would be straight forward.

;SYNTAX:  SaveIcon(hIcon, filename$)
;         where  'hIcon' is a handle of an icon/cursor either loaded from a resource file or with LoadImage_() etc.

;NOTES.

;  i)   This library can only be used with icons/cursors which are already stored in memory
;       (either loaded from disc, or created with CreateIconIndirect_() etc.) and
;       saves the icon/cursor in the same format (depth etc.) as stored internally by Windows.

;       However, this code can quite 'easily'  be adapted so that icons/cursors can be created from 
;       scratch using native Purebasic commands (where images are stored in any format)
;       and then, using the code adapted from this library, saved in .ico/.cur format. It's 
;       just a question of messing with colour tables etc. 

;  ii)  If using this library to save an icon/cursor loaded With LoadImage_(), then it may appear that
;       the new icon/cursor is of a different size. This will be because the original .ico/.cur file has multiple
;       icons in it of different sizes. Windows explorer may report one size whilst you loaded another etc.

;TODO (maybe!)
;       -Extend the code to save multiple icons/cursors in the same .ico file.    
;       -Extend to cursor .cur files  (DONE!)
;**************************************************************************************************


;Result = non-zero if no error.
Procedure.i SaveIcon(hIcon, filename$)
  Protected result, iconinfo.ICONINFO, hbmMask, hbmColor
  Protected cbitmap.BITMAP, cwidth, cheight, cbitsperpixel, colorcount, colorplanes
  Protected mbitmap.BITMAP, mwidth, mheight, fIcon, xHotspot, yHotspot
  Protected file, imagebytecount, hdc, oldbitmap, mem, bytesinrow, temp
  Protected *bitmapinfo.BITMAPINFO
  ;Get information regarding the icon.
    If Not(GetIconInfo_(hIcon, iconinfo)) : ProcedureReturn 0 : EndIf ;Not a valid icon handle.
    fIcon=2-iconinfo\fIcon ;icon = 1, cursor = 2,
    If fIcon=2 ;Cursor.
      xHotspot=iconinfo\xHotspot
      yHotspot=iconinfo\yHotspot
    EndIf
  ;Allocate memory for a BITMAPINFO structure + a color table with 256 entries.
    *bitmapinfo = AllocateMemory(SizeOf(BITMAPINFO) + SizeOf(RGBQUAD)<<8)
    If *bitmapinfo = 0 : ProcedureReturn 0 :EndIf
  ;Get the mask (AND) bitmap, which, if the icon is B/W monochrome, contains the colour bitmap.
    hbmMask=iconinfo\hbmMask
    GetObject_(hbmMask, SizeOf(BITMAP),mbitmap)
    mwidth= mbitmap\bmWidth
    mheight= mbitmap\bmHeight
  ;Get the colour (XOR) bitmap.
    hbmColor=iconinfo\hbmColor
    If hbmColor
      GetObject_(hbmColor, SizeOf(BITMAP),cbitmap)
      cwidth= cbitmap\bmWidth
      cheight= cbitmap\bmHeight
      cbitsperpixel = cbitmap\bmBitsPixel
      If cbitsperpixel = 0 : cbitsperpixel = 1 : EndIf
      If cbitsperpixel < 8
        colorcount=Pow(2,cbitsperpixel) ;colorcount = 0 if 8 or more bpp.
      EndIf
      colorplanes=cbitmap\bmplanes
    Else ;Monochrome icon.
      cwidth= mwidth
      cheight= mheight/2
      cbitsperpixel = 1
      colorcount=2
      colorplanes=1
      mheight=cheight
    EndIf
  ;Ready to start creating the file.
  file=CreateFile(#PB_Any,filename$)
  If file
  ;Write the data.
  ;word = 0
    WriteWord(file,0)
  ;word = 1 for icon, 2 for cursor.
    WriteWord(file,ficon) ;1 for icon, 2 for cursor. 
  ;word = number of icons in file.
    WriteWord(file,1)  ;***CHANGE IF EXTENDING CODE TO MORE THAN ONE ICON***
  ;16 byte ICONDIRENTRY structure, one for each icon.
    WriteByte(file, cwidth)
    WriteByte(file, cheight)
    WriteByte(file, colorcount)
    WriteByte(file, 0) ;Reserved.
    If ficon=1 ;Icon.
      WriteWord(file, colorplanes) ;Should equal 1, -but just in case!
      WriteWord(file, cbitsperpixel) 
    Else ;Cursor.
      WriteWord(file, xhotspot) 
      WriteWord(file, yhotspot) 
    EndIf
    WriteLong(file,0) ;TEMPORARY! WE NEED TO RETURN WHEN WE KNOW THE EXACT QUANTITY.
                      ; Size of (InfoHeader + ANDbitmap + XORbitmap)  
    WriteLong(file,Loc(file)+4)  ;FilePos, where InfoHeader starts
  ;Now the image data in the form BITMAPINFOHEADER (40 bytes) + colour map for the colour bitmap
  ;+ bits of colour bitmap + bits of mask bitmap. Gulp! One for each icon.
  ;40 byte BITMAPINFOHEADER structure.
    imagebytecount=SizeOf(BITMAPINFOHEADER)
    WriteLong(file, imagebytecount) ;Should be 40.
    WriteLong(file, cwidth)
    WriteLong(file, cheight+mheight) ;Combined heights of colour + mask images.
    WriteWord(file, colorplanes) ;Should equal 1, -but just in case!
    WriteWord(file, cbitsperpixel)
    WriteLong(file, 0) ;Compression.
    WriteLong(file, 0) ;Image size. Valid to set to zero if there's no compression.
    WriteLong(file, 0) ;Unused.
    WriteLong(file, 0) ;Unused.
    WriteLong(file, 0) ;Unused.
    WriteLong(file, 0) ;Unused.
  ;Colour map. Only applies for <= 8 bpp.
    hdc=CreateCompatibleDC_(0) ;Needed in order to get the colour table.
    If hbmColor = 0 ;Monochrome icon.
      WriteLong(file, #Black)
      WriteLong(file, #White)
      imagebytecount+SizeOf(rgbquad)*2
    ElseIf cbitsperpixel<=8 ;Includes 1 bit non-monochrome icons.
      ;Get colour table.
        temp=Pow(2,cbitsperpixel) 
        bytesinrow = SizeOf(rgbquad)*temp
        mem=AllocateMemory(bytesinrow)
        oldbitmap=SelectObject_(hdc, hbmColor)
        GetDIBColorTable_(hdc, 0, temp, mem)      
        WriteData(file, mem, bytesinrow) ;Write color table.
        FreeMemory(mem)
        SelectObject_(hdc, oldbitmap)
        imagebytecount+bytesinrow
    EndIf
  ;Now the colour image bits. We use GetDiBits_() for this.
    bytesinrow = (cwidth*cbitsperpixel+31)/32*4  ;Aligned to a 4-byte boundary.
    bytesinrow * cheight
    mem=AllocateMemory(bytesinrow)
    *bitmapinfo\bmiHeader\biSize=SizeOf(BITMAPINFOHEADER)
    *bitmapinfo\bmiHeader\biWidth=cwidth
    *bitmapinfo\bmiHeader\biPlanes=colorplanes
    *bitmapinfo\bmiHeader\biBitCount=cbitsperpixel
    If hbmColor
      *bitmapinfo\bmiHeader\biHeight=cheight
      GetDIBits_(hdc,hbmColor,0,cheight,mem,*bitmapinfo,#DIB_RGB_COLORS)
    Else ;Monochrome color image is the bottom half of the mask image.
      *bitmapinfo\bmiHeader\biHeight=2*cheight
      GetDIBits_(hdc,hbmMask,0,cheight,mem,*bitmapinfo,#DIB_RGB_COLORS)
    EndIf
    WriteData(file, mem, bytesinrow) 
    FreeMemory(mem)
    imagebytecount+bytesinrow
  ;Now the mask image bits. We use GetDiBits_() for this.
    bytesinrow = (mwidth+31)/32*4  ;Aligned to a 4-byte boundary.
    bytesinrow * mheight
    mem=AllocateMemory(bytesinrow)
    *bitmapinfo\bmiHeader\biWidth=mwidth
    *bitmapinfo\bmiHeader\biPlanes=1
    *bitmapinfo\bmiHeader\biBitCount=1
    If hbmColor
      *bitmapinfo\bmiHeader\biHeight=mheight
      GetDIBits_(hdc,hbmMask,0,mheight,mem,*bitmapinfo,#DIB_RGB_COLORS)
    Else
      *bitmapinfo\bmiHeader\biHeight=2*mheight
      GetDIBits_(hdc,hbmMask,mheight,mheight,mem,*bitmapinfo,#DIB_RGB_COLORS)
    EndIf
    WriteData(file, mem, bytesinrow) 
    FreeMemory(mem)
    imagebytecount+bytesinrow
    DeleteDC_(hdc)
  ;Finally, return to the field we missed out.
    FileSeek(file, 14)
    WriteLong(file, imagebytecount)
    CloseFile(file)
    result= 1 ;Signal everything is fine.
  Else
    result= 0
  EndIf
  DeleteObject_(hbmMask) ;These are copies created as a result of GetIconInfo_() and so require deleting.
  DeleteObject_(hbmColor)
  FreeMemory(*bitmapinfo)
  ProcedureReturn result
EndProcedure
Example:

Code: Select all

;Test file for SaveIcon.

XIncludeFile "SaveIcon.pbi"


;Load an icon or cursor - either from file or use one of Windows default icons.

;UNCOMMENT AS APPROPRIATE.
;   Define icon=LoadImage_(GetModuleHandle_(0),@"setup.ico",#IMAGE_ICON,0,0,#LR_LOADFROMFILE)
;   Define cursor=LoadImage_(GetModuleHandle_(0),@"test1.cur",#IMAGE_CURSOR,0,0,#LR_LOADFROMFILE)
;   Define icon=LoadIcon_(0,#IDI_ASTERISK)
;   Define cursor=LoadCursor_(0,#IDC_ARROW)

;Now call the SaveIcon procedure.

;UNCOMMENT AS APPROPRIATE.
;   SaveIcon(icon, "test.ico")
;   SaveIcon(cursor, "test.cur")
Regards.
Last edited by srod on Tue Apr 27, 2010 8:56 pm, edited 3 times in total.
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8433
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Hmm. Seems to work for some icons and not others. Here's two that appear to work, but the resulting icon is just white:

http://www.networkmaestro.com/icons.zip
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

They both work okay here!

The resulting icon for 'setup.ico' is changed to 16x16, but this will be because the original ico file contains multiple icons.

Can't explain why they work here but not on your system!
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8433
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

OOPS - Sorry I seem to have spoken too soon, they show just white in my Nero PhotoSnap Viewer, where most icons show normally, but they work perfectly embedded in exe's and they show with the Windows Picture and Fax viewer. So probably no problem.
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

I've been working from a rather old document detailing the .ico file format (11 years old!) Maybe something has changed and Nero hasn't spotted that it might be a slightly out of date format?

Mind you I found the same specification from another source as well!

I'll keep an eye out for more recent specs.
I may look like a mule, but I'm not a complete ass.
Dare
Addict
Addict
Posts: 1965
Joined: Mon May 29, 2006 1:01 am
Location: Outback

Post by Dare »

May or may not be of interest:

http://www.axialis.com/tutorials/tutori ... icons.html

(Vista icons).

Thanks for the code, srod.
Dare2 cut down to size
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

See the size of those icon files? 400 kb!!! Jeepers! :shock:

They look good though, and backward compatible in that our current .ico files will work no problem.
I may look like a mule, but I'm not a complete ass.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Code updated to include cursors.

See the first post.
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8433
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Done some testing with it - so far it's working very well! Another homerun from srod :!: gotta think of a new killer lib to compete.. he's pulling ahead...
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

netmaestro wrote:Done some testing with it - so far it's working very well! Another homerun from srod :!: gotta think of a new killer lib to compete.. he's pulling ahead...
I've got to get back to my projects and stop getting distracted!!! :) Thinking of emigrating to Alaska so I can get some work done! Mind you the Bahamas sounds more attractive... not sure how much work I'd get done though? :D
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8433
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: Saving icons in .ico files (updated to include cursors)

Post by netmaestro »

Warning: I have only tested with 32-bit and 1-bit monochrome bitmaps so far.
I found a boo-boo:

Code: Select all

;Get colour table.
      temp=Pow(2,cbitsperpixel) 
      bytesinrow = SizeOf(rgbquad)*temp
      mem=AllocateMemory(bytesinrow)
      GetDIBColorTable_(hdc, 0, temp, mem)      
      WriteData(file, mem, bytesinrow) ;Write color table.
      FreeMemory(mem)
      imagebytecount+bytesinrow
    EndIf
It is my opinion, after much testing, that GetIconInfo is always returning a 32bit color bitmap regardless of the image depth in the hIcon. (doesn't apply to 1-bit as there's no color bitmap at all in those) It isn't documented as such, but as I say, after much testing I found that's what it's doing. So when you save the icon, it's always saved with a 32bit color bitmap. To test my version of this, I had to forcefeed the Save procedure with an 8bit image. When I did the same test with yours, I found that it didn't work because you made a boo-boo: in order for GetDibColorTable_() to work, the bitmap has to be selected into the hdc. You aren't doing that, and so actual saving of an 8bit icon won't work. It will seem to work however, because of GetIconInfo never supplying you with an 8bit color bitmap. Thought you might be interested in knowing this bit of (hard-won) information.
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: Saving icons in .ico files (updated to include cursors)

Post by srod »

Then you must be using an old/corrupted version of the code netmaestro because I am indeed selecting the color bitmap into the hdc. I do not recall adding this following some bug report, though I am not ruling that out completely! :)

Code: Select all

;Colour map. Only applies for <= 8 bpp.
    hdc=CreateCompatibleDC_(0) ;Needed in order to get the colour table.
    If hbmColor = 0 ;Monochrome icon.
      WriteLong(file, #Black)
      WriteLong(file, #White)
      imagebytecount+SizeOf(rgbquad)*2
    ElseIf cbitsperpixel<=8 ;Includes 1 bit non-monochrome icons.
;Get colour table.
      temp=Pow(2,cbitsperpixel) 
      bytesinrow = SizeOf(rgbquad)*temp
      mem=AllocateMemory(bytesinrow)
      oldbitmap=SelectObject_(hdc, hbmColor)
      GetDIBColorTable_(hdc, 0, temp, mem)      
      WriteData(file, mem, bytesinrow) ;Write color table.
      FreeMemory(mem)
      SelectObject_(hdc, oldbitmap)
      imagebytecount+bytesinrow
    EndIf
As for the 32-bit images being returned by GetIconInfo_(). Yes I noticed that when I first created this code, but decided at the time that it was probably more to do with the icons I was feeding to the routine. If you are saying that this is a Windows peculiarity then, well, kind of makes sense that Windows would do this I guess. Does make a lot of my code pretty much redundant then! :)
I may look like a mule, but I'm not a complete ass.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: Saving icons in .ico files (updated to include cursors)

Post by srod »

My mistake, looking at the code I initially posted in this thread; I did indeed miss out the SelectObject_() etc. I must have realised the mistake some time ago, corrected the code, but not updated the code in this thread. Must be going senile in my old age! :)

Still, I haven't fixed that GetDIBits_() issue anyhow (color table being appended) and so I think I'll just remove the code. I'll repost when I get around to correcting the code.

Thanks for the heads up.
I may look like a mule, but I'm not a complete ass.
Mr Coder
User
User
Posts: 54
Joined: Tue Apr 13, 2010 8:02 am

Re:

Post by Mr Coder »

srod, any chance of fixing your code and uploading it here again? I found a bug with it in 4.50 as shown here: http://www.purebasic.fr/english/viewtop ... 13&t=42049
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: Re:

Post by srod »

Mr Coder wrote:srod, any chance of fixing your code and uploading it here again? I found a bug with it in 4.50 as shown here: http://www.purebasic.fr/english/viewtop ... 13&t=42049
Code updated in first post. A long standing bug has been fixed which I hope was the cause of your problem. :) If it still crashes then you will need to send me the icon you are trying to save as well as any additional code you are using etc.
I may look like a mule, but I'm not a complete ass.
Post Reply