~gitpocket/gitpocket

6
20596d989af6d5379c73ccc5d6d896426228c226
mainfeed-discovery
gitpocket/avatar.go -rw-r--r--2.5 KiB ViewLogBlameView rawPermalink 20596d989a feat(profile): generated identicons 8 days ago
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package main

import (
	"bytes"
	"crypto/sha256"
	"errors"
	"image"
	"image/color"
	_ "image/gif"
	_ "image/jpeg"
	"image/png"
	"math"
	"sync"

	"golang.org/x/image/draw"
	_ "golang.org/x/image/webp"
)

const avatarSize = 256

var decoding sync.Mutex

func cropAvatar(data []byte, x, y, size int) ([]byte, error) {
	cfg, _, err := image.DecodeConfig(bytes.NewReader(data))
	if err != nil {
		return nil, errors.New("Avatar must be a PNG, JPEG, GIF or WebP image")
	}
	if cfg.Width > 6000 || cfg.Height > 6000 || cfg.Width*cfg.Height > 16_000_000 {
		return nil, errors.New("Avatar image is too large, keep it under 16 megapixels")
	}
	decoding.Lock()
	defer decoding.Unlock()
	img, _, err := image.Decode(bytes.NewReader(data))
	if err != nil {
		return nil, errors.New("Avatar must be a PNG, JPEG, GIF or WebP image")
	}
	b := img.Bounds()
	if size <= 0 || size > b.Dx() || size > b.Dy() {
		size = min(b.Dx(), b.Dy())
		x = (b.Dx() - size) / 2
		y = (b.Dy() - size) / 2
	}
	x = max(0, min(x, b.Dx()-size))
	y = max(0, min(y, b.Dy()-size))
	src := image.Rect(b.Min.X+x, b.Min.Y+y, b.Min.X+x+size, b.Min.Y+y+size)
	dst := image.NewRGBA(image.Rect(0, 0, avatarSize, avatarSize))
	draw.CatmullRom.Scale(dst, dst.Bounds(), img, src, draw.Src, nil)
	var out bytes.Buffer
	if err := png.Encode(&out, dst); err != nil {
		return nil, err
	}
	return out.Bytes(), nil
}

func identicon(seed string) []byte {
	sum := sha256.Sum256([]byte(seed))
	h := float64(uint16(sum[28])<<8|uint16(sum[29])) / 65536
	fg := hsl(h, 0.45+float64(sum[30])/255*0.2, 0.5+float64(sum[31])/255*0.12)
	const cell, pad = 44, 18
	img := image.NewRGBA(image.Rect(0, 0, avatarSize, avatarSize))
	draw.Draw(img, img.Bounds(), &image.Uniform{color.RGBA{240, 240, 240, 255}}, image.Point{}, draw.Src)
	for i := 0; i < 15; i++ {
		if sum[i]&1 != 0 {
			continue
		}
		row, col := i%5, i/5
		for _, c := range []int{col, 4 - col} {
			r := image.Rect(pad+c*cell, pad+row*cell, pad+(c+1)*cell, pad+(row+1)*cell)
			draw.Draw(img, r, &image.Uniform{fg}, image.Point{}, draw.Src)
		}
	}
	var out bytes.Buffer
	png.Encode(&out, img)
	return out.Bytes()
}

func hsl(h, s, l float64) color.RGBA {
	q := l + s - l*s
	if l < 0.5 {
		q = l * (1 + s)
	}
	p := 2*l - q
	ch := func(t float64) uint8 {
		t -= math.Floor(t)
		switch {
		case t < 1.0/6:
			return uint8(255 * (p + (q-p)*6*t))
		case t < 0.5:
			return uint8(255 * q)
		case t < 2.0/3:
			return uint8(255 * (p + (q-p)*(2.0/3-t)*6))
		}
		return uint8(255 * p)
	}
	return color.RGBA{ch(h + 1.0/3), ch(h), ch(h - 1.0/3), 255}
}