diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..28a6b03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +avatars +.idea diff --git a/Makefile b/Makefile deleted file mode 100755 index f27b5d6..0000000 --- a/Makefile +++ /dev/null @@ -1,29 +0,0 @@ - -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.15.6.$(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} - -clean : - rm -rf ${TARGETS} - -realclean : - rm -rf ${BUILDDIR} - diff --git a/README.md b/README.md index def4d59..3789b5b 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,32 @@ -# Golang port of DiceBear Avatars, which in turn were inspired by 8biticon avatars +# Golang port of DiceBear Avatars + +which in turn were inspired by 8biticon avatars ![Example images](mosaic.png) -## Build & Run +## Usage + +import `codeberg.org/Codeberg/avatars` & use `string svg = avatars.MakeAvatar(seed)` + +## Build & Use Example ```shell -# will build the binary into ${BUILDDIR}. The golang compiler is fetched and installed into ${BUILDDIR} -make BUILDDIR=$PWD +# will build the binary +go build -o avatars example/main.go # Create a random avatar and write it to /tmp/avatar.svg. Note that for the same seed string always the same image will be generated. -$PWD/gitea/bin/avatar my-special-seed-string > /tmp/avatar.svg +./avatars my-special-seed-string > /tmp/avatar.svg # Open SVG file with image viewer eog /tmp/avatar.svg ``` - ## Create mosaic with example images using rsvg and imagemagick montage ```shell -make BUILDDIR=$PWD +go build -o avatars example/main.go for i in {1..40} ; do - $PWD/gitea/bin/avatar my-special-seed-string_$i > /tmp/avatar_$i.svg + ./avatars my-special-seed-string_$i > /tmp/avatar_$i.svg rsvg-convert /tmp/avatar_$i.svg -z 10 -o /tmp/avatar_$i.png done montage -tile 10x4 /tmp/avatar_*.png /tmp/mosaic.png eog /tmp/mosaic.png ``` - diff --git a/main.go b/avatars.go similarity index 91% rename from main.go rename to avatars.go index 5d34e4d..d09e428 100644 --- a/main.go +++ b/avatars.go @@ -1,32 +1,24 @@ -package main +package avatars import ( "fmt" "math" "math/bits" - "os" - "strings" "strconv" + "strings" ) -func main() { - if len(os.Args) < 2 { - fmt.Println("please specify a seed string") - return - } - fmt.Println(makeAvatar(os.Args[1])) -} - -func makeAvatar(seedString string) string { +// MakeAvatar create svg from seed +func MakeAvatar(seedString string) string { var seed uint64 for _, c := range []byte(seedString) { seed = bits.RotateLeft64(seed, 8) seed ^= uint64(c) } - if seed & 1 == 0 { - return femaleAvatar(seed) + if seed&1 == 0 { + return femaleAvatar(seed, "") } else { - return maleAvatar(seed) + return maleAvatar(seed, "") } } @@ -36,35 +28,35 @@ func makeAvatar(seedString string) string { * cf. https://github.com/DiceBear/avatars/blob/master/packages/avatars-male-sprites/src/index.ts */ -type LCG struct { +type lcg struct { seed uint64 } -func (g *LCG) random() uint32 { +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 float64) bool { +func (g *lcg) binomial(p float64) bool { /* Sample from Binomial distribution with probability p */ var sample = float64(g.random()) * float64(1.0/4294967295.0) return sample > p } -func (g *LCG) pickOne(s []string) string { +func (g *lcg) pickOne(s []string) string { /* Pick one element from list */ var N = uint32(len(s)) return s[g.random()%N] } -func (g *LCG) pickOneFloat(s []float64) float64 { +func (g *lcg) pickOneFloat(s []float64) float64 { /* Pick one element from list - float version*/ var N = uint32(len(s)) return s[g.random()%N] } -func (g *LCG) pickAorB(p float64, a string, b string) string { +func (g *lcg) pickAorB(p float64, a string, b string) string { /* Pick a or b with probability p of picking a */ if g.binomial(p) { return a @@ -72,28 +64,28 @@ func (g *LCG) pickAorB(p float64, a string, b string) string { return b } -func LinearCongruentialGenerator(seed uint64) *LCG { - g := new(LCG) +func linearCongruentialGenerator(seed uint64) *lcg { + g := new(lcg) g.seed = seed return g } -type RGB struct { +type rgb struct { r uint8 g uint8 b uint8 a uint8 } -type HSV struct { +type hsv struct { h float64 s float64 v float64 } -func f2rgb(r, g, b float64) *RGB { - // make RGB from 3 floats - c := new(RGB) +func f2rgb(r, g, b float64) *rgb { + // make rgb from 3 floats + c := new(rgb) c.r = uint8(r * 1.0 / 255) c.g = uint8(g * 1.0 / 255) c.b = uint8(b * 1.0 / 255) @@ -101,23 +93,23 @@ func f2rgb(r, g, b float64) *RGB { return c } -func f2hsv(h, s, v float64) *HSV { - // make HSV from 3 floats - c := new(HSV) +func f2hsv(h, s, v float64) *hsv { + // make hsv from 3 floats + c := new(hsv) c.h = h c.s = s c.v = v return c } -func rgb(s string) *RGB { - c := new(RGB) +func to_rgb(s string) *rgb { + c := new(rgb) c.a = 255 fmt.Sscanf(s, "#%02x%02x%02x", &c.r, &c.g, &c.b) return c } -func (c *RGB) to_hsv() *HSV { +func (c *rgb) to_hsv() *hsv { var h float64 var s float64 var v float64 @@ -150,7 +142,7 @@ func (c *RGB) to_hsv() *HSV { return f2hsv(h, s, v) } -func (c *HSV) to_rgb() *RGB { +func (c *hsv) to_rgb() *rgb { h := int((c.h / 60)) f := c.h/60 - float64(h) p := c.v * (1 - c.s) @@ -174,7 +166,7 @@ func (c *HSV) to_rgb() *RGB { return f2rgb(0, 0, 0) } -func (c *RGB) brighterThan(ref *RGB, delta float64) *RGB { +func (c *rgb) brighterThan(ref *rgb, delta float64) *rgb { primary := c.to_hsv() secondary := ref.to_hsv() if primary.v >= secondary.v+delta { @@ -187,7 +179,7 @@ func (c *RGB) brighterThan(ref *RGB, delta float64) *RGB { return primary.to_rgb() } -func (c *RGB) darkerThan(ref *RGB, delta float64) *RGB { +func (c *rgb) darkerThan(ref *rgb, delta float64) *rgb { primary := c.to_hsv() secondary := ref.to_hsv() if primary.v <= secondary.v-delta { @@ -200,7 +192,7 @@ func (c *RGB) darkerThan(ref *RGB, delta float64) *RGB { return primary.to_rgb() } -func (c *RGB) brighterOrDarkerThan(ref *RGB, delta float64) *RGB { +func (c *rgb) brighterOrDarkerThan(ref *rgb, delta float64) *rgb { primary := c.to_hsv() secondary := ref.to_hsv() if primary.v <= secondary.v { @@ -210,32 +202,31 @@ func (c *RGB) brighterOrDarkerThan(ref *RGB, delta float64) *RGB { } } -func (c *RGB) withAlpha(alpha float64) *RGB { +func (c *rgb) withAlpha(alpha float64) *rgb { c.a = uint8(alpha * 255) return c } -func (c *RGB) html() string { - if (c.a == 255) { +func (c *rgb) html() string { + if c.a == 255 { return fmt.Sprintf("#%02x%02x%02x", c.r, c.g, c.b) } return fmt.Sprintf("#%02x%02x%02x%02x", c.r, c.g, c.b, c.a) } -func maleAvatar(seed uint64) string { - var g = LinearCongruentialGenerator(seed) - var skinColor = rgb(g.pickOne([]string{"#FFDBAC", "#F5CFA0", "#EAC393", "#E0B687", "#CB9E6E", "#B68655", "#A26D3D", "#8D5524"})) - var hairColor = rgb(g.pickOne([]string{"#090806", "#2c222b", "#71635a", "#b7a69e", "#b89778", "#a56b46", "#b55239", "#8d4a43", +func maleAvatar(seed uint64, mood string) string { + var g = linearCongruentialGenerator(seed) + var skinColor = to_rgb(g.pickOne([]string{"#FFDBAC", "#F5CFA0", "#EAC393", "#E0B687", "#CB9E6E", "#B68655", "#A26D3D", "#8D5524"})) + var hairColor = to_rgb(g.pickOne([]string{"#090806", "#2c222b", "#71635a", "#b7a69e", "#b89778", "#a56b46", "#b55239", "#8d4a43", "#91553d", "#533d32", "#3b3024", "#554838", "#4e433f", "#504444", "#6a4e42", "#a7856a", "#977961"})).brighterOrDarkerThan(skinColor, 17) - var eyesColor = rgb(g.pickOne([]string{"#76778b", "#697b94", "#647b90", "#5b7c8b", "#588387"})) + var eyesColor = to_rgb(g.pickOne([]string{"#76778b", "#697b94", "#647b90", "#5b7c8b", "#588387"})) var eyebrowsColor = hairColor.darkerThan(skinColor, 7).darkerThan(hairColor, 10) var mustacheColor = hairColor.darkerThan(skinColor, 7).withAlpha(g.pickOneFloat([]float64{1, 0.75, 0.5})) - var mouthColor = rgb(g.pickOne([]string{"#eec1ad", "#dbac98", "#d29985"})).brighterOrDarkerThan(skinColor, 10) - var glassesColor = rgb(g.pickOne([]string{"#5f705c", "#43677d", "#5e172d", "#ffb67a", "#a04b5d", "#191919", "#323232", "#4b4b4b"})) - var clothesColor = rgb(g.pickOne([]string{"#5bc0de", "#5cb85c", "#428bca", "#03396c", "#005b96", "#6497b1", "#1b85b8", "#5a5255", "#559e83", "#ae5a41", "#c3cb71", "#666547", "#ffe28a"})) - var hatColor = rgb(g.pickOne([]string{"#18293b", "#2e1e05", "#989789", "#3d6ba7", "#517459", "#a62116"})) + var mouthColor = to_rgb(g.pickOne([]string{"#eec1ad", "#dbac98", "#d29985"})).brighterOrDarkerThan(skinColor, 10) + var glassesColor = to_rgb(g.pickOne([]string{"#5f705c", "#43677d", "#5e172d", "#ffb67a", "#a04b5d", "#191919", "#323232", "#4b4b4b"})) + var clothesColor = to_rgb(g.pickOne([]string{"#5bc0de", "#5cb85c", "#428bca", "#03396c", "#005b96", "#6497b1", "#1b85b8", "#5a5255", "#559e83", "#ae5a41", "#c3cb71", "#666547", "#ffe28a"})) + var hatColor = to_rgb(g.pickOne([]string{"#18293b", "#2e1e05", "#989789", "#3d6ba7", "#517459", "#a62116"})) - var mood = "" if mood == "" { mood = g.pickOne([]string{"sad", "happy", "surprised"}) } @@ -383,23 +374,22 @@ func maleAvatar(seed uint64) string { return strings.NewReplacer(m...).Replace(s) } -func femaleAvatar(seed uint64) string { - var g = LinearCongruentialGenerator(seed) +func femaleAvatar(seed uint64, mood string) string { + var g = linearCongruentialGenerator(seed) - var skinColor = rgb(g.pickOne([]string{"#FFDBAC", "#F5CFA0", "#EAC393", "#E0B687", "#CB9E6E", "#B68655", "#A26D3D", "#8D5524"})) - var hairColor = rgb(g.pickOne([]string{"#090806", "#2c222b", "#71635a", "#b7a69e", "#d6c4c2", "#cabfb1", "#dcd0ba", "#fff5e1", + var skinColor = to_rgb(g.pickOne([]string{"#FFDBAC", "#F5CFA0", "#EAC393", "#E0B687", "#CB9E6E", "#B68655", "#A26D3D", "#8D5524"})) + var hairColor = to_rgb(g.pickOne([]string{"#090806", "#2c222b", "#71635a", "#b7a69e", "#d6c4c2", "#cabfb1", "#dcd0ba", "#fff5e1", "#e6cea8", "#e5c8a8", "#debc99", "#b89778", "#a56b46", "#b55239", "#8d4a43", "#91553d", "#533d32", "#3b3024", "#554838", "#4e433f", "#504444", "#6a4e42", "#a7856a", "#977961"})).brighterOrDarkerThan(skinColor, 17) - var eyesColor = rgb(g.pickOne([]string{"#76778b", "#697b94", "#647b90", "#5b7c8b", "#588387"})) + var eyesColor = to_rgb(g.pickOne([]string{"#76778b", "#697b94", "#647b90", "#5b7c8b", "#588387"})) var eyebrowsColor = hairColor.darkerThan(skinColor, 7).darkerThan(hairColor, 10) - var accessoriesColor = rgb(g.pickOne([]string{"#daa520", "#ffd700", "#eee8aa", "#fafad2", "#d3d3d3", "#a9a9a9"})) - var mouthColor = rgb(g.pickOne([]string{"#dbac98", "#d29985", "#c98276", "#e35d6a", "#e32153", "#de0f0d"})).brighterOrDarkerThan(skinColor, 10) - var glassesColor = rgb(g.pickOne([]string{"#5f705c", "#43677d", "#5e172d", "#ffb67a", "#a04b5d", "#191919", "#323232", "#4b4b4b"})) - var clothesColor = rgb(g.pickOne([]string{"#d11141", "#00b159", "#00aedb", "#f37735", "#ffc425", "#740001", "#ae0001", "#eeba30", + var accessoriesColor = to_rgb(g.pickOne([]string{"#daa520", "#ffd700", "#eee8aa", "#fafad2", "#d3d3d3", "#a9a9a9"})) + var mouthColor = to_rgb(g.pickOne([]string{"#dbac98", "#d29985", "#c98276", "#e35d6a", "#e32153", "#de0f0d"})).brighterOrDarkerThan(skinColor, 10) + var glassesColor = to_rgb(g.pickOne([]string{"#5f705c", "#43677d", "#5e172d", "#ffb67a", "#a04b5d", "#191919", "#323232", "#4b4b4b"})) + var clothesColor = to_rgb(g.pickOne([]string{"#d11141", "#00b159", "#00aedb", "#f37735", "#ffc425", "#740001", "#ae0001", "#eeba30", "#96ceb4", "#ffeead", "#ff6f69", "#ffcc5c", "#88d8b0"})) - var hatColor = rgb(g.pickOne([]string{"#cc6192", "#2663a3", "#a62116", "#3d8a6b", "#614f8a"})) + var hatColor = to_rgb(g.pickOne([]string{"#cc6192", "#2663a3", "#a62116", "#3d8a6b", "#614f8a"})) - var mood = "" if mood == "" { mood = g.pickOne([]string{"sad", "happy", "surprised"}) } diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..a13362d --- /dev/null +++ b/example/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" + "os" + + "codeberg.org/Codeberg/avatars" +) + +func main() { + if len(os.Args) < 2 { + fmt.Println("please specify a seed string") + return + } + fmt.Println(avatars.MakeAvatar(os.Args[1])) +}