package entity import ( "bytes" "encoding/base64" "fmt" "hash/adler32" "image" "image/color" "image/draw" "image/png" "math" "strings" "unicode" "unicode/utf8" ) const ( avatarSize = 256 avatarSat = 0.3 avatarVal = 0.9 glyphW = 5 glyphH = 7 glyphPx = 20 ) // LetterAvatarPNG draws a first-letter avatar. Hue follows FastAdmin letter_avatar (adler32 % 360). func LetterAvatarPNG(text string) ([]byte, error) { img := image.NewRGBA(image.Rect(0, 0, avatarSize, avatarSize)) bg := AvatarColor(text) draw.Draw(img, img.Bounds(), &image.Uniform{C: bg}, image.Point{}, draw.Src) if letter := AvatarLetter(text); letter != "" { if rows, ok := avatarGlyphs[letter[0]]; ok { blitGlyph(img, rows, color.White) } else { fillPerson(img, color.White) } } else { fillPerson(img, color.White) } var buf bytes.Buffer if err := png.Encode(&buf, img); err != nil { return nil, err } return buf.Bytes(), nil } const avatarDataURIPrefix = "data:image/svg+xml;base64," // LetterAvatarDataURI is a compact SVG fallback (FastAdmin letter_avatar). Always fits users.avatar VARCHAR(512). func LetterAvatarDataURI(text string) string { c := AvatarColor(text) fill := fmt.Sprintf("rgb(%d,%d,%d)", c.R, c.G, c.B) var svg string if letter := AvatarLetter(text); letter != "" { svg = fmt.Sprintf( ``+ ``+ `%s`, fill, letter, ) } else { svg = fmt.Sprintf( ``+ ``+ ``, fill, ) } return avatarDataURIPrefix + base64.StdEncoding.EncodeToString([]byte(svg)) } // AvatarLetter is the uppercase first rune. Non-Latin glyphs use the silhouette fallback. func AvatarLetter(text string) string { text = strings.TrimSpace(text) if text == "" { return "?" } r, _ := utf8.DecodeRuneInString(text) if r == utf8.RuneError { return "?" } r = unicode.ToUpper(r) if r > unicode.MaxASCII || !unicode.IsLetter(r) && !unicode.IsDigit(r) { return "" } return string(r) } // AvatarColor is the pastel background from adler32(text) % 360, sat 0.3 / val 0.9. func AvatarColor(text string) color.RGBA { hue := float64(adler32.Checksum([]byte(text))%360) / 360 return hsvRGB(hue, avatarSat, avatarVal) } func blitGlyph(img *image.RGBA, rows [glyphH]byte, c color.Color) { gw, gh := glyphW*glyphPx, glyphH*glyphPx ox := (avatarSize - gw) / 2 oy := (avatarSize - gh) / 2 for y := 0; y < glyphH; y++ { row := rows[y] for x := 0; x < glyphW; x++ { if row&(1<