package qidenticon import ( "crypto/sha512" "encoding/binary" "image" "image/color" "math" "github.com/fogleman/gg" ) // Code derives a code for use with Render. func Code(str string) uint64 { buf := sha512.Sum512([]byte(str)) return binary.BigEndian.Uint64(buf[56:]) } type Settings struct { // TwoColor specifies if the identicon should be // generated using one or two colors. TwoColor bool // Alpha specifies the transparency of the generated identicon. Alpha uint8 } // DefaultSettings returns a Settings object with the recommended settings. func DefaultSettings() *Settings { return &Settings{ TwoColor: true, Alpha: 255, } } // Render generates an identicon. // code is a code derived by the Code function. // totalSize specifies the total size in pixels. It is recommended that // this is divisible by 3. func Render(code uint64, totalSize int, settings *Settings) image.Image { penWidth := 0 middleType := int(code & 0x03) middleInvert := code>>2&0x01 == 1 cornerType := int(code >> 3 & 0x0f) cornerInvert := code>>7&0x01 == 1 cornerTurn := int(code >> 8 & 0x03) sideType := int(code >> 10 & 0x0f) sideInvert := code>>14&0x01 == 1 sideTurn := int(code >> 15 & 0x03) blue := code >> 17 & 0x1f green := code >> 22 & 0x1f red := code >> 27 & 0x1f secondRed := code >> 32 & 0x1f secondGreen := code >> 37 & 0x1f secondBlue := code >> 42 & 0x1f swapCross := code>>47&0x01 == 1 middleType = middlePatchSet[middleType] foreColor := color.RGBA{R: uint8(red) << 3, G: uint8(green) << 3, B: uint8(blue) << 3, A: settings.Alpha} var secondColor color.RGBA if settings.TwoColor { secondColor = color.RGBA{R: uint8(secondRed) << 3, G: uint8(secondGreen) << 3, B: uint8(secondBlue) << 3, A: settings.Alpha} } else { secondColor = foreColor } var middleColor color.Color if swapCross { middleColor = foreColor } else { middleColor = secondColor } image := gg.NewContext(totalSize, totalSize) patchSize := float64(totalSize) / 3 drawPatch(gg.Point{X: 1, Y: 1}, 0, middleInvert, middleType, image, patchSize, middleColor, penWidth) for i, p := range []gg.Point{{X: 1, Y: 0}, {X: 2, Y: 1}, {X: 1, Y: 2}, {X: 0, Y: 1}} { drawPatch(p, sideTurn+1+i, sideInvert, sideType, image, patchSize, foreColor, penWidth) } for i, p := range []gg.Point{{X: 0, Y: 0}, {X: 2, Y: 0}, {X: 2, Y: 2}, {X: 0, Y: 2}} { drawPatch(p, cornerTurn+1+i, cornerInvert, cornerType, image, patchSize, secondColor, penWidth) } return image.Image() } func drawPatch(pos gg.Point, turn int, invert bool, type_ int, image *gg.Context, patchSize float64, foreColor color.Color, penWidth int) { path := pathSet[type_] turn %= 4 image.Push() image.Translate(pos.X*patchSize+float64(penWidth)/2, pos.Y*patchSize+float64(penWidth)/2) image.RotateAbout(float64(turn)*math.Pi/2, patchSize/2, patchSize/2) for _, p := range path { image.LineTo(p.X/4*patchSize, p.Y/4*patchSize) } image.ClosePath() if invert { image.MoveTo(0, 0) image.LineTo(0, patchSize) image.LineTo(patchSize, patchSize) image.LineTo(patchSize, 0) image.ClosePath() } image.SetColor(foreColor) image.Fill() image.Pop() } var pathSet = [][]gg.Point{ // [0] full square: {{X: 0, Y: 0}, {X: 4, Y: 0}, {X: 4, Y: 4}, {X: 0, Y: 4}}, // [1] right-angled triangle pointing top-left: {{X: 0, Y: 0}, {X: 4, Y: 0}, {X: 0, Y: 4}}, // [2] upwardy triangle: {{X: 2, Y: 0}, {X: 4, Y: 4}, {X: 0, Y: 4}}, // [3] left half of square, standing rectangle: {{X: 0, Y: 0}, {X: 2, Y: 0}, {X: 2, Y: 4}, {X: 0, Y: 4}}, // [4] square standing on diagonale: {{X: 2, Y: 0}, {X: 4, Y: 2}, {X: 2, Y: 4}, {X: 0, Y: 2}}, // [5] kite pointing topleft: {{X: 0, Y: 0}, {X: 4, Y: 2}, {X: 4, Y: 4}, {X: 2, Y: 4}}, // [6] Sierpinski triangle, fractal triangles: {{X: 2, Y: 0}, {X: 4, Y: 4}, {X: 2, Y: 4}, {X: 3, Y: 2}, {X: 1, Y: 2}, {X: 2, Y: 4}, {X: 0, Y: 4}}, // [7] sharp angled lefttop pointing triangle: {{X: 0, Y: 0}, {X: 4, Y: 2}, {X: 2, Y: 4}}, // [8] small centered square: {{X: 1, Y: 1}, {X: 3, Y: 1}, {X: 3, Y: 3}, {X: 1, Y: 3}}, // [9] two small triangles: {{X: 2, Y: 0}, {X: 4, Y: 0}, {X: 0, Y: 4}, {X: 0, Y: 2}, {X: 2, Y: 2}}, // [10] small topleft square: {{X: 0, Y: 0}, {X: 2, Y: 0}, {X: 2, Y: 2}, {X: 0, Y: 2}}, // [11] downpointing right-angled triangle on bottom: {{X: 0, Y: 2}, {X: 4, Y: 2}, {X: 2, Y: 4}}, // [12] uppointing right-angled triangle on bottom: {{X: 2, Y: 2}, {X: 4, Y: 4}, {X: 0, Y: 4}}, // [13] small rightbottom pointing right-angled triangle on topleft: {{X: 2, Y: 0}, {X: 2, Y: 2}, {X: 0, Y: 2}}, // [14] small lefttop pointing right-angled triangle on topleft: {{X: 0, Y: 0}, {X: 2, Y: 0}, {X: 0, Y: 2}}, // [15] empty: {}, } // get the [0] full square, [4] square standing on diagonale, [8] small centered square, or [15] empty tile: var middlePatchSet = []int{0, 4, 8, 15}