Files
go_win_template/pkg/icongen/main.go
Geliebte a2a1161f10 feat: 接入托盘 / 添加图标生成工具 / 重命名并完善前端
- 托盘:用 getlantern/systray 重写 internal/tray,支持显示/隐藏窗口与退出
- 图标:新增 pkg/icongen 工具,支持 png/jpg/gif/bmp/webp,输出 ico/png 多个尺寸
- 重命名:项目默认名改为「青梧映星」,wails.json/productname/companyname/copyright 同步更新
- 顶栏:新增顶部导航栏(主题切换、设置、用户),iconfont 自托管 fa-solid-900.woff2
- 主题:CSS 变量 + html[data-theme] 控制白昼/黑夜切换,同时调用 WindowSetDarkTheme/LightTheme
- 底部:版权栏包含工作室、开发者与点击跳转客服链接
- 打包:wails build + exe 版本更新至 1.20 MB
2026-09-18 01:30:13 +08:00

161 lines
4.8 KiB
Go
Raw 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.
// Command icongen 根据一张图片快速生成当前项目的托盘图标与打包图标,
// 已存在的图标文件会被直接覆盖。
//
// 用法(在项目根目录执行):
//
// go run ./pkg/icongen -src /path/to/image.png
//
// 可选参数:
//
// go run ./pkg/icongen -src logo.jpg -root . -appicon 1024 -tray 256
package main
import (
"bytes"
"encoding/binary"
"flag"
"fmt"
"image"
imagedraw "image/draw"
"image/png"
"log"
"os"
"path/filepath"
"golang.org/x/image/draw"
_ "golang.org/x/image/webp"
_ "image/gif"
_ "image/jpeg"
)
// ico 文件中包含的尺寸
var icoSizes = []int{256, 128, 64, 48, 32, 16}
func main() {
src := flag.String("src", "", "源图片路径(png/jpg/jpeg/gif/bmp/webp)")
root := flag.String("root", ".", "项目根目录")
appicon := flag.Int("appicon", 1024, "build/appicon.png 尺寸")
icon := flag.Int("icon", 1024, "build/icon.png 尺寸")
tray := flag.Int("tray", 256, "trayicons/assets 图标尺寸")
flag.Parse()
if *src == "" {
log.Fatal("[-src] 源图片路径不能为空")
}
f, err := os.Open(*src)
if err != nil {
log.Fatalf("打开源图片失败: %v", err)
}
defer f.Close()
srcImg, _, err := image.Decode(f)
if err != nil {
log.Fatalf("解码源图片失败(支持 png/jpg/gif/bmp/webp): %v", err)
}
log.Printf("源图片: %s (%dx%d)", *src, srcImg.Bounds().Dx(), srcImg.Bounds().Dy())
cropped := centerCrop(srcImg)
// PNG 目标:源图居中裁剪后缩放
writePNG(filepath.Join(*root, "build", "appicon.png"), cropped, *appicon)
writePNG(filepath.Join(*root, "build", "icon.png"), cropped, *icon)
writePNG(filepath.Join(*root, "assets", "icon.png"), cropped, *tray)
writePNG(filepath.Join(*root, "trayicons", "icon.png"), cropped, *tray)
// ICO 目标:Windows exe / 托盘图标(多尺寸)
writeICO(filepath.Join(*root, "build", "windows", "icon.ico"), cropped, icoSizes)
log.Println("完成,所有图标已按需覆盖。")
}
// centerCrop 从图像中心裁剪出正方形区域。
func centerCrop(srcImg image.Image) image.Image {
b := srcImg.Bounds()
w, h := b.Dx(), b.Dy()
side := w
if h < side {
side = h
}
x0 := b.Min.X + (w-side)/2
y0 := b.Min.Y + (h-side)/2
rect := image.Rect(0, 0, side, side)
out := image.NewRGBA(rect)
imagedraw.Draw(out, rect, srcImg, image.Pt(x0, y0), imagedraw.Src)
return out
}
// writePNG 按 size 缩放后写出 PNG 文件。
func writePNG(path string, cropped image.Image, size int) {
dst := image.NewRGBA(image.Rect(0, 0, size, size))
draw.CatmullRom.Scale(dst, dst.Bounds(), cropped, cropped.Bounds(), draw.Over, nil)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
log.Fatalf("创建目录失败 %s: %v", filepath.Dir(path), err)
}
out, err := os.Create(path)
if err != nil {
log.Fatalf("创建文件失败 %s: %v", path, err)
}
defer out.Close()
if err := png.Encode(out, dst); err != nil {
log.Fatalf("编码 PNG 失败 %s: %v", path, err)
}
fmt.Printf(" [ok] %s (%dx%d)\n", path, size, size)
}
// writeICO 写出多尺寸 ICO(Windows 图标,PNG 条目压缩,Vista+ 支持)。
func writeICO(path string, cropped image.Image, sizes []int) {
type entry struct {
w, h byte
data []byte
}
entries := make([]entry, 0, len(sizes))
for _, s := range sizes {
dst := image.NewRGBA(image.Rect(0, 0, s, s))
draw.CatmullRom.Scale(dst, dst.Bounds(), cropped, cropped.Bounds(), draw.Over, nil)
var pngBuf bytes.Buffer
if err := png.Encode(&pngBuf, dst); err != nil {
log.Fatalf("编码 ICO 条目失败 (%dpx): %v", s, err)
}
w, h := byte(s), byte(s)
if s == 256 {
w, h = 0, 0 // 0 表示 256
}
entries = append(entries, entry{w: w, h: h, data: pngBuf.Bytes()})
}
var buf bytes.Buffer
// ICONDIR(6 字节头)
binary.Write(&buf, binary.LittleEndian, uint16(0)) // Reserved
binary.Write(&buf, binary.LittleEndian, uint16(1)) // Type: icon
binary.Write(&buf, binary.LittleEndian, uint16(len(entries)))
// ICONDIRENTRY(16 字节 x N)
cur := 6 + 16*len(entries)
for _, e := range entries {
binary.Write(&buf, binary.LittleEndian, e.w)
binary.Write(&buf, binary.LittleEndian, e.h)
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(e.data)))
binary.Write(&buf, binary.LittleEndian, uint32(cur))
cur += len(e.data)
}
// PNG 图像数据
for _, e := range entries {
buf.Write(e.data)
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
log.Fatalf("创建目录失败 %s: %v", filepath.Dir(path), err)
}
if err := os.WriteFile(path, buf.Bytes(), 0o644); err != nil {
log.Fatalf("写入 ICO 失败 %s: %v", path, err)
}
fmt.Printf(" [ok] %s (ico %v)\n", path, sizes)
}