I have written code for saving icons to disk and this code includes a procedure to minimize the filesize of the saved icon by counting the colors actually used in the original and creating the output icon using the minimum necessary. For example, if an icon is 5 kb in size because of the format it was saved in, but it only uses 16 or less actual colors, my routine will save it at 1 k.
Code: Select all
;=======================================================================
; Library: IconLib
; Author: Lloyd Gallant (netmaestro)
; Date: October 15, 2009
; Target OS: Microsoft Windows All
; Target Compiler: Requires PureBasic 4.40
;
; Functions: WriteIconFile
; Writes an Array of 4bit-32bit hIcons
; to an .ico file on disk
;
; WriteCursorFile
; writes a 1bit-32bit hIcon or 1bit hCursor
; to a .cur file on disk
;=======================================================================
Structure ICONDIR
idReserved.w ; // Reserved (must be 0)
idType.w ; // Resource type (1 for icons)
idCount.w ; // How many images?
idEntries.ICONHEADER[0] ; // The entries for each image
EndStructure
Structure ICONARRAY
icons.l[0]
EndStructure
Procedure CountColorsUsed(pBitmap)
GetObject_(pBitmap, SizeOf(BITMAP), @bmp.BITMAP)
*bmi.BITMAPINFO = AllocateMemory(SizeOf(BITMAPINFO)+SizeOf(RGBQUAD)*255)
With *bmi\bmiHeader
\biSize = SizeOf(BITMAPINFOHEADER)
\biWidth = bmp\bmWidth
\biHeight = bmp\bmHeight
\biPlanes = 1
\biBitCount = 32
EndWith
hDC = CreateCompatibleDC_(#Null)
GetDIBits_(hDC, pBitmap, 0, bmp\bmHeight, #Null, *bmi, #DIB_RGB_COLORS)
*pPixels = AllocateMemory(*bmi\bmiHeader\biSizeImage)
iRes = GetDIBits_(hDC, pBitmap, 0, bmp\bmHeight, *pPixels, *bmi, #DIB_RGB_COLORS)
Global mapsize = MemorySize(*pPixels)/2
Global NewMap Colors.RGBQUAD(mapsize)
*p.RGBQUAD = *pPixels
For i=1 To MemorySize(*pPixels)/SizeOf(RGBQUAD)
With Colors(Hex(PeekL(*p)))
\rgbBlue = *p\rgbBlue
\rgbGreen = *p\rgbGreen
\rgbRed = *p\rgbRed
\rgbReserved = *p\rgbReserved
EndWith
*p+SizeOf(RGBQUAD)
Next
result = MapSize(colors())
If result <= 16
*colortable = AllocateMemory(16*SizeOf(RGBQUAD))
ElseIf result <= 256
*colortable= AllocateMemory(256*SizeOf(RGBQUAD))
Else
*colortable = 0
EndIf
If *colortable
*writeptr.RGBQUAD = *colortable
ForEach colors()
With *writeptr
\rgbBlue = colors()\rgbBlue
\rgbGreen = colors()\rgbGreen
\rgbRed = colors()\rgbRed
\rgbReserved = 0
EndWith
*writeptr+SizeOf(RGBQUAD)
Next
EndIf
DeleteDC_(hdc)
ClearMap(colors())
FreeMemory(*pPixels)
FreeMemory(*bmi)
ProcedureReturn *colortable
EndProcedure
Procedure MinimizeColors(hBitmap)
; Count actual colors used:
; if <= 16 -----> convert To 4bit
; elseif <= 256 -----> convert to 8bit
; else -----> return original bitmap
*colortable = CountColorsUsed(hBitmap)
hImageOut = 0
If *colortable
Select MemorySize(*colortable)
Case 64
newdepth = 4
Case 1024
newdepth = 8
EndSelect
GetObject_(hBitmap,SizeOf(BITMAP),bmp.BITMAP)
w = bmp\bmWidth
h = bmp\bmHeight
hdcSrc = CreateCompatibleDC_(0)
With bmi.BITMAPINFO
\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER)
\bmiHeader\biWidth = w
\bmiHeader\biHeight = h
\bmiHeader\biPlanes = 1
\bmiHeader\biBitCount = 32
EndWith
GetDIBits_(hdcSrc, hBitmap, 0, h, #Null, @bmi, #DIB_RGB_COLORS)
*colorbits = AllocateMemory(bmi\bmiHeader\biSizeImage)
GetDIBits_(hdcSrc, hBitmap, 0, h, *colorbits, @bmi, #DIB_RGB_COLORS)
With bmiReduced.BITMAPINFO
\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER)
\bmiHeader\biWidth = w
\bmiHeader\biHeight = h
\bmiHeader\biPlanes = 1
\bmiHeader\biBitCount = newdepth
EndWith
hdcDest = CreateCompatibleDC_(0)
hImageOut = CreateDIBSection_(hdcDest, @bmiReduced, #DIB_PAL_COLORS, @*bitsReduced, 0, 0)
SelectObject_(hdcDest, hImageOut)
SetDIBColorTable_(hdcDest, 0, MemorySize(*colortable)/SizeOf(RGBQUAD), *colortable)
GdiFlush_()
SetDIBits_(hdcSrc, hImageOut, 0, h, *colorbits, @bmi, #DIB_PAL_COLORS)
DeleteDC_(hdcSrc)
DeleteDC_(hdcDest)
FreeMemory(*colortable)
FreeMemory(*colorbits)
EndIf
If GetObjectType_(hImageOut) = #OBJ_BITMAP
ProcedureReturn hImageOut
Else
ProcedureReturn hBitmap
EndIf
EndProcedure
;=======================================================================
; Library Function: WriteIconFile
; Author: Lloyd Gallant (netmaestro)
; Date: October 15, 2009
; Target OS: Microsoft Windows All
; Target Compiler: PureBasic 4.40
;
; Function: Writes an array of 4bit-32bit hIcons
; to an .ico file on disk
;=======================================================================
ProcedureDLL.l WriteIconFile( *hIcon_array.ICONARRAY, num_hIcons, filename$)
; netmaestro October 2009 based on http://msdn.microsoft.com/en-us/library/ms997538.aspx
; contributors: srod
Protected hdc, iinf.ICONINFO, mask, color, bmpColor.BITMAP, bmpMask.BITMAP, *bi_color.BITMAPINFO, *bi_mask.BITMAPINFO
Protected *maskbits, *colorbits, *file, *direntryptr,*imagedataptr, *id.ICONDIR, *ih.ICONHEADER, *ii.ICONIMAGE
Protected i, testicon, thisfile, sz_color, sz_mask, result=0, writesuccess, icons_processed=0
NewList Valid_hIcon.i() ; Valid_hIcon list gets filled with validated hIcons only
For i=0 To num_hIcons-1
testicon = *hIcon_array\icons[i]
FillMemory(@iinf, SizeOf(ICONINFO), 0)
GetIconInfo_(testicon, @iinf)
If iinf\hbmMask And iinf\hbmColor And iinf\fIcon=1 ; 1-bit icons are not supported, valid depths are 4,8,16,32
AddElement(Valid_hIcon())
Valid_hIcon()=testicon
EndIf
Next
hdc = CreateCompatibleDC_(#Null)
*file = AllocateMemory(1024*1024*4) ; 4 megabytes should be more than enough
*direntryptr = *file
; File header
*id.ICONDIR = *direntryptr
With *id
\idType = 1
\idCount = ListSize(Valid_hIcon())
EndWith
*direntryptr+SizeOf(ICONDIR) ; ICONHEADER entries start here
*imagedataptr = *direntryptr + ListSize(Valid_hIcon())*SizeOf(ICONHEADER) ; Leave room for <listsize> ICONHEADER entries
; Image data entries start here
ForEach Valid_hIcon()
GetIconInfo_(Valid_hIcon(), @iinf.ICONINFO)
usecolortable = #False
num_colors = 0
sz_colortable = 0
mask = iinf\hbmMask
color = MinimizeColors(iinf\hbmColor)
;===================================================
; Get this entry's color bits
;===================================================
GetObject_(color, SizeOf(BITMAP), bmpColor.BITMAP)
If bmpColor\bmBitsPixel <= 8
usecolortable = #True
num_colors = Int(Pow(2,bmpColor\bmBitsPixel))
sz_colortable = num_colors*SizeOf(RGBQUAD)
If *colortable : FreeMemory(*colortable) : EndIf
*colortable = AllocateMemory(sz_colortable)
old = SelectObject_(hdc, color)
GetDIBColorTable_(hdc, 0, num_colors, *colortable)
SelectObject_(hdc, old)
EndIf
If *bi_color : FreeMemory(*bi_color) : EndIf
*bi_color = AllocateMemory(SizeOf(BITMAPINFOHEADER)+256*SizeOf(RGBQUAD))
*bi_color\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER)
GetDIBits_(hdc, color, 0, bmpColor\bmHeight, 0, *bi_color, #DIB_RGB_COLORS )
sz_color = *bi_color\bmiHeader\biSizeImage
*bi_color\bmiHeader\biBitCount = bmpColor\bmBitsPixel
If *colorbits : FreeMemory(*colorbits) : EndIf
*colorbits = AllocateMemory(sz_color)
GetDIBits_(hdc, color, 0, bmpColor\bmHeight, *colorbits, *bi_color, #DIB_RGB_COLORS )
;===================================================
; Get this entry's mask bits
;===================================================
GetObject_(mask, SizeOf(BITMAP), bmpMask.BITMAP)
If *bi_mask : FreeMemory(*bi_mask) : EndIf
*bi_mask = AllocateMemory(SizeOf(BITMAPINFOHEADER)+256*SizeOf(RGBQUAD))
*bi_mask\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER)
GetDIBits_(hdc, mask, 0, bmpMask\bmHeight, 0, *bi_mask, #DIB_RGB_COLORS )
sz_mask = *bi_mask\bmiHeader\biSizeImage
*bi_mask\bmiHeader\biBitCount = bmpMask\bmBitsPixel
If *maskbits : FreeMemory(*maskbits) : EndIf
*maskbits = AllocateMemory( sz_mask )
GetDIBits_(hdc, mask, 0, bmpMask\bmHeight, *maskbits, *bi_mask, #DIB_RGB_COLORS )
;===================================================
; Write this entry to the file
;===================================================
; Icon header first, in the headers section
*ih.ICONHEADER = *direntryptr
With *ih
\bWidth = bmpcolor\bmWidth
\bHeight = bmpcolor\bmHeight
\wPlanes = 1
\wBitCount = bmpcolor\bmBitsPixel
\dwBytesinRes = sz_colortable + sz_color + sz_mask + SizeOf(ICONIMAGE)
\dwBytesOffset = *imagedataptr - *file
EndWith
*direntryptr+SizeOf(ICONHEADER)
; ICONIMAGE structure next, in the imagedata section
*ii.ICONIMAGE = *imagedataptr
With *ii\icHeader
\biSize = SizeOf(BITMAPINFOHEADER)
\biWidth = bmpColor\bmWidth
\biHeight = bmpColor\bmHeight * 2
\biPlanes = 1
\biBitCount = bmpcolor\bmBitsPixel
\biSizeImage = sz_colortable + sz_color + sz_mask + SizeOf(ICONIMAGE)
EndWith
*imagedataptr+SizeOf(BITMAPINFOHEADER)
; Lastly, colortable (if present), color and mask bits
If usecolortable : CopyMemory( *colortable, *imagedataptr, sz_colortable ) : *imagedataptr + sz_colortable : EndIf
CopyMemory( *colorbits, *imagedataptr, sz_color ) : *imagedataptr + sz_color
CopyMemory( *maskbits, *imagedataptr, sz_mask ) : *imagedataptr + sz_mask ; Pointer is ready for the next entry
icons_processed + 1
If color:DeleteObject_(color):EndIf
DeleteObject_(mask)
Next
DeleteDC_(hdc)
;===========================================================
; All icons are processed, file can be written to disk
;===========================================================
If icons_processed
thisfile = CreateFile(#PB_Any, filename$)
If thisfile
writesuccess = *imagedataptr-*file
result = WriteData( thisfile, *file, writesuccess )
CloseFile( thisfile )
Else
MessageRequester("Info:", "Function WriteIconFile in library IconLib failed with error: unable to create file "+filename$+" ", #MB_ICONERROR)
EndIf
FreeMemory(*bi_color)
FreeMemory(*bi_mask)
If *colortable : FreeMemory(*colortable) : EndIf
FreeMemory(*colorbits)
FreeMemory(*maskbits)
EndIf
FreeMemory(*file)
If result = writesuccess And result <> 0
ProcedureReturn icons_processed
Else
ProcedureReturn 0
EndIf
EndProcedure
;=======================================================================
; Library Function: WriteCursorFile
; Author: Lloyd Gallant (netmaestro)
; Date: October 16, 2009
; Target OS: Microsoft Windows All
; Target Compiler: PureBasic 4.40
;
; Function: Writes a 16bit-32bit or 1bit hCursor
; to a .cur file on disk
;=======================================================================
ProcedureDLL.l WriteCursorFile(hCursor, xHotspot, yHotspot, filename$)
GetIconInfo_(hCursor, @ii.ICONINFO)
If Not (ii\hbmMask)
ProcedureReturn 0
EndIf
mask = ii\hbmMask
color = ii\hbmColor
usecolortable = #False
num_colors = 0
sz_colortable = 0
hdc = CreateCompatibleDC_(#Null)
;===================================================
; Get color bits
;===================================================
If color
GetObject_(color, SizeOf(BITMAP), bmpColor.BITMAP)
If bmpColor\bmBitsPixel <= 8
usecolortable = #True
num_colors = Int(Pow(2,bmpColor\bmBitsPixel))
sz_colortable = num_colors*SizeOf(RGBQUAD)
If *colortable : FreeMemory(*colortable) : EndIf
*colortable = AllocateMemory(sz_colortable)
old = SelectObject_(hdc, color)
GetDIBColorTable_(hdc, 0, num_colors, *colortable)
SelectObject_(hdc, old)
EndIf
*bi_color.BITMAPINFO = AllocateMemory(SizeOf(BITMAPINFOHEADER)+255*SizeOf(RGBQUAD))
*bi_color\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER)
GetDIBits_(hdc, color, 0, bmpColor\bmHeight, 0, *bi_color, #DIB_RGB_COLORS )
sz_color = *bi_color\bmiHeader\biSizeImage
*bi_color\bmiHeader\biBitCount = bmpColor\bmBitsPixel
*colorbits = AllocateMemory(sz_color)
GetDIBits_(hdc, color, 0, bmpColor\bmHeight, *colorbits, *bi_color, #DIB_RGB_COLORS )
Else ; 1-bit
sz_color = 0
sz_colortable = 2*SizeOf(RGBQUAD)
EndIf
;===================================================
; Get mask bits
;===================================================
GetObject_(mask, SizeOf(BITMAP), bmpMask.BITMAP)
*bi_mask.BITMAPINFO = AllocateMemory(SizeOf(BITMAPINFOHEADER)+255*SizeOf(RGBQUAD))
With *bi_mask\bmiHeader
\biSize = SizeOf(BITMAPINFOHEADER)
\biWidth = bmpMask\bmWidth
\biHeight = bmpMask\bmHeight
\biPlanes = bmpMask\bmPlanes
\biBitCount = 0
EndWith
GetDIBits_(hdc, mask, 0, bmpMask\bmHeight, 0, *bi_mask, #DIB_PAL_COLORS )
sz_mask = *bi_mask\bmiHeader\biSizeImage
*bi_mask\bmiHeader\biBitCount = bmpMask\bmBitsPixel
*maskbits = AllocateMemory( sz_mask )
GetDIBits_(hdc, mask, 0, bmpMask\bmHeight, *maskbits, *bi_mask, #DIB_PAL_COLORS )
;===================================================
; Start writing the file
;===================================================
*file = AllocateMemory(1024*1024)
*writeptr = *file
*id.ICONDIR = *writeptr
With *id
\idType = 2
\idCount = 1
EndWith
*writeptr+SizeOf(ICONDIR)
*ih.ICONHEADER = *writeptr
With *ih
If color : \bHeight = bmpColor\bmHeight : Else : \bHeight = bmpMask\bmHeight/2 : EndIf
\bWidth = bmpMask\bmWidth
\wPlanes = xHotspot
\wBitCount = yHotspot
\dwBytesinRes = sz_colortable + sz_color + sz_mask + SizeOf(ICONIMAGE)
\dwBytesOffset = SizeOf(ICONDIR)+SizeOf(ICONHEADER)
EndWith
*writeptr+SizeOf(ICONHEADER)
*ii.ICONIMAGE = *writeptr
With *ii\icHeader
\biSize = SizeOf(BITMAPINFOHEADER)
\biWidth = bmpMask\bmWidth
If color : \biHeight = bmpColor\bmHeight * 2 : Else : \biHeight = bmpMask\bmHeight : EndIf
\biPlanes = 1
If color : \biBitCount = bmpColor\bmBitsPixel : Else : \biBitCount = 1 : EndIf
\biSizeImage = sz_colortable + sz_color + sz_mask + SizeOf(ICONIMAGE)
EndWith
*writeptr+SizeOf(BITMAPINFOHEADER)
If Not color ; 1-bit
PokeL( *writeptr, #Black ) : *writeptr + SizeOf(LONG)
PokeL( *writeptr, #White ) : *writeptr + SizeOf(LONG)
Else
If usecolortable : CopyMemory( *colortable, *writeptr, sz_colortable ) : *writeptr + sz_colortable : EndIf
EndIf
If color: CopyMemory( *colorbits, *writeptr, sz_color ) : *writeptr + sz_color : EndIf
CopyMemory( *maskbits, *writeptr, sz_mask ) : *writeptr + sz_mask
If color : DeleteObject_(color) : EndIf
If mask : DeleteObject_(mask) : EndIf
If *bi_color : FreeMemory(*bi_color) : EndIf
If *bi_mask : FreeMemory(*bi_mask) : EndIf
If *colorbits : FreeMemory(*colorbits) : EndIf
If *colortable : FreeMemory(*colortable) : EndIf
If *maskbits : FreeMemory(*maskbits) : EndIf
thisfile = CreateFile(#PB_Any, filename$)
If thisfile
writesuccess = *writeptr-*file
result = WriteData( thisfile, *file, writesuccess )
CloseFile( thisfile )
Else
MessageRequester("Info:", "Function WriteCursorFile in library IconLib failed with error: unable to create file "+filename$+" ", #MB_ICONERROR)
EndIf
FreeMemory(*file)
DeleteDC_(hdc)
If result = writesuccess And result <> 0
ProcedureReturn 1
Else
ProcedureReturn 0
EndIf
EndProcedure
Dim icos(1)
icos(0) = LoadImage(0,#PB_Compiler_Home+"Examples\sources\data\cdplayer.ico")
WriteIconFile(icos(),1,"c:\cdplayer.test.ico")