// Package icon draws the KeepOrg padlock at any size. // // The glyph is geometry rather than a shipped image, so one definition serves // the tray (a small PNG, or an ICO on Windows, made at run time) and the // packages (the Linux icon theme and the macOS .icns, written at build time // by tools/genicon). package icon import ( "bytes" "encoding/binary" "image" "image/color" "image/png" "math" ) // design is the edge length of the coordinate system the shapes are drawn in. const design = 32.0 // subsamples is the per-axis supersampling rate that antialiases the curves. const subsamples = 4 var ( // Brand is the padlock's colour where it stands on its own, in a tray // that may be light or dark. Brand = color.NRGBA{R: 0x2f, G: 0x6f, B: 0xd6, A: 0xff} // Template is the macOS menu-bar form: black, which the system recolours // for light and dark menu bars from the alpha channel alone. Template = color.NRGBA{A: 0xff} ) // lock reports whether a design-space point is inside the padlock. func lock(x, y float64) bool { if keyhole(x, y) { return false } // Body: a rounded rectangle. if roundedRect(x, y, 5, 14.5, 27, 29, 3) { return true } // Shackle: the top half of a ring, with straight legs into the body. d := math.Hypot(x-16, y-12) if y <= 12 && d >= 5 && d <= 8.5 { return true } return y > 12 && y < 15 && ((x >= 7.5 && x <= 11) || (x >= 21 && x <= 24.5)) } func keyhole(x, y float64) bool { return math.Hypot(x-16, y-20.5) <= 2.6 || (x >= 15 && x <= 17 && y >= 20.5 && y <= 25.5) } // badge is the app icon's background: a rounded square filling the canvas. func badge(x, y float64) bool { return roundedRect(x, y, 0.5, 0.5, 31.5, 31.5, 7) } func roundedRect(x, y, x0, y0, x1, y1, r float64) bool { if x < x0 || x > x1 || y < y0 || y > y1 { return false } cx := math.Max(x0+r, math.Min(x, x1-r)) cy := math.Max(y0+r, math.Min(y, y1-r)) return math.Hypot(x-cx, y-cy) <= r } // coverage estimates how much of an output pixel a shape covers. func coverage(in func(x, y float64) bool, px, py int, scale float64) float64 { step := scale / subsamples hits := 0 for sy := 0; sy < subsamples; sy++ { for sx := 0; sx < subsamples; sx++ { if in(float64(px)*scale+(float64(sx)+0.5)*step, float64(py)*scale+(float64(sy)+0.5)*step) { hits++ } } } return float64(hits) / (subsamples * subsamples) } // Tray draws the bare padlock in c on a transparent background, for the // system tray or the macOS menu bar. func Tray(size int, c color.NRGBA) *image.NRGBA { img := image.NewNRGBA(image.Rect(0, 0, max(size, 0), max(size, 0))) scale := design / float64(max(size, 1)) for py := 0; py < size; py++ { for px := 0; px < size; px++ { if a := coverage(lock, px, py, scale); a > 0 { img.SetNRGBA(px, py, color.NRGBA{c.R, c.G, c.B, uint8(float64(c.A)*a + 0.5)}) } } } return img } // App draws the application icon: a white padlock on a brand-coloured // rounded square, for launchers, the Dock and file managers. func App(size int) *image.NRGBA { img := image.NewNRGBA(image.Rect(0, 0, max(size, 0), max(size, 0))) scale := design / float64(max(size, 1)) // The padlock is drawn smaller inside the badge, around its centre. inner := func(x, y float64) bool { return lock((x-16)/0.72+16, (y-16.5)/0.72+16) } for py := 0; py < size; py++ { for px := 0; px < size; px++ { bg := coverage(badge, px, py, scale) if bg == 0 { continue } fg := coverage(inner, px, py, scale) mix := func(b, w uint8) uint8 { return uint8(float64(b)*(1-fg) + float64(w)*fg + 0.5) } img.SetNRGBA(px, py, color.NRGBA{mix(Brand.R, 0xff), mix(Brand.G, 0xff), mix(Brand.B, 0xff), uint8(255*bg + 0.5)}) } } return img } // PNG encodes an image. Encoding an in-memory NRGBA cannot fail. func PNG(img image.Image) []byte { var b bytes.Buffer _ = png.Encode(&b, img) return b.Bytes() } // ICO wraps PNG-encoded images in an ICO container, which is what the // Windows tray takes. PNG entries are valid in ICO since Windows Vista. func ICO(images ...*image.NRGBA) []byte { var hdr, data bytes.Buffer le := binary.LittleEndian _ = binary.Write(&hdr, le, [3]uint16{0, 1, uint16(len(images))}) offset := 6 + 16*len(images) for _, img := range images { p := PNG(img) w, h := img.Bounds().Dx(), img.Bounds().Dy() dim := func(n int) uint8 { // 0 means 256 in an ICO directory entry if n >= 256 { return 0 } return uint8(n) } _ = binary.Write(&hdr, le, struct { W, H, Colors, Reserved uint8 Planes, Bits uint16 Size, Offset uint32 }{dim(w), dim(h), 0, 0, 1, 32, uint32(len(p)), uint32(offset)}) data.Write(p) offset += len(p) } return append(hdr.Bytes(), data.Bytes()...) }