diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 08708948940fa..8e6a105078a4a 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1639,8 +1639,20 @@ ROUTER = console ;AVATAR_UPLOAD_PATH = data/avatars ;REPOSITORY_AVATAR_UPLOAD_PATH = data/repo-avatars ;; +;; How Gitea deals with missing user avatars +;; * random = random avatar will be displayed +;; * image = default image will be used +;USER_AVATAR_FALLBACK = random +;; +;; How Gitea deals with missing organization avatars +;; * random = random avatar will be displayed; +;; * image = default image will be used +;ORGANIZATION_AVATAR_FALLBACK = random +;; ;; How Gitea deals with missing repository avatars -;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used +;; * none = no avatar will be displayed +;; * random = random avatar will be displayed +;; * image = default image will be used ;REPOSITORY_AVATAR_FALLBACK = none ;REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png ;; diff --git a/go.mod b/go.mod index 78495cc6a252a..f3f04ead31686 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.18 require ( code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b code.gitea.io/sdk/gitea v0.15.1 + codeberg.org/Codeberg/avatars v1.0.0 gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb gitea.com/go-chi/cache v0.2.0 gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5 @@ -27,6 +28,7 @@ require ( github.com/emirpasic/gods v1.18.1 github.com/ethantkoenig/rupture v1.0.1 github.com/felixge/fgprof v0.9.2 + github.com/fogleman/gg v1.3.0 github.com/gliderlabs/ssh v0.3.4 github.com/go-ap/activitypub v0.0.0-20220615144428-48208c70483b github.com/go-ap/jsonld v0.0.0-20220615144122-1d862b15410d @@ -60,6 +62,8 @@ require ( github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 github.com/klauspost/compress v1.15.3 github.com/klauspost/cpuid/v2 v2.0.12 + github.com/lafriks/go-avatars v0.3.0 + github.com/lafriks/go-svg v0.3.2 github.com/lib/pq v1.10.5 github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 github.com/markbates/goth v1.72.0 @@ -183,6 +187,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.2 // indirect @@ -276,6 +281,7 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.21.0 // indirect + golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect diff --git a/go.sum b/go.sum index dca68d9a8e7d8..74b39265d8db4 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,8 @@ code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b/go.mod h1:zcNbT/aJE code.gitea.io/sdk/gitea v0.11.3/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M= code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA= +codeberg.org/Codeberg/avatars v1.0.0 h1:MRx5QxuT/oVCcPvC5rXwgwWKD7hc6J0GnZ0Kl67lYEM= +codeberg.org/Codeberg/avatars v1.0.0/go.mod h1:ML/htpPRb3+owhkm4+qG2ZrXnk5WXaQLASOZ5GLCPi8= contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= @@ -434,6 +436,8 @@ github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -682,6 +686,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 h1:+eHOFJl1BaXrQxKX+T06f78590z4qA2ZzBTqahsKSE4= github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -1064,6 +1070,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/go-gypsy v1.0.0/go.mod h1:chkXM0zjdpXOiqkCW1XcCHDfjfk14PH2KKkQWxfJUcU= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lafriks/go-avatars v0.3.0 h1:+azteSFi6cr8TlvglfaBuwueeBwF8n84YYpKW0EWVEU= +github.com/lafriks/go-avatars v0.3.0/go.mod h1:EW0FFXaF0VZo4/vRoh1ASxqwKizRWfkbfrC21gEwxJY= +github.com/lafriks/go-svg v0.3.2 h1:wuSgV5Jh+aSGe+zPwXIOo2qb3UBRMKtAQmGfWwgIPqM= +github.com/lafriks/go-svg v0.3.2/go.mod h1:Gh57dXusEHiJBarKTI+t+LJVLU3Y7EvU/OUHWQUIBV0= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= @@ -1701,6 +1711,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/integrations/user_avatar_test.go b/integrations/user_avatar_test.go index 2bf6fde5ff1d1..02b97532d60e1 100644 --- a/integrations/user_avatar_test.go +++ b/integrations/user_avatar_test.go @@ -22,14 +22,14 @@ import ( func TestUserAvatar(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) // owner of the repo3, is an org + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) // normal user seed := user2.Email if len(seed) == 0 { seed = user2.Name } - img, err := avatar.RandomImage([]byte(seed)) + img, err := avatar.RandomImage(avatar.KindUser, []byte(seed)) if err != nil { assert.NoError(t, err) return diff --git a/models/organization/org.go b/models/organization/org.go index 044ea065637c5..c0c47733dbcb7 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -281,6 +281,7 @@ func CreateOrganization(org *Organization, owner *user_model.User) (err error) { if err = db.Insert(ctx, org); err != nil { return fmt.Errorf("insert organization: %v", err) } + if err = user_model.GenerateRandomAvatar(ctx, org.AsUser()); err != nil { return fmt.Errorf("generate random avatar: %v", err) } diff --git a/models/repo/avatar.go b/models/repo/avatar.go index cdf85bf1ac1ae..7d43560c9542b 100644 --- a/models/repo/avatar.go +++ b/models/repo/avatar.go @@ -34,11 +34,16 @@ func generateRandomAvatar(ctx context.Context, repo *Repository) error { idToString := fmt.Sprintf("%d", repo.ID) seed := idToString - img, err := avatar.RandomImage([]byte(seed)) + img, err := avatar.RandomImage(avatar.KindRepo, []byte(seed)) if err != nil { return fmt.Errorf("RandomImage: %v", err) } + if img == nil { + // use default repo image + return nil + } + repo.Avatar = idToString if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error { @@ -66,13 +71,13 @@ func (repo *Repository) relAvatarLink(ctx context.Context) string { switch mode := setting.RepoAvatar.Fallback; mode { case "image": return setting.RepoAvatar.FallbackImage - case "random": + case "none": + // default behaviour: do not display avatar + return "" + default: if err := generateRandomAvatar(ctx, repo); err != nil { log.Error("generateRandomAvatar: %v", err) } - default: - // default behaviour: do not display avatar - return "" } } return setting.AppSubURL + "/repo-avatars/" + url.PathEscape(repo.Avatar) diff --git a/models/user/avatar.go b/models/user/avatar.go index 6a44a3bcb3c32..bcea720ef4f04 100644 --- a/models/user/avatar.go +++ b/models/user/avatar.go @@ -32,11 +32,20 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error { seed = u.Name } - img, err := avatar.RandomImage([]byte(seed)) + avatarKind := avatar.KindUser + if u.IsOrganization() { + avatarKind = avatar.KindOrg + } + img, err := avatar.RandomImage(avatarKind, []byte(seed)) if err != nil { return fmt.Errorf("RandomImage: %v", err) } + if img == nil { + // use default user image + return nil + } + u.Avatar = avatars.HashEmail(seed) // Don't share the images so that we can delete them easily @@ -53,7 +62,7 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error { return err } - log.Info("New random avatar created: %d", u.ID) + log.Info("New random avatar for user[%d] created", u.ID) return nil } diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go index 6ca75ed90f0d7..650730792497e 100644 --- a/modules/avatar/avatar.go +++ b/modules/avatar/avatar.go @@ -1,3 +1,4 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. // Copyright 2014 The Gogs Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. @@ -8,13 +9,15 @@ import ( "bytes" "fmt" "image" - "image/color" _ "image/gif" // for processing gif images _ "image/jpeg" // for processing jpeg images _ "image/png" // for processing png images + "code.gitea.io/gitea/modules/avatar/dicebear" "code.gitea.io/gitea/modules/avatar/identicon" + "code.gitea.io/gitea/modules/avatar/none" + "code.gitea.io/gitea/modules/avatar/robot" "code.gitea.io/gitea/modules/setting" "github.com/nfnt/resize" @@ -24,21 +27,87 @@ import ( // AvatarSize returns avatar's size const AvatarSize = 290 +// Kind represent the type an avatar will be generated for +type Kind uint + +const ( + // User represent users + KindUser Kind = 0 + // Repo represent repositorys + KindRepo Kind = 1 + // Org represent organisations + KindOrg Kind = 2 +) + +type randomImageGenerator interface { + Name() string +} + +type randomUserImageGenerator interface { + randomImageGenerator + RandomUserImage(int, []byte) (image.Image, error) +} + +type randomOrgImageGenerator interface { + randomImageGenerator + RandomOrgImage(int, []byte) (image.Image, error) +} + +type randomRepoImageGenerator interface { + randomImageGenerator + RandomRepoImage(int, []byte) (image.Image, error) +} + +var ( + userImageGenerator randomUserImageGenerator = identicon.Identicon{} + orgImageGenerator randomOrgImageGenerator = identicon.Identicon{} + repoImageGenerator randomRepoImageGenerator = identicon.Identicon{} + generators = []randomImageGenerator{ + dicebear.DiceBear{}, + identicon.Identicon{}, + none.None{}, + robot.Robot{}, + } +) + +// TODO: Init() +func init() { + userPreference := "none" + orgPreference := "none" + repoPreference := setting.RepoAvatar.FallbackImage + + for _, g := range generators { + if g, ok := g.(randomUserImageGenerator); ok && userPreference == g.Name() { + userImageGenerator = g + } + if g, ok := g.(randomOrgImageGenerator); ok && orgPreference == g.Name() { + orgImageGenerator = g + } + if g, ok := g.(randomRepoImageGenerator); ok && repoPreference == g.Name() { + repoImageGenerator = g + } + } +} + // RandomImageSize generates and returns a random avatar image unique to input data // in custom size (height and width). -func RandomImageSize(size int, data []byte) (image.Image, error) { - // we use white as background, and use dark colors to draw blocks - imgMaker, err := identicon.New(size, color.White, identicon.DarkColors...) - if err != nil { - return nil, fmt.Errorf("identicon.New: %v", err) +func RandomImageSize(kind Kind, size int, seed []byte) (image.Image, error) { + switch kind { + case KindUser: + return userImageGenerator.RandomUserImage(size, seed) + case KindOrg: + return orgImageGenerator.RandomOrgImage(size, seed) + case KindRepo: + return repoImageGenerator.RandomRepoImage(size, seed) + default: + return nil, fmt.Errorf("avatar kind %v not supported", kind) } - return imgMaker.Make(data), nil } // RandomImage generates and returns a random avatar image unique to input data // in default size (height and width). -func RandomImage(data []byte) (image.Image, error) { - return RandomImageSize(AvatarSize, data) +func RandomImage(kind Kind, seed []byte) (image.Image, error) { + return RandomImageSize(kind, AvatarSize, seed) } // Prepare accepts a byte slice as input, validates it contains an image of an diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go index a2acc5443894e..d1dfa4a94c4e7 100644 --- a/modules/avatar/avatar_test.go +++ b/modules/avatar/avatar_test.go @@ -14,15 +14,15 @@ import ( ) func Test_RandomImageSize(t *testing.T) { - _, err := RandomImageSize(0, []byte("gitea@local")) + _, err := RandomImageSize(KindOrg, 0, []byte("gitea@local")) assert.Error(t, err) - _, err = RandomImageSize(64, []byte("gitea@local")) + _, err = RandomImageSize(KindRepo, 64, []byte("gitea@local")) assert.NoError(t, err) } func Test_RandomImage(t *testing.T) { - _, err := RandomImage([]byte("gitea@local")) + _, err := RandomImage(KindUser, []byte("gitea@local")) assert.NoError(t, err) } diff --git a/modules/avatar/dicebear/generate.go b/modules/avatar/dicebear/generate.go new file mode 100644 index 0000000000000..1b6bac9bd4f90 --- /dev/null +++ b/modules/avatar/dicebear/generate.go @@ -0,0 +1,60 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package dicebear + +import ( + "fmt" + "image" + "image/draw" + "strings" + + "codeberg.org/Codeberg/avatars" + "github.com/fogleman/gg" + "github.com/lafriks/go-svg" + "github.com/lafriks/go-svg/renderer" + rendr_gg "github.com/lafriks/go-svg/renderer/gg" +) + +// DiceBear is used to generate pseudo-random avatars +type DiceBear struct{} + +func (DiceBear) Name() string { + return "dicebear" +} + +func (DiceBear) RandomUserImage(size int, data []byte) (image.Image, error) { + return randomImageSize(size, data) +} + +func (DiceBear) RandomOrgImage(size int, data []byte) (image.Image, error) { + size /= 2 + space := size / 20 + img := image.NewRGBA(image.Rect(0, 0, size*2, size*2)) + + for i := 0; i < 4; i++ { + av, err := randomImageSize(size, []byte(fmt.Sprintf("%s-%d", string(data), i))) + if err != nil { + return nil, err + } + pos := image.Rect((i-(i/2)*2)*(size+space), (i/2)*(size+space), ((i-(i/2)*2)+1)*(size+space), ((i/2)+1)*(size+space)) + draw.Draw(img, pos, av, image.Point{}, draw.Over) + } + + return img, nil +} + +func randomImageSize(size int, data []byte) (image.Image, error) { + svgAvatar := avatars.MakeAvatar(string(data)) + + s, err := svg.Parse(strings.NewReader(svgAvatar), svg.IgnoreErrorMode) + if err != nil { + return nil, err + } + + gc := gg.NewContext(size, size) + rendr_gg.Draw(gc, s, renderer.Target(0, 0, float64(size), float64(size))) + + return gc.Image(), nil +} diff --git a/modules/avatar/identicon/generate.go b/modules/avatar/identicon/generate.go new file mode 100644 index 0000000000000..75dc606086ad6 --- /dev/null +++ b/modules/avatar/identicon/generate.go @@ -0,0 +1,41 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package identicon + +import ( + "fmt" + "image" + "image/color" +) + +// Identicon is used to generate pseudo-random avatars +type Identicon struct{} + +func (Identicon) Name() string { + return "identicon" +} + +func (Identicon) RandomUserImage(size int, data []byte) (image.Image, error) { + return randomImageSize(size, data) +} + +func (Identicon) RandomOrgImage(size int, data []byte) (image.Image, error) { + return randomImageSize(size, data) +} + +func (Identicon) RandomRepoImage(size int, data []byte) (image.Image, error) { + return randomImageSize(size, data) +} + +// randomImageSize generates and returns a random avatar image unique to input data +// in custom size (height and width). +func randomImageSize(size int, data []byte) (image.Image, error) { + // we use white as background, and use dark colors to draw blocks + imgMaker, err := new(size, color.White, DarkColors...) + if err != nil { + return nil, fmt.Errorf("identicon.New: %v", err) + } + return imgMaker.Make(data), nil +} diff --git a/modules/avatar/identicon/identicon.go b/modules/avatar/identicon/identicon.go index cc7e2a791d018..509e49e954c85 100644 --- a/modules/avatar/identicon/identicon.go +++ b/modules/avatar/identicon/identicon.go @@ -16,19 +16,19 @@ import ( const minImageSize = 16 -// Identicon is used to generate pseudo-random avatars -type Identicon struct { +// identicon is used to generate pseudo-random avatars +type identicon struct { foreColors []color.Color backColor color.Color size int rect image.Rectangle } -// New returns an Identicon struct with the correct settings +// new returns an Identicon struct with the correct settings // size image size // back background color // fore all possible foreground colors. only one foreground color will be picked randomly for one image -func New(size int, back color.Color, fore ...color.Color) (*Identicon, error) { +func new(size int, back color.Color, fore ...color.Color) (*identicon, error) { if len(fore) == 0 { return nil, fmt.Errorf("foreground is not set") } @@ -37,7 +37,7 @@ func New(size int, back color.Color, fore ...color.Color) (*Identicon, error) { return nil, fmt.Errorf("size %d is smaller than min size %d", size, minImageSize) } - return &Identicon{ + return &identicon{ foreColors: fore, backColor: back, size: size, @@ -46,7 +46,7 @@ func New(size int, back color.Color, fore ...color.Color) (*Identicon, error) { } // Make generates an avatar by data -func (i *Identicon) Make(data []byte) image.Image { +func (i *identicon) Make(data []byte) image.Image { h := sha256.New() h.Write(data) sum := h.Sum(nil) @@ -61,7 +61,7 @@ func (i *Identicon) Make(data []byte) image.Image { return i.render(c, b1, b2, b1Angle, b2Angle, foreColor) } -func (i *Identicon) render(c, b1, b2, b1Angle, b2Angle, foreColor int) image.Image { +func (i *identicon) render(c, b1, b2, b1Angle, b2Angle, foreColor int) image.Image { p := image.NewPaletted(i.rect, []color.Color{i.backColor, i.foreColors[foreColor]}) drawBlocks(p, i.size, centerBlocks[c], blocks[b1], blocks[b2], b1Angle, b2Angle) return p diff --git a/modules/avatar/identicon/identicon_test.go b/modules/avatar/identicon/identicon_test.go index 44635fbb3bd50..62e069e3e83b4 100644 --- a/modules/avatar/identicon/identicon_test.go +++ b/modules/avatar/identicon/identicon_test.go @@ -24,7 +24,7 @@ func TestGenerate(t *testing.T) { } backColor := color.White - imgMaker, err := New(64, backColor, DarkColors...) + imgMaker, err := new(64, backColor, DarkColors...) assert.NoError(t, err) for i := 0; i < 100; i++ { s := strconv.Itoa(i) diff --git a/modules/avatar/none/none.go b/modules/avatar/none/none.go new file mode 100644 index 0000000000000..df31f2add60e0 --- /dev/null +++ b/modules/avatar/none/none.go @@ -0,0 +1,28 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package none + +import ( + "image" +) + +// None wont generate an image +type None struct{} + +func (None) Name() string { + return "none" +} + +func (None) RandomUserImage(size int, data []byte) (image.Image, error) { + return nil, nil +} + +func (None) RandomOrgImage(size int, data []byte) (image.Image, error) { + return nil, nil +} + +func (None) RandomRepoImage(size int, data []byte) (image.Image, error) { + return nil, nil +} diff --git a/modules/avatar/robot/generate.go b/modules/avatar/robot/generate.go new file mode 100644 index 0000000000000..e4ce23428a1b8 --- /dev/null +++ b/modules/avatar/robot/generate.go @@ -0,0 +1,48 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package robot + +import ( + "fmt" + "image" + "image/draw" + + "github.com/lafriks/go-avatars" +) + +// Robot is used to generate pseudo-random avatars +type Robot struct{} + +func (Robot) Name() string { + return "robot" +} + +func (Robot) RandomUserImage(size int, data []byte) (image.Image, error) { + a, err := avatars.Generate(string(data)) + if err != nil { + return nil, err + } + return a.Image(avatars.RenderSize(size)) +} + +func (Robot) RandomOrgImage(size int, data []byte) (image.Image, error) { + size /= 2 + img := image.NewRGBA(image.Rect(0, 0, size*2, size*2)) + + for i := 0; i < 4; i++ { + a, err := avatars.Generate(fmt.Sprintf("%s-%d", string(data), i)) + if err != nil { + return nil, err + } + av, err := a.Image(avatars.RenderSize(size)) + if err != nil { + return nil, err + } + pos := image.Rect((i-(i/2)*2)*size, (i/2)*size, ((i-(i/2)*2)+1)*size, ((i/2)+1)*size) + draw.Draw(img, pos, av, image.Point{}, draw.Over) + } + + return img, nil +}