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  }