1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package eu.siacs.conversations.utils;
18
19import android.util.Log;
20
21import java.io.IOException;
22import java.io.InputStream;
23
24public class ExifHelper {
25 private static final String TAG = "CameraExif";
26
27 public static int getOrientation(InputStream is) {
28 if (is == null) {
29 return 0;
30 }
31
32 byte[] buf = new byte[8];
33 int length = 0;
34
35 // ISO/IEC 10918-1:1993(E)
36 while (read(is, buf, 2) && (buf[0] & 0xFF) == 0xFF) {
37 int marker = buf[1] & 0xFF;
38
39 // Check if the marker is a padding.
40 if (marker == 0xFF) {
41 continue;
42 }
43
44 // Check if the marker is SOI or TEM.
45 if (marker == 0xD8 || marker == 0x01) {
46 continue;
47 }
48 // Check if the marker is EOI or SOS.
49 if (marker == 0xD9 || marker == 0xDA) {
50 return 0;
51 }
52
53 // Get the length and check if it is reasonable.
54 if (!read(is, buf, 2)) {
55 return 0;
56 }
57 length = pack(buf, 0, 2, false);
58 if (length < 2) {
59 Log.e(TAG, "Invalid length");
60 return 0;
61 }
62 length -= 2;
63
64 // Break if the marker is EXIF in APP1.
65 if (marker == 0xE1 && length >= 6) {
66 if (!read(is, buf, 6)) return 0;
67 length -= 6;
68 if (pack(buf, 0, 4, false) == 0x45786966 &&
69 pack(buf, 4, 2, false) == 0) {
70 break;
71 }
72 }
73
74 // Skip other markers.
75 try {
76 is.skip(length);
77 } catch (IOException ex) {
78 return 0;
79 }
80 length = 0;
81 }
82
83 // JEITA CP-3451 Exif Version 2.2
84 if (length > 8) {
85 int offset = 0;
86 byte[] jpeg = new byte[length];
87 if (!read(is, jpeg, length)) {
88 return 0;
89 }
90
91 // Identify the byte order.
92 int tag = pack(jpeg, offset, 4, false);
93 if (tag != 0x49492A00 && tag != 0x4D4D002A) {
94 Log.e(TAG, "Invalid byte order");
95 return 0;
96 }
97 boolean littleEndian = (tag == 0x49492A00);
98
99 // Get the offset and check if it is reasonable.
100 int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
101 if (count < 10 || count > length) {
102 Log.e(TAG, "Invalid offset");
103 return 0;
104 }
105 offset += count;
106 length -= count;
107
108 // Get the count and go through all the elements.
109 count = pack(jpeg, offset - 2, 2, littleEndian);
110 while (count-- > 0 && length >= 12) {
111 // Get the tag and check if it is orientation.
112 tag = pack(jpeg, offset, 2, littleEndian);
113 if (tag == 0x0112) {
114 // We do not really care about type and count, do we?
115 int orientation = pack(jpeg, offset + 8, 2, littleEndian);
116 switch (orientation) {
117 case 1:
118 return 0;
119 case 3:
120 return 180;
121 case 6:
122 return 90;
123 case 8:
124 return 270;
125 }
126 Log.i(TAG, "Unsupported orientation");
127 return 0;
128 }
129 offset += 12;
130 length -= 12;
131 }
132 }
133
134 Log.i(TAG, "Orientation not found");
135 return 0;
136 }
137
138 private static int pack(byte[] bytes, int offset, int length,
139 boolean littleEndian) {
140 int step = 1;
141 if (littleEndian) {
142 offset += length - 1;
143 step = -1;
144 }
145
146 int value = 0;
147 while (length-- > 0) {
148 value = (value << 8) | (bytes[offset] & 0xFF);
149 offset += step;
150 }
151 return value;
152 }
153
154 private static boolean read(InputStream is, byte[] buf, int length) {
155 try {
156 return is.read(buf, 0, length) == length;
157 } catch (IOException ex) {
158 return false;
159 }
160 }
161}