code.gitea.io/gitea@v1.19.3/modules/avatar/identicon/identicon.go (about)

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