Adjusting cell phone photo orientation with Go

Rotating 90 Degrees

Not all incorrectly saved images are upside down. Sometimes the images are also on their sides and need to be rotated by 90 degrees. In these cases, calling exiftool will display something like Orientation: 90 CW for the JPEG file. This indicates that you need to rotate the image 90 degrees clockwise to display it with the correct orientation. In Figure 4, Gimp determines that a photo of the little-known Billy Goat Hill park in San Francisco needs to be rotated a quarter turn to the right and offers to complete the task right away.

Figure 4: Gimp notices that the image needs to be rotated by 90 degrees.

But how does a quarter rotation of the pixels work in a 2D matrix? Figure 5 shows schematically how the first row of pixels with the values (1,2,3,4) ends up as the rightmost column in the result after rotating the matrix clockwise by 90 degrees.

Figure 5: Matrix transformation after rotating an image 90 degrees clockwise.

While the algorithm converts rows into columns, the target image's dimension also changes. Cell phone photos are typically rectangular rather than square, and a photo rotated 90 degrees not only changes its pixel values but also swaps the width and height of the resulting overall image. Listing 4 accounts for this by having line 10 swap the dimensions in bounds in the X and Y direction, so that the modifiable target image generated in line 12 by NewRGBA() already has the dimensions of the rotated rectangle rather than those of the original.

Listing 4


01 package main
03 import (
04   "image"
05 )
07 func rot90(jimg image.Image) *image.RGBA {
08   bounds := jimg.Bounds()
09   width, height := bounds.Max.X, bounds.Max.Y
10   bounds.Max.X, bounds.Max.Y = bounds.Max.Y, bounds.Max.X
12   dimg := image.NewRGBA(bounds)
14   for y := 0; y < height; y++ {
15     for x := 0; x < width; x++ {
16       org := jimg.At(x, y)
17       dimg.Set(height-y, x, org)
18     }
19   }
21   return dimg
22 }

Changing X to Y

The double for loop starting in line 14 of Listing 4 iterates line by line through the source image, retrieves the current pixel values by calling jimg.At(), and uses dimg.Set() to store them column by column from right to left in the target matrix – it's as simple as that.

With these two algorithms now cast in code, the main program in Listing 5 can fetch a photo from disk, read the Exif headers, determine whether there is an Orientation tag in the headers, and initiate rotation to the corrected format. If you have used exiftool to read the JPEG photos' Exif headers thus far, you might be surprised to find that the true value of an Orientation tag in the Exif headers is not a string with the degree information at all, but an integer value. Common settings are the values 6 (90 degrees clockwise), 3 (180 degrees), or 8 (90 degrees counterclockwise). Other values of the standard, which also supports mirrored photos, are shown in Figure 6, but they occur less frequently in practice.

Listing 5


01 package main
03 import (
04   "flag"
05   "fmt"
06   ""
07   "os"
08   "path"
09 )
11 func main() {
12   flag.Usage = func() {
13     fmt.Printf("Usage: %s jpg-file\n", path.Base(os.Args[0]))
14     os.Exit(1)
15   }
17   flag.Parse()
18   if len(flag.Args()) != 1 {
19     flag.Usage()
20   }
22   jpgFile := flag.Arg(0)
24   f, err := os.Open(jpgFile)
25   if err != nil {
26     panic(err)
27   }
29   data, err := exif.Decode(f)
30   if err != nil {
31     panic(err)
32   }
34   orient, err := data.Get(exif.Orientation)
35   if err != nil {
36     fmt.Printf("No orientation header found.\n")
37     os.Exit(0)
38   }
40   val, err := orient.Int(0)
41   if err != nil {
42     panic(err)
43   }
45   switch val {
46   case 3:
47     imgMod(jpgFile, jpgFile, rot180)
48   case 6:
49     imgMod(jpgFile, jpgFile, rot90)
50   default:
51     panic("Unknown orientation")
52   }
53 }
Figure 6: Integer values in the Exif header indicate the photo's orientation.

The Exif tags hidden in a JPEG photo are not easy to read, but fortunately the Go community on GitHub provides some libraries that simplify the task to calling a Decode() function. Listing 5 shows the main autorot program, which uses the goexif library provided by GitHub user rwcarlsen. I used a similar open source product for image manipulation in an earlier column [2].

Using data.Get(), Listing 5 in line 34 retrieves the value of the Orientation tag from the mess of Exif tag data. If the tag is not found, there is nothing to do, because the image already has the correct orientation. However, if the program finds a tag, line 40 fetches the first integer value of the tag (index  ), and the switch construct starting in line 45 determines what kind of corrective rotation (90 or 180 degrees) the image needs and calls the modifier function imgMod with the appropriate algorithm function as a parameter. The calls to imgMod() in Listing 5 use identical names for the source and target files so autorot simply overwrites the original files. If that seems too dangerous, you can rename the target file to .bak, and you're guaranteed no accidents.

When Go's image library writes the JPEG image back to disk, it completely omits the original Exif data, so there's no longer an Orientation header there, indicating correct rotation.

Compiling the Program

You can use the command sequence from Listing 6 to create the autorot binary. The go compiler fetches the required libraries from GitHub, compiles them, and links them. As always, this creates an executable that already contains all the dependencies. It can be copied to another computer without any problems and will run there without any complaints, given a similar operating system and processor architecture.

Listing 6

Compiling autorot

$ go mod init autorot
$ go mod tidy
$ go build autorot.go rotate-90.go rotate-180.go imgmod.go

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Image Processing with EXIF

    You can rename and modify JPEG files without touching the graphics by manipulating settings in the file header.

  • Straight to the Point

    With the Fyne framework, Go offers an easy-to-use graphical interface for all popular platforms. As a sample application, Mike uses an algorithm to draw arrows onto images.

  • imgp

    In no time at all, imgp can change the resolution of images, as well as convert files from PNG to JPEG, remove metadata, and rotate images.

  • Digital_Photo_Intro.pdf

    The Linux environment includes some powerful tools for editing, managing, and scanning digital images.

  • Treasure Hunt

    A geolocation guessing game based on the popular Wordle evaluates a player's guesses based on the distance from and direction to the target location. Mike Schilli turns this concept into a desktop game in Go using the photos from his private collection.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More