2
2
package letteravatar
3
3
4
4
import (
5
+ "fmt"
6
+ "hash/fnv"
5
7
"image"
6
8
"image/color"
7
9
"image/draw"
8
- "math/rand"
9
- "sync"
10
- "time"
10
+ "math/rand/v2"
11
11
12
- "github.com/goki/freetype "
13
- "github.com/goki/freetype/truetype "
12
+ "golang.org/x/image/font "
13
+ "golang.org/x/image/font/opentype "
14
14
"golang.org/x/image/math/fixed"
15
15
)
16
16
17
17
// Options are letter-avatar parameters.
18
18
type Options struct {
19
- Font * truetype .Font
19
+ Font * opentype .Font
20
20
Palette []color.Color
21
21
LetterColor color.Color
22
22
FontSize int
@@ -27,47 +27,40 @@ type Options struct {
27
27
PaletteKey string
28
28
}
29
29
30
- var defaultLetterColor = color.RGBA {0xf0 , 0xf0 , 0xf0 , 0xf0 }
30
+ var defaultLetterColor = color.RGBA {R : 0xf0 , G : 0xf0 , B : 0xf0 , A : 0xf0 }
31
31
32
32
// Draw generates a new letter-avatar image of the given size using the given letter
33
33
// with the given options. Default parameters are used if a nil *Options is passed.
34
34
func Draw (size int , letters []rune , options * Options ) (image.Image , error ) {
35
- font := defaultFont
36
- if options != nil && options .Font != nil {
37
- font = options .Font
35
+ if options == nil {
36
+ options = & Options {}
38
37
}
39
-
40
- palette := defaultPalette
41
- if options != nil && options .Palette != nil {
42
- palette = options .Palette
38
+ if options .Font == nil {
39
+ options .Font = defaultFont
43
40
}
44
-
45
- var letterColor color.Color = defaultLetterColor
46
- if options != nil && options .LetterColor != nil {
47
- letterColor = options .LetterColor
41
+ if options .Palette == nil {
42
+ options .Palette = defaultPalette
43
+ }
44
+ if options .LetterColor == nil {
45
+ options .LetterColor = defaultLetterColor
48
46
}
49
47
50
- var bgColor color.Color = color.RGBA {0x00 , 0x00 , 0x00 , 0xff }
51
- if len (palette ) > 0 {
52
- if options != nil && len (options .PaletteKey ) > 0 {
53
- bgColor = palette [ keyindex (len (palette ), options .PaletteKey )]
48
+ var bgColor color.Color = color.RGBA {A : 0xff }
49
+ if len (options . Palette ) > 0 {
50
+ if len (options .PaletteKey ) > 0 {
51
+ bgColor = options . Palette [ randomIndex (len (options . Palette ), options .PaletteKey )]
54
52
} else {
55
- bgColor = palette [ randint (len (palette ))]
53
+ bgColor = options . Palette [ rand . IntN (len (options . Palette ))]
56
54
}
57
55
}
58
56
59
- fontSize := float64 (options .FontSize )
60
- if options .FontSize == 0 {
61
- fontSize = float64 (size ) * 0.6
62
- }
63
-
64
- return drawAvatar (bgColor , letterColor , font , size , fontSize , letters )
57
+ return drawAvatar (bgColor , options .LetterColor , options .Font , size , float64 (options .FontSize ), letters )
65
58
}
66
59
67
- func drawAvatar (bgColor , fgColor color.Color , font * truetype .Font , size int , fontSize float64 , letters []rune ) (image.Image , error ) {
60
+ func drawAvatar (bgColor , fgColor color.Color , f * opentype .Font , size int , fontSize float64 , letters []rune ) (image.Image , error ) {
68
61
dst := newRGBA (size , size , bgColor )
69
62
70
- src , err := drawString (bgColor , fgColor , font , fontSize , letters )
63
+ src , err := drawString (bgColor , fgColor , f , size , fontSize , letters )
71
64
if err != nil {
72
65
return nil , err
73
66
}
@@ -78,60 +71,96 @@ func drawAvatar(bgColor, fgColor color.Color, font *truetype.Font, size int, fon
78
71
return dst , nil
79
72
}
80
73
81
- func drawString (bgColor , fgColor color.Color , font * truetype.Font , fontSize float64 , letters []rune ) (image.Image , error ) {
82
- c := freetype .NewContext ()
83
- c .SetDPI (72 )
74
+ func drawString (bgColor , fgColor color.Color , f * opentype.Font , size int , fontSize float64 , letters []rune ) (image.Image , error ) {
75
+ // 自动计算字体大小
76
+ if fontSize <= 0 {
77
+ fontSize = calculateFontSize (len (letters ), size )
78
+ }
84
79
85
- bb := font .Bounds (c .PointToFixed (fontSize ))
86
- w := bb .Max .X .Ceil () - bb .Min .X .Floor ()
87
- h := bb .Max .Y .Ceil () - bb .Min .Y .Floor ()
88
- if len (letters ) > 0 {
89
- w = w + int (fontSize )* (len (letters )- 1 )
80
+ face , err := opentype .NewFace (f , & opentype.FaceOptions {
81
+ Size : fontSize ,
82
+ DPI : 72 ,
83
+ Hinting : font .HintingFull ,
84
+ })
85
+ if err != nil {
86
+ return nil , fmt .Errorf ("failed to create font face: %v" , err )
90
87
}
88
+ defer face .Close ()
91
89
92
- dst := newRGBA ( w , h , bgColor )
93
- src := image . NewUniform ( fgColor )
90
+ // 计算文本尺寸以居中显示
91
+ textWidth , ascent , descent , _ := calculateTextDimensions ( face , string ( letters ) )
94
92
95
- c .SetDst (dst )
96
- c .SetSrc (src )
97
- c .SetClip (dst .Bounds ())
98
- c .SetFontSize (fontSize )
99
- c .SetFont (font )
93
+ x := (size - textWidth ) / 2
94
+ y := size / 2 + (ascent - descent )/ 2
100
95
101
- p , err := c .DrawString (string (letters ), fixed.Point26_6 {X : 0 , Y : bb .Max .Y })
102
- if err != nil {
103
- return nil , err
96
+ dst := newRGBA (x , y , bgColor )
97
+ drawText (dst , face , string (letters ), x , y , fgColor )
98
+
99
+ return dst , nil
100
+ }
101
+
102
+ // calculateFontSize 根据图像大小和文本长度确定最佳字体大小
103
+ func calculateFontSize (textLength int , imageSize int ) float64 {
104
+ // 以图像大小的2/3作为初始大小估计
105
+ initialSize := float64 (imageSize ) * 2.0 / 3.0
106
+
107
+ // 根据文本长度调整
108
+ if textLength > 1 {
109
+ initialSize = initialSize * 3.0 / float64 (textLength + 2 )
110
+ }
111
+
112
+ // 确保字体大小至少为12px且不超过图像大小
113
+ if initialSize < 12 {
114
+ initialSize = 12
115
+ } else if initialSize > float64 (imageSize ) {
116
+ initialSize = float64 (imageSize )
104
117
}
105
118
106
- return dst . SubImage ( image . Rect ( 0 , 0 , p . X . Ceil (), h )), nil
119
+ return initialSize
107
120
}
108
121
109
- func newRGBA (w , h int , c color.Color ) * image.RGBA {
110
- img := image .NewRGBA (image .Rect (0 , 0 , w , h ))
111
- rgba := color .RGBAModel .Convert (c ).(color.RGBA )
112
- for x := 0 ; x < w ; x ++ {
113
- for y := 0 ; y < h ; y ++ {
114
- img .SetRGBA (x , y , rgba )
122
+ // calculateTextDimensions 计算文本的宽度和高度,返回宽度、上升部分高度、下降部分高度和行间距
123
+ func calculateTextDimensions (face font.Face , text string ) (width int , ascent int , descent int , lineGap int ) {
124
+ metrics := face .Metrics ()
125
+ ascent = metrics .Ascent .Ceil ()
126
+ descent = metrics .Descent .Ceil ()
127
+ lineGap = metrics .Height .Ceil () - ascent - descent
128
+
129
+ // 计算文本宽度
130
+ var w fixed.Int26_6
131
+ for _ , r := range text {
132
+ adv , ok := face .GlyphAdvance (r )
133
+ if ! ok {
134
+ adv , _ = face .GlyphAdvance (' ' ) // 如果字符不支持,使用空格的宽度
115
135
}
136
+ w += adv
116
137
}
117
- return img
138
+
139
+ return w .Ceil (), ascent , descent , lineGap
118
140
}
119
141
120
- func keyindex (n int , key string ) int {
121
- var index int64
122
- for _ , r := range key {
123
- index = (index + int64 (r )) % int64 (n )
142
+ // drawText 将文本绘制到图像上
143
+ func drawText (img * image.RGBA , face font.Face , text string , x , y int , textColor color.Color ) {
144
+ d := & font.Drawer {
145
+ Dst : img ,
146
+ Src : image .NewUniform (textColor ),
147
+ Face : face ,
148
+ Dot : fixed.Point26_6 {X : fixed .I (x ), Y : fixed .I (y )},
124
149
}
125
- return int ( index )
150
+ d . DrawString ( text )
126
151
}
127
152
128
- var (
129
- rng = rand .New (rand .NewSource (time .Now ().UnixNano ()))
130
- rngMu = new (sync.Mutex )
131
- )
153
+ func newRGBA (w , h int , c color.Color ) * image.RGBA {
154
+ img := image .NewRGBA (image .Rect (0 , 0 , w , h ))
155
+ draw .Draw (img , img .Bounds (), image .NewUniform (c ), image.Point {}, draw .Src )
156
+ return img
157
+ }
132
158
133
- func randint (n int ) int {
134
- rngMu .Lock ()
135
- defer rngMu .Unlock ()
136
- return rng .Intn (n )
159
+ func randomIndex (n int , key string ) int {
160
+ h := fnv .New64a ()
161
+ if _ , err := h .Write ([]byte (key )); err != nil {
162
+ return 0
163
+ }
164
+ rd := rand .New (rand .NewPCG (h .Sum64 (), (h .Sum64 ()>> 1 )| 1 ))
165
+ return rd .IntN (n )
137
166
}
0 commit comments