Skip to content

Commit

Permalink
Code refactoring. Add avatar generation for username
Browse files Browse the repository at this point in the history
  • Loading branch information
o1egl committed Dec 26, 2016
1 parent c921e14 commit 24a017d
Show file tree
Hide file tree
Showing 10 changed files with 499 additions and 103 deletions.
19 changes: 19 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
language: go
go:
- 1.7
- tip
before_install:
- export PATH=$PATH:$GOPATH/bin
- go get github.com/modocache/gover
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
install:
- go get -t -v ./...
script:
- diff -u <(echo -n) <(gofmt -d -s $(find . -type f -name '*.go' -not -path "./bindata/*"))
- go test -coverprofile=govatar.coverprofile
- gover
- goveralls -coverprofile=gover.coverprofile -service=travis-ci
matrix:
allow_failures:
- go: tip
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ clean:
rm -rf build/

assets:
go-bindata -nomemcopy -pkg govatar data/...
go-bindata -nomemcopy -pkg bindata -o ./bindata/bindata.go -ignore "(.+)\.go" store/...

$(PLATFORMS):
GOOS=$(os) GOARCH=$(arch) go build -ldflags "-X main.version=${VERSION}" -o 'build/govatar$(ext)' github.com/o1egl/govatar/govatar
Expand Down
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# GOvatar
[![License](http://img.shields.io/:license-mit-blue.svg)](LICENSE)
[![GoDoc](https://godoc.org/github.com/o1egl/govatar?status.svg)](https://godoc.org/github.com/o1egl/govatar)
[![Build Status](http://img.shields.io/travis/o1egl/govatar.svg?style=flat-square)](https://travis-ci.org/o1egl/govatar)
[![Coverage Status](http://img.shields.io/coveralls/o1egl/govatar.svg?style=flat-square)](https://coveralls.io/r/o1egl/govatar)

![GOvatar image](files/avatars.jpg)

Expand All @@ -20,32 +23,35 @@ $ go get -u github.com/o1egl/govatar/...
## Usage

```bash
$ govatar generate male -o avatar.png # Generates random avatar.png for male
$ govatar generate female -o avatar.png # Generates random avatar.png for female
$ govatar -h # Display help message
$ govatar generate male -o avatar.png # Generates random avatar.png for male
$ govatar generate female -o avatar.png # Generates random avatar.png for female
$ govatar generate male -u [email protected] -o avatar.png # Generates avatar.png for specified username
$ govatar -h # Display help message
```

#### As lib

Generates avatar and save it to file
Generates avatar and save it to filePath

```go
govatar.GenerateFile(govatar.MALE, "/path/to/file")
err := govatar.GenerateFile(govatar.MALE, "/path/to/avatar.jpg"
err := govatar.GenerateFileFromUsername(govatar.MALE, "username", "/path/to/avatar.jpg")
````

Generates avatar and return it as image.Image

```go
img, err := govatar.Generate(govatar.MALE)
img, err := govatar.GenerateFromUsername(govatar.MALE, "username")
````
## Copyright, License & Contributors
### Adding new skins
1. Add new skins to data/background, male/clothes, female/hair and etc...
2. Run ``$ go-bindata -nomemcopy -pkg govatar data/...`` for building embedded assets.
1. Add new skins to background, male/clothes, female/hair and etc...
2. Run ``$ make assets`` for building embedded assets.
3. Submit pull request :)
### Submitting a Pull Request
Expand Down
2 changes: 1 addition & 1 deletion bindata.go → bindata/bindata.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@
// data/male/mouth/mouth9.png
// DO NOT EDIT!

package govatar
package bindata

import (
"bytes"
Expand Down
5 changes: 0 additions & 5 deletions glide.yaml

This file was deleted.

155 changes: 82 additions & 73 deletions govatar.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package govatar
import (
"bytes"
"errors"
"github.com/skarademir/naturalsort"
"github.com/o1egl/govatar/bindata"
"hash/fnv"
"image"
"image/draw"
"image/gif"
Expand All @@ -17,6 +18,8 @@ import (
"time"
)

var unknownGender = errors.New("Unknown gender")

type person struct {
Clothes []string
Eye []string
Expand All @@ -25,139 +28,145 @@ type person struct {
Mouth []string
}

type data struct {
type store struct {
Background []string
Male person
Female person
}

// Data represents assets database
var Data *data

func init() {
male := getPerson("male")
female := getPerson("female")
Data = &data{Background: readAssetsFrom("data/background"), Male: male, Female: female}
rand.Seed(time.Now().UTC().UnixNano())
}
var assetsStore *store

// Gender represents gender type
type Gender int

// Male and female constants
const (
MALE Gender = iota
FEMALE
)

func init() {
male := getPerson(MALE)
female := getPerson(FEMALE)
assetsStore = &store{Background: readAssetsFrom("data/background"), Male: male, Female: female}
rand.Seed(time.Now().UTC().UnixNano())
}

// Generate generates random avatar
func Generate(gender Gender) (image.Image, error) {
switch gender {
case MALE:
return randomAvatar(assetsStore.Male, time.Now().UnixNano())
case FEMALE:
return randomAvatar(Data.Female)
return randomAvatar(assetsStore.Female, time.Now().UnixNano())
default:
return randomAvatar(Data.Male)
return nil, unknownGender
}
}

// GenerateFile generates random avatar and save it to specified file
func GenerateFile(gender Gender, file string) error {
// GenerateFile generates random avatar and save it to specified file.
// Image format depends on file extension (jpeg, jpg, png, gif). Default is png
func GenerateFile(gender Gender, filePath string) error {
img, err := Generate(gender)
if err != nil {
return err
}
outFile, err := os.Create(file)
defer outFile.Close()
if err != nil {
return err
}
switch strings.ToLower(filepath.Ext(file)) {
case ".jpeg", ".jpg":
err = jpeg.Encode(outFile, img, &jpeg.Options{80})
case ".gif":
err = gif.Encode(outFile, img, nil)
default:
err = png.Encode(outFile, img)
}
return err
return saveToFile(img, filePath)
}

// GenerateFromAssets generates avatar from given assets
func GenerateFromAssets(gender Gender, assets []int) (image.Image, error) {
// GenerateFromUsername generates avatar from string
func GenerateFromUsername(gender Gender, username string) (image.Image, error) {
h := fnv.New32a()
_, err := h.Write([]byte(username))
if err != nil {
return nil, err
}
switch gender {
case MALE:
return randomAvatar(assetsStore.Male, int64(h.Sum32()))
case FEMALE:
return specificAvatar(Data.Female, assets)
return randomAvatar(assetsStore.Female, int64(h.Sum32()))
default:
return specificAvatar(Data.Male, assets)
return nil, unknownGender
}
}

func specificAvatar(p person, assets []int) (image.Image, error) {
if len(assets) < 6 {
return nil, errors.New("Wrong assets size")
}
if isOut(Data.Background, assets[0]) ||
isOut(p.Face, assets[1]) ||
isOut(p.Clothes, assets[2]) ||
isOut(p.Mouth, assets[3]) ||
isOut(p.Hair, assets[4]) ||
isOut(p.Eye, assets[5]) {
return nil, errors.New("Wrong assets params")
// GenerateFileFromUsername generates avatar from string and save it to specified file.
// Image format depends on file extension (jpeg, jpg, png, gif). Default is png
func GenerateFileFromUsername(gender Gender, username string, filePath string) error {
img, err := GenerateFromUsername(gender, username)
if err != nil {
return err
}
avatar := image.NewRGBA(image.Rect(0, 0, 400, 400))
var err error
err = drawImg(avatar, Data.Background[assets[0]], err)
err = drawImg(avatar, p.Face[assets[1]], err)
err = drawImg(avatar, p.Clothes[assets[2]], err)
err = drawImg(avatar, p.Mouth[assets[3]], err)
err = drawImg(avatar, p.Hair[assets[4]], err)
err = drawImg(avatar, p.Eye[assets[5]], err)
return avatar, err
return saveToFile(img, filePath)
}

func isOut(slice []string, i int) bool {
if i < 0 || i > len(slice)-1 {
return true
func saveToFile(img image.Image, filePath string) error {
outFile, err := os.Create(filePath)
defer outFile.Close()
if err != nil {
return err
}
switch strings.ToLower(filepath.Ext(filePath)) {
case ".jpeg", ".jpg":
err = jpeg.Encode(outFile, img, &jpeg.Options{Quality: 80})
case ".gif":
err = gif.Encode(outFile, img, nil)
default:
err = png.Encode(outFile, img)
}
return false
return err
}

func randomAvatar(p person) (image.Image, error) {
func randomAvatar(p person, seed int64) (image.Image, error) {
rnd := rand.New(rand.NewSource(seed))
avatar := image.NewRGBA(image.Rect(0, 0, 400, 400))
var err error
err = drawImg(avatar, RandString(Data.Background), err)
err = drawImg(avatar, RandString(p.Face), err)
err = drawImg(avatar, RandString(p.Clothes), err)
err = drawImg(avatar, RandString(p.Mouth), err)
err = drawImg(avatar, RandString(p.Hair), err)
err = drawImg(avatar, RandString(p.Eye), err)
err = drawImg(avatar, randSliceString(rnd, assetsStore.Background), err)
err = drawImg(avatar, randSliceString(rnd, p.Face), err)
err = drawImg(avatar, randSliceString(rnd, p.Clothes), err)
err = drawImg(avatar, randSliceString(rnd, p.Mouth), err)
err = drawImg(avatar, randSliceString(rnd, p.Hair), err)
err = drawImg(avatar, randSliceString(rnd, p.Eye), err)
return avatar, err
}

func drawImg(dst draw.Image, asset string, err error) error {
if err != nil {
return err
}
src, _, err := image.Decode(bytes.NewReader(MustAsset(asset)))
src, _, err := image.Decode(bytes.NewReader(bindata.MustAsset(asset)))
if err != nil {
return err
}
draw.Draw(dst, dst.Bounds(), src, image.Point{0, 0}, draw.Over)
return nil
}

func getPerson(gender string) person {
func getPerson(gender Gender) person {
var genderPath string

switch gender {
case FEMALE:
genderPath = "female"
case MALE:
genderPath = "male"
}

return person{
Clothes: readAssetsFrom("data/" + gender + "/clothes"),
Eye: readAssetsFrom("data/" + gender + "/eye"),
Face: readAssetsFrom("data/" + gender + "/face"),
Hair: readAssetsFrom("data/" + gender + "/hair"),
Mouth: readAssetsFrom("data/" + gender + "/mouth")}
Clothes: readAssetsFrom("data/" + genderPath + "/clothes"),
Eye: readAssetsFrom("data/" + genderPath + "/eye"),
Face: readAssetsFrom("data/" + genderPath + "/face"),
Hair: readAssetsFrom("data/" + genderPath + "/hair"),
Mouth: readAssetsFrom("data/" + genderPath + "/mouth"),
}
}

func readAssetsFrom(dir string) []string {
assets, _ := AssetDir(dir)
assets, _ := bindata.AssetDir(dir)
for i, asset := range assets {
assets[i] = filepath.Join(dir, asset)
}
sort.Sort(naturalsort.NaturalSort(assets))
sort.Sort(naturalSort(assets))
return assets
}
30 changes: 21 additions & 9 deletions govatar/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package main

import (
"fmt"
"github.com/urfave/cli"
"github.com/o1egl/govatar"
"os"
"github.com/urfave/cli"
"log"
"os"
)

var version = "0.1.0-dev"
var version = "0.2.0-dev"

func main() {
app := cli.NewApp()
Expand All @@ -18,29 +18,41 @@ func main() {
app.Author = "Oleg Lobanov"
app.Commands = []cli.Command{
{
Name: "generate",
Name: "generate",
ArgsUsage: "<(male|m)|(female|f)>",
Aliases: []string{"g"},
Usage: "Generates random avatar",
Aliases: []string{"g"},
Usage: "Generates random avatar",
Flags: []cli.Flag{
cli.StringFlag{
Name: "output,o",
Name: "output,o",
Value: "avatar.png",
Usage: "Output file name",
},
cli.StringFlag{
Name: "username,u",
Value: "",
Usage: "Username",
},
},
Action: func(c *cli.Context) {
var g govatar.Gender
var err error
switch c.Args().First() {
case "male", "m":
g = govatar.MALE
case "female", "f":
g = govatar.FEMALE
default:
fmt.Println("Incorrect gender param. Run `govatar help generate`")
os.Exit(0)
os.Exit(1)
}

username := c.String("username")
if username != "" {
err = govatar.GenerateFileFromUsername(g, username, c.String("output"))
} else {
err = govatar.GenerateFile(g, c.String("output"))
}
err := govatar.GenerateFile(g, c.String("output"))
if err != nil {
log.Fatal(err)
}
Expand Down
Loading

0 comments on commit 24a017d

Please sign in to comment.