Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
3.8 KiB
141 lines
3.8 KiB
// Copyright 2021 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. |
|
|
|
// 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 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)) |
|
} |
|
} |
|
}
|
|
|