commit cd118344c1eb192a21a7d18710492dd5ed92bb50 Author: Codeberg Date: Sat Mar 23 21:56:39 2019 +0100 initial commit diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..fd119d8 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ + +HOSTNAME_FQDN := codeberg.org + +export BUILDDIR := /tmp/build +export GOROOT := ${BUILDDIR}/go +export GOPATH := ${BUILDDIR}/gitea +export PATH := ${GOROOT}/bin:${GOPATH}/bin:${PATH} + +GOTAR = go1.11.2.$(shell uname | tr [:upper:] [:lower:])-amd64.tar.gz +ORIGIN = ssh://prlgc.com/git/gogs-gitea + +TARGETS = ${GOPATH}/bin/avatar + +all : ${TARGETS} + +${GOPATH}/bin/avatar : main.go ${GOROOT}/bin/go + go build -o $@ $< + +${GOROOT}/bin/go : + mkdir -p ${GOROOT}/Downloads + wget -c --no-verbose --directory-prefix=${GOROOT}/Downloads https://dl.google.com/go/${GOTAR} + tar xfz ${GOROOT}/Downloads/${GOTAR} -C ${BUILDDIR} + +deployment : deploy-avatar + +deploy-avatar : ${GOPATH}/bin/avatar + -ssh root@${HOSTNAME_FQDN} systemctl stop avatar + scp $< root@${HOSTNAME_FQDN}:/usr/local/bin/ + scp -r etc/* root@${HOSTNAME_FQDN}:/etc/ + ssh root@${HOSTNAME_FQDN} systemctl daemon-reload + ssh root@${HOSTNAME_FQDN} systemctl enable avatar + ssh root@${HOSTNAME_FQDN} systemctl start avatar + ssh root@${HOSTNAME_FQDN} systemctl status avatar + +clean : + ${MAKE} -C ${GOPATH}/src/code.gitea.io/gitea clean + +realclean : + rm -rf ${BUILDDIR} + diff --git a/main.go b/main.go new file mode 100644 index 0000000..b3f2728 --- /dev/null +++ b/main.go @@ -0,0 +1,269 @@ +package main + +import ( + "os" + "fmt" + "strings" +) + +func main() { + var seed uint64 + fmt.Sscanf(os.Args[1], "%llu", seed) + fmt.Println(maleAvatar(seed)) +} + + +/** + * Based on TypeScript DiceBear Avatars, which in turn were inspired by 8biticon avatars: + * (MIT License, Copyright (c) 2012 Plastic Jam, Copyright (c) 2019 DiceBear) + * cf. https://github.com/DiceBear/avatars/blob/master/packages/avatars-male-sprites/src/index.ts + */ + +type LCG struct { + seed uint64 +} + +func (g * LCG) random () uint32 { + /* Linear Congruent Generator, POSIX/glibc [de]rand48 setting, bits [47..0] are output bits */ + g.seed = (25214903917 * g.seed + 11) % 281474976710656 + return uint32(g.seed) +} + +func (g * LCG) binomial (p float32) bool { + /* Sample from Binomial distribution with probability p */ + var sample = float32(g.random()) * float32(1.0 / 4294967295.0) + return sample > p +} + +func (g * LCG) pickone (s []string) string { + /* Pick one element from list */ + var N uint32 = uint32(len(s)) + return s[g.random() % N] +} + +func (g * LCG) pickoneFloat (s []float32) float32 { + /* Pick one element from list - float version*/ + var N uint32 = uint32(len(s)) + return s[g.random() % N] +} + + +func (g * LCG) pick_a_or_b (p float32, a string, b string) string { + /* Pick a or b with probability p of picking a */ + if g.binomial(p) { + return a + } + return b +} + +func LinearCongruentialGenerator (seed uint64) * LCG { + g := new(LCG) + g.seed = seed + return g +} + +type COLOR struct { + r uint8 + g uint8 + b uint8 +} + +func Color (s string) * COLOR { + c := new(COLOR) + fmt.Sscanf(s, "#%02x%02x%02x", c.r, c.g, c.b) + return c +} + +func add255 (x uint8, y uint8) uint8 { + if x < 255 - y { + return x + y + } + return 255 +} + +func sub255 (x uint8, y uint8) uint8 { + if x > y { + return x - y + } + return 0 +} + +func (c * COLOR) brighterOrDarkerThan (ref COLOR, delta uint8) string { + /* XXX FIXME: THIS IS PROBABLY NOT CORRECT. Need to look into original implementation. */ + if true { + return fmt.Sprintf("#%02x%02x%02x", add255(c.r, delta), add255(c.g, delta), add255(c.b, delta)) + } + return ref.darkerThan(ref, delta) +} + +func (c * COLOR) darkerThan (ref COLOR, delta uint8) string { + /* XXX FIXME: THIS IS NOT CORRECT. The original implementation does darkening in HSV space. + * cf. https://github.com/DiceBear/avatars/blob/master/packages/avatars/src/color.ts + */ + return fmt.Sprintf("#%02x%02x%02x", sub255(c.r, delta), sub255(c.g, delta), sub255(c.b, delta)) +} + +func (c COLOR) withAlpha (alpha float32) string { + return fmt.Sprintf("#%02x%02x%02x", c.r, c.g, c.b, uint8(255 * alpha)) +} + +func maleAvatar (seed uint64) string { + var g = LinearCongruentialGenerator(seed) + var skinColor = g.pickone([]string { "#FFDBAC", "#F5CFA0", "#EAC393", "#E0B687", "#CB9E6E", "#B68655", "#A26D3D", "#8D5524" }); + var hairColor = Color(g.pickone([]string { "#090806", "#2c222b", "#71635a", "#b7a69e", "#b89778", "#a56b46", "#b55239", "#8d4a43", + "#91553d", "#533d32", "#3b3024", "#554838", "#4e433f", "#504444", "#6a4e42", "#a7856a", "#977961" })).brighterOrDarkerThan(*Color(skinColor), 17) + var eyesColor = g.pickone([]string { "#76778b", "#697b94", "#647b90", "#5b7c8b", "#588387" }) + var eyebrowsColor = Color(Color(hairColor).darkerThan(*Color(skinColor), 7)).darkerThan(*Color(hairColor), 10) + var mustacheColor = Color(Color(hairColor).darkerThan(*Color(skinColor), 7)).withAlpha(g.pickoneFloat([]float32 { 1, 0.75, 0.5 })) + var mouthColor = Color(g.pickone([]string { "#eec1ad", "#dbac98", "#d29985" })).brighterOrDarkerThan(*Color(skinColor), 10) + var glassesColor = g.pickone([]string { "#5f705c", "#43677d", "#5e172d", "#ffb67a", "#a04b5d", "#191919", "#323232", "#4b4b4b" }) + var clothesColor = g.pickone([]string { "#5bc0de", "#5cb85c", "#428bca", "#03396c", "#005b96", "#6497b1", "#1b85b8", "#5a5255", "#559e83", "#ae5a41", "#c3cb71", "#666547", "#ffe28a" }) + var hatColor = g.pickone([]string { "#18293b", "#2e1e05", "#989789", "#3d6ba7", "#517459", "#a62116" }); + + var mood = "" + if mood == "" { + mood = g.pickone([]string { "sad", "happy", "surprised" }) + } + var mouth string + if mood == "sad" { + mouth = "" + + "" + + "" + + "" + + "" + } else if mood == "happy" { + mouth = "" + + "" + + "" + + "" + } else if mood == "surprised" { + mouth = "" + + "" + } + + var s = strings.Join([]string { + "", + // Head + "", + // Eyes + g.pickone([]string { + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + }), + // Eyebrows + g.pickone([]string { + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + }), + // Mustache (50% chance) + g.pick_a_or_b(0.5, + g.pickone([]string { + "", + "", + "", + "", + }), + ""), + // Mouth + mouth, + // Glasses (25% chance) + g.pick_a_or_b(0.25, + g.pickone([]string { + "", + "", + "", + "", + "", + "", + }), + ""), + // Clothes + g.pickone([]string { + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + }), + // Hair (95% chance) + g.pick_a_or_b(0.95, + g.pickone([]string { + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + }), + ""), + // Hat (5% chance) + g.pick_a_or_b(0.05, + g.pickone([]string { + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + }), + ""), + "", + }, "") + + m := []string{ + "${skinColor}", skinColor, + "${hairColor}", hairColor, + "${eyesColor}", eyesColor, + "${eyebrowsColor}", eyebrowsColor, + "${mustacheColor}", mustacheColor, + "${mouthColor}", mouthColor, + "${glassesColor}", glassesColor, + "${clothesColor}", clothesColor, + "${hatColor}", hatColor, + } + return strings.NewReplacer(m...).Replace(s) +} +