Merge pull request 'Split Lib & Example' (#6) from 6543/avatars:split into master

Reviewed-on: https://codeberg.org/Codeberg/avatars/pulls/6
This commit is contained in:
hw 2021-02-02 02:51:22 +01:00
commit e339735667
5 changed files with 83 additions and 100 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
avatars
.idea

View File

@ -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}

View File

@ -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) ![Example images](mosaic.png)
## Build & Run ## Usage
import `codeberg.org/Codeberg/avatars` & use `string svg = avatars.MakeAvatar(seed)`
## Build & Use Example
```shell ```shell
# will build the binary into ${BUILDDIR}. The golang compiler is fetched and installed into ${BUILDDIR} # will build the binary
make BUILDDIR=$PWD 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. # 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 # Open SVG file with image viewer
eog /tmp/avatar.svg eog /tmp/avatar.svg
``` ```
## Create mosaic with example images using rsvg and imagemagick montage ## Create mosaic with example images using rsvg and imagemagick montage
```shell ```shell
make BUILDDIR=$PWD go build -o avatars example/main.go
for i in {1..40} ; do 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 rsvg-convert /tmp/avatar_$i.svg -z 10 -o /tmp/avatar_$i.png
done done
montage -tile 10x4 /tmp/avatar_*.png /tmp/mosaic.png montage -tile 10x4 /tmp/avatar_*.png /tmp/mosaic.png
eog /tmp/mosaic.png eog /tmp/mosaic.png
``` ```

View File

@ -1,32 +1,24 @@
package main package avatars
import ( import (
"fmt" "fmt"
"math" "math"
"math/bits" "math/bits"
"os"
"strings"
"strconv" "strconv"
"strings"
) )
func main() { // MakeAvatar create svg from seed
if len(os.Args) < 2 { func MakeAvatar(seedString string) string {
fmt.Println("please specify a seed string")
return
}
fmt.Println(makeAvatar(os.Args[1]))
}
func makeAvatar(seedString string) string {
var seed uint64 var seed uint64
for _, c := range []byte(seedString) { for _, c := range []byte(seedString) {
seed = bits.RotateLeft64(seed, 8) seed = bits.RotateLeft64(seed, 8)
seed ^= uint64(c) seed ^= uint64(c)
} }
if seed&1 == 0 { if seed&1 == 0 {
return femaleAvatar(seed) return femaleAvatar(seed, "")
} else { } 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 * cf. https://github.com/DiceBear/avatars/blob/master/packages/avatars-male-sprites/src/index.ts
*/ */
type LCG struct { type lcg struct {
seed uint64 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 */ /* Linear Congruent Generator, POSIX/glibc [de]rand48 setting, bits [47..0] are output bits */
g.seed = (25214903917*g.seed + 11) % 281474976710656 g.seed = (25214903917*g.seed + 11) % 281474976710656
return uint32(g.seed) 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 */ /* Sample from Binomial distribution with probability p */
var sample = float64(g.random()) * float64(1.0/4294967295.0) var sample = float64(g.random()) * float64(1.0/4294967295.0)
return sample > p return sample > p
} }
func (g *LCG) pickOne(s []string) string { func (g *lcg) pickOne(s []string) string {
/* Pick one element from list */ /* Pick one element from list */
var N = uint32(len(s)) var N = uint32(len(s))
return s[g.random()%N] 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*/ /* Pick one element from list - float version*/
var N = uint32(len(s)) var N = uint32(len(s))
return s[g.random()%N] 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 */ /* Pick a or b with probability p of picking a */
if g.binomial(p) { if g.binomial(p) {
return a return a
@ -72,28 +64,28 @@ func (g *LCG) pickAorB(p float64, a string, b string) string {
return b return b
} }
func LinearCongruentialGenerator(seed uint64) *LCG { func linearCongruentialGenerator(seed uint64) *lcg {
g := new(LCG) g := new(lcg)
g.seed = seed g.seed = seed
return g return g
} }
type RGB struct { type rgb struct {
r uint8 r uint8
g uint8 g uint8
b uint8 b uint8
a uint8 a uint8
} }
type HSV struct { type hsv struct {
h float64 h float64
s float64 s float64
v float64 v float64
} }
func f2rgb(r, g, b float64) *RGB { func f2rgb(r, g, b float64) *rgb {
// make RGB from 3 floats // make rgb from 3 floats
c := new(RGB) c := new(rgb)
c.r = uint8(r * 1.0 / 255) c.r = uint8(r * 1.0 / 255)
c.g = uint8(g * 1.0 / 255) c.g = uint8(g * 1.0 / 255)
c.b = uint8(b * 1.0 / 255) c.b = uint8(b * 1.0 / 255)
@ -101,23 +93,23 @@ func f2rgb(r, g, b float64) *RGB {
return c return c
} }
func f2hsv(h, s, v float64) *HSV { func f2hsv(h, s, v float64) *hsv {
// make HSV from 3 floats // make hsv from 3 floats
c := new(HSV) c := new(hsv)
c.h = h c.h = h
c.s = s c.s = s
c.v = v c.v = v
return c return c
} }
func rgb(s string) *RGB { func to_rgb(s string) *rgb {
c := new(RGB) c := new(rgb)
c.a = 255 c.a = 255
fmt.Sscanf(s, "#%02x%02x%02x", &c.r, &c.g, &c.b) fmt.Sscanf(s, "#%02x%02x%02x", &c.r, &c.g, &c.b)
return c return c
} }
func (c *RGB) to_hsv() *HSV { func (c *rgb) to_hsv() *hsv {
var h float64 var h float64
var s float64 var s float64
var v float64 var v float64
@ -150,7 +142,7 @@ func (c *RGB) to_hsv() *HSV {
return f2hsv(h, s, v) return f2hsv(h, s, v)
} }
func (c *HSV) to_rgb() *RGB { func (c *hsv) to_rgb() *rgb {
h := int((c.h / 60)) h := int((c.h / 60))
f := c.h/60 - float64(h) f := c.h/60 - float64(h)
p := c.v * (1 - c.s) p := c.v * (1 - c.s)
@ -174,7 +166,7 @@ func (c *HSV) to_rgb() *RGB {
return f2rgb(0, 0, 0) 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() primary := c.to_hsv()
secondary := ref.to_hsv() secondary := ref.to_hsv()
if primary.v >= secondary.v+delta { if primary.v >= secondary.v+delta {
@ -187,7 +179,7 @@ func (c *RGB) brighterThan(ref *RGB, delta float64) *RGB {
return primary.to_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() primary := c.to_hsv()
secondary := ref.to_hsv() secondary := ref.to_hsv()
if primary.v <= secondary.v-delta { if primary.v <= secondary.v-delta {
@ -200,7 +192,7 @@ func (c *RGB) darkerThan(ref *RGB, delta float64) *RGB {
return primary.to_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() primary := c.to_hsv()
secondary := ref.to_hsv() secondary := ref.to_hsv()
if primary.v <= secondary.v { 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) c.a = uint8(alpha * 255)
return c return c
} }
func (c *RGB) html() string { func (c *rgb) html() string {
if (c.a == 255) { if c.a == 255 {
return fmt.Sprintf("#%02x%02x%02x", c.r, c.g, c.b) 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) return fmt.Sprintf("#%02x%02x%02x%02x", c.r, c.g, c.b, c.a)
} }
func maleAvatar(seed uint64) string { func maleAvatar(seed uint64, mood string) string {
var g = LinearCongruentialGenerator(seed) var g = linearCongruentialGenerator(seed)
var skinColor = rgb(g.pickOne([]string{"#FFDBAC", "#F5CFA0", "#EAC393", "#E0B687", "#CB9E6E", "#B68655", "#A26D3D", "#8D5524"})) var skinColor = to_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", 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) "#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 eyebrowsColor = hairColor.darkerThan(skinColor, 7).darkerThan(hairColor, 10)
var mustacheColor = hairColor.darkerThan(skinColor, 7).withAlpha(g.pickOneFloat([]float64{1, 0.75, 0.5})) 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 mouthColor = to_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 glassesColor = to_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 clothesColor = to_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 hatColor = to_rgb(g.pickOne([]string{"#18293b", "#2e1e05", "#989789", "#3d6ba7", "#517459", "#a62116"}))
var mood = ""
if mood == "" { if mood == "" {
mood = g.pickOne([]string{"sad", "happy", "surprised"}) mood = g.pickOne([]string{"sad", "happy", "surprised"})
} }
@ -383,23 +374,22 @@ func maleAvatar(seed uint64) string {
return strings.NewReplacer(m...).Replace(s) return strings.NewReplacer(m...).Replace(s)
} }
func femaleAvatar(seed uint64) string { func femaleAvatar(seed uint64, mood string) string {
var g = LinearCongruentialGenerator(seed) var g = linearCongruentialGenerator(seed)
var skinColor = rgb(g.pickOne([]string{"#FFDBAC", "#F5CFA0", "#EAC393", "#E0B687", "#CB9E6E", "#B68655", "#A26D3D", "#8D5524"})) var skinColor = to_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 hairColor = to_rgb(g.pickOne([]string{"#090806", "#2c222b", "#71635a", "#b7a69e", "#d6c4c2", "#cabfb1", "#dcd0ba", "#fff5e1",
"#e6cea8", "#e5c8a8", "#debc99", "#b89778", "#a56b46", "#b55239", "#8d4a43", "#91553d", "#e6cea8", "#e5c8a8", "#debc99", "#b89778", "#a56b46", "#b55239", "#8d4a43", "#91553d",
"#533d32", "#3b3024", "#554838", "#4e433f", "#504444", "#6a4e42", "#a7856a", "#977961"})).brighterOrDarkerThan(skinColor, 17) "#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 eyebrowsColor = hairColor.darkerThan(skinColor, 7).darkerThan(hairColor, 10)
var accessoriesColor = rgb(g.pickOne([]string{"#daa520", "#ffd700", "#eee8aa", "#fafad2", "#d3d3d3", "#a9a9a9"})) var accessoriesColor = to_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 mouthColor = to_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 glassesColor = to_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 clothesColor = to_rgb(g.pickOne([]string{"#d11141", "#00b159", "#00aedb", "#f37735", "#ffc425", "#740001", "#ae0001", "#eeba30",
"#96ceb4", "#ffeead", "#ff6f69", "#ffcc5c", "#88d8b0"})) "#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 == "" { if mood == "" {
mood = g.pickOne([]string{"sad", "happy", "surprised"}) mood = g.pickOne([]string{"sad", "happy", "surprised"})
} }

16
example/main.go Normal file
View File

@ -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]))
}