Image Orientation Patch

Recently, when I was blogging[1], I found that when I inserted the photos I took with my iPhone5, it looked wired. Namely, the photos will auto-rotate -90 degrees like a man lying between the road when reading the post on my laptop but it looked all well when reading on the iPhone. I guessed it was about the resolution and tested with my friend’s 1080 FHD display but it still rotated. So, I tried to find the answer and wrote this post.

For details can you watch the following picture:

In normal minds, we may though if the picture’s orientation is from top to bottom, that’s good. And we just need this sort of thought! When this thought came to my mind, I just started to seek for a way to make this picture look more “normal”.

First, trying to use the easiest way to hack. As I’m using Qiniu OSS, they provide me with almost free(some services are not free, e.g. file hosting, CDN, imageslim, but the price is not too high… maybe my traffic is not too large so I thought the price is not too high, lol) Image processing tools. When turned on the Picture Processing option in 融合 CDN, I can easily use ?imageMogr2/rotate/90 to rotate the image in clockwise direction, i.e. https://sqn.onthetree.top/blog/image/posts/jpegs-prevent-auto-rotate-in-markdowns/lie-down.png?imageMogr2/rotate/90 That looks like folowing:

It looks the problem is solved, isn’t it? However, no, the problem is not this easy, so far.

Although the image looks good on the laptop’s browser and markdown editor, but how about on mobile? Like what I’ve mentioned, even though we do nothing to the image and publish to my blog, it looks good on mobile(I suppose it was because the mobile’s ppi is much more higher than the laptop’s), everything looks fine on mobile. Since we rotate the image here, how could it look well on mobile? In fact, on mobile, the picture lies down, just like the first picture I’ve shown.

This means, I have to find another way to modify the picture which will not affect the image display on mobile.

After searching on the web, I’ve found something interesting. When we take photos with our smartphones, it will attach some extra information to the photo, which is known as “EXIF”. And one item of the EXIF info is Orientation. It seems what we need to find, right? Then, we should known how it works. Luckily, through the Qiniu built-in tool, we can look into the image’s EXIF info[2] by some link like this: https://sqn.onthetree.top/blog/image/posts/looking-for-jobs/spdbccc_5.JPG?exif

Then you can see the exif info like following(for read-friendly view, pre-formatted):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
{
"ApertureValue": {
"val": "2.53 EV (f/2.4)",
"type": 5
},
"BrightnessValue": {
"val": "4.81 EV (96.39 cd/m^2)",
"type": 10
},
"ColorSpace": {
"val": "sRGB",
"type": 3
},
"ComponentsConfiguration": {
"val": "Y Cb Cr -",
"type": 7
},
"DateTime": {
"val": "2017:03:24 10:16:42",
"type": 2
},
"DateTimeDigitized": {
"val": "2017:03:24 10:16:42",
"type": 2
},
"DateTimeOriginal": {
"val": "2017:03:24 10:16:42",
"type": 2
},
"ExifVersion": {
"val": "Exif Version 2.21",
"type": 7
},
"ExposureBiasValue": {
"val": "0.00 EV",
"type": 10
},
"ExposureMode": {
"val": "Auto exposure",
"type": 3
},
"ExposureProgram": {
"val": "Normal program",
"type": 3
},
"ExposureTime": {
"val": "1/60 sec.",
"type": 5
},
"FNumber": {
"val": "f/2.4",
"type": 5
},
"Flash": {
"val": "Flash did not fire, auto mode",
"type": 3
},
"FlashPixVersion": {
"val": "FlashPix Version 1.0",
"type": 7
},
"FocalLength": {
"val": "4.1 mm",
"type": 5
},
"FocalLengthIn35mmFilm": {
"val": "33",
"type": 3
},
"GPSAltitude": {
"val": "42.849",
"type": 5
},
"GPSAltitudeRef": {
"val": "Sea level",
"type": 1
},
"GPSDateStamp": {
"val": "2017:03:24",
"type": 2
},
"GPSDestBearing": {
"val": "264.82",
"type": 5
},
"GPSDestBearingRef": {
"val": "T",
"type": 2
},
"GPSImgDirection": {
"val": "264.82",
"type": 5
},
"GPSImgDirectionRef": {
"val": "T",
"type": 2
},
"GPSLatitude": {
"val": "31, 13, 37.06",
"type": 5
},
"GPSLatitudeRef": {
"val": "N",
"type": 2
},
"GPSLongitude": {
"val": "121, 31, 26.96",
"type": 5
},
"GPSLongitudeRef": {
"val": "E",
"type": 2
},
"GPSSpeed": {
"val": " 0",
"type": 5
},
"GPSSpeedRef": {
"val": "K",
"type": 2
},
"GPSTimeStamp": {
"val": "02:16:23.00",
"type": 5
},
"ISOSpeedRatings": {
"val": "64",
"type": 3
},
"Make": {
"val": "Apple",
"type": 2
},
"MakerNote": {
"val": "244 bytes undefined data",
"type": 7
},
"MeteringMode": {
"val": "Pattern",
"type": 3
},
"Model": {
"val": "iPhone 5",
"type": 2
},
"Orientation": {
"val": "Top-left",
"type": 3
},
"PixelXDimension": {
"val": "2448",
"type": 4
},
"PixelYDimension": {
"val": "3264",
"type": 4
},
"ResolutionUnit": {
"val": "Inch",
"type": 3
},
"SceneCaptureType": {
"val": "Standard",
"type": 3
},
"SceneType": {
"val": "Directly photographed",
"type": 7
},
"SensingMethod": {
"val": "One-chip color area sensor",
"type": 3
},
"ShutterSpeedValue": {
"val": "5.91 EV (1/60 sec.)",
"type": 10
},
"Software": {
"val": "10.2.1",
"type": 2
},
"SubSecTimeDigitized": {
"val": "938",
"type": 2
},
"SubSecTimeOriginal": {
"val": "938",
"type": 2
},
"SubjectArea": {
"val": "Within rectangle (width 1795, height 1077) around (x,y) = (1631,1223)",
"type": 3
},
"WhiteBalance": {
"val": "Auto white balance",
"type": 3
},
"XResolution": {
"val": "72",
"type": 5
},
"YResolution": {
"val": "72",
"type": 5
}
}

Now, we just need to focus on this section:

1
2
3
4
"Orientation": {
"val": "Top-left",
"type": 3
}

How does it mean by “Top-Left”[3]? If you have this question in your mind, it means you have to search a little further. Thankfully, here is a table which can explains this sort of info:

Value Kind
1 Top-Left
2 Top-right
3 Bottom-Right
4 Bottom-Left
5 Left-Top
6 Right-Top
7 Right-Bottom
8 Left-Bottom

Fine, now we know “Top-Left” means kind 1, so what is “kind 1”? Luckily, here is one picture can illustrate this problem clearly.

For further illustration, I’d rather tell you that “kind 1” which is known as top-left as well means the camera shots normally and “kind 6” means the camera rotates for 90 degrees in counterclockwise direction and shots when you take a photo[4].

Since we know the EXIF, that means we just need to change the Orientation info to 1. I just want the fastest approach this goal, which means I just want a tool to enable myself to change[5] the EXIF info[6]. However, this software needs to install which is unacceptable for me at present(From my point of view, the application should be like Hot Swapping, just as most OS X applications do, download dmg mirror, mount it, copy the application to Application Directory, done ). As I’m using Debian, some command line tools[7] are also acceptable. However, sadly to find this tool can not handle Orientation section in EXIF info[8].

Okay, I can Reinventing the wheel, thought to myself, alghough I’ve known DRY Principle(In software engineering, don’t repeat yourself (DRY) is a principle of software development, aimed at reducing repetition of information of all kinds, especially useful in multi-tier architectures). But the best way to solve this problem is to build the wheel, since no tool can fulfill my requirements. The procedure should be:

  1. Open the image file
  2. Read the EXIF and pick Orietation item
  3. Rotate the image according to the orientation kind
  4. Set the orientation item to 1(normal kind)
  5. Save the file

Here is also an image to show how to adjust the image in different situations:

And the final program follows(using Python 2.7.12):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
from PIL import Image
import os, argparse, pexif
def picProcessing(picPath, isForce=False):
try:
img = pexif.JpegFile.fromFile(picPath)
#Get the orientation if it exists
orientation = img.exif.primary.Orientation[0]
print "The orientation of this pic is", orientation, "(will change to 1)"
# Set Orientation to 1
img.exif.primary.Orientation[0] = 1
img.writeFile(picPath)
# now rotate the image using the Python Image Library (PIL)
print "Preparing to rotate the image..."
img = Image.open(picPath)
exifInfo = img.info['exif']
if isForce:
img = img.rotate(-90)
elif orientation is 6: img = img.rotate(-90, expand=1)
elif orientation is 8: img = img.rotate(90, expand=1)
elif orientation is 3: img = img.rotate(180, expand=1)
elif orientation is 2: img = img.transpose(Image.FLIP_LEFT_RIGHT)
elif orientation is 5: img = img.rotate(-90, expand=1).transpose(Image.FLIP_LEFT_RIGHT)
elif orientation is 7: img = img.rotate(90, expand=1).transpose(Image.FLIP_LEFT_RIGHT)
elif orientation is 4: img = img.rotate(180, expand=1).transpose(Image.FLIP_LEFT_RIGHT)
#save the result
img.save(picPath, exif=exifInfo)
except:
print "ERROR!!!"
pass
def args_settings():
parser = argparse.ArgumentParser()
parser.add_argument("-d", "--directory",
help="Desired directory to be processed.", default=os.getcwd())
parser.add_argument("-A", "--all", help="Processing all the files under this directory",
action="store_true", default=False)
parser.add_argument("-f", "--filename",
help="Desired filename to be processed(extension is needed).",
default='')
parser.add_argument("-F", "--force", action="store_true",
help="Ignore the Orientation Info and rotate the image file of 90 degrees.")
parser.add_argument("-r", "--recursive", action="store_true",
help="Recursively visit the files under the subfolders.")
args = parser.parse_args()
return args
def fileChooser(args):
isAll = args.all
filename = args.filename
directory = args.directory
isRecursive = args.recursive
if args.force:
isForce = True
else:
isForce = False
if os.path.exists(directory):
if isAll:
if isRecursive:
print "You've choosed the all the files under this directory."
for root, subdirs, files in os.walk(directory):
for File in files:
if File.lower().endswith(('.jpg', '.jpeg')):
picPath = os.path.join(directory, root, File)
if (os.path.isfile(picPath)):
print "Processing...", File
picProcessing(picPath, isForce)
else:
print "File", File, "does not exist!"
else:
print "You've choosed the files under current directory."
for thisFile in os.listdir(directory):
if thisFile.lower().endswith(('.jpg', '.jpeg')):
picPath = os.path.join(directory, thisFile)
if os.path.isfile(picPath):
print "Processing...", thisFile
picProcessing(picPath, isForce)
else:
print "File", thisFile, "does not exist!"
else:
if args.filename == '':
print "You must specify the filename if you choose not all files."
else:
print "You've choosed '{}' to process.".format(filename)
filepath = os.path.join(directory, filename)
if os.path.isfile(filepath):
print "Processing...", filename
picProcessing(filepath, isForce)
print "Done!"
else:
print "File does not exist!"
else:
print "The directory does not exist!"
if __name__ == "__main__":
args = args_settings()
fileChooser(args)

And you can easily get the code here[9]. Before you can use this snippet, there’s two more thing you have to do.

  1. Install pillow
  2. Install pexif

On OS X, we have to use Homebrew install some external libraries[10], using such commands:brew link jpeg and brew install libtiff libjpeg webp little-cms2. Then using pip install Pillow and pip install pexif to install dependencies. After installing, you can switch to the directory which contains the py file and using python picpatch.py -h to find the help info. It looks likes following:

1
2
3
4
5
6
7
8
9
10
11
12
usage: picpatch.py [-h] [-d DIRECTORY] [-A] [-f FILENAME] [-F] [-r]
optional arguments:
-h, --help show this help message and exit
-d DIRECTORY, --directory DIRECTORY
Desired directory to be processed.
-A, --all Processing all the files under this directory
-f FILENAME, --filename FILENAME
Desired filename to be processed(extension is needed).
-F, --force Ignore the Orientation Info and rotate the image file
of 90 degrees.
-r, --recursive Recursively visit the files under the subfolders.

As the parameters show, this program can specify pariticular file or match all the files under the determined directory(Recursive or not). For example, I’ve copied some pics under a test folder(The location is /Users/d0zingcat/Desktop/tst). The structure looks like following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.
|____allinfinance_1.JPG
|____allinfinance_2.JPG
|____p1
| |____allinfinance_3.JPG
| |____p11
| | |____allinfinance_4.JPG
| | |____indoorstar_2.JPG
| | |____indoorstar_3.JPG
|____p2
| |____p21
| | |____p22
| | | |____allinfinance_5.JPG
| | | |____indoorstar_1.JPG
| | | |____rcc_1.JPG
| | | |____rcc_2.JPG
| | | |____rcc_3.JPG

If using python picpatch.py -d /Users/d0zingcat/Desktop/tst -A you will get the result:

1
2
3
4
5
6
7
You've choosed the files under current directory.
Processing... allinfinance_1.JPG
The orientation of this pic is 1 (will change to 1)
Preparing to rotate the image...
Processing... allinfinance_2.JPG
The orientation of this pic is 1 (will change to 1)
Preparing to rotate the image...

It means the program only process the files under the root directory, but not files under its subfolders. And if you use python picpatch.py -d /Users/d0zingcat/Desktop/tst -A -r it will process all the files even they are under its subfolders or sub-subfolders.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
You've choosed the all the files under this directory.
Processing... allinfinance_1.JPG
The orientation of this pic is 1 (will change to 1)
Preparing to rotate the image...
Processing... allinfinance_2.JPG
The orientation of this pic is 1 (will change to 1)
Preparing to rotate the image...
Processing... allinfinance_3.JPG
The orientation of this pic is 1 (will change to 1)
Preparing to rotate the image...
Processing... allinfinance_4.JPG
The orientation of this pic is 1 (will change to 1)
Preparing to rotate the image...
Processing... indoorstar_2.JPG
The orientation of this pic is 1 (will change to 1)
Preparing to rotate the image...
Processing... indoorstar_3.JPG
The orientation of this pic is 1 (will change to 1)
Preparing to rotate the image...
Processing... allinfinance_5.JPG
The orientation of this pic is 1 (will change to 1)
Preparing to rotate the image...
Processing... indoorstar_1.JPG
The orientation of this pic is 1 (will change to 1)
Preparing to rotate the image...
Processing... rcc_1.JPG
The orientation of this pic is 1 (will change to 1)
Preparing to rotate the image...
Processing... rcc_2.JPG
The orientation of this pic is 1 (will change to 1)
Preparing to rotate the image...
Processing... rcc_3.JPG
The orientation of this pic is 1 (will change to 1)
Preparing to rotate the image...

Also, using -f parameter can specify particular file, to avoid modify the files not needing to be processed. Here is also a parameter which is -F which means force(As I was wroting such problem, I found some pictures’ orientation kinds are 1 but they sitll rotated. So, the best way is to force the program rotate the image to “right position” we desired, and the default rotation degree is 90 in counterclockwise).

But there’s still a little problem, after rotating the image, the exif will change, i.e. X dimension resolution and Y dimension resolution will change, but the program just use the original exif and attach it to the new image, so stupid. XD

Have fun.

==============================FINE============================

When writing this program, I’ve refered to Modify or Delete Exif tag ‘Orientation’ in Python, this thread helped me a a lot. And when this program was completed, I was astonished to find this repo[11]. Although I’ve not tested if this program works well(As I’ve already had my own full-feature program), it means maybe I can save a lot of time if I searched more carefully. Sad.


  1. 求职面试记

  2. 图片EXIF信息

  3. 关于图片 EXIF 信息中旋转参数 Orientation 的理解

  4. EXIF Orientation Handling Is a Ghetto

  5. How do I edit my EXIF data with a Mac?

  6. EXIF Tool

  7. Removing EXIF Data From Images and Photos in Linux

  8. How to change camera info in Exif using command line

  9. picpatch.py

  10. Pillow Installation

  11. python-image-orientation-patch