summaryrefslogtreecommitdiffstats
path: root/modules/avatar/identicon/identicon.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/avatar/identicon/identicon.go')
-rw-r--r--modules/avatar/identicon/identicon.go140
1 files changed, 140 insertions, 0 deletions
diff --git a/modules/avatar/identicon/identicon.go b/modules/avatar/identicon/identicon.go
new file mode 100644
index 00000000..40471565
--- /dev/null
+++ b/modules/avatar/identicon/identicon.go
@@ -0,0 +1,140 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+// Copied and modified from https://github.com/issue9/identicon/ (MIT License)
+// Generate pseudo-random avatars by IP, E-mail, etc.
+
+package identicon
+
+import (
+ "crypto/sha256"
+ "fmt"
+ "image"
+ "image/color"
+)
+
+const minImageSize = 16
+
+// 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
+// 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) {
+ if len(fore) == 0 {
+ return nil, fmt.Errorf("foreground is not set")
+ }
+
+ if size < minImageSize {
+ return nil, fmt.Errorf("size %d is smaller than min size %d", size, minImageSize)
+ }
+
+ return &Identicon{
+ foreColors: fore,
+ backColor: back,
+ size: size,
+ rect: image.Rect(0, 0, size, size),
+ }, nil
+}
+
+// Make generates an avatar by data
+func (i *Identicon) Make(data []byte) image.Image {
+ h := sha256.New()
+ h.Write(data)
+ sum := h.Sum(nil)
+
+ b1 := int(sum[0]+sum[1]+sum[2]) % len(blocks)
+ b2 := int(sum[3]+sum[4]+sum[5]) % len(blocks)
+ c := int(sum[6]+sum[7]+sum[8]) % len(centerBlocks)
+ b1Angle := int(sum[9]+sum[10]) % 4
+ b2Angle := int(sum[11]+sum[12]) % 4
+ foreColor := int(sum[11]+sum[12]+sum[15]) % len(i.foreColors)
+
+ return i.render(c, b1, b2, b1Angle, b2Angle, foreColor)
+}
+
+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
+}
+
+/*
+# Algorithm
+
+Origin: An image is split into 9 areas
+
+```
+ -------------
+ | 1 | 2 | 3 |
+ -------------
+ | 4 | 5 | 6 |
+ -------------
+ | 7 | 8 | 9 |
+ -------------
+```
+
+Area 1/3/9/7 use a 90-degree rotating pattern.
+Area 1/3/9/7 use another 90-degree rotating pattern.
+Area 5 uses a random pattern.
+
+The Patched Fix: make the image left-right mirrored to get rid of something like "swastika"
+*/
+
+// draw blocks to the paletted
+// c: the block drawer for the center block
+// b1,b2: the block drawers for other blocks (around the center block)
+// b1Angle,b2Angle: the angle for the rotation of b1/b2
+func drawBlocks(p *image.Paletted, size int, c, b1, b2 blockFunc, b1Angle, b2Angle int) {
+ nextAngle := func(a int) int {
+ return (a + 1) % 4
+ }
+
+ padding := (size % 3) / 2 // in cased the size can not be aligned by 3 blocks.
+
+ blockSize := size / 3
+ twoBlockSize := 2 * blockSize
+
+ // center
+ c(p, blockSize+padding, blockSize+padding, blockSize, 0)
+
+ // left top (1)
+ b1(p, 0+padding, 0+padding, blockSize, b1Angle)
+ // center top (2)
+ b2(p, blockSize+padding, 0+padding, blockSize, b2Angle)
+
+ b1Angle = nextAngle(b1Angle)
+ b2Angle = nextAngle(b2Angle)
+ // right top (3)
+ // b1(p, twoBlockSize+padding, 0+padding, blockSize, b1Angle)
+ // right middle (6)
+ // b2(p, twoBlockSize+padding, blockSize+padding, blockSize, b2Angle)
+
+ b1Angle = nextAngle(b1Angle)
+ b2Angle = nextAngle(b2Angle)
+ // right bottom (9)
+ // b1(p, twoBlockSize+padding, twoBlockSize+padding, blockSize, b1Angle)
+ // center bottom (8)
+ b2(p, blockSize+padding, twoBlockSize+padding, blockSize, b2Angle)
+
+ b1Angle = nextAngle(b1Angle)
+ b2Angle = nextAngle(b2Angle)
+ // lef bottom (7)
+ b1(p, 0+padding, twoBlockSize+padding, blockSize, b1Angle)
+ // left middle (4)
+ b2(p, 0+padding, blockSize+padding, blockSize, b2Angle)
+
+ // then we make it left-right mirror, so we didn't draw 3/6/9 before
+ for x := 0; x < size/2; x++ {
+ for y := 0; y < size; y++ {
+ p.SetColorIndex(size-x, y, p.ColorIndexAt(x, y))
+ }
+ }
+}