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.
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.
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
rotate-90.go
01 package main 02 03 import ( 04 "image" 05 ) 06 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 11 12 dimg := image.NewRGBA(bounds) 13 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 } 20 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
autorot.go
01 package main 02 03 import ( 04 "flag" 05 "fmt" 06 "github.com/rwcarlsen/goexif/exif" 07 "os" 08 "path" 09 ) 10 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 } 16 17 flag.Parse() 18 if len(flag.Args()) != 1 { 19 flag.Usage() 20 } 21 22 jpgFile := flag.Arg(0) 23 24 f, err := os.Open(jpgFile) 25 if err != nil { 26 panic(err) 27 } 28 29 data, err := exif.Decode(f) 30 if err != nil { 31 panic(err) 32 } 33 34 orient, err := data.Get(exif.Orientation) 35 if err != nil { 36 fmt.Printf("No orientation header found.\n") 37 os.Exit(0) 38 } 39 40 val, err := orient.Int(0) 41 if err != nil { 42 panic(err) 43 } 44 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 }
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
« Previous 1 2 3 Next »
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
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.
News
-
Gnome OS Adopting systemd-sysupdate
Gnome OS is about to undergo a major under-the-hood change that promises enhanced security.
-
Endless OS 6 has Arrived
After more than a year since the last update, the latest release of Endless OS is now available for general usage.
-
Fedora Asahi 40 Remix Available for Macs with Apple Silicon
If you've been anticipating KDE's Plasma 6 for your Apple Silicon-powered Mac, then you're in luck.
-
Red Hat Adds New Deployment Option for Enterprise Linux Platforms
Red Hat has re-imagined enterprise Linux for an AI future with Image Mode.
-
OSJH and LPI Release 2024 Open Source Pros Job Survey Results
See what open source professionals look for in a new role.
-
Proton 9.0-1 Released to Improve Gaming with Steam
The latest release of Proton 9 adds several improvements and fixes an issue that has been problematic for Linux users.
-
So Long Neofetch and Thanks for the Info
Today is a day that every Linux user who enjoys bragging about their system(s) will mourn, as Neofetch has come to an end.
-
Ubuntu 24.04 Comes with a “Flaw"
If you're thinking you might want to upgrade from your current Ubuntu release to the latest, there's something you might want to consider before doing so.
-
Canonical Releases Ubuntu 24.04
After a brief pause because of the XZ vulnerability, Ubuntu 24.04 is now available for install.
-
Linux Servers Targeted by Akira Ransomware
A group of bad actors who have already extorted $42 million have their sights set on the Linux platform.