Converting a .png to a .ico file

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
teadrinker
Posts: 4412
Joined: 29 Mar 2015, 09:41
Contact:

Re: Converting a .png to a .ico file

16 Aug 2021, 18:41

malcev wrote: You tell that there is nothing new under the sun!
Not me, but Solomon!
Don't take it so seriously, it's just a saying. :)
teadrinker
Posts: 4412
Joined: 29 Mar 2015, 09:41
Contact:

Re: Converting a .png to a .ico file

17 Aug 2021, 04:00

Managed to pack some images to png.
Failed to save the bit depth other than 32 bpp. If someone wants to try, comment out lines if (bitsPerPixel != 32) { ... }.

Code: Select all

SetBatchLines, -1

pngImagePath := "D:\Downloads\image.png"
destIcoPath := A_Desktop . "\test.ico"

Icons := []
PngDataFor48  := {size:  48, pack: true}
PngDataFor256 := {size: 256, pack: true}

for k, v in [16, 24, 32, PngDataFor48, PngDataFor256] {
   hBitmap := ScaleImage(pngImagePath, v)
   ( !IsObject(v) && hIcon := HIconFromHBitmap(hBitmap) )
   Icons.Push( IsObject(v) ? v : hIcon )
   DllCall("DeleteObject", "Ptr", hBitmap)
}
CollectIcon(destIcoPath, Icons)
for k, v in Icons {
   if !IsObject(v)
      DllCall("DestroyIcon", "Ptr", v)
   else {
      v.PngData.SetCapacity(0)
      v.PngData := ""
      v := ""
   }
}

ScaleImage(sourceImgFile, Data) {
   static CLSID_WICImagingFactory       := "{CACAF262-9370-4615-A13B-9F5539DA4C0A}"
         , IID_IWICImagingFactory       := "{EC5EC8A9-C395-4314-9C77-54D7A935FF70}"
         , IID_IWICPixelFormatInfo      := "{e8eda601-3d48-431a-ab44-69059be88bbe}"
         , IID_IWICComponentInfo        := "{23bc3f0a-698b-4357-886b-f24d50671334}"
         , GUID_ContainerFormatPng      := "{1b7cfaf4-713f-473c-bbcd-6137425faeaf}"
         , GUID_WICPixelFormat32bppBGRA := "{6fddc324-4e03-4bfe-b185-3d77768dc90f}"
         
         , GENERIC_READ := 0x80000000, WICDecodeMetadataCacheOnDemand := 0, WICBitmapEncoderNoCache := 0x00000002
         , WICBitmapUseAlpha := 0, WICBitmapInterpolationModeFant := 0x3, WICBitmapInterpolationModeHighQualityCubic := 0x4
         , InterpolationMode := A_OSVersion ~= "^\d" ? WICBitmapInterpolationModeHighQualityCubic : WICBitmapInterpolationModeFant
         , szBITMAP := 16 + A_PtrSize*2, szBITMAPINFOHEADER := 40, szDIBSECTION := szBITMAP + szBITMAPINFOHEADER + 8 + A_PtrSize*3
         , GUID, _ := VarSetCapacity(GUID, 16, 0), STATFLAG_NONAME := 0x00000001, STREAM_SEEK_SET := 0
         
   if !IsObject(Data)
      maxSize := Data
   else
      pack := Data.pack, maxSize := Data.size
   IWICImagingFactory := ComObjCreate(CLSID_WICImagingFactory, IID_IWICImagingFactory)
   Vtable( IWICImagingFactory , CreateDecoderFromFilename :=  3 ).Call( "WStr", sourceImgFile, "Ptr", 0, "UInt", GENERIC_READ
                                                                      , "UInt", WICDecodeMetadataCacheOnDemand, "PtrP", IWICBitmapDecoder, "UInt" )
   Vtable( IWICBitmapDecoder  , GetFrame                  := 13 ).Call( "UInt", 0, "PtrP", IWICBitmapSource )
   Vtable( IWICBitmapSource   , GetPixelFormat            :=  4 ).Call("Ptr", &GUID)
   Vtable( IWICImagingFactory , CreateComponentInfo       :=  6 ).Call("Ptr", &GUID, "PtrP", IWICComponentInfo)
   
   IWICPixelFormatInfo := ComObjQuery(IWICComponentInfo, IID_IWICPixelFormatInfo), ObjRelease(IWICComponentInfo)
   Vtable( IWICPixelFormatInfo, GetBitsPerPixel           := 13 ).Call("UIntP", bitsPerPixel)
   
   if (bitsPerPixel != 32) {
      DllCall("ole32\CLSIDFromString", "WStr", GUID_WICPixelFormat32bppBGRA, "Ptr", &GUID)
      hr := DllCall("Windowscodecs\WICConvertBitmapSource", "Ptr", &GUID, "Ptr", IWICBitmapSource, "PtrP", DestIWICBitmapSource)
      if (hr = 0)
         ObjRelease(IWICBitmapSource), IWICBitmapSource := DestIWICBitmapSource, bitsPerPixel := 32
   }
   Vtable( IWICBitmapSource   , GetSize                   :=  3 ).Call("UIntP", width, "UIntP", height)
   
   if (width > height)
      height *= maxSize/width, width := maxSize
   else
      width *= maxSize/height, height := maxSize
   width := Round(width), height := Round(height)
   stride := (width*bitsPerPixel + 7)//8
   size := stride*height
   
   hBitmap := CreateDIBSection(width, -height, pBits, bitsPerPixel)
   Vtable( IWICImagingFactory, CreateBitmapScaler := 11 ).Call("PtrP", IWICBitmapScaler)
   Vtable( IWICBitmapScaler  , Initialize         :=  8 ).Call("Ptr" , IWICBitmapSource, "UInt", width, "UInt", height, "UInt", InterpolationMode)
   Vtable( IWICBitmapScaler  , CopyPixels         :=  7 ).Call("Ptr", 0, "UInt", stride, "UInt", size, "Ptr", pBits)
   for k, v in [IWICBitmapScaler, IWICPixelFormatInfo, IWICBitmapSource, IWICBitmapDecoder]
      ObjRelease(v)

   if !(width = maxSize && height = maxSize)
      BitmapPadding(hBitmap, maxSize, width, height, bitsPerPixel), stride := (maxSize*bitsPerPixel + 7)//8
   if pack {
      VarSetCapacity(DIBSECTION, szDIBSECTION, 0)
      DllCall("ole32\CLSIDFromString", "WStr", GUID_ContainerFormatPng, "Ptr", &GUID)
      
      DllCall("GetObject", "Ptr", hBitmap, "UInt", szDIBSECTION, "Ptr", &DIBSECTION)
      pBits := NumGet(DIBSECTION, 16 + A_PtrSize), imgSize := NumGet(DIBSECTION, szBITMAP + 20, "UInt")
      DllCall("Ole32\CreateStreamOnHGlobal", "Ptr", 0, "UInt", true, "PtrP", IStream)
      Vtable( IWICImagingFactory   , CreateStream          := 14 ).Call("PtrP", IWICStream)
      Vtable( IWICStream           , InitializeFromIStream := 14 ).Call("Ptr", IStream)
      Vtable( IWICImagingFactory   , CreateEncoder         :=  8 ).Call("Ptr", &GUID, "Ptr", 0, "PtrP", IWICBitmapEncoder)
      Vtable( IWICBitmapEncoder    , Initialize            :=  3 ).Call("Ptr", IWICStream, "UInt", WICBitmapEncoderNoCache)
      Vtable( IWICBitmapEncoder    , CreateNewFrame        := 10 ).Call("PtrP", IWICBitmapFrameEncode, "Ptr", 0)
      Vtable( IWICBitmapFrameEncode, Initialize            :=  3 ).Call("Ptr", 0)
      Vtable( IWICBitmapFrameEncode, SetSize               :=  4 ).Call("UInt", maxSize, "UInt", maxSize)
      DllCall("ole32\CLSIDFromString", "WStr", GUID_WICPixelFormat32bppBGRA, "Ptr", &GUID)
      Vtable( IWICBitmapFrameEncode, SetPixelFormat        :=  6 ).Call("Ptr", &GUID)
      Vtable( IWICBitmapFrameEncode, WritePixels           := 10 ).Call("UInt", maxSize, "UInt", stride, "UInt", imgSize, "Ptr", pBits)
      Vtable( IWICBitmapFrameEncode, Commit                := 12 ).Call()
      Vtable( IWICBitmapEncoder    , Commit                := 11 ).Call()
      
      VarSetCapacity(STATSTG, 64 + A_PtrSize*2, 0)
      Vtable( IWICStream, Stat := 12 ).Call("Ptr", &STATSTG, "UInt", STATFLAG_NONAME)
      pngDataSize := NumGet(STATSTG, A_PtrSize*2, "UInt64")
      Data.SetCapacity("PngData", pngDataSize)
      Vtable( IWICStream, Seek :=  5 ).Call("Int64", 0, "UInt", STREAM_SEEK_SET, "Ptr", 0)
      Vtable( IWICStream, Read :=  3 ).Call("Ptr", Data.GetAddress("PngData"), "UInt", pngDataSize, "Ptr", 0)
      for k, v in [IWICBitmapFrameEncode, IWICBitmapEncoder, IStream, IWICStream]
         ObjRelease(v)
      Data.bitCount := bitsPerPixel
   }
   ObjRelease(IWICImagingFactory)
   Return hBitmap
}

Vtable(ptr, n) {
   Return Func("DllCall").Bind(NumGet(NumGet(ptr+0), A_PtrSize*n), "Ptr", ptr)
}

CreateDIBSection(w, h, ByRef ppvBits := 0, bpp := 32) {
   hDC := DllCall("GetDC", "Ptr", 0, "Ptr")
   VarSetCapacity(BITMAPINFO, 40, 0)
   NumPut(40 , BITMAPINFO,  0)
   NumPut( w , BITMAPINFO,  4)
   NumPut( h , BITMAPINFO,  8)
   NumPut( 1 , BITMAPINFO, 12)
   NumPut(bpp, BITMAPINFO, 14)
   hBM := DllCall("CreateDIBSection", "Ptr", hDC, "Ptr", &BITMAPINFO, "UInt", 0, "PtrP", ppvBits, "Ptr", 0, "UInt", 0, "Ptr")
   DllCall("ReleaseDC", "Ptr", 0, "Ptr", hDC)
   return hBM
}

BitmapPadding(ByRef hBitmap, maxSize, width, height, bitsPerPixel) {
   hDC_New := DllCall("CreateCompatibleDC", "Ptr", 0, "Ptr")
   hBM_New := CreateDIBSection(maxSize, -maxSize,, bitsPerPixel)
   oBM_New := DllCall("SelectObject", "Ptr", hDC_New, "Ptr", hBM_New, "Ptr")
   
   hDC := DllCall("CreateCompatibleDC", "Ptr", 0, "Ptr")
   oBM := DllCall("SelectObject", "Ptr", hDC, "Ptr", hBitmap, "Ptr")
   DllCall("BitBlt", "Ptr", hDC_New, "Int", (maxSize - width)//2, "Int", (maxSize - height)//2, "Int", width, "Int", height
                             , "Ptr", hDC, "Int", 0, "Int", 0, "UInt", 0x00CC0020)
   DllCall("SelectObject", "Ptr", hDC, "Ptr", oBM, "Ptr")
   DllCall("DeleteDC", "Ptr", hDC)
   DllCall("DeleteObject", "Ptr", hBitmap)
   DllCall("SelectObject", "Ptr", hDC_New, "Ptr", oBM_New, "Ptr")
   DllCall("DeleteDC", "Ptr", hDC_New)
   hBitmap := hBM_New
}

HIconFromHBitmap(hBitmap) {
   VarSetCapacity(BITMAP, size := 4*4 + A_PtrSize*2, 0)
   DllCall("GetObject", "Ptr", hBitmap, "Int", size, "Ptr", &BITMAP)
   width := NumGet(BITMAP, 4, "UInt"), height := NumGet(BITMAP, 8, "UInt")
   hDC := DllCall("GetDC", "Ptr", 0, "Ptr")
   hCBM := DllCall("CreateCompatibleBitmap", "Ptr", hDC, "Int", width, "Int", height, "Ptr")
   VarSetCapacity( ICONINFO, 4*2 + A_PtrSize*3, 0)
   NumPut(1      , ICONINFO)
   NumPut(hCBM   , ICONINFO, 4*2 + A_PtrSize)
   NumPut(hBitmap, ICONINFO, 4*2 + A_PtrSize*2)
   hIcon := DllCall("CreateIconIndirect", "Ptr", &ICONINFO, "Ptr")
   DllCall("DeleteObject", "Ptr", hCBM), DllCall("ReleaseDC", "Ptr", 0, "Ptr", hDC)
   Return hIcon
}

CollectIcon(destFile, Icons) {
   static szICONHEADER := 6, szICONDIRENTRY := 16, szBITMAPINFOHEADER := 40, DIB_RGB_COLORS := 0
   
   VarSetCapacity(ICONHEADER  , szICONHEADER  , 0)
   VarSetCapacity(ICONDIRENTRY, szICONDIRENTRY, 0)
   VarSetCapacity(ICONINFO, 8 + A_PtrSize*3, 0)

   NumPut(1, ICONHEADER, 2, "UShort")
   NumPut(Icons.Count(), ICONHEADER, 4, "UShort")
   File := FileOpen(destFile, "w", "cp0")
   File.RawWrite(ICONHEADER, szICONHEADER)

   allDataSize := 0
   for k, data in Icons {
      if IsObject(data) {
         width := height := data.size
         bitCount := data.bitCount
      }
      else {
         VarSetCapacity( clrBITMAPINFO, szBITMAPINFOHEADER, 0)
         VarSetCapacity(_mskBITMAPINFO, szBITMAPINFOHEADER, 0)
         VarSetCapacity( mskBITMAPINFO, szBITMAPINFOHEADER + 8, 0)
         
         DllCall("GetIconInfo", "Ptr", data, "Ptr", &ICONINFO)
         hbmMask  := NumGet(ICONINFO, 8 + A_PtrSize)
         hbmColor := NumGet(ICONINFO, 8 + A_PtrSize*2)

         NumPut(szBITMAPINFOHEADER, clrBITMAPINFO, "UInt")
         hDC := DllCall("GetDC", "Ptr", 0, "Ptr")
         DllCall("GetDIBits", "Ptr", hDC, "Ptr", hbmColor, "UInt", 0, "UInt", 0, "Ptr", 0, "Ptr", &clrBITMAPINFO, "UInt", DIB_RGB_COLORS)
         width    := NumGet(clrBITMAPINFO,  4, "UInt")
         height   := NumGet(clrBITMAPINFO,  8, "UInt")
         bitCount := NumGet(clrBITMAPINFO, 14, "UShort")
         clrSize  := NumGet(clrBITMAPINFO, 20, "UInt")

         VarSetCapacity(clrBits, clrSize, 0)
         NumPut(bitCount, clrBITMAPINFO, 14, "UShort")
         DllCall("GetDIBits", "Ptr", hDC, "Ptr", hbmColor, "UInt", 0, "UInt", height, "Ptr", &clrBits, "Ptr", &clrBITMAPINFO, "UInt", DIB_RGB_COLORS)

         NumPut(szBITMAPINFOHEADER, _mskBITMAPINFO, "UInt")
         DllCall("GetDIBits", "Ptr", hDC, "Ptr", hbmMask, "UInt", 0, "UInt", 0, "Ptr", 0, "Ptr", &_mskBITMAPINFO, "UInt", DIB_RGB_COLORS)
         VarSetCapacity(mskBits, clrSize, 0)
         DllCall("RtlMoveMemory", "Ptr", &mskBITMAPINFO, "Ptr", &_mskBITMAPINFO, "Ptr", szBITMAPINFOHEADER)
         DllCall("GetDIBits", "Ptr", hDC, "Ptr", hbmMask, "UInt", 0, "UInt", height, "Ptr", &mskBits, "Ptr", &mskBITMAPINFO, "UInt", DIB_RGB_COLORS)

         DllCall("ReleaseDC", "Ptr", 0, "Ptr", hDC)
         DllCall("DeleteObject", "Ptr", hbmMask)
         DllCall("DeleteObject", "Ptr", hbmColor)

         mskSize      := NumGet(mskBITMAPINFO, 20, "UInt")
         iconDataSize := clrSize + mskSize
      }
      clrPlanes   := 0
      bytesInRes  := IsObject(data) ? data.GetCapacity("PngData") : szBITMAPINFOHEADER + iconDataSize
      offset      := szICONHEADER + szICONDIRENTRY*Icons.Count() + allDataSize
      allDataSize += bytesInRes
      
      NumPut(width     , ICONDIRENTRY,  0, "UChar")
      NumPut(height    , ICONDIRENTRY,  1, "UChar" )
      NumPut(clrPlanes , ICONDIRENTRY,  4, "UShort")
      NumPut(bitCount  , ICONDIRENTRY,  6, "UShort")
      NumPut(bytesInRes, ICONDIRENTRY,  8, "UInt")
      NumPut(offset    , ICONDIRENTRY, 12, "UInt")

      if !IsObject(data) {
         NumPut(height*2    , clrBITMAPINFO,  8, "UInt")
         NumPut(0           , clrBITMAPINFO, 16, "UInt")
         NumPut(iconDataSize, clrBITMAPINFO, 20, "UInt")
      }
      File.Pos  := szICONHEADER + szICONDIRENTRY*(k - 1)
      File.RawWrite(ICONDIRENTRY, szICONDIRENTRY)
      
      File.Pos := offset
      if IsObject(data)
         File.RawWrite( data.GetAddress("PngData") + 0, data.GetCapacity("PngData") )
      else {
         File.RawWrite(clrBITMAPINFO, szBITMAPINFOHEADER)
         File.RawWrite(clrBits, clrSize)
         File.RawWrite(mskBits, mskSize)
      }
   }
   File.Close()
}
Any image file can be passed to the input including an icon, not just png.
User avatar
JoeWinograd
Posts: 2214
Joined: 10 Feb 2014, 20:00
Location: U.S. Central Time Zone

Re: Converting a .png to a .ico file

17 Aug 2021, 20:14

Hi teadrinker,
teadrinker wrote:Managed to pack some images to png.
Works perfectly! I added PngDataFor16, PngDataFor24, and PngDataFor32, and it created the five different size icons with the correct colors...and with a very reasonable size. For example, one 256x256 PNG that is 24,156 bytes converted into the 5-image ICO file at 36,946 bytes; another 256x256 PNG that is 72,017 bytes converted into the 5-image ICO file at 77,176 bytes. Superb! Thanks very much, Joe
User avatar
fade2gray
Posts: 85
Joined: 21 Apr 2015, 12:28

Re: Converting a .png to a .ico file

08 Dec 2023, 11:43

Hi, I've been using @teadrinker's code (15 Aug 2021, 01:57 above) in v1 script for a couple of years now, and while I've successfully converted the bulk of my script to v2, I'm stuck with trying to convert the functions of the Png2Icon converter.

Running the HIconFromHBitmap function through the AHK-v2-script-converter returns the following code but gives me invalid parameter errors at line 9.

Can anyone help me with the conversion to v2, please?

Code: Select all

HIconFromHBitmap(hBitmap) {
    BITMAP := Buffer(size := 4*4 + A_PtrSize*2, 0) ; V1toV2: if 'BITMAP' is a UTF-16 string, use 'VarSetStrCapacity(&BITMAP, size := 4*4 + A_PtrSize*2)'
    DllCall("GetObject", "Ptr", hBitmap, "Int", size, "Ptr", BITMAP)
    width := NumGet(BITMAP, 4, "UInt"), height := NumGet(BITMAP, 8, "UInt")
    hDC := DllCall("GetDC", "Ptr", 0, "Ptr")
    hCBM := DllCall("CreateCompatibleBitmap", "Ptr", hDC, "Int", width, "Int", height, "Ptr")
    ICONINFO := Buffer(4*2 + A_PtrSize*3, 0) ; V1toV2: if 'ICONINFO' is a UTF-16 string, use 'VarSetStrCapacity(&ICONINFO, 4*2 + A_PtrSize*3)'
    NumPut("UPtr", 1, ICONINFO)
    NumPut(4*2 + A_PtrSize, hCBM, ICONINFO)
    NumPut(4*2 + A_PtrSize*2, hBitmap, ICONINFO)
    hIcon := DllCall("CreateIconIndirect", "Ptr", ICONINFO, "Ptr")
    DllCall("DeleteObject", "Ptr", hCBM), DllCall("ReleaseDC", "Ptr", 0, "Ptr", hDC)
    Return hIcon
}
iPhilip
Posts: 835
Joined: 02 Oct 2013, 12:21

Re: Converting a .png to a .ico file

08 Dec 2023, 12:05

@fade2gray You are almost there.

Code: Select all

    NumPut("UPtr", 1, ICONINFO)
    NumPut(4*2 + A_PtrSize, hCBM, ICONINFO)
    NumPut(4*2 + A_PtrSize*2, hBitmap, ICONINFO)
should be

Code: Select all

    NumPut("Int", 1, ICONINFO)
    NumPut("Ptr", hCBM, ICONINFO, 4*2 + A_PtrSize)
    NumPut("Ptr", hBitmap, ICONINFO, 4*2 + A_PtrSize*2)
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
User avatar
fade2gray
Posts: 85
Joined: 21 Apr 2015, 12:28

Re: Converting a .png to a .ico file

08 Dec 2023, 12:43

Thanks @iPhilip, after getting over that hurdle and after converting the HiconToFile function to v2, I get "Expected a Number but got a VarRef" at File.RawWrite(&clrDIBSECTION + szBITMAP, szBITMAPINFOHEADER).

Edit: The error was rectified by changing File.RawWrite(&clrDIBSECTION + szBITMAP, szBITMAPINFOHEADER) to File.RawWrite(clrDIBSECTION.Ptr + szBITMAP, szBITMAPINFOHEADER).

I also found that when the HiconToFile function returned to the Png2Icon function, DllCall("DeleteObject", hBitmap) needed to be changed to DllCall("DeleteObject", "Ptr", hBitmap).

With @iPhilip's help, the v2 version of @teadrinker's code is:

Spoiler
Last edited by fade2gray on 09 Dec 2023, 11:25, edited 1 time in total.
iPhilip
Posts: 835
Joined: 02 Oct 2013, 12:21

Re: Converting a .png to a .ico file

08 Dec 2023, 12:56

fade2gray wrote:
08 Dec 2023, 12:43
Thanks @iPhilip, after getting over that hurdle and after converting the HiconToFile function to v2, I get "Expected a Number but got a VarRef" at File.RawWrite(&clrDIBSECTION + szBITMAP, szBITMAPINFOHEADER).
You are welcome.

The & operator creates a VarRef, which is a value representing a reference to a variable (See documentation page). In v1 the same operator passes the address of clrDIBSECTION's contents in memory. If clrDIBSECTION is a variable containing the address (I can't tell until you show more code), just omit the & operator.
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
User avatar
fade2gray
Posts: 85
Joined: 21 Apr 2015, 12:28

Re: Converting a .png to a .ico file

09 Dec 2023, 11:27

iPhilip wrote:
08 Dec 2023, 12:56
The & operator creates a VarRef, which is a value representing a reference to a variable (See documentation page). In v1 the same operator passes the address of clrDIBSECTION's contents in memory. If clrDIBSECTION is a variable containing the address (I can't tell until you show more code), just omit the & operator.
With your inspiration, I managed to sort it out - see the edit to my previous post.
iPhilip
Posts: 835
Joined: 02 Oct 2013, 12:21

Re: Converting a .png to a .ico file

09 Dec 2023, 12:23

:thumbup:
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Bing [Bot], Lpanatt, macromint, peter_ahk and 276 guests