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
-
Budgie 10.10 Scheduled for Q1 2025 with a Surprising Desktop Update
If Budgie is your desktop environment of choice, 2025 is going to be a great year for you.
-
Firefox 134 Offers Improvements for Linux Version
Fans of Linux and Firefox rejoice, as there's a new version available that includes some handy updates.
-
Serpent OS Arrives with a New Alpha Release
After months of silence, Ikey Doherty has released a new alpha for his Serpent OS.
-
HashiCorp Cofounder Unveils Ghostty, a Linux Terminal App
Ghostty is a new Linux terminal app that's fast, feature-rich, and offers a platform-native GUI while remaining cross-platform.
-
Fedora Asahi Remix 41 Available for Apple Silicon
If you have an Apple Silicon Mac and you're hoping to install Fedora, you're in luck because the latest release supports the M1 and M2 chips.
-
Systemd Fixes Bug While Facing New Challenger in GNU Shepherd
The systemd developers have fixed a really nasty bug amid the release of the new GNU Shepherd init system.
-
AlmaLinux 10.0 Beta Released
The AlmaLinux OS Foundation has announced the availability of AlmaLinux 10.0 Beta ("Purple Lion") for all supported devices with significant changes.
-
Gnome 47.2 Now Available
Gnome 47.2 is now available for general use but don't expect much in the way of newness, as this is all about improvements and bug fixes.
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.