package main import ( "flag" "fmt" "image" "image/color" _ "image/jpeg" "image/png" _ "image/png" "os" "slices" ) func paletteFromImage(img image.Image) (p []color.Color) { for i := 0; i < img.Bounds().Dx(); i++ { p = append(p, img.At(i, 0)) } return } func parseHexColor(s string) (c color.RGBA, err error) { c.A = 0xff switch len(s) { case 7: _, err = fmt.Sscanf(s, "#%02x%02x%02x", &c.R, &c.G, &c.B) case 4: _, err = fmt.Sscanf(s, "#%1x%1x%1x", &c.R, &c.G, &c.B) // Double the hex digits: c.R *= 17 c.G *= 17 c.B *= 17 default: err = fmt.Errorf("invalid length, must be 7 or 4") } return } func mean(c1, c2 color.Color) color.Color { r1, g1, b1, _ := c1.RGBA() r2, g2, b2, _ := c2.RGBA() return color.RGBA{ uint8((r1 + r2) / 2), uint8((g1 + g2) / 2), uint8((b1 + b2) / 2), 0xFF, } } type Convert struct { image.Image color.Palette } func (c Convert) ColorModel() color.Model { return c.Image.ColorModel() } func (c Convert) Bounds() image.Rectangle { return c.Image.Bounds() } func (c Convert) At(x, y int) color.Color { if isMeanCloser(c.Palette, c.Image.At(x, y)) { first, second := twoClosest(c.Palette, c.Image.At(x, y)) // chessboard texture if (x%2 != 0) && (y%2 != 0) || (x%2 == 0) && (y%2 == 0) { return first } return second } return c.Palette.Convert(c.Image.At(x, y)) } func twoClosest(p color.Palette, c color.Color) (first, second color.Color) { first = p.Convert(c) i := p.Index(c) p2 := slices.Delete(slices.Clone(p), i, i+1) second = p2.Convert(c) return } func isMeanCloser(p color.Palette, c color.Color) bool { first, second := twoClosest(p, c) mp := color.Palette{first, mean(first, second), second} i := mp.Index(c) return i == mp.Index(mean(first, second)) } func main() { paletteFile := flag.String("p", "", "provide palette file") flag.Parse() var palette []color.Color var colors []string _ = []string{ "#ffffec", // soft-white "#ff0000", // red "#00ff00", // green "#0000ff", // blue "#000000", // black } funniPalette := []string{ "#ffffec", "#000000", "#4a3544", "#757575", "#8e8e8e", "#666666", "#4c4c4c", "#3b3b3b", "#949494", "#ffffff", "#665361", "#3f3f3f", "#525252", "#838383", "#111111", "#070707", "#b9b9b9", "#dfdfdf", } colors = funniPalette if *paletteFile != "" { f, err := os.Open(*paletteFile) if err != nil { panic(err) } img, _, err := image.Decode(f) if err != nil { panic(err) } palette = paletteFromImage(img) } else { palette = func() (p []color.Color) { for _, v := range colors { c, err := parseHexColor(v) if err != nil { panic(err) } p = append(p, c) } return }() } img, _, err := image.Decode(os.Stdin) if err != nil { panic(err) } palette = append(palette, color.RGBA{88, 88, 88, 0xFF}) err = png.Encode(os.Stdout, Convert{img, palette}) if err != nil { panic(err) } }