1 /*
2  * Copyright (C) 2013 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 
17 package com.android.ex.photo.util;
18 
19 import android.util.Log;
20 
21 import java.io.ByteArrayInputStream;
22 import java.io.InputStream;
23 
24 public class Exif {
25     private static final String TAG = "CameraExif";
26 
27     /**
28      * Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
29      * @param inputStream The input stream will not be closed for you.
30      * @param byteSize Recommended parameter declaring the length of the input stream. If you
31      *                 pass in -1, we will have to read more from the input stream.
32      * @return 0, 90, 180, or 270.
33      */
getOrientation(final InputStream inputStream, final long byteSize)34     public static int getOrientation(final InputStream inputStream, final long byteSize) {
35         if (inputStream == null) {
36             return 0;
37         }
38 
39         /*
40           Looking at this algorithm, we never look ahead more than 8 bytes. As long as we call
41           advanceTo() at the end of every loop, we should never have to reallocate a larger buffer.
42 
43           Also, the most we ever read backwards is 4 bytes. pack() reads backwards if the encoding
44           is in little endian format. These following two lines potentially reads 4 bytes backwards:
45 
46           int tag = pack(jpeg, offset, 4, false);
47           count = pack(jpeg, offset - 2, 2, littleEndian);
48 
49           To be safe, we will always advance to some index-4, so we'll need 4 more for the +8
50           look ahead, which makes it a +12 look ahead total. Use 16 just in case my analysis is off.
51 
52           This means we only need to allocate a single 16 byte buffer.
53 
54           Note: If you do not pass in byteSize parameter, a single large allocation will occur.
55           For a 1MB image, I see one 30KB allocation. This is due to the line containing:
56 
57           has(jpeg, byteSize, offset + length - 1)
58 
59           where length is a variable int (around 30KB above) read from the EXIF headers.
60 
61           This is still much better than allocating a 1MB byte[] which we were doing before.
62          */
63 
64         final int lookAhead = 16;
65         final int readBackwards = 4;
66         final InputStreamBuffer jpeg = new InputStreamBuffer(inputStream, lookAhead, false);
67 
68         int offset = 0;
69         int length = 0;
70 
71         if (has(jpeg, byteSize, 1)) {
72             // JPEG image files begin with FF D8. Only JPEG images have EXIF data.
73             final boolean possibleJpegFormat = jpeg.get(0) == (byte) 0xFF
74                     && jpeg.get(1) == (byte) 0xD8;
75             if (!possibleJpegFormat) {
76                 return 0;
77             }
78         }
79 
80         // ISO/IEC 10918-1:1993(E)
81         while (has(jpeg, byteSize, offset + 3) && (jpeg.get(offset++) & 0xFF) == 0xFF) {
82             final int marker = jpeg.get(offset) & 0xFF;
83 
84             // Check if the marker is a padding.
85             if (marker == 0xFF) {
86                 continue;
87             }
88             offset++;
89 
90             // Check if the marker is SOI or TEM.
91             if (marker == 0xD8 || marker == 0x01) {
92                 continue;
93             }
94             // Check if the marker is EOI or SOS.
95             if (marker == 0xD9 || marker == 0xDA) {
96                 // Loop ends.
97                 jpeg.advanceTo(offset - readBackwards);
98                 break;
99             }
100 
101             // Get the length and check if it is reasonable.
102             length = pack(jpeg, offset, 2, false);
103             if (length < 2 || !has(jpeg, byteSize, offset + length - 1)) {
104                 Log.e(TAG, "Invalid length");
105                 return 0;
106             }
107 
108             // Break if the marker is EXIF in APP1.
109             if (marker == 0xE1 && length >= 8 &&
110                     pack(jpeg, offset + 2, 4, false) == 0x45786966 &&
111                     pack(jpeg, offset + 6, 2, false) == 0) {
112                 offset += 8;
113                 length -= 8;
114                 // Loop ends.
115                 jpeg.advanceTo(offset - readBackwards);
116                 break;
117             }
118 
119             // Skip other markers.
120             offset += length;
121             length = 0;
122 
123             // Loop ends.
124             jpeg.advanceTo(offset - readBackwards);
125         }
126 
127         // JEITA CP-3451 Exif Version 2.2
128         if (length > 8) {
129             // Identify the byte order.
130             int tag = pack(jpeg, offset, 4, false);
131             if (tag != 0x49492A00 && tag != 0x4D4D002A) {
132                 Log.e(TAG, "Invalid byte order");
133                 return 0;
134             }
135             final boolean littleEndian = (tag == 0x49492A00);
136 
137             // Get the offset and check if it is reasonable.
138             int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
139             if (count < 10 || count > length) {
140                 Log.e(TAG, "Invalid offset");
141                 return 0;
142             }
143             offset += count;
144             length -= count;
145 
146             // Offset has changed significantly.
147             jpeg.advanceTo(offset - readBackwards);
148 
149             // Get the count and go through all the elements.
150             count = pack(jpeg, offset - 2, 2, littleEndian);
151 
152             while (count-- > 0 && length >= 12) {
153                 // Get the tag and check if it is orientation.
154                 tag = pack(jpeg, offset, 2, littleEndian);
155                 if (tag == 0x0112) {
156                     // We do not really care about type and count, do we?
157                     final int orientation = pack(jpeg, offset + 8, 2, littleEndian);
158                     switch (orientation) {
159                         case 1:
160                             return 0;
161                         case 3:
162                             return 180;
163                         case 6:
164                             return 90;
165                         case 8:
166                             return 270;
167                     }
168                     Log.i(TAG, "Unsupported orientation");
169                     return 0;
170                 }
171                 offset += 12;
172                 length -= 12;
173 
174                 // Loop ends.
175                 jpeg.advanceTo(offset - readBackwards);
176             }
177         }
178 
179         return 0;
180     }
181 
pack(final InputStreamBuffer bytes, int offset, int length, final boolean littleEndian)182     private static int pack(final InputStreamBuffer bytes, int offset, int length,
183             final boolean littleEndian) {
184         int step = 1;
185         if (littleEndian) {
186             offset += length - 1;
187             step = -1;
188         }
189 
190         int value = 0;
191         while (length-- > 0) {
192             value = (value << 8) | (bytes.get(offset) & 0xFF);
193             offset += step;
194         }
195         return value;
196     }
197 
has(final InputStreamBuffer jpeg, final long byteSize, final int index)198     private static boolean has(final InputStreamBuffer jpeg, final long byteSize, final int index) {
199         if (byteSize >= 0) {
200             return index < byteSize;
201         } else {
202             // For large values of index, this will cause the internal buffer to resize.
203             return jpeg.has(index);
204         }
205     }
206 
207     @Deprecated
getOrientation(final byte[] jpeg)208     public static int getOrientation(final byte[] jpeg) {
209         return getOrientation(new ByteArrayInputStream(jpeg), jpeg.length);
210     }
211 }
212