Files
identicon-algorithms/main.go

380 lines
8.5 KiB
Go

package main
import (
"crypto/md5"
"crypto/sha1"
"fmt"
"math"
"slices"
"strconv"
"github.com/fogleman/gg"
)
func main() {
dc := gg.NewContext(800, 800)
GitHub(dc, "64578396")
// XBitPolygons(dc, "BigBor14", 6, 6)
// FourBitSquares(dc, "Jaybee18")
dc.SavePNG("out.png")
}
// Bit font character overlay mit xor
func hue_to_rgb(a float32, b float32, hue float32) float32 {
var h float32
if hue < 0.0 {
h = hue + 1.0
} else if hue > 1.0 {
h = hue - 1.0
} else {
h = hue
}
if h < 1.0 / 6.0 {
return a + (b - a) * 6.0 * h
}
if h < 1.0 / 2.0 {
return b
}
if h < 2.0 / 3.0 {
return a + (b - a) * (2.0 / 3.0 - h) * 6.0
}
return a
}
func foreground(source []uint8) (int, int, int) {
h1 := (uint16(source[12]) & 0x0f) << 8;
h2 := uint16(source[13])
h := uint32(h1 | h2)
s := uint32(source[14])
l := uint32(source[15])
hue := float32(h * 360) / float32(4095)
sat := float32(s * 20) / float32(255)
lum := float32(l * 20) / float32(255)
sat = 65.0 - sat
lum = 75.0 - lum
hue = hue / 360.0
sat = sat / 100.0
lum = lum / 100.0
var b float32
if lum <= 0.5 {
b = lum * (sat + 1.0)
} else {
b = lum + sat - lum * sat
}
a := lum * 2.0 - b
r := hue_to_rgb(a, b, hue + 1.0 / 3.0)
g := hue_to_rgb(a, b, hue)
b = hue_to_rgb(a, b, hue - 1.0 / 3.0)
return int(math.Round(float64(r * 255))), int(math.Round(float64(g * 255))), int(math.Round(float64(b * 255)))
}
// Translated to Go from: https://github.com/dgraham/identicon
func GitHub(dc *gg.Context, userId string) {
fmt.Printf("md5.Sum([]byte(userId)): %v\n", md5.Sum([]byte(userId)))
hash := md5.Sum([]byte(userId))
// userIdHash := "da02efbbbe97337447671769e0f50c4a" // ohne newline (richtiges symbol)
w := 800
h := 800
cols := 5
rows := 5
pw := w / cols
ph := h / rows
// Background
dc.SetRGB255(240, 240, 240)
dc.DrawRectangle(0, 0, float64(w), float64(h))
dc.Fill()
// Convert to bytes
// bytes := []uint8{}
// for i := 0; i < 32; i+=2 {
// integer, _ := strconv.ParseUint(string(userIdHash[i]) + string(userIdHash[i+1]), 16, 8)
// bytes = append(bytes, uint8(integer))
// }
// fmt.Println(bytes)
dc.SetRGB255(foreground(hash[:]))
// Foreground
enabledVertices := make([]bool, 25)
// for i, character := range userIdHash[:15] {
// integer, _ := strconv.ParseUint(string(character), 16, 8)
// fmt.Println(integer)
// painted := integer % 2 == 0
// rowIdx := i % 5
// colIdx := 2 - i / 5
// mirrColIdx := 2 + i / 5
// enabledVertices[rowIdx * 5 + colIdx] = painted
// enabledVertices[rowIdx * 5 + mirrColIdx] = painted
// }
for i := range 15 {
// integer, _ := strconv.ParseUint(string(character), 16, 8)
byte := hash[i/2]
var nibble uint8 // 4 bits are called a nibble
if i % 2 == 0 {
nibble = (byte & 0xf0) >> 4 // left side
} else {
nibble = byte & 0x0f // right side
}
painted := nibble % 2 == 0
rowIdx := i % 5
colIdx := 2 - i / 5
mirrColIdx := 2 + i / 5
enabledVertices[rowIdx * 5 + colIdx] = painted
enabledVertices[rowIdx * 5 + mirrColIdx] = painted
}
// 216 166 130
// Foreground color
// da02efbbbe97337447671769e0f50c4a
// h1 = 3 -> 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 -> 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0
// h2 = 3 -> 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1
// h = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1 = 771
// s = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 = 7
// l = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 = 4
// hue = 277560 / 4095 = 67,78021978 (73710)
// sat = 140 / 255 = 0,54901961
// lum = 80 / 255 = 0,31372549
//
// 67.78021978, 64.45098039, 74.68627451
// ---
//
// l = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 1 1 0 1 1 1 0 0 0 1 = 23409
// hue = 73710 / 4095 = 21.538462
// sat = 3213 / 255 = 12.6
// lum = 1836 / 255 = 7.2
// Solution: 18, 64.3, 69.2
for row, pix := range slices.Collect(slices.Chunk(enabledVertices, 5)) {
for col, painted := range pix {
if painted {
x := col * pw
y := row * ph
dc.DrawRectangle(float64(x), float64(y), float64(pw), float64(ph))
dc.Fill()
}
}
}
}
func XBitPolygons(dc *gg.Context, username string, columns int, rows int) {
hash := hash(username)
dc.SetRGB255(255, 255, 255)
dc.DrawRectangle(0, 0, 800, 800)
dc.Fill()
colorString := hash[0:6]
r, g, b := rgbFromHex(colorString)
dc.SetRGB255(r, g, b)
enabledVertices := [][]bool{}
for _, character := range hash[6:6+rows+1] {
integer, _ := strconv.ParseUint(string(character), 16, columns/2+1)
row := []bool{}
for j := range columns/2+1 {
on := hasBit(integer, j)
row = append(row, on)
}
enabledVertices = append(enabledVertices, row)
}
w := 800 / columns
h := 800 / rows
for i := range rows {
for j := range columns/2 {
topLeft, tlx, tly := enabledVertices[i][j], float64(j*w), float64(i*h)
topRight, trx, try := enabledVertices[i][j+1], float64(j*w+w), float64(i*h)
bottomRight, brx, bry := enabledVertices[i+1][j+1], float64(j*w+w), float64(i*h+h)
bottomLeft, blx, bly := enabledVertices[i+1][j], float64(j*w), float64(i*h+h)
mx, my := tlx + float64(w/2), tly + float64(h/2)
count := 0
if topLeft {count++}
if topRight {count++}
if bottomRight {count++}
if bottomLeft {count++}
if topLeft && topRight {
dc.NewSubPath()
dc.MoveTo(mx, my)
dc.LineTo(tlx, tly)
dc.LineTo(trx, try)
dc.ClosePath()
}
if topRight && bottomRight {
dc.NewSubPath()
dc.MoveTo(mx, my)
dc.LineTo(trx, try)
dc.LineTo(brx, bry)
dc.ClosePath()
}
if bottomRight && bottomLeft {
dc.NewSubPath()
dc.MoveTo(mx, my)
dc.LineTo(brx, bry)
dc.LineTo(blx, bly)
dc.ClosePath()
}
if bottomLeft && topLeft {
dc.NewSubPath()
dc.MoveTo(mx, my)
dc.LineTo(blx, bly)
dc.LineTo(tlx, tly)
dc.ClosePath()
}
dc.Fill()
}
}
// Reverse
for i := range len(enabledVertices) {
slices.Reverse(enabledVertices[i])
}
// Mirror left side
for i := range rows {
for j := range columns/2 {
topLeft, tlx, tly := enabledVertices[i][j], float64(w*(columns/2)+j*w), float64(i*h)
topRight, trx, try := enabledVertices[i][j+1], float64(w*(columns/2)+j*w+w), float64(i*h)
bottomRight, brx, bry := enabledVertices[i+1][j+1], float64(w*(columns/2)+j*w+w), float64(i*h+h)
bottomLeft, blx, bly := enabledVertices[i+1][j], float64(w*(columns/2)+j*w), float64(i*h+h)
mx, my := tlx+float64(w/2), tly+float64(h/2)
count := 0
if topLeft {count++}
if topRight {count++}
if bottomRight {count++}
if bottomLeft {count++}
if topLeft && topRight {
dc.NewSubPath()
dc.MoveTo(mx, my)
dc.LineTo(tlx, tly)
dc.LineTo(trx, try)
dc.ClosePath()
}
if topRight && bottomRight {
dc.NewSubPath()
dc.MoveTo(mx, my)
dc.LineTo(trx, try)
dc.LineTo(brx, bry)
dc.ClosePath()
}
if bottomRight && bottomLeft {
dc.NewSubPath()
dc.MoveTo(mx, my)
dc.LineTo(brx, bry)
dc.LineTo(blx, bly)
dc.ClosePath()
}
if bottomLeft && topLeft {
dc.NewSubPath()
dc.MoveTo(mx, my)
dc.LineTo(blx, bly)
dc.LineTo(tlx, tly)
dc.ClosePath()
}
dc.Fill()
}
}
}
/*
8x8 square of 100x100 px squares
First 6 characters of the hash are the color
For the next 8 characters each character
represents 4 bits of a row being on or off.
The image is then mirrored to the right side.
9d26bf3cf1fb2b2f02d4a2013333f4fcd551cb2e
9d26bf -> color
3 -> row1
c -> row2
...
b -> row8
3 = 0 0 1 1
c = 1 1 0 0
...
b = 1 0 1 1
*/
func FourBitSquares(dc *gg.Context, username string) {
hash := hash(username)
colorString := hash[0:6]
r, g, b := rgbFromHex(colorString)
dc.SetRGB255(r, g, b)
for i, character := range hash[6:14] {
integer, _ := strconv.ParseUint(string(character), 16, 4)
for j := range 4 {
on := hasBit(integer, j)
dc.DrawRectangle(float64(j * 100), float64(i * 100), 100, 100)
dc.DrawRectangle(700 - float64(j * 100), float64(i * 100), 100, 100)
if !on {
dc.SetRGB255(r, g, b)
} else {
dc.SetRGB255(255, 255, 255)
}
dc.Fill()
}
}
}
func rgbFromHex(hex string) (int, int, int) {
r, _ := strconv.ParseUint(hex[0:2], 16, 8)
g, _ := strconv.ParseUint(hex[2:4], 16, 8)
b, _ := strconv.ParseUint(hex[4:6], 16, 8)
return int(r), int(g), int(b)
}
func hash(str string) string {
h := sha1.New()
h.Write([]byte(str))
res := h.Sum(nil)
return fmt.Sprintf("%x", res)
}
func hasBit(n uint64, pos int) bool {
val := n & (1 << pos)
return (val > 0)
}
func Map[T any, M any](a []T, f func(T) M) []M {
n := make([]M, len(a))
for i, e := range a {
n[i] = f(e)
}
return n
}