More saving icons: updated Nov 15, 2009

Share your advanced PureBasic knowledge/code with the community.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

More saving icons: updated Nov 15, 2009

Post by netmaestro »

This is similar to a solution posted some time ago by srod, and its purpose is to save a valid icon handle to a file. Srod's will be better than mine because his is designed to handle icons of 8bits depth and under as well as the higher depths, whereas this isn't made for anything less than 16 bits. Also his will do cursors, this one won't. However, within the bounds of its design, it seems to work pretty well.

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:
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.
I dunno if you could run into problems in some cases if it isn't filled or not.

*** 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" )
Any problems, let me know.

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
Last edited by netmaestro on Sat Nov 06, 2010 3:52 pm, edited 18 times in total.
BERESHEIT
Edwin Knoppert
Addict
Addict
Posts: 1073
Joined: Fri Apr 25, 2003 11:13 pm
Location: Netherlands
Contact:

Re: More saving icons

Post by Edwin Knoppert »

If you like to extend it?
A PNG compressed icon has the same structures but set to 0's and the ico data is the full png filedata.
While Windows XP can not show these icons, you actually can by reading the png data and feed it to gdiplus for example.
I did that with my icon combine tool (iconbrowser on my website's free downloads)

Btw, thanks for the code.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: More saving icons

Post by srod »

@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:
The biSizeImage field is normally only used when the bitmap is compressed using a run-length encoding and for other purposes it is usually safe to leave it at zero. I have certainly not encountered any problems with doing this and I do it a lot! :)
Last edited by srod on Tue Oct 13, 2009 6:03 pm, edited 1 time in total.
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: More saving icons

Post by netmaestro »

Oh, that's why! I wondered why it needed to be in both headers, that makes sense. There is one thing I'm confused about though, in your routine when your doing GetDIBits on the mask, you're using DIB_RGB_COLORS. So was I initially but it kept blowing my pointer to smithereens. After a couple hours' making absolutely sure I had enough memory allocated and getting nowhere I tried DIB_PAL_COLORS for it and it magically started to work properly. When you're using GetDIBits on a 1-depth bitmap, which is correct? I'm still a bit worried that something else is amiss but if there is I don't know what it might be.
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: More saving icons

Post by srod »

I am pretty sure that the reason your code crashes when using DIB_RGB_COLORS is that GetDIBits_() appends a color table to the given BITMAPINFO structure. In the case of your mask image, there will be two entries in the color table; #Black and #White.... confirmed (just ran a test).

If you switch the bi_mask BITMAPINFO variable to a pointer and allocate enough memory for this pointer for a BITMAPINFOHEADER structure + 2*SizeOf(RGBQUAD) then your code works fine with DIB_RGB_COLORS.

The only puzzle is... why does my code not crash because I also forgot about the color table? Ah I know, it doesn't crash because it overwites other local variables on the stack as my routine is in a procedure.

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

Re: More saving icons

Post by netmaestro »

Ok thanks, that explains it. I switched to the pointer as you suggested and made room for the two entries, now there's no problem. I appreciate your assistance :mrgreen:

I'm not finished with this, the plan from the beginning was to write a procedure that takes one hIcon and two optional hIcons and it writes what it gets to the single icon file. Then you can use it to create an ico file containing three different-sized icons, say one at 48*48, one 32*32 and a 16*16. Gdiplus does such a beautiful job of resizing alpha images that a person could probably pass it one at 32*32 and have it create two others at the other sizes for the ico file. It's better than letting the OS resize a single icon for the various sizes it needs.
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: More saving icons

Post by srod »

Having multiple icons in a single icon file would be useful yes.
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: More saving icons: Now with multiple hIcons

Post by netmaestro »

Code is updated, it's now able to place an unlimited number of icons in the file.
Last edited by netmaestro on Sun Nov 01, 2009 6:17 pm, edited 1 time in total.
BERESHEIT
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: More saving icons: Now with multiple entries

Post by netmaestro »

***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.
BERESHEIT
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: More saving icons: updated Nov 15, 2009

Post by netmaestro »

Update Nov 15: Code is modified to automatically save at the lowest bitdepth possible based on the number of unique colors found in the icon's color bitmap. This has the effect of dramatically reducing the size of the saved icons.
BERESHEIT
Denis
Enthusiast
Enthusiast
Posts: 778
Joined: Fri Apr 25, 2003 5:10 pm
Location: Doubs - France

Re: More saving icons: updated Nov 15, 2009

Post by Denis »

Hi netmaestro,

This morning, i've tested your code inside PureIconManager (adapted to it) .
For now, my tests are Ok.
I'm interesting to dramatically reduce the size for ico files (as you wrote), in particular for compressed icons (Vista) with a low resolution you want to extract without compression.

I just added GdiFlush_() a second time after SetDIBits_() API. But I wonder whether that is really necessary.

I will use your procs (adapted) [CountColorsUsed() & MinimizeColors()] and adapted my own saveicon proc with your's.

I will add a link to this tread in PureIconManager.

Thank you for sharing.
A+
Denis
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Re: More saving icons: updated Nov 15, 2009

Post by rsts »

Sorry for a belated reply, but I just had a need to create some icons from images and found this.

Works a treat Mr maestro :D

Extremely well done and very useful.

cheers
IdeasVacuum
Always Here
Always Here
Posts: 6426
Joined: Fri Oct 23, 2009 2:33 am
Location: Wales, UK
Contact:

Re: More saving icons: updated Nov 15, 2009

Post by IdeasVacuum »

This code works very nicely but I can't figure-out how to get it to create an icon file containing several different sizes from the import of a single bitmap (png) file via the GDIplus companion piece?

It's the CreateBitmapFromFile() method that's got me, because here the image file is simply being loaded and is later applied to the first array element of hIcon using CreateHICONFromBitmap().

If LoadImage() could be used, the image could then be resized - but how to pass that image to CreateHICONFromBitmap()?
IdeasVacuum
If it sounds simple, you have not grasped the complexity.
IdeasVacuum
Always Here
Always Here
Posts: 6426
Joined: Fri Oct 23, 2009 2:33 am
Location: Wales, UK
Contact:

Re: More saving icons: updated Nov 15, 2009

Post by IdeasVacuum »

Struggling to get this to work. The return from CreateHICONFromBitmap() is 2, "Invalid Parameter":

Code: Select all

                   ;App Ico Image 255 (Vista/Win7)
                      iAppIco255PBID = CreateImage(#PB_Any,255,255,32|#PB_Image_Transparent)
                   If(iAppIco255PBID)

                          If StartDrawing(ImageOutput(iAppIco255PBID))

                                  DrawingMode(#PB_2DDrawing_AlphaBlend)
                                          Box(0,0,255,255,RGBA(255,255,255,0))
                                   DrawImage(iAppIco255OSID,0,0)
                                  StopDrawing()
                                   ;SaveImage(iAppIco255PBID, sPath + "AppIco255.png", #PB_ImagePlugin_PNG, 10, 32)
                          EndIf
                   EndIf

                   ;App Icon
                   InitGDIPlus()

                   Define iMyIcons.ICONARRAY
                   Define iOneIcon.i

                  ;return = CreateHICONFromBitmap(@iAppIco256PBID, iOneIcon)
                   return = CreateHICONFromBitmap(@iAppIco255PBID, iIcons\icons[0])

                   If WriteIconFile(@iMyIcons, 1, sPath + "AppIco.ico")

                            DeleteObject_(iMyIcons)
                                FreeImage(iAppIco255PBID)
                   EndIf

                   ShutdownGDIPlus()
IdeasVacuum
If it sounds simple, you have not grasped the complexity.
collectordave
Addict
Addict
Posts: 1310
Joined: Fri Aug 28, 2015 6:10 pm
Location: Portugal

Re: More saving icons: updated Nov 15, 2009

Post by collectordave »

Sorry to be a pain.

I am using this to save .ico files from images created by a vectoricon designer. Basically writing .png images then converting each one.

Reading code there is a WriteCursorFile procedure. Can this be used to create custom cursors? If so could you possibly post an example of creating a cursor file using this function?

Regards

CD
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
Post Reply