| 1 | // Package seedmark renders version 1 of the Seedmark avatar design without |
| 2 | // JavaScript. It follows the upstream generator at c808a94; fixtures pin its |
| 3 | // visual output. Avatars are decorative and are not identity verification. |
| 4 | package seedmark |
| 5 | |
| 6 | import ( |
| 7 | "strconv" |
| 8 | "strings" |
| 9 | "unicode/utf16" |
| 10 | ) |
| 11 | |
| 12 | const Version = 1 |
| 13 | |
| 14 | var families = [...]string{"masks" , "creatures" , "botanical" , "orbital" , "landscapes" , "glyphs" , "woven" , "machines" } |
| 15 | |
| 16 | var palettes = [...][4]string{ |
| 17 | {"#142d36" , "#f5dfad" , "#ed7351" , "#65c4ae" }, |
| 18 | {"#24223c" , "#f7dfbf" , "#a89cfc" , "#f7788b" }, |
| 19 | {"#163b32" , "#f4eacb" , "#b4cf66" , "#f69c64" }, |
| 20 | {"#3a2330" , "#ffe5b9" , "#ef7689" , "#8dceba" }, |
| 21 | {"#1d304c" , "#e8eff0" , "#f4b855" , "#72b9d0" }, |
| 22 | {"#422d26" , "#f8e8c8" , "#e8a04d" , "#72afa0" }, |
| 23 | {"#283526" , "#f4edcf" , "#d3c65e" , "#ed8863" }, |
| 24 | {"#292745" , "#efe8f5" , "#d89ada" , "#8fcabc" }, |
| 25 | {"#18343c" , "#f8e4d0" , "#f88c66" , "#a7cee0" }, |
| 26 | {"#393127" , "#f4ebd8" , "#adb77a" , "#da8c6f" }, |
| 27 | {"#2c2440" , "#fbe8c2" , "#f2b54f" , "#b2a1e2" }, |
| 28 | {"#1b3537" , "#e9f0d7" , "#79c7b8" , "#e5a4aa" }, |
| 29 | } |
| 30 | |
| 31 | type recipe struct { |
| 32 | Family string |
| 33 | Palette [4]string |
| 34 | Layout struct{ Variant, Flip, Tilt int } |
| 35 | Silhouette struct{ Variant, Width, Height int } |
| 36 | Features struct{ Variant, Count, Accent int } |
| 37 | Texture struct{ Variant, Marks int } |
| 38 | } |
| 39 | |
| 40 | type rng struct{ a, b, c, d uint32 } |
| 41 | |
| 42 | func newRNG(seed, namespace string) *rng { |
| 43 | var framed strings.Builder |
| 44 | for _, part := range []string{"seedmark" , "1" , seed, namespace} { |
| 45 | framed.WriteString(strconv.Itoa(len(utf16.Encode([]rune(part))))) |
| 46 | framed.WriteByte(':' ) |
| 47 | framed.WriteString(part) |
| 48 | } |
| 49 | a, b, c, d := uint32(1779033703), uint32(3144134277), uint32(1013904242), uint32(2773480762) |
| 50 | for _, unit := range utf16.Encode([]rune(framed.String())) { |
| 51 | k := uint32(unit) |
| 52 | a = b ^ (a^k)*597399067 |
| 53 | b = c ^ (b^k)*2869860233 |
| 54 | c = d ^ (c^k)*951274213 |
| 55 | d = a ^ (d^k)*2716044179 |
| 56 | } |
| 57 | a = (c ^ (a >> 18)) * 597399067 |
| 58 | b = (d ^ (b >> 22)) * 2869860233 |
| 59 | c = (a ^ (c >> 17)) * 951274213 |
| 60 | d = (b ^ (d >> 19)) * 2716044179 |
| 61 | return &rng{a ^ b ^ c ^ d, b ^ a, c ^ a, d ^ a} |
| 62 | } |
| 63 | |
| 64 | func (r *rng) integer(minimum, maximum int) int { |
| 65 | t := r.a + r.b + r.d |
| 66 | r.d++ |
| 67 | r.a = r.b ^ (r.b >> 9) |
| 68 | r.b = r.c + (r.c << 3) |
| 69 | r.c = ((r.c << 21) | (r.c >> 11)) + t |
| 70 | return minimum + int(float64(t)/4294967296*float64(maximum-minimum+1)) |
| 71 | } |
| 72 | |
| 73 | func recipeFor(seed string) recipe { |
| 74 | var r recipe |
| 75 | r.Family = families[newRNG(seed, "family" ).integer(0, len(families)-1)] |
| 76 | r.Palette = palettes[newRNG(seed, "palette" ).integer(0, len(palettes)-1)] |
| 77 | layout, silhouette := newRNG(seed, "layout" ), newRNG(seed, "silhouette" ) |
| 78 | features, texture := newRNG(seed, "features" ), newRNG(seed, "texture" ) |
| 79 | r.Layout.Variant, r.Layout.Flip = layout.integer(0, 3), layout.integer(0, 1) |
| 80 | r.Layout.Tilt = [...]int{-12, 0, 12}[layout.integer(0, 2)] |
| 81 | r.Silhouette.Variant = silhouette.integer(0, 3) |
| 82 | r.Silhouette.Width, r.Silhouette.Height = silhouette.integer(26, 38), silhouette.integer(28, 42) |
| 83 | r.Features.Variant, r.Features.Count = features.integer(0, 3), features.integer(2, 5) |
| 84 | r.Features.Accent = features.integer(0, 1) |
| 85 | r.Texture.Variant, r.Texture.Marks = texture.integer(0, 2), texture.integer(2, 4) |
| 86 | return r |
| 87 | } |
| 88 | |
| 89 | // Avatar returns a standalone 64-pixel SVG for the full seed. The version 1 |
| 90 | // geometry remains fixed; CSS may resize the SVG without changing its design. |
| 91 | func Avatar(seed string) string { |
| 92 | r := recipeFor(seed) |
| 93 | render := map[string]func(recipe) string{"masks" : masks, "creatures" : creatures, "botanical" : botanical, "orbital" : orbital, "landscapes" : landscapes, "glyphs" : glyphs, "woven" : woven, "machines" : machines} |
| 94 | texture := "" |
| 95 | for i := 0; i < r.Texture.Marks; i++ { |
| 96 | x := float64(8 + i*6) |
| 97 | if r.Texture.Variant == 1 { |
| 98 | texture += circle(x, 6, 1.5, r.Palette[3]) |
| 99 | } else if r.Texture.Variant == 2 { |
| 100 | texture += rect(x-1, 5, 3, 3, 0, r.Palette[2]) |
| 101 | } |
| 102 | } |
| 103 | flip := "1" |
| 104 | if r.Layout.Flip != 0 { |
| 105 | flip = "-1" |
| 106 | } |
| 107 | transform := "translate(32 32) rotate(" + strconv.Itoa(r.Layout.Tilt) + ") scale(" + flip + " 1) translate(-32 -32)" |
| 108 | return `<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><rect width="64" height="64" fill="` + r.Palette[0] + `"/>` + texture + group(render[r.Family](r), transform) + `</svg>` |
| 109 | } |