Files
2026-06-24 03:16:40 +08:00

104 lines
3.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2026 S.A. (SNOWARE). Written for SCU Team.
// Licensed under MulanPubL-2.0.
// You may obtain a copy at: http://license.coscl.org.cn/MulanPubL-2.0
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
// trayicon_windows 模块:托盘图标加载,将 PNG 解码、缩放并包装为 Windows ICO 格式。
// getlantern/systray 在 Windows 上存在 GDI+ → CreateIconIndirect 兼容性 bug
// 解法是将 PNG 包装成 ICO 格式(现代 Windows 原生支持 ICO 内嵌 PNG)。
package main
import (
"bytes"
"encoding/binary"
"image"
"image/png"
"os"
"path/filepath"
"github.com/rs/zerolog/log"
)
// loadIcon 加载托盘图标。
// 优先从 image.png 文件读取,回退到内嵌 iconData。
// 解码 PNG → 缩放到 32x32 → 重新编码 → 包装为 ICO 格式返回。
func loadIcon(iconData []byte) []byte {
raw := iconData
exePath, err := os.Executable()
if err == nil {
iconFile := filepath.Join(filepath.Dir(exePath), "image.png")
if data, err := os.ReadFile(iconFile); err == nil {
raw = data
log.Debug().Str("file", iconFile).Msg("从文件读取图标")
}
}
img, err := png.Decode(bytes.NewReader(raw))
if err != nil {
log.Warn().Err(err).Msg("PNG 解码失败")
return nil
}
icon := resizeIcon(img, 32, 32)
var pngBuf bytes.Buffer
if err := png.Encode(&pngBuf, icon); err != nil {
log.Warn().Err(err).Msg("PNG 重新编码失败")
return nil
}
ico := pngToICO(pngBuf.Bytes(), 32, 32)
log.Debug().Int("size", len(ico)).Msg("托盘图标已生成 (ICO 32x32)")
return ico
}
// pngToICO 将 PNG 字节数据包装为 Windows ICO 文件格式。
// ICO 结构:6 字节文件头 + 16 字节目录项 + PNG 图像数据。
func pngToICO(pngData []byte, w, h int) []byte {
var buf bytes.Buffer
// ICO 文件头
binary.Write(&buf, binary.LittleEndian, uint16(0)) // 保留
binary.Write(&buf, binary.LittleEndian, uint16(1)) // 类型: ICO
binary.Write(&buf, binary.LittleEndian, uint16(1)) // 图标数量
// 处理 256 边界的宽度/高度
width := uint8(w)
if w >= 256 {
width = 0
}
height := uint8(h)
if h >= 256 {
height = 0
}
// ICO 目录项
binary.Write(&buf, binary.LittleEndian, width)
binary.Write(&buf, binary.LittleEndian, height)
binary.Write(&buf, binary.LittleEndian, uint8(0))
binary.Write(&buf, binary.LittleEndian, uint8(0))
binary.Write(&buf, binary.LittleEndian, uint16(1))
binary.Write(&buf, binary.LittleEndian, uint16(32))
binary.Write(&buf, binary.LittleEndian, uint32(len(pngData)))
binary.Write(&buf, binary.LittleEndian, uint32(22))
buf.Write(pngData)
return buf.Bytes()
}
// resizeIcon 使用最近邻插值将图像缩放到指定尺寸。
// 返回 *image.RGBA 避免与原图共享底层数据。
func resizeIcon(src image.Image, w, h int) *image.RGBA {
dst := image.NewRGBA(image.Rect(0, 0, w, h))
sb := src.Bounds()
for y := range h {
for x := range w {
sx := sb.Min.X + x*sb.Dx()/w
sy := sb.Min.Y + y*sb.Dy()/h
dst.Set(x, y, src.At(sx, sy))
}
}
return dst
}