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.

identicon.go 3.8KB

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