package main import ( "bytes" "errors" "image" _ "image/gif" _ "image/jpeg" "image/png" "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 }