The Go slack has a cute little dancing Gopher that appears to have come from Egon Elbre. I love it!
This little dancing Gopher made me think of Party Parrot, so I wanted to parrot-ize him. Normally I might just open up Gimp and start editing, but this is the Go Gopher, we can do better than that!
My plan was to use Go’s image packages to edit each frame and replace the blue with the correct parrot color for that frame by walking over the pixels in each frame.
Once I got into the package docs however, I realized that since gif’s are paletted, I can just tweak the palette on each frame and be done. Much simpler. Let’s get into then, shall we?
Colors!
First things first, I needed to declare the party parrot frame colors, and the light and dark blue that the dancing gopher uses. I grabbed the blues with Sip and I already had the parrot colors on hand. Sure, I could precompute these and declare, but let’s keep it interesting.
Note that I have a DarkParrotColors slice as well, this is for the corresponding dark blue replacements. I generate these with darken which I’ll show in a moment.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
var ( ParrotColors []color.Color DarkParrotColors []color.Color LightGopherBlue color.Color DarkGopherBlue color.Color ) func init() { var err error for _, s := range []string{ "FF6B6B", "FF6BB5", "FF81FF", "FF81FF", "D081FF", "81ACFF", "81FFFF", "81FF81", "FFD081", "FF8181", } { c, err := hexToColor(s) if err != nil { log.Fatal(err) } ParrotColors = append(ParrotColors, c) DarkParrotColors = append(DarkParrotColors, darken(c)) } LightGopherBlue, err = hexToColor("8BD0FF") if err != nil { log.Fatal(err) } DarkGopherBlue, err = hexToColor("82C2EE") if err != nil { log.Fatal(err) } } |
Also notable is the hexToColor which just unpacks an HTML hex RGB representation into a color.Color.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
func hexToColor(hex string) (color.Color, error) { c := color.RGBA{0, 0, 0, 255} r, err := strconv.ParseInt(hex[0:2], 16, 16) if err != nil { return c, err } g, err := strconv.ParseInt(hex[2:4], 16, 16) if err != nil { return c, err } b, err := strconv.ParseInt(hex[4:6], 16, 16) if err != nil { return c, err } c.R = uint8(r) c.G = uint8(g) c.B = uint8(b) return c, nil } |
Here is the darken function, pretty simple.
115 116 117 118 119 120 121 |
func darken(c color.Color) color.Color { r, g, b, a := c.RGBA() r = r - 15 g = g - 15 b = b - 15 return color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)} } |
Now I need to pull in the gif and decode it, all very boilerplate.
57 58 59 60 61 62 63 64 65 66 67 68 |
// Open the dancing gopher gif f, err := os.Open("dancing-gopher.gif") if err != nil { log.Fatal(err) } defer f.Close() // Decode the gif so we can edit it gopher, err := gif.DecodeAll(f) if err != nil { log.Fatal(err) } |
After that, I iterate over the frames and edit the palettes.
73 74 75 76 77 78 79 |
for i, frame := range gopher.Image { lbi = frame.Palette.Index(LightGopherBlue) dbi = frame.Palette.Index(DarkGopherBlue) frame.Palette[lbi] = ParrotColors[i%len(ParrotColors)] frame.Palette[dbi] = DarkParrotColors[i%len(DarkParrotColors)] } |
Lastly, more boilerplate to write it out to disk.
83 84 85 |
o, _ := os.OpenFile("party-gopher.gif", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) defer o.Close() gif.EncodeAll(o, gopher) |
You can grab the code on Github, and thanks again to Egon Elbre for the excellent original gif!