1package ico
2
3import (
4 "bytes"
5 "encoding/binary"
6 "fmt"
7 "image"
8 "image/png"
9)
10
11type IconDir struct {
12 Reserved uint16 // must be 0
13 Type uint16 // 1 for ICO, 2 for CUR
14 Count uint16 // number of images
15}
16
17type IconDirEntry struct {
18 Width uint8 // 0 means 256
19 Height uint8 // 0 means 256
20 ColorCount uint8
21 Reserved uint8 // must be 0
22 ColorPlanes uint16 // 0 or 1
23 BitsPerPixel uint16
24 SizeInBytes uint32
25 Offset uint32
26}
27
28func ImageToIco(img image.Image) ([]byte, error) {
29 // encode image as png
30 var pngBuf bytes.Buffer
31 if err := png.Encode(&pngBuf, img); err != nil {
32 return nil, fmt.Errorf("failed to encode PNG: %w", err)
33 }
34 pngData := pngBuf.Bytes()
35
36 // get image dimensions
37 bounds := img.Bounds()
38 width := bounds.Dx()
39 height := bounds.Dy()
40
41 // prepare output buffer
42 var icoBuf bytes.Buffer
43
44 iconDir := IconDir{
45 Reserved: 0,
46 Type: 1, // ICO format
47 Count: 1, // One image
48 }
49
50 w := uint8(width)
51 h := uint8(height)
52
53 // width/height of 256 should be stored as 0
54 if width == 256 {
55 w = 0
56 }
57 if height == 256 {
58 h = 0
59 }
60
61 iconDirEntry := IconDirEntry{
62 Width: w,
63 Height: h,
64 ColorCount: 0, // 0 for PNG (32-bit)
65 Reserved: 0,
66 ColorPlanes: 1,
67 BitsPerPixel: 32, // PNG with alpha
68 SizeInBytes: uint32(len(pngData)),
69 Offset: 6 + 16, // Size of ICONDIR + ICONDIRENTRY
70 }
71
72 // write IconDir
73 if err := binary.Write(&icoBuf, binary.LittleEndian, iconDir); err != nil {
74 return nil, fmt.Errorf("failed to write ICONDIR: %w", err)
75 }
76
77 // write IconDirEntry
78 if err := binary.Write(&icoBuf, binary.LittleEndian, iconDirEntry); err != nil {
79 return nil, fmt.Errorf("failed to write ICONDIRENTRY: %w", err)
80 }
81
82 // write PNG data directly
83 if _, err := icoBuf.Write(pngData); err != nil {
84 return nil, fmt.Errorf("failed to write PNG data: %w", err)
85 }
86
87 return icoBuf.Bytes(), nil
88}