Impl identicons serverside, broken though for some reason
Some checks are pending
/ test (push) Waiting to run

This commit is contained in:
Melody Becker 2025-04-10 16:40:15 +02:00
parent da2a89010c
commit f2616c041b
Signed by: mstar
SSH key fingerprint: SHA256:9VAo09aaVNTWKzPW7Hq2LW+ox9OdwmTSHRoD4mlz1yI
3 changed files with 430 additions and 0 deletions

100
shared/identicon/block.go Normal file
View file

@ -0,0 +1,100 @@
package identicon
import (
"git.mstar.dev/mstar/canvas"
mathutils "git.mstar.dev/mstar/goutils/math"
)
type vec2 struct {
X, Y float64
}
type block struct {
canvas *canvas.Canvas
blockType int
primary string
secondary string
hash int
pos vec2
cellSize int
margin int
scale float64
}
func (b *block) Offset() {
b.canvas.Save()
b.canvas.Translate(0.6*b.scale, -0.6*b.scale)
}
func (b *block) ResetOffset() {
b.canvas.Restore()
}
func (b *block) MakePath(hash int, offset int) {
mod := mathutils.Abs(hash+offset) % 4
switch mod {
case 0:
// Top
b.canvas.BeginPath()
b.canvas.MoveTo(float64(b.pos.X), float64(b.pos.Y))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), float64(b.pos.Y))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), b.pos.Y+float64(int(b.cellSize)))
b.canvas.ClosePath()
case 1:
// Right
b.canvas.BeginPath()
b.canvas.MoveTo(b.pos.X+float64(int(b.cellSize)), float64(b.pos.Y))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), b.pos.Y+float64(int(b.cellSize)))
b.canvas.LineTo(float64(b.pos.X), b.pos.Y+float64(int(b.cellSize)))
b.canvas.ClosePath()
case 2:
// Bottom
b.canvas.BeginPath()
b.canvas.MoveTo(float64(b.pos.X), float64(b.pos.Y))
b.canvas.LineTo(float64(b.pos.X), b.pos.Y+float64(int(b.cellSize)))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), b.pos.Y+float64(int(b.cellSize)))
b.canvas.ClosePath()
case 3:
// Left
b.canvas.BeginPath()
b.canvas.MoveTo(float64(b.pos.X), float64(b.pos.Y))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), float64(b.pos.Y))
b.canvas.LineTo(float64(b.pos.X), b.pos.Y+float64(int(b.cellSize)))
b.canvas.ClosePath()
default:
// Top
b.canvas.BeginPath()
b.canvas.MoveTo(float64(b.pos.X), float64(b.pos.Y))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), float64(b.pos.Y))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), b.pos.Y+float64(int(b.cellSize)))
b.canvas.ClosePath()
}
}
func (b *block) Draw() {
b.Offset()
switch b.blockType {
case 1:
b.MakePath(b.hash, b.hash%3)
b.canvas.SetFillStyle(b.primary)
b.canvas.Fill()
b.MakePath(b.hash, b.hash%5)
b.canvas.SetFillStyle(b.secondary)
b.canvas.Fill()
case 2:
b.MakePath(b.hash, b.hash%4)
b.canvas.SetFillStyle(b.secondary)
b.canvas.Fill()
b.MakePath(b.hash, b.hash%3)
b.canvas.SetFillStyle(b.primary)
b.canvas.Fill()
default:
b.MakePath(b.hash, b.hash%7)
b.canvas.SetFillStyle(b.secondary)
b.canvas.Fill()
b.MakePath(b.hash, b.hash%8)
b.canvas.SetFillStyle(b.primary)
b.canvas.Fill()
}
b.ResetOffset()
}

View file

@ -0,0 +1,218 @@
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()
}
}

112
shared/identicon/shape.go Normal file
View file

@ -0,0 +1,112 @@
package identicon
import (
"math"
"git.mstar.dev/mstar/canvas"
mathutils "git.mstar.dev/mstar/goutils/math"
)
type shape struct {
canvas *canvas.Canvas
hash int
primary string
secondary string
pos vec2
scale float64
cellSize int
strokeColor string
}
func (s *shape) GetColor() string {
if mathutils.Abs(s.hash) == 0 {
return s.primary
} else {
return s.secondary
}
}
func (s *shape) MakePath() {
mod := mathutils.Abs(s.hash+1) % 4
switch mod {
case 0:
// square
s.canvas.BeginPath()
s.canvas.MoveTo(float64(s.pos.X), float64(s.pos.Y))
s.canvas.LineTo(float64(s.pos.X+float64(s.cellSize/2)), float64(s.pos.Y))
s.canvas.LineTo(
float64(s.pos.X+float64(s.cellSize/2)),
float64(s.pos.Y-float64(s.cellSize/2)),
)
s.canvas.LineTo(float64(s.pos.X), float64(s.pos.Y-float64(s.cellSize/2)))
s.canvas.ClosePath()
case 1:
// circle
s.canvas.BeginPath()
s.canvas.Arc(
float64(s.pos.X)+float64(s.cellSize)/math.Pi-5,
float64(s.pos.Y)-float64(s.cellSize)/math.Pi+5,
float64(s.cellSize)/3,
0,
math.Pi*2,
true,
)
case 2:
// triangle
s.canvas.BeginPath()
s.canvas.MoveTo(float64(s.pos.X), float64(s.pos.Y))
s.canvas.LineTo(float64(s.pos.X)+float64(s.cellSize)*0.65, float64(s.pos.Y))
s.canvas.LineTo(float64(s.pos.X), float64(s.pos.Y)-float64(s.cellSize)*0.65)
s.canvas.ClosePath()
case 3:
// oval
s.canvas.BeginPath()
s.canvas.MoveTo(
float64(s.pos.X)-float64(s.cellSize)*0.2,
float64(s.pos.Y)+float64(s.cellSize)*0.2,
)
s.canvas.QuadraticCurveTo(
float64(s.pos.X)+float64(s.cellSize)*0.4,
float64(s.pos.Y),
float64(s.pos.X)+float64(s.cellSize)*0.5,
float64(s.pos.Y)-float64(s.cellSize)*0.5,
)
s.canvas.MoveTo(
float64(s.pos.X)+float64(s.cellSize)*0.5,
float64(s.pos.Y)-float64(s.cellSize)*0.5,
)
s.canvas.QuadraticCurveTo(
float64(s.pos.X),
float64(s.pos.Y)-float64(s.cellSize)*0.4,
float64(s.pos.X)-float64(s.cellSize)*0.2,
float64(s.pos.Y)+float64(s.cellSize)*0.2,
)
default:
// square
s.canvas.BeginPath()
s.canvas.MoveTo(float64(s.pos.X), float64(s.pos.Y))
s.canvas.LineTo(float64(s.pos.X+float64(s.cellSize/2)), float64(s.pos.Y))
s.canvas.LineTo(
float64(s.pos.X+float64(s.cellSize/2)),
float64(s.pos.Y-float64(s.cellSize/2)),
)
s.canvas.LineTo(float64(s.pos.X), float64(s.pos.Y-float64(s.cellSize/2)))
s.canvas.ClosePath()
}
}
func (s *shape) Draw(hasStroke bool, strokeWeight int) {
color := s.GetColor()
// THIS SHIT ISN'T IMPLEMENTED! I hope it will work regardless
// s.canvas.SetGlobalCompositeOperation("source-over")
s.MakePath()
s.canvas.SetFillStyle(color)
s.canvas.SetStrokeStyle(s.strokeColor)
s.canvas.SetLineWidth(s.scale * (4 / 5 * float64(strokeWeight)) / float64(canvasSize))
s.canvas.SetLineJoin(canvas.Round)
s.canvas.SetLineCap(canvas.Round)
s.canvas.Fill()
if hasStroke {
s.canvas.Stroke()
}
}