One more thing, it requires PB v4.40 as it uses the structures ICONHEADER and ICONIMAGE which are new to this release. If you're using an older PureBasic, you can get the structures at the link provided in the code.
@srod: Once I had my proc running well and fairly tested, I ran Windiff on an icon saved by both our programs. Two bytes were different, at 43 and 44. The difference came in because I filled the biSizeImage member of the BITMAPINFOHEADER in the ICONIMAGE structure. You left yours at 0. I filled mine because I read this:
I dunno if you could run into problems in some cases if it isn't filled or not.MSDN wrote:The icHeader member has the form of a DIB BITMAPINFOHEADER. Only the following members are used: biSize, biWidth, biHeight, biPlanes, biBitCount, biSizeImage. All other members must be 0.
*** Update Oct 13: Code is modified to place up to 4 hIcon's in the file
***Update Oct 16: Code is modified to receive an array for unlimited # of hIcons in the file, also validation is performed on all input to ensure output file integrity. Invalid hIcons are skipped, return value of the procedure is # of hIcons successfully processed. Code is tested as .PBI, tailbite library and compiled DLL, no problems found.
***Update Nov 1:
-Code is modified to save all bit depths for icons from 4-32 (1-bit icons unsupported)
-Added function WriteCursorFile to save all depths of hIcon or hCursor to .cur file (one cursor per file only)
-All supported bit depths for both functions are tested working properly.
***Update Nov 15:
-Code is modified to overcome the problem of GetIconInfo_() always returning a 32bit color bitmap. Actual colors used in the icon are counted and if a lower bit-depth is warranted, the color bitmap is converted to the lower depth. For example, the cdplayer.ico found in the PureBasic distribution is 1k in size. Before this update, the saved version of that icon would have been 5k. Now the saved version is identical in size to the original. Tested with 4 and 8bit depths.
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 (newmaestro)
; 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 (newmaestro)
; 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
; ; test proggie:
;
; Dim myicons.l(3)
;
; myicons(0) = 0 ; bad one just to test
; myicons(1) = LoadIcon_(0, #IDI_QUESTION)
; myicons(2) = LoadIcon_(0, #IDI_ERROR)
; myicons(3) = LoadIcon_(0, #IDI_INFORMATION)
;
; Debug WriteIconFile( myicons(), 4, "c:\mytest.ico" )
Also, here's a companion piece for it, using gdiplus to load an image and convert it to an hIcon which can then be saved with WriteIconFile:
Code: Select all
IncludeFile "writeiconfile.pbi" ; or srod's SaveIcon.pbi ;)
Global *token, *image
If OpenLibrary(0, "gdiplus.dll") = 0
MessageRequester("Error","Required component gdiplus.dll is not found. Please install it and retry ", #MB_ICONERROR)
End
EndIf
Prototype GdiplusStartup( *token, *input, mode )
Prototype GdipCreateBitmapFromFile(*filename, *image)
Prototype GdipCreateHICONFromBitmap(*image, hIcon.i)
Prototype GdipDisposeImage( *image )
Prototype GdiplusShutdown( *token )
Global Startup.GdiplusStartup = GetFunction( 0, "GdiplusStartup" )
Global CreateBitmapFromFile.GdipCreateBitmapFromFile = GetFunction( 0, "GdipCreateBitmapFromFile" )
Global CreateHICONFromBitmap.GdipCreateHICONFromBitmap = GetFunction( 0, "GdipCreateHICONFromBitmap")
Global DisposeImage.GdipDisposeImage = GetFunction( 0, "GdipDisposeImage" )
Global Shutdown.GdiplusShutdown = GetFunction( 0, "GdiplusShutdown" )
CompilerIf Defined(GdiplusStartupInput, #PB_Structure) = 0
Structure GdiplusStartupInput
GdiPlusVersion.l
*DebugEventCallback.Debug_Event
SuppressBackgroundThread.l
SuppressExternalCodecs.l
EndStructure
CompilerEndIf
Procedure InitGDIPlus()
input.GdiplusStartupInput
input\GdiPlusVersion = 1
input\DebugEventCallback = #Null
input\SuppressBackgroundThread = #False
input\SuppressExternalCodecs = #False
Startup( @*token, @input, #Null)
EndProcedure
Procedure ShutdownGDIPlus()
Shutdown(*token)
CloseLibrary(0)
EndProcedure
pattern$ = "PNG, BMP, JPEG, TIFF|*.png;*.bmp;*.jpg;*.jpeg;*.tiff|PNG (*.png)|*.png|BMP (*.bmp)|*.bmp|JPEG (*.jpg)|*.jpg|TIFF (*.tif)|*.tif"
inpath$ = OpenFileRequester("Choose an image to convert to icon:","",pattern$, 0)
prompt$ = RemoveString(GetFilePart(inpath$),"."+GetExtensionPart(inpath$))
prompt$ +".ico"
If FileSize(inpath$) < 1
MessageRequester("Info:","No file selected. Ending...",#MB_ICONINFORMATION)
End
EndIf
InitGDIPlus()
inUnicode$ = Space(Len(inpath$)*2+2)
PokeS(@inUnicode$, inpath$, -1, #PB_Unicode)
inpath_as_bstr = SysAllocString_(@inUnicode$)
pattern$ = "ICON (*.ico)|*.ico;"
outpath$ = SaveFileRequester("Choose a path to save the .ico file:",prompt$,pattern$, 0)
outpath$ = RemoveString(outpath$, ".ico")
outpath$ + ".ico"
CreateBitmapFromFile(inpath_as_bstr, @*image)
CreateHICONFromBitmap(*image, @hIcon.i)
DisposeImage(*image)
ShutdownGDIPlus()
If WriteIconFile(@hIcon, 1, outpath$)
MessageRequester("Info:", "Icon successfully saved to " + outpath$, #MB_ICONINFORMATION)
DeleteObject_(hIcon)
EndIf