171 lines
4.2 KiB
Go
171 lines
4.2 KiB
Go
package identicon
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"errors"
|
|
"image/color"
|
|
"math/rand"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// Generator represents a predefined set of configurations that can be reused to
|
|
// create multiple icons by passing a Text string only.
|
|
type Generator struct {
|
|
// Namespace that will be concatenated previous to the icon generation.
|
|
Namespace string
|
|
// Size is the number of blocks of the figure.
|
|
Size int
|
|
// Density * Size = times to iterate over the hash of Text:Namespace:Seed.
|
|
Density int
|
|
// hashFunction used to generate a fixed length array of bytes.
|
|
hashFunction func([]byte) []byte
|
|
// fillColorFunction used to pick a color to fill the squares of the figure.
|
|
fillColorFunction func([]byte) color.Color
|
|
// backgroundColorFunction used to pick a background color for the figure.
|
|
backgroundColorFunction func([]byte, color.Color) color.Color
|
|
// isRandom flag to decide whether the generated image will be randomized.
|
|
isRandom bool
|
|
// rand is the source of randomness.
|
|
rand *rand.Rand
|
|
}
|
|
|
|
// option Configuration functional approach
|
|
type option func(*Generator)
|
|
|
|
// New returns a pointer to a Generator with the desired configuration.
|
|
func New(
|
|
namespace string,
|
|
size int,
|
|
density int,
|
|
opts ...option,
|
|
) (*Generator, error) {
|
|
|
|
if size < MinSize {
|
|
// Smaller values will generate a meaningless Generator.
|
|
return nil, errors.New(
|
|
"Size cannot be less than " + strconv.Itoa(MinSize),
|
|
)
|
|
}
|
|
|
|
if density < 1 {
|
|
return nil, errors.New(
|
|
"Density cannot be less than 1",
|
|
)
|
|
}
|
|
|
|
g := Generator{
|
|
Size: size,
|
|
Namespace: namespace,
|
|
Density: density,
|
|
isRandom: false,
|
|
hashFunction: _sha256,
|
|
fillColorFunction: _fillColor,
|
|
backgroundColorFunction: _backgroundColor,
|
|
}
|
|
|
|
g.Option(opts...)
|
|
|
|
return &g, nil
|
|
}
|
|
|
|
// Draw returns a pointer to an IdentIcon with a generated figure and a color.
|
|
func (g Generator) Draw(text string) (*IdentIcon, error) {
|
|
|
|
var randomGenerator *rand.Rand
|
|
|
|
if g.isRandom {
|
|
// In order to generate a randomized Canvas, use UnixNano as the source
|
|
// of randomess. By doing this, random numbers won't be consistent.
|
|
randomGenerator = rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
} else {
|
|
// rand will generate consistent values since the source is Size.
|
|
randomGenerator = rand.New(rand.NewSource(int64(g.Size)))
|
|
}
|
|
|
|
ii, err := newIdentIcon(
|
|
text,
|
|
g.Namespace,
|
|
g.Size,
|
|
g.Density,
|
|
g.isRandom,
|
|
randomGenerator,
|
|
g.hashFunction,
|
|
g.fillColorFunction,
|
|
g.backgroundColorFunction,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Generate Canvas
|
|
ii.Draw()
|
|
|
|
return ii, nil
|
|
}
|
|
|
|
// Option sets the options specified.
|
|
func (g *Generator) Option(opts ...option) {
|
|
for _, opt := range opts {
|
|
opt(g)
|
|
}
|
|
}
|
|
|
|
// SetHashFunction replaces the default hash function (Sha256).
|
|
func SetHashFunction(hf func([]byte) []byte) option {
|
|
return func(g *Generator) {
|
|
g.hashFunction = hf
|
|
}
|
|
}
|
|
|
|
// SetFillColorFunction replaces the default color generation function (HSL).
|
|
func SetFillColorFunction(fcf func([]byte) color.Color) option {
|
|
return func(g *Generator) {
|
|
g.fillColorFunction = fcf
|
|
}
|
|
}
|
|
|
|
// SetBackgroundColorFunction replaces the default background's color generation
|
|
// function (HSL).
|
|
func SetBackgroundColorFunction(bcf func([]byte, color.Color) color.Color) option {
|
|
return func(g *Generator) {
|
|
g.backgroundColorFunction = bcf
|
|
}
|
|
}
|
|
|
|
// SetRandom to append a random string to the generator text everytime Draw is
|
|
// called.
|
|
func SetRandom(r bool) option {
|
|
return func(g *Generator) {
|
|
g.isRandom = r
|
|
}
|
|
}
|
|
|
|
func _sha256(b []byte) []byte {
|
|
digest := sha256.Sum256(b)
|
|
return digest[:]
|
|
}
|
|
|
|
func _fillColor(hashBytes []byte) color.Color {
|
|
cb1, cb2 := uint32(hashBytes[0]), uint32(hashBytes[1])
|
|
h := (cb1 + cb2) % 360
|
|
s := (cb1 % 30) + 60
|
|
l := (cb2 % 20) + 40
|
|
|
|
// Some colors in the HSL color model are too bright and don't play well
|
|
// with the default background color. This is a naïve normalization method.
|
|
if (h >= 50 && h <= 85) || (h >= 170 && h <= 190) {
|
|
s = 80
|
|
l -= 20
|
|
} else if h > 85 && h < 170 {
|
|
l -= 10
|
|
}
|
|
|
|
return HSL{h, s, l}
|
|
}
|
|
|
|
func _backgroundColor(hashBytes []byte, fill color.Color) color.Color {
|
|
return color.NRGBA{R: 240, G: 240, B: 240, A: 255}
|
|
}
|