// 一次性 icon 生成工具 — 產 visionA Local logo 的多種解析度 PNG // 用法:go run gen_icon.go // // 此檔案為 standalone 工具,不屬於 visiona-local app。 // 用 build tag 避免被一般 go build 抓到(只有明確 go run 才執行)。 //go:build ignore package main import ( "fmt" "image" "image/color" "image/png" "math" "os" "path/filepath" "strconv" ) var ( bgTop = color.RGBA{0x1A, 0x1F, 0x36, 0xFF} bgBottom = color.RGBA{0x0E, 0x12, 0x22, 0xFF} lensTop = color.RGBA{0x6E, 0xA8, 0xFF, 0xFF} lensBot = color.RGBA{0x4F, 0x7E, 0xFF, 0xFF} lensMid = color.RGBA{0x4F, 0x7E, 0xFF, 0x99} centerGlow = color.RGBA{0x6E, 0xF3, 0xC5, 0xE0} white = color.RGBA{0xFF, 0xFF, 0xFF, 0xFF} mint = color.RGBA{0x6E, 0xF3, 0xC5, 0xFF} lightBlue = color.RGBA{0x6E, 0xA8, 0xFF, 0xFF} ) func lerpColor(a, b color.RGBA, t float64) color.RGBA { if t < 0 { t = 0 } if t > 1 { t = 1 } return color.RGBA{ R: uint8(float64(a.R)*(1-t) + float64(b.R)*t), G: uint8(float64(a.G)*(1-t) + float64(b.G)*t), B: uint8(float64(a.B)*(1-t) + float64(b.B)*t), A: uint8(float64(a.A)*(1-t) + float64(b.A)*t), } } func blendAlpha(dst, src color.RGBA) color.RGBA { if src.A == 0 { return dst } if src.A == 0xFF { return src } sa := float64(src.A) / 255.0 da := float64(dst.A) / 255.0 out := 1.0 - (1.0-sa)*(1.0-da) if out == 0 { return dst } return color.RGBA{ R: uint8((float64(src.R)*sa + float64(dst.R)*da*(1-sa)) / out), G: uint8((float64(src.G)*sa + float64(dst.G)*da*(1-sa)) / out), B: uint8((float64(src.B)*sa + float64(dst.B)*da*(1-sa)) / out), A: uint8(out * 255), } } func rgba(c color.Color) color.RGBA { if r, ok := c.(color.RGBA); ok { return r } r, g, b, a := c.RGBA() return color.RGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)} } func drawRoundedRect(img *image.RGBA, size int, radius float64, colorAt func(y int) color.RGBA) { cx, cy := float64(size)/2, float64(size)/2 for y := 0; y < size; y++ { c := colorAt(y) for x := 0; x < size; x++ { dx := math.Max(0, math.Abs(float64(x)-cx+0.5)-(float64(size)/2-radius)) dy := math.Max(0, math.Abs(float64(y)-cy+0.5)-(float64(size)/2-radius)) d := math.Hypot(dx, dy) if d <= radius { a := 1.0 if d > radius-1 { a = radius - d } if a < 0 { continue } if a > 1 { a = 1 } cc := c cc.A = uint8(float64(cc.A) * a) img.Set(x, y, blendAlpha(rgba(img.At(x, y)), cc)) } } } } func drawCircleRing(img *image.RGBA, cx, cy, rOuter, rInner float64, c color.RGBA) { size := img.Bounds().Dx() for y := int(cy-rOuter-1) - 1; y <= int(cy+rOuter+1)+1; y++ { for x := int(cx-rOuter-1) - 1; x <= int(cx+rOuter+1)+1; x++ { if x < 0 || y < 0 || x >= size || y >= size { continue } d := math.Hypot(float64(x)-cx+0.5, float64(y)-cy+0.5) if d <= rOuter && d >= rInner { a := 1.0 if d > rOuter-1 { a = rOuter - d } else if d < rInner+1 { a = d - rInner } if a < 0 { continue } if a > 1 { a = 1 } cc := c cc.A = uint8(float64(cc.A) * a) img.Set(x, y, blendAlpha(rgba(img.At(x, y)), cc)) } } } } func drawFilledCircle(img *image.RGBA, cx, cy, r float64, c color.RGBA) { size := img.Bounds().Dx() for y := int(cy-r-1) - 1; y <= int(cy+r+1)+1; y++ { for x := int(cx-r-1) - 1; x <= int(cx+r+1)+1; x++ { if x < 0 || y < 0 || x >= size || y >= size { continue } d := math.Hypot(float64(x)-cx+0.5, float64(y)-cy+0.5) if d <= r { a := 1.0 if d > r-1 { a = r - d } if a < 0 { continue } cc := c cc.A = uint8(float64(cc.A) * a) img.Set(x, y, blendAlpha(rgba(img.At(x, y)), cc)) } } } } func drawRadialGlow(img *image.RGBA, cx, cy, r float64, c color.RGBA) { size := img.Bounds().Dx() for y := int(cy-r-1) - 1; y <= int(cy+r+1)+1; y++ { for x := int(cx-r-1) - 1; x <= int(cx+r+1)+1; x++ { if x < 0 || y < 0 || x >= size || y >= size { continue } d := math.Hypot(float64(x)-cx+0.5, float64(y)-cy+0.5) if d <= r { t := 1.0 - d/r t = t * t cc := c cc.A = uint8(float64(cc.A) * t) img.Set(x, y, blendAlpha(rgba(img.At(x, y)), cc)) } } } } func drawLine(img *image.RGBA, x1, y1, x2, y2, width float64, c color.RGBA) { size := img.Bounds().Dx() r := width / 2 minX := int(math.Min(x1, x2) - r - 1) maxX := int(math.Max(x1, x2) + r + 1) minY := int(math.Min(y1, y2) - r - 1) maxY := int(math.Max(y1, y2) + r + 1) dx := x2 - x1 dy := y2 - y1 lenSq := dx*dx + dy*dy for y := minY; y <= maxY; y++ { for x := minX; x <= maxX; x++ { if x < 0 || y < 0 || x >= size || y >= size { continue } px := float64(x) + 0.5 py := float64(y) + 0.5 var t float64 if lenSq > 0 { t = ((px-x1)*dx + (py-y1)*dy) / lenSq if t < 0 { t = 0 } if t > 1 { t = 1 } } cx := x1 + t*dx cy := y1 + t*dy d := math.Hypot(px-cx, py-cy) if d <= r { a := 1.0 if d > r-1 { a = r - d } if a < 0 { continue } cc := c cc.A = uint8(float64(cc.A) * a) img.Set(x, y, blendAlpha(rgba(img.At(x, y)), cc)) } } } } func renderLogo(size int) *image.RGBA { img := image.NewRGBA(image.Rect(0, 0, size, size)) s := float64(size) k := s / 1024.0 radius := 220.0 * k drawRoundedRect(img, size, radius, func(y int) color.RGBA { t := float64(y) / s return lerpColor(bgTop, bgBottom, t) }) cx := 512.0 * k cy := 512.0 * k outerR := 360.0 * k outerW := 36.0 * k drawCircleRing(img, cx, cy, outerR, outerR-outerW, lensTop) drawCircleRing(img, cx, cy, outerR, outerR-outerW/2, lerpColor(lensTop, lensBot, 0.6)) midR := 296.0 * k midW := 14.0 * k drawCircleRing(img, cx, cy, midR, midR-midW, lensMid) glowR := 240.0 * k drawRadialGlow(img, cx, cy, glowR, centerGlow) lineW := 44.0 * k drawLine(img, 360*k, 360*k, 512*k, 640*k, lineW, white) drawLine(img, 664*k, 360*k, 512*k, 640*k, lineW, white) drawFilledCircle(img, 360*k, 360*k, 22*k, mint) drawFilledCircle(img, 664*k, 360*k, 22*k, lightBlue) drawFilledCircle(img, 512*k, 640*k, 26*k, white) drawFilledCircle(img, 760*k, 264*k, 14*k, mint) drawCircleRing(img, 760*k, 264*k, 24*k, 21*k, color.RGBA{0x6E, 0xF3, 0xC5, 0x66}) return img } func savePNG(img *image.RGBA, path string) error { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return err } f, err := os.Create(path) if err != nil { return err } defer f.Close() return png.Encode(f, img) } func main() { if len(os.Args) < 2 { fmt.Fprintln(os.Stderr, "usage: go run gen_icon.go ") os.Exit(1) } outDir := os.Args[1] sizes := []int{16, 24, 32, 48, 64, 96, 128, 256, 512, 1024} for _, s := range sizes { img := renderLogo(s) path := filepath.Join(outDir, "icon-"+strconv.Itoa(s)+".png") if err := savePNG(img, path); err != nil { fmt.Fprintln(os.Stderr, "save", path, err) os.Exit(1) } fmt.Println("wrote", path) } }