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