package main import ( "bytes" "crypto/sha256" "errors" "image" "image/color" _ "image/gif" _ "image/jpeg" "image/png" "math" "sync" "golang.org/x/image/draw" _ "golang.org/x/image/webp" ) const avatarSize = 256 var decoding sync.Mutex func cropAvatar(data []byte, x, y, size int) ([]byte, error) { cfg, _, err := image.DecodeConfig(bytes.NewReader(data)) if err != nil { return nil, errors.New("Avatar must be a PNG, JPEG, GIF or WebP image") } if cfg.Width > 6000 || cfg.Height > 6000 || cfg.Width*cfg.Height > 16_000_000 { return nil, errors.New("Avatar image is too large, keep it under 16 megapixels") } decoding.Lock() defer decoding.Unlock() img, _, err := image.Decode(bytes.NewReader(data)) if err != nil { return nil, errors.New("Avatar must be a PNG, JPEG, GIF or WebP image") } b := img.Bounds() if size <= 0 || size > b.Dx() || size > b.Dy() { size = min(b.Dx(), b.Dy()) x = (b.Dx() - size) / 2 y = (b.Dy() - size) / 2 } x = max(0, min(x, b.Dx()-size)) y = max(0, min(y, b.Dy()-size)) src := image.Rect(b.Min.X+x, b.Min.Y+y, b.Min.X+x+size, b.Min.Y+y+size) dst := image.NewRGBA(image.Rect(0, 0, avatarSize, avatarSize)) draw.CatmullRom.Scale(dst, dst.Bounds(), img, src, draw.Src, nil) var out bytes.Buffer if err := png.Encode(&out, dst); err != nil { return nil, err } return out.Bytes(), nil } func identicon(seed string) []byte { sum := sha256.Sum256([]byte(seed)) h := float64(uint16(sum[28])<<8|uint16(sum[29])) / 65536 fg := hsl(h, 0.45+float64(sum[30])/255*0.2, 0.5+float64(sum[31])/255*0.12) const cell, pad = 44, 18 img := image.NewRGBA(image.Rect(0, 0, avatarSize, avatarSize)) draw.Draw(img, img.Bounds(), &image.Uniform{color.RGBA{240, 240, 240, 255}}, image.Point{}, draw.Src) for i := 0; i < 15; i++ { if sum[i]&1 != 0 { continue } row, col := i%5, i/5 for _, c := range []int{col, 4 - col} { r := image.Rect(pad+c*cell, pad+row*cell, pad+(c+1)*cell, pad+(row+1)*cell) draw.Draw(img, r, &image.Uniform{fg}, image.Point{}, draw.Src) } } var out bytes.Buffer png.Encode(&out, img) return out.Bytes() } func hsl(h, s, l float64) color.RGBA { q := l + s - l*s if l < 0.5 { q = l * (1 + s) } p := 2*l - q ch := func(t float64) uint8 { t -= math.Floor(t) switch { case t < 1.0/6: return uint8(255 * (p + (q-p)*6*t)) case t < 0.5: return uint8(255 * q) case t < 2.0/3: return uint8(255 * (p + (q-p)*(2.0/3-t)*6)) } return uint8(255 * p) } return color.RGBA{ch(h + 1.0/3), ch(h), ch(h - 1.0/3), 255} }