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 47.1 Released with a Few Fixes
The latest release of the Gnome desktop is all about fixing a few nagging issues and not about bringing new features into the mix.
-
System76 Unveils an Ampere-Powered Thelio Desktop
If you're looking for a new desktop system for developing autonomous driving and software-defined vehicle solutions. System76 has you covered.
-
VirtualBox 7.1.4 Includes Initial Support for Linux kernel 6.12
The latest version of VirtualBox has arrived and it not only adds initial support for kernel 6.12 but another feature that will make using the virtual machine tool much easier.
-
New Slimbook EVO with Raw AMD Ryzen Power
If you're looking for serious power in a 14" ultrabook that is powered by Linux, Slimbook has just the thing for you.
-
The Gnome Foundation Struggling to Stay Afloat
The foundation behind the Gnome desktop environment is having to go through some serious belt-tightening due to continued financial problems.
-
Thousands of Linux Servers Infected with Stealth Malware Since 2021
Perfctl is capable of remaining undetected, which makes it dangerous and hard to mitigate.
-
Halcyon Creates Anti-Ransomware Protection for Linux
As more Linux systems are targeted by ransomware, Halcyon is stepping up its protection.
-
Valve and Arch Linux Announce Collaboration
Valve and Arch have come together for two projects that will have a serious impact on the Linux distribution.
-
Hacker Successfully Runs Linux on a CPU from the Early ‘70s
From the office of "Look what I can do," Dmitry Grinberg was able to get Linux running on a processor that was created in 1971.
-
OSI and LPI Form Strategic Alliance
With a goal of strengthening Linux and open source communities, this new alliance aims to nurture the growth of more highly skilled professionals.