Mostafa Asgari

Mostafa Asgari

Draw Github's contribution activity using Go

I wanted to know how image package works and how can I draw images in Go language. So I decided to draw Github's contribution activity that you find in each user's profile. Here is my contribution activity calendar in Github:

act-mostafa.png

Show me the code

First, you should create an image. In order to do that, you need to import image packge and create the image with your desired width and height:

img := image.NewRGBA(image.Rect(0, 0, imageWidth, imageHeight))

Then in order to draw something, you should use draw package. First I set the background color to white:

draw.Draw(img, img.Bounds(), image.White, image.Point{}, draw.Src)

Now let's focus on drawing a box. I wanted to draw a box with a border. But draw package is very low level and doesn't have a function like drawRectangle(x, y, w, h, borderSize). But it has draw function which can copy/draw from another image to the destination image. So in order to draw a green box, at position x=10, y=10 with width and height equals to 50, you would write:

green := color.RGBA{0, 255, 0, 255}
pos := image.Rectangle{image.Point{10,10}, image.Point{50,50}}
draw.Draw(destinationImg, pos, &image.Uniform{green}, image.Point{}, draw.Src)

Have you noticed the last line is very similar to when I set the background color to white? Basically, this line copy/draw from the source image (which here is an unbounded image filled with green) at position 0,0, to the destination image at a desired location (here controlled by pos variable).

Ok. Now to draw a box with a border, we draw two boxes. The first box is bigger and shows our border color, and the other one is slightly smaller and denotes the fill color. The end result is a box with a border. The picture below depicts the idea:

box.png The responsibility of drawBox function is to draw a box with a border and filled it with a random color. Now that we can draw a box, drawing boxes for the entire year is easy. Just put drawBox inside a loop . Here is the sample result:

output.jpg

Colors

Wait a second! you told me that drawBox function fills the box randomly. But why the majority of boxes filled with white color? Shouldn't be the distribution uniform? The answer is I wanted to mimic real life, and in real life, most developers are inactive in Github. So I deliberately gave the inactivity color more chance to be selected. I created a list of fill colors:

fillColors    = []color.RGBA{
        {155, 233, 168, 255},
        {64, 196, 99, 255},
        {48, 161, 78, 255},
        {33, 110, 57, 255},
    }

Then I appended inactivity color to this list randomly:

    // Most users are not active, so this code simulate the user's inactivity
    // the lower `lazynessLevel`, the more activity you see
    lazynessLevel := rand.Intn(100) + 1
    for i := 1; i <= lazynessLevel; i++ {
        fillColors = append(fillColors, inactiveColor)
    }

Drawing text

The last piece of the puzzle is drawing text for month names and days. Unfortunately, the standard library doesn't provide drawing text, so you should import external packages. I also don't know the details of these packages. For instance, I wish there were a simpler way to choose the Font Face and Font Size. By the way, here is the code that does the job:

import    "golang.org/x/image/font"
import    "golang.org/x/image/font/basicfont"
import    "golang.org/x/image/math/fixed"

func drawString(img *draw.Image, x, y int, text string) {
    black := color.RGBA{0, 0, 0, 255}
    point := fixed.Point26_6{fixed.Int26_6(x * 64), fixed.Int26_6(y * 64)}

    d := font.Drawer{
        Dst:  *img,
        Src:  image.NewUniform(black),
        Face: basicfont.Face7x13,
        Dot:  point,
    }
    d.DrawString(text)
}

Here are some image outputs of the program:

1.png

2.png

3.png

Conclusion

I mentioned the most important parts of the code. If you want to see the full functional program, please check it out here.

 
Share this