Image processing with Go

A few days ago I wanted to do some batch image processing. It could be done in Photoshop, I know, but I wanted to have some fun as well and learn some algorithms. I started studying Go in January and it sounded like an opportunity to practice a little, so I began to write my own program to process images: blzimg.

blzimg will have some image operations. The first of them (and the only one until now) is called “lightest”. It merges the lightest pixels of a list of images into a single image.

Comparing the luminance of pixels

The first image operation I wanted to do was to get some images and, for every pixel (x,y) of them, their RGB values would be compared. The lightest pixels at the same position (x,y) compose the final image.

Images talk better than text. Let’s use these 3 images:

3x3 boxes with the first column white 3x3 boxes with the second column white 3x3 boxes with the third column white
img1.jpg img2.jpg img3.jpg

The lightest operation will merge these three images into a final image that will be this:


The grey pixels where fully replaced by the white pixels, since the latter are lighter. The formula to obtain the lightest pixel of an image was borrowed from this question at StackOverflow. The L is for luminance, and r, g, b are the color components of a pixel:

L = 0.2126 * r + 0.7152 * g + 0.0722 * b

The more the luminance, the lighter a pixel is. Our main idea is that if the luminance value L for a pixel is greater than the L value for another pixel, that pixel will be the lightest one and, therefore, be chosen.

I don’t know if it’s the scientifically proved best choice, but it has worked for my purposes. This is my implementation in Go:

Comparing images

At first, I created a function to receive a slice of image.Image‘s and travel through their pixels, comparing them:

In this old version, notice that the first image was read twice in the for loop and an empty slice would ruin everything. 😀 But our idea is this: store the lightest pixel and compare it to the pixel in the current image. If the newer pixel is lightest, we replace the current lightest pixel.

I created this function using TDD and it worked well with image.Image‘s, but how can we parse images from File‘s and keep the code testable?

Using containers for testing

The first version of Result() function received a slice of image.Image‘s, based on my test where I created some image.Image‘s to verify. But it has some limitations in the real world. How could I handle real files?

  • If I used a slice of image.Image‘s as arguments, I would have to get a list of files, decode them and create a very heavy slice of image.Image‘s to pass to Result().
  • If I used a slice of File‘s as parameter, it would become harder to do unit testing.

I created an interface to solve both cases: ImageContainer.

Its implementations must have a GetImage() function that will return a image.Image only when needed, so it’s a more lightweight approach. For example, a FileImageContainer would keep the file path within it and return the image.Image when GetImage() is called. An ImageItselfContainer, used in the unit tests, can keep the image data itself and returns this data when GetImage() is called. This is the current implementation:

The final version of Result(), now using an ImageContainer‘s instead of image.Image‘s, is shown below. The image operation won’t know (and it doesn’t need to know!) what kind of container it’s dealing with, and now the same code can handle image.Image‘s and File‘s!

Parsing command line arguments with cli.go

To parse command line arguments I used cli.go. It’s a library that parses command line parameters and creates a nice help output:

$ blzimg 
   blzimg - Execute some operations on images

   blzimg [global options] command [command options] [arguments...]


   Esdras Beleza  
   lightest, l  Merge the lightest pixels of some images in a single one
   help, h      Shows a list of commands or help for one command
   --output "final.jpg" Output file
   --help, -h           show help
   --version, -v        print the version

Finally: running blzimg

A few days ago I took some crappy pictures just to test my new Rokinon 12mm lens with my Fuji X-E1 camera. I used a intervalometer to take these pictures with a interval of 20 seconds between them, in a total of 8 minutes that were compressed in this timelapse of a few seconds:

What if we run blzimg to merge all the images from this timelapse into a single image using the lightest operation?

blzimg --output final.jpg lightest 2015-04-07_21-*

Since the lightest points in these pictures are the clouds and the stars, the output is a giant cloud and the amazing beginning of a star trail as if it were below the clouds:


Show me the code!

The full code can be downloaded at my GitHub. 😀

9 thoughts on “Image processing with Go”

  1. Nice! In your gist code for image_container.go (not in your actual Git repo), line 19 should be:

    func (i ImageItselfContainer) GetImage() image.Image {
    return i.Image

  2. Please be aware that the Luminance weights you are using are only valid for sRGB / Rec. 709 colourspaces and they are assuming a linear input.



  3. SoFi هي شركة مالية قائمة على الإنترنت والجوال تقدم مجموعة من المنتجات للادخار والاستثمار والتأمين والاقتراض. إقراض الطلاب هو عرض رئيسي من SoFi. تقدم قروض الطلاب الخاصة لطلاب الكلية وأولياء أمورهم وطلاب الدراسات العليا. تقدم SoFi أيضًا إعادة تمويل قروض الطلاب للمقترضين الذين يرغبون في دمج ديون قرض الطلاب أو تغيير شروط القرض

  4. الدرجة والعمل المطلوب لإعادة تمويل قروض الطلاب
    وأوضح الايجابيات
    تقديرات المعدل باستخدام فحص ائتماني مرن: يمكنك معرفة ما إذا كان من المحتمل أن تحصل على الموافقة والحصول على معاينة للمعدل الذي يمكن أن يقدمه لك SoFi – دون التأثير على رصيدك.
    يُسمح بالموقعين المشاركين ، مع خيار الإفراج عن المُوقِع المشترك: يمكنك التقدم بطلب للحصول على قرض طالب SoFi مع مُوقِّع مشارك ، والذي يمكن أن يحسّن فرصك في الموافقة ويساعدك في التأهل للحصول على معدل أفضل

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.