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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. // SPDX-License-Identifier: MIT
  2. package identicon
  3. import (
  4. "crypto/md5"
  5. "fmt"
  6. "image"
  7. "image/color"
  8. "math/rand"
  9. )
  10. const (
  11. minSize = 16 // 图片的最小尺寸
  12. maxForeColors = 32 // 在New()函数中可以指定的最大颜色数量
  13. )
  14. // Identicon 用于产生统一尺寸的头像
  15. //
  16. // 可以根据用户提供的数据,经过一定的算法,自动产生相应的图案和颜色。
  17. type Identicon struct {
  18. foreColors []color.Color
  19. backColor color.Color
  20. size int
  21. rect image.Rectangle
  22. }
  23. // New 声明一个 Identicon 实例
  24. //
  25. // size 表示整个头像的大小;
  26. // back 表示前景色;
  27. // fore 表示所有可能的前景色,会为每个图像随机挑选一个作为其前景色。
  28. func New(size int, back color.Color, fore ...color.Color) (*Identicon, error) {
  29. if len(fore) == 0 || len(fore) > maxForeColors {
  30. return nil, fmt.Errorf("前景色数量必须介于[1]~[%d]之间,当前为[%d]", maxForeColors, len(fore))
  31. }
  32. if size < minSize {
  33. return nil, fmt.Errorf("参数 size 的值(%d)不能小于 %d", size, minSize)
  34. }
  35. return &Identicon{
  36. foreColors: fore,
  37. backColor: back,
  38. size: size,
  39. // 画布坐标从0开始,其长度应该是 size-1
  40. rect: image.Rect(0, 0, size, size),
  41. }, nil
  42. }
  43. // Make 根据 data 数据产生一张唯一性的头像图片
  44. func (i *Identicon) Make(data []byte) image.Image {
  45. h := md5.New()
  46. h.Write(data)
  47. sum := h.Sum(nil)
  48. b1 := int(sum[0]+sum[1]+sum[2]) % len(blocks)
  49. b2 := int(sum[3]+sum[4]+sum[5]) % len(blocks)
  50. c := int(sum[6]+sum[7]+sum[8]) % len(centerBlocks)
  51. b1Angle := int(sum[9]+sum[10]) % 4
  52. b2Angle := int(sum[11]+sum[12]) % 4
  53. color := int(sum[11]+sum[12]+sum[15]) % len(i.foreColors)
  54. return i.render(c, b1, b2, b1Angle, b2Angle, color)
  55. }
  56. // Rand 随机生成图案
  57. func (i *Identicon) Rand(r *rand.Rand) image.Image {
  58. b1 := r.Intn(len(blocks))
  59. b2 := r.Intn(len(blocks))
  60. c := r.Intn(len(centerBlocks))
  61. b1Angle := r.Intn(4)
  62. b2Angle := r.Intn(4)
  63. color := r.Intn(len(i.foreColors))
  64. return i.render(c, b1, b2, b1Angle, b2Angle, color)
  65. }
  66. func (i *Identicon) render(c, b1, b2, b1Angle, b2Angle, foreColor int) image.Image {
  67. p := image.NewPaletted(i.rect, []color.Color{i.backColor, i.foreColors[foreColor]})
  68. drawBlocks(p, i.size, centerBlocks[c], blocks[b1], blocks[b2], b1Angle, b2Angle)
  69. return p
  70. }
  71. // Make 根据 data 数据产生一张唯一性的头像图片
  72. //
  73. // size 头像的大小。
  74. // back, fore头像的背景和前景色。
  75. func Make(size int, back, fore color.Color, data []byte) (image.Image, error) {
  76. i, err := New(size, back, fore)
  77. if err != nil {
  78. return nil, err
  79. }
  80. return i.Make(data), nil
  81. }
  82. // 将九个方格都填上内容。
  83. // p 为画板;
  84. // c 为中间方格的填充函数;
  85. // b1、b2 为边上 8 格的填充函数;
  86. // b1Angle 和 b2Angle 为 b1、b2 的起始旋转角度。
  87. func drawBlocks(p *image.Paletted, size int, c, b1, b2 blockFunc, b1Angle, b2Angle int) {
  88. incr := func(a int) int {
  89. if a >= 3 {
  90. a = 0
  91. } else {
  92. a++
  93. }
  94. return a
  95. }
  96. padding := (size % 6) / 2 // 不能除尽的,边上留白。
  97. blockSize := size / 3
  98. twoBlockSize := 2 * blockSize
  99. c(p, blockSize+padding, blockSize+padding, blockSize, 0)
  100. b1(p, 0+padding, 0+padding, blockSize, b1Angle)
  101. b2(p, blockSize+padding, 0+padding, blockSize, b2Angle)
  102. b1Angle = incr(b1Angle)
  103. b2Angle = incr(b2Angle)
  104. b1(p, twoBlockSize+padding, 0+padding, blockSize, b1Angle)
  105. b2(p, twoBlockSize+padding, blockSize+padding, blockSize, b2Angle)
  106. b1Angle = incr(b1Angle)
  107. b2Angle = incr(b2Angle)
  108. b1(p, twoBlockSize+padding, twoBlockSize+padding, blockSize, b1Angle)
  109. b2(p, blockSize+padding, twoBlockSize+padding, blockSize, b2Angle)
  110. b1Angle = incr(b1Angle)
  111. b2Angle = incr(b2Angle)
  112. b1(p, 0+padding, twoBlockSize+padding, blockSize, b1Angle)
  113. b2(p, 0+padding, blockSize+padding, blockSize, b2Angle)
  114. }