linstrom/shared/identicon/identicons.go
mstar f2616c041b
Some checks are pending
/ test (push) Waiting to run
Impl identicons serverside, broken though for some reason
2025-04-10 16:40:15 +02:00

218 lines
5.7 KiB
Go

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()
}
}