Files
winauth-go/tools/gen_ico/main.go
T
iceking2nd 133091a32e feat(ui): 首次启动加密引导 + 真窗口图标 (.syso)
首次启动:
- 配置文件不存在时弹欢迎对话框:选「启用密码保护」或「稍后再说」
- 选启用直接进 setPasswordDialog,OK 时 store.SetPassword 触发首次保存
- io/fs.ErrNotExist 检测新分支
- modalCard 抽出 modalShell 内核以便 welcomeDialog 自定义 footer

真窗口图标:
- tools/gen_ico 从 PNG 生成多分辨率 .ico(7 尺寸:16~256)
- 提交 rsrc_windows.syso(rsrc 通过 goproxy.cn 生成),go build 自动链入
- tray_windows.go 改用嵌入资源 ID=1,失败 fallback IDI_APPLICATION
- README 更新完整的图标生成流程
2026-06-12 10:07:13 +08:00

145 lines
3.5 KiB
Go

// gen_ico produces a multi-resolution .ico file from a single PNG
// source. The output embeds each size as a PNG payload (Vista+ .ico
// format) so the result stays small and crisp.
//
// Usage:
//
// go run ./tools/gen_ico [src.png] [dst.ico]
//
// Defaults:
//
// src = internal/ui/assets/app_icon.png
// dst = internal/ui/assets/app_icon.ico
//
// After generating the .ico, embed it into the Windows binary via:
//
// go run github.com/akavel/rsrc@latest \
// -ico internal/ui/assets/app_icon.ico \
// -o internal/ui/assets/rsrc_windows.syso
//
// rsrc_windows.syso is auto-linked by `go build` on Windows.
package main
import (
"bytes"
"encoding/binary"
"fmt"
"image"
"image/png"
"io"
"os"
"golang.org/x/image/draw"
)
// icoSizes are the embedded resolutions. Windows picks the largest size
// that fits the target use (title bar = 16, Alt-Tab = 32, Explorer = up
// to 256) and scales down. Skipping sizes Windows can't read anyway
// keeps the file under 200 KB.
var icoSizes = []int{16, 24, 32, 48, 64, 128, 256}
// icoEntry holds one image's dimensions and its encoded PNG bytes.
type icoEntry struct {
w, h int
png []byte
}
func main() {
srcPath := "internal/ui/assets/app_icon.png"
dstPath := "internal/ui/assets/app_icon.ico"
if len(os.Args) > 1 {
srcPath = os.Args[1]
}
if len(os.Args) > 2 {
dstPath = os.Args[2]
}
if err := run(srcPath, dstPath); err != nil {
fmt.Fprintln(os.Stderr, "gen_ico:", err)
os.Exit(1)
}
}
func run(srcPath, dstPath string) error {
srcF, err := os.Open(srcPath)
if err != nil {
return fmt.Errorf("open src: %w", err)
}
defer srcF.Close()
src, _, err := image.Decode(srcF)
if err != nil {
return fmt.Errorf("decode src: %w", err)
}
var entries []icoEntry
for _, size := range icoSizes {
dst := image.NewNRGBA(image.Rect(0, 0, size, size))
// CatmullRom gives sharper results at small sizes than the
// default nearest-neighbour.
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
var buf bytes.Buffer
if err := png.Encode(&buf, dst); err != nil {
return fmt.Errorf("encode %dpx: %w", size, err)
}
entries = append(entries, icoEntry{w: size, h: size, png: buf.Bytes()})
}
out, err := os.Create(dstPath)
if err != nil {
return fmt.Errorf("create dst: %w", err)
}
defer out.Close()
return writeICO(out, entries)
}
// writeICO serialises a Vista+ PNG-based .ico file: ICONDIR + one
// ICONDIRENTRY per image + the PNG bytes concatenated.
func writeICO(w io.Writer, entries []icoEntry) error {
if _, err := w.Write([]byte{
0, 0, // reserved
1, 0, // type = 1 (icon)
byte(len(entries)), 0, // count
}); err != nil {
return err
}
const dirHdrLen = 6
const dirEntryLen = 16
headerLen := dirHdrLen + dirEntryLen*len(entries)
offset := headerLen
for _, e := range entries {
wByte := byte(e.w)
if e.w >= 256 {
// 0 means 256 in the .ico directory.
wByte = 0
}
hByte := byte(e.h)
if e.h >= 256 {
hByte = 0
}
size := uint32(len(e.png))
entry := make([]byte, dirEntryLen)
entry[0] = wByte
entry[1] = hByte
entry[2] = 0 // color palette
entry[3] = 0 // reserved
binary.LittleEndian.PutUint16(entry[4:], 1) // planes
binary.LittleEndian.PutUint16(entry[6:], 32) // bit count
binary.LittleEndian.PutUint32(entry[8:], size)
binary.LittleEndian.PutUint32(entry[12:], uint32(offset))
if _, err := w.Write(entry); err != nil {
return err
}
offset += int(size)
}
for _, e := range entries {
if _, err := w.Write(e.png); err != nil {
return err
}
}
return nil
}