forked from disintegration/letteravatar
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdraw.go
137 lines (112 loc) · 3.38 KB
/
draw.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Package letteravatar generates letter-avatars.
package letteravatar
import (
"image"
"image/color"
"image/draw"
"math/rand"
"sync"
"time"
"github.com/goki/freetype"
"github.com/goki/freetype/truetype"
"golang.org/x/image/math/fixed"
)
// Options are letter-avatar parameters.
type Options struct {
Font *truetype.Font
Palette []color.Color
LetterColor color.Color
FontSize int
// PaletteKey is used to pick the background color from the Palette.
// Using the same PaletteKey leads to the same background color being picked.
// If PaletteKey is empty (default) the background color is picked randomly.
PaletteKey string
}
var defaultLetterColor = color.RGBA{0xf0, 0xf0, 0xf0, 0xf0}
// Draw generates a new letter-avatar image of the given size using the given letter
// with the given options. Default parameters are used if a nil *Options is passed.
func Draw(size int, letters []rune, options *Options) (image.Image, error) {
font := defaultFont
if options != nil && options.Font != nil {
font = options.Font
}
palette := defaultPalette
if options != nil && options.Palette != nil {
palette = options.Palette
}
var letterColor color.Color = defaultLetterColor
if options != nil && options.LetterColor != nil {
letterColor = options.LetterColor
}
var bgColor color.Color = color.RGBA{0x00, 0x00, 0x00, 0xff}
if len(palette) > 0 {
if options != nil && len(options.PaletteKey) > 0 {
bgColor = palette[keyindex(len(palette), options.PaletteKey)]
} else {
bgColor = palette[randint(len(palette))]
}
}
fontSize := float64(options.FontSize)
if options.FontSize == 0 {
fontSize = float64(size) * 0.6
}
return drawAvatar(bgColor, letterColor, font, size, fontSize, letters)
}
func drawAvatar(bgColor, fgColor color.Color, font *truetype.Font, size int, fontSize float64, letters []rune) (image.Image, error) {
dst := newRGBA(size, size, bgColor)
src, err := drawString(bgColor, fgColor, font, fontSize, letters)
if err != nil {
return nil, err
}
r := src.Bounds().Add(dst.Bounds().Size().Div(2)).Sub(src.Bounds().Size().Div(2))
draw.Draw(dst, r, src, src.Bounds().Min, draw.Src)
return dst, nil
}
func drawString(bgColor, fgColor color.Color, font *truetype.Font, fontSize float64, letters []rune) (image.Image, error) {
c := freetype.NewContext()
c.SetDPI(72)
bb := font.Bounds(c.PointToFixed(fontSize))
w := bb.Max.X.Ceil() - bb.Min.X.Floor()
h := bb.Max.Y.Ceil() - bb.Min.Y.Floor()
if len(letters) > 0 {
w = w + int(fontSize)*(len(letters)-1)
}
dst := newRGBA(w, h, bgColor)
src := image.NewUniform(fgColor)
c.SetDst(dst)
c.SetSrc(src)
c.SetClip(dst.Bounds())
c.SetFontSize(fontSize)
c.SetFont(font)
p, err := c.DrawString(string(letters), fixed.Point26_6{X: 0, Y: bb.Max.Y})
if err != nil {
return nil, err
}
return dst.SubImage(image.Rect(0, 0, p.X.Ceil(), h)), nil
}
func newRGBA(w, h int, c color.Color) *image.RGBA {
img := image.NewRGBA(image.Rect(0, 0, w, h))
rgba := color.RGBAModel.Convert(c).(color.RGBA)
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
img.SetRGBA(x, y, rgba)
}
}
return img
}
func keyindex(n int, key string) int {
var index int64
for _, r := range key {
index = (index + int64(r)) % int64(n)
}
return int(index)
}
var (
rng = rand.New(rand.NewSource(time.Now().UnixNano()))
rngMu = new(sync.Mutex)
)
func randint(n int) int {
rngMu.Lock()
defer rngMu.Unlock()
return rng.Intn(n)
}