forgejo/modules/avatar/identicon/identicon.go

141 lines
3.8 KiB
Go
Raw Normal View History

// 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 (
[GITEA] Drop sha256-simd in favor of stdlib - In Go 1.21 the crypto/sha256 [got a massive improvement](https://go.dev/doc/go1.21#crypto/sha256) by utilizing the SHA instructions for AMD64 CPUs, which sha256-simd already was doing. The performance is now on par and I think it's preferable to use the standard library rather than a package when possible. ``` cpu: AMD Ryzen 5 3600X 6-Core Processor │ simd.txt │ go.txt │ │ sec/op │ sec/op vs base │ Hash/8Bytes-12 63.25n ± 1% 73.38n ± 1% +16.02% (p=0.002 n=6) Hash/64Bytes-12 98.73n ± 1% 105.30n ± 1% +6.65% (p=0.002 n=6) Hash/1K-12 567.2n ± 1% 572.8n ± 1% +0.99% (p=0.002 n=6) Hash/8K-12 4.062µ ± 1% 4.062µ ± 1% ~ (p=0.396 n=6) Hash/1M-12 512.1µ ± 0% 510.6µ ± 1% ~ (p=0.485 n=6) Hash/5M-12 2.556m ± 1% 2.564m ± 0% ~ (p=0.093 n=6) Hash/10M-12 5.112m ± 0% 5.127m ± 0% ~ (p=0.093 n=6) geomean 13.82µ 14.27µ +3.28% │ simd.txt │ go.txt │ │ B/s │ B/s vs base │ Hash/8Bytes-12 120.6Mi ± 1% 104.0Mi ± 1% -13.81% (p=0.002 n=6) Hash/64Bytes-12 618.2Mi ± 1% 579.8Mi ± 1% -6.22% (p=0.002 n=6) Hash/1K-12 1.682Gi ± 1% 1.665Gi ± 1% -0.98% (p=0.002 n=6) Hash/8K-12 1.878Gi ± 1% 1.878Gi ± 1% ~ (p=0.310 n=6) Hash/1M-12 1.907Gi ± 0% 1.913Gi ± 1% ~ (p=0.485 n=6) Hash/5M-12 1.911Gi ± 1% 1.904Gi ± 0% ~ (p=0.093 n=6) Hash/10M-12 1.910Gi ± 0% 1.905Gi ± 0% ~ (p=0.093 n=6) geomean 1.066Gi 1.032Gi -3.18% ``` (cherry picked from commit abd94ff5b59c86e793fd9bf12187ea6cfd1f3fa1) (cherry picked from commit 15e81637abf70576a564cf9eecaa9640228afb5b) Conflicts: go.mod https://codeberg.org/forgejo/forgejo/pulls/1581 (cherry picked from commit 325d92917f655c999b81b08832ee623d6b669f0f) Conflicts: modules/context/context_cookie.go https://codeberg.org/forgejo/forgejo/pulls/1617 (cherry picked from commit 358819e8959886faa171ac16541097500d0a703e) (cherry picked from commit 362fd7aae17832fa922fa017794bc564ca43060d) (cherry picked from commit 4f64ee294ee05c93042b6ec68f0a179ec249dab9) (cherry picked from commit 4bde77f7b13c5f961c141c01b6da1f9eda5ec387)
2023-09-29 22:45:31 +00:00
"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 splitted 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))
}
}
}