package identicon import ( "errors" "fmt" "image" "math" "strings" "git.mstar.dev/mstar/canvas" "git.mstar.dev/mstar/canvas/backend/softwarebackend" mathutils "git.mstar.dev/mstar/goutils/math" "git.mstar.dev/mstar/goutils/sliceutils" ) type identicon struct { canvas *canvas.Canvas primary string secondary string margin int scale float64 cellSize int hash int blocks []block shape shape hasStroke bool strokeWeight int strokeColor string compositeOperation string palette []string } const canvasSize = 500 var ( palette = []string{ "#F44336", "#E91E63", "#9C27B0", "#673AB7", "#3F51B5", "#2196F3", "#03A9F4", "#00BCD4", "#009688", "#4CAF50", "#8BC34A", "#CDDC39", "#FFEB3B", "#FFC107", "#FF9800", "#FF5722", "#795548", "#607D8B", } validOperations = []string{ "source-over", "source-in", "source-out", "source-atop", "destination-over", "destination-in", "destination-out", "destination-atop", "lighter", "copy", "xor", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity", } ) // Deprecated: Doesn't work func GenerateIdenticon(text string) *image.RGBA { backend := softwarebackend.New(canvasSize, canvasSize) canvas := canvas.New(backend) i := identicon{ canvas: canvas, hash: hash(text), } i.Init() i.Draw() tmp := *backend.Image return &tmp } func hash(text string) int { runes := []rune(text) return sliceutils.Compact( sliceutils.Map(runes, func(t rune) int { return int(t) }), func(acc, next int) int { acc = ((acc << 5) - acc) + next return acc & acc }, ) } func (i *identicon) SetUsername(username string) { i.hash = hash(username) } func (i *identicon) SetPalette(pal []string) error { if len(pal) < 2 { return errors.New("palette to small") } i.palette = pal return nil } func (i *identicon) SetCompositeOperation(newOp string) error { newOp = strings.ToLower(newOp) if !sliceutils.Contains(validOperations, newOp) { return errors.New("invalid operation") } i.compositeOperation = newOp return nil } func (i *identicon) Draw() { i.Init() i.canvas.Save() i.canvas.Translate(float64(canvasSize/2), float64(canvasSize/2)) i.canvas.Rotate(-math.Pi / 4) i.canvas.Translate(float64(canvasSize/2), float64(canvasSize/2)) i.GenerateBlocks() i.DrawBlocks() if i.hasStroke { i.DrawOutline() } i.shape = shape{ canvas: i.canvas, hash: i.hash, primary: i.primary, secondary: i.secondary, pos: vec2{ X: float64(i.margin*int(i.scale)) + 1.5*float64(i.cellSize), Y: float64(i.margin)*i.scale + 0.5*float64(i.cellSize), }, scale: i.scale, cellSize: i.cellSize, strokeColor: i.strokeColor, } i.shape.Draw(i.hasStroke, i.strokeWeight) i.canvas.Restore() } func (i *identicon) Init() { i.blocks = []block{} i.shape = shape{} i.primary = palette[mathutils.Abs(i.hash%len(palette))] i.secondary = palette[mathutils.Abs(hash(fmt.Sprint(i.hash))%len(palette))] // i.canvas.SetGlobalCompositeOperation("source-over") i.canvas.ClearRect(0, 0, canvasSize, canvasSize) // i.canvas.SetGlobalCompositeOperation(i.compositeOperation) } func (i *identicon) Offset() { i.canvas.Save() i.canvas.Translate(0.6*i.scale, -0.6*i.scale) } func (i *identicon) ResetOffset() { i.canvas.Restore() } func (i *identicon) GenerateBlocks() { i.blocks = append(i.blocks, block{ canvas: i.canvas, blockType: 1, primary: i.primary, secondary: i.secondary, hash: i.hash, pos: vec2{float64(i.margin) * i.scale, float64(i.margin) * i.scale}, cellSize: i.cellSize, margin: i.margin, scale: i.scale, }, block{ canvas: i.canvas, blockType: 2, primary: i.primary, secondary: i.primary, hash: i.hash, pos: vec2{float64(i.margin) * i.scale, float64(i.canvas.Height()) / 2}, cellSize: i.cellSize, margin: i.margin, scale: i.scale, }, block{ canvas: i.canvas, blockType: 3, primary: i.primary, secondary: i.secondary, hash: i.hash, pos: vec2{float64(i.canvas.Width()) / 2, float64(i.canvas.Height()) / 2}, cellSize: i.cellSize, margin: i.margin, scale: i.scale, }, ) } func (i *identicon) DrawOutline() { i.Offset() // i.canvas.SetGlobalCompositeOperation("source-over") i.canvas.BeginPath() i.canvas.MoveTo(float64(i.margin)*i.scale, float64(i.margin)*i.scale) i.canvas.LineTo(float64(i.margin)*i.scale, float64(canvasSize)-float64(i.margin)*i.scale) i.canvas.LineTo( float64(i.canvas.Width())-float64(i.margin)*i.scale, float64(i.canvas.Height())-float64(i.margin)*i.scale, ) i.canvas.LineTo( float64(i.canvas.Width())-float64(i.margin)*i.scale, float64(i.canvas.Height())/2, ) i.canvas.LineTo(float64(i.canvas.Width())/2, float64(i.canvas.Height())/2) i.canvas.LineTo(float64(i.canvas.Width())/2, float64(i.margin)*i.scale) i.canvas.ClosePath() i.canvas.SetStrokeStyle(i.strokeColor) i.canvas.SetLineWidth(i.scale * (float64(i.strokeWeight) / float64(i.canvas.Width()))) i.canvas.SetLineJoin(canvas.Round) i.canvas.SetLineCap(canvas.Round) i.canvas.Stroke() i.canvas.BeginPath() i.canvas.MoveTo(float64(i.canvas.Width())/2, float64(i.canvas.Height())/2) i.canvas.LineTo(float64(i.margin)*i.scale, float64(i.canvas.Height())/2) i.canvas.MoveTo(float64(i.canvas.Width())/2, float64(i.canvas.Height())/2) i.canvas.LineTo( float64(i.canvas.Width())/2, float64(i.canvas.Height())-(float64(i.margin)*i.scale), ) i.canvas.Stroke() i.ResetOffset() } func (i *identicon) DrawBlocks() { for _, b := range i.blocks { b.Draw() } }