github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/avatar/identicon/identicon.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 // Copied and modified from https://github.com/issue9/identicon/ (MIT License) 7 // Generate pseudo-random avatars by IP, E-mail, etc. 8 9 package identicon 10 11 import ( 12 "crypto/sha256" 13 "fmt" 14 "image" 15 "image/color" 16 ) 17 18 const minImageSize = 16 19 20 // Identicon is used to generate pseudo-random avatars 21 type Identicon struct { 22 foreColors []color.Color 23 backColor color.Color 24 size int 25 rect image.Rectangle 26 } 27 28 // New returns an Identicon struct with the correct settings 29 // size image size 30 // back background color 31 // fore all possible foreground colors. only one foreground color will be picked randomly for one image 32 func New(size int, back color.Color, fore ...color.Color) (*Identicon, error) { 33 if len(fore) == 0 { 34 return nil, fmt.Errorf("foreground is not set") 35 } 36 37 if size < minImageSize { 38 return nil, fmt.Errorf("size %d is smaller than min size %d", size, minImageSize) 39 } 40 41 return &Identicon{ 42 foreColors: fore, 43 backColor: back, 44 size: size, 45 rect: image.Rect(0, 0, size, size), 46 }, nil 47 } 48 49 // Make generates an avatar by data 50 func (i *Identicon) Make(data []byte) image.Image { 51 h := sha256.New() 52 h.Write(data) 53 sum := h.Sum(nil) 54 55 b1 := int(sum[0]+sum[1]+sum[2]) % len(blocks) 56 b2 := int(sum[3]+sum[4]+sum[5]) % len(blocks) 57 c := int(sum[6]+sum[7]+sum[8]) % len(centerBlocks) 58 b1Angle := int(sum[9]+sum[10]) % 4 59 b2Angle := int(sum[11]+sum[12]) % 4 60 foreColor := int(sum[11]+sum[12]+sum[15]) % len(i.foreColors) 61 62 return i.render(c, b1, b2, b1Angle, b2Angle, foreColor) 63 } 64 65 func (i *Identicon) render(c, b1, b2, b1Angle, b2Angle, foreColor int) image.Image { 66 p := image.NewPaletted(i.rect, []color.Color{i.backColor, i.foreColors[foreColor]}) 67 drawBlocks(p, i.size, centerBlocks[c], blocks[b1], blocks[b2], b1Angle, b2Angle) 68 return p 69 } 70 71 /* 72 # Algorithm 73 74 Origin: An image is splitted into 9 areas 75 76 ``` 77 ------------- 78 | 1 | 2 | 3 | 79 ------------- 80 | 4 | 5 | 6 | 81 ------------- 82 | 7 | 8 | 9 | 83 ------------- 84 ``` 85 86 Area 1/3/9/7 use a 90-degree rotating pattern. 87 Area 1/3/9/7 use another 90-degree rotating pattern. 88 Area 5 uses a random pattern. 89 90 The Patched Fix: make the image left-right mirrored to get rid of something like "swastika" 91 */ 92 93 // draw blocks to the paletted 94 // c: the block drawer for the center block 95 // b1,b2: the block drawers for other blocks (around the center block) 96 // b1Angle,b2Angle: the angle for the rotation of b1/b2 97 func drawBlocks(p *image.Paletted, size int, c, b1, b2 blockFunc, b1Angle, b2Angle int) { 98 nextAngle := func(a int) int { 99 return (a + 1) % 4 100 } 101 102 padding := (size % 3) / 2 // in cased the size can not be aligned by 3 blocks. 103 104 blockSize := size / 3 105 twoBlockSize := 2 * blockSize 106 107 // center 108 c(p, blockSize+padding, blockSize+padding, blockSize, 0) 109 110 // left top (1) 111 b1(p, 0+padding, 0+padding, blockSize, b1Angle) 112 // center top (2) 113 b2(p, blockSize+padding, 0+padding, blockSize, b2Angle) 114 115 b1Angle = nextAngle(b1Angle) 116 b2Angle = nextAngle(b2Angle) 117 // right top (3) 118 // b1(p, twoBlockSize+padding, 0+padding, blockSize, b1Angle) 119 // right middle (6) 120 // b2(p, twoBlockSize+padding, blockSize+padding, blockSize, b2Angle) 121 122 b1Angle = nextAngle(b1Angle) 123 b2Angle = nextAngle(b2Angle) 124 // right bottom (9) 125 // b1(p, twoBlockSize+padding, twoBlockSize+padding, blockSize, b1Angle) 126 // center bottom (8) 127 b2(p, blockSize+padding, twoBlockSize+padding, blockSize, b2Angle) 128 129 b1Angle = nextAngle(b1Angle) 130 b2Angle = nextAngle(b2Angle) 131 // lef bottom (7) 132 b1(p, 0+padding, twoBlockSize+padding, blockSize, b1Angle) 133 // left middle (4) 134 b2(p, 0+padding, blockSize+padding, blockSize, b2Angle) 135 136 // then we make it left-right mirror, so we didn't draw 3/6/9 before 137 for x := 0; x < size/2; x++ { 138 for y := 0; y < size; y++ { 139 p.SetColorIndex(size-x, y, p.ColorIndexAt(x, y)) 140 } 141 } 142 }