Files
winauth-go/tools/gen_icons/main.go
T
iceking2nd fedc198452 feat(ui): vendor 图标系统 + 应用图标占位
- 新增 tools/gen_icons 生成 7 张 vendor 占位 PNG + 256x256 应用图标
- internal/ui/vendor_icons.go 用 //go:embed 加载并按 Authenticator.Name() 查表
- entry 行圆环中心叠 16dp vendor 图标,HOTP 直接用图标占位
- 图标为通用色块 + 首字母,无第三方商标;用户可按文件名替换为真实 logo
- assets/README 说明替换方式与 Windows .syso 嵌入步骤
2026-06-12 03:47:00 +08:00

190 lines
5.5 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.
// Code generator for placeholder vendor icons + the app window icon.
//
// We embed PNG sources rather than drawing the icons in Gio at every
// frame so that:
// - the work happens once at build/dev time, not per redraw;
// - users can swap any single PNG for a real vendor logo without
// changing code, just dropping it under internal/ui/assets/
// vendor_icons/<name>.png.
//
// Run from the repository root:
//
// go run ./tools/gen_icons
//
// Output:
// - internal/ui/assets/vendor_icons/<vendor>.png (7 files)
// - internal/ui/assets/app_icon.png (256x256)
//
// The icons are intentionally generic (coloured rounded squares with a
// single white initial) so they carry no third-party trademarks. Replace
// them with real vendor logos at your discretion and at your own legal
// risk.
package main
import (
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"log"
"os"
"path/filepath"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
)
type spec struct {
name string // file name without extension; also lookup key
letter string // single character to centre on the tile
rgb [3]uint8
}
var vendorSpecs = []spec{
{"google", "G", [3]uint8{0x42, 0x85, 0xF4}},
{"microsoft", "M", [3]uint8{0x00, 0xA4, 0xEF}},
{"okta", "O", [3]uint8{0x00, 0x7D, 0xC1}},
{"hotp", "#", [3]uint8{0x6A, 0x6A, 0x6A}},
{"battlenet", "B", [3]uint8{0x14, 0x8E, 0xFF}},
{"steam", "S", [3]uint8{0x1B, 0x2A, 0x38}},
// "qr" is used when the entry was added via QR scan and the source
// vendor is unknown to us — a neutral grid-like tile keeps the row
// from looking broken.
{"qr", "Q", [3]uint8{0x3A, 0x3A, 0x3A}},
}
const (
tileSize = 64
cornerRad = 12
appIconSz = 256
)
func main() {
root := filepath.Join("internal", "ui", "assets")
vendorDir := filepath.Join(root, "vendor_icons")
if err := os.MkdirAll(vendorDir, 0o755); err != nil {
log.Fatal(err)
}
for _, s := range vendorSpecs {
path := filepath.Join(vendorDir, s.name+".png")
if err := writePNG(path, drawTile(tileSize, cornerRad, s.rgb, s.letter)); err != nil {
log.Fatal(err)
}
fmt.Println("wrote", path)
}
appPath := filepath.Join(root, "app_icon.png")
if err := writePNG(appPath, drawAppIcon()); err != nil {
log.Fatal(err)
}
fmt.Println("wrote", appPath)
}
// drawTile paints a rounded square of size×size pixels, filled with the
// given RGB and stamped with a single white character in the middle.
// We approximate rounded corners by simply skipping pixels outside the
// rounded-rect mask; that is cheap and good enough for 64×64 art.
func drawTile(size, radius int, rgb [3]uint8, letter string) *image.NRGBA {
img := image.NewNRGBA(image.Rect(0, 0, size, size))
fill := color.NRGBA{R: rgb[0], G: rgb[1], B: rgb[2], A: 0xff}
for y := 0; y < size; y++ {
for x := 0; x < size; x++ {
if insideRoundRect(x, y, size, size, radius) {
img.Set(x, y, fill)
}
}
}
stampLetter(img, letter)
return img
}
// drawAppIcon scales the "WinAuth" mark up to 256×256 so we have a
// reusable file users can convert to .ico for window-class registration.
func drawAppIcon() *image.NRGBA {
rgb := [3]uint8{0x10, 0x70, 0xff}
img := image.NewNRGBA(image.Rect(0, 0, appIconSz, appIconSz))
fill := color.NRGBA{R: rgb[0], G: rgb[1], B: rgb[2], A: 0xff}
for y := 0; y < appIconSz; y++ {
for x := 0; x < appIconSz; x++ {
if insideRoundRect(x, y, appIconSz, appIconSz, 48) {
img.Set(x, y, fill)
}
}
}
// Centred "W" approximation using the basicfont — small but legible.
drawCenteredText(img, "W")
return img
}
func insideRoundRect(x, y, w, h, r int) bool {
if x < r && y < r {
dx, dy := r-x, r-y
return dx*dx+dy*dy <= r*r
}
if x >= w-r && y < r {
dx, dy := x-(w-r-1), r-y
return dx*dx+dy*dy <= r*r
}
if x < r && y >= h-r {
dx, dy := r-x, y-(h-r-1)
return dx*dx+dy*dy <= r*r
}
if x >= w-r && y >= h-r {
dx, dy := x-(w-r-1), y-(h-r-1)
return dx*dx+dy*dy <= r*r
}
return true
}
// stampLetter writes a single character centred on the tile using
// basicfont.Face7x13. basicfont is part of golang.org/x/image and ships
// with the project already, so this adds no new dependency.
func stampLetter(img draw.Image, letter string) {
face := basicfont.Face7x13
advance := font.MeasureString(face, letter).Ceil()
bounds := img.Bounds()
x := bounds.Min.X + (bounds.Dx()-advance)/2
y := bounds.Min.Y + bounds.Dy()/2 + face.Ascent/2
d := &font.Drawer{
Dst: img,
Src: image.NewUniform(color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}),
Face: face,
Dot: fixed.P(x, y),
}
d.DrawString(letter)
}
// drawCenteredText is the same as stampLetter but tuned for the big
// app-icon canvas: it stretches the glyph by drawing it several times
// with small offsets so the result reads at 256×256.
func drawCenteredText(img draw.Image, s string) {
face := basicfont.Face7x13
advance := font.MeasureString(face, s).Ceil()
bounds := img.Bounds()
x := bounds.Min.X + (bounds.Dx()-advance*6)/2
y := bounds.Min.Y + bounds.Dy()/2 + face.Ascent*3
d := &font.Drawer{
Dst: img,
Src: image.NewUniform(color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}),
Face: face,
}
// Bloat the small glyph by stamping it in a 6×6 grid of pixel
// offsets — crude but yields a readable "W" on the app icon.
for dy := 0; dy < 6; dy++ {
for dx := 0; dx < 6; dx++ {
d.Dot = fixed.P(x+dx, y+dy)
d.DrawString(s)
}
}
}
func writePNG(path string, img image.Image) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
return png.Encode(f, img)
}