Impl identicons serverside, broken though for some reason
Some checks are pending
/ test (push) Waiting to run
Some checks are pending
/ test (push) Waiting to run
This commit is contained in:
parent
da2a89010c
commit
f2616c041b
3 changed files with 430 additions and 0 deletions
100
shared/identicon/block.go
Normal file
100
shared/identicon/block.go
Normal 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()
|
||||||
|
}
|
218
shared/identicon/identicons.go
Normal file
218
shared/identicon/identicons.go
Normal 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
112
shared/identicon/shape.go
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue