• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.bumptech.glide.load.resource.bitmap;
2 
3 import static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.GIF;
4 import static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.JPEG;
5 import static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.PNG;
6 import static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.PNG_A;
7 import static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.UNKNOWN;
8 
9 import android.util.Log;
10 
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.io.UnsupportedEncodingException;
14 import java.nio.ByteBuffer;
15 import java.nio.ByteOrder;
16 
17 /**
18  * A class for parsing the exif orientation and other data from an image header.
19  */
20 public class ImageHeaderParser {
21     private static final String TAG = "ImageHeaderParser";
22 
23     /**
24      * The format of the image data including whether or not the image may include transparent pixels.
25      */
26     public static enum ImageType {
27         /** GIF type. */
28         GIF(true),
29         /** JPG type. */
30         JPEG(false),
31         /** PNG type with alpha. */
32         PNG_A(true),
33         /** PNG type without alpha. */
34         PNG(false),
35         /** Unrecognized type. */
36         UNKNOWN(false);
37         private final boolean hasAlpha;
38 
ImageType(boolean hasAlpha)39         ImageType(boolean hasAlpha) {
40             this.hasAlpha = hasAlpha;
41         }
42 
hasAlpha()43         public boolean hasAlpha() {
44             return hasAlpha;
45         }
46     }
47 
48     private static final int GIF_HEADER = 0x474946;
49     private static final int PNG_HEADER = 0x89504E47;
50     private static final int EXIF_MAGIC_NUMBER = 0xFFD8;
51     // "MM".
52     private static final int MOTOROLA_TIFF_MAGIC_NUMBER = 0x4D4D;
53     // "II".
54     private static final int INTEL_TIFF_MAGIC_NUMBER = 0x4949;
55     private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0";
56     private static final byte[] JPEG_EXIF_SEGMENT_PREAMBLE_BYTES;
57     private static final int SEGMENT_SOS = 0xDA;
58     private static final int MARKER_EOI = 0xD9;
59     private static final int SEGMENT_START_ID = 0xFF;
60     private static final int EXIF_SEGMENT_TYPE = 0xE1;
61     private static final int ORIENTATION_TAG_TYPE = 0x0112;
62     private static final int[] BYTES_PER_FORMAT = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 };
63 
64     private final StreamReader streamReader;
65 
66     static {
67         byte[] bytes = new byte[0];
68         try {
69             bytes = JPEG_EXIF_SEGMENT_PREAMBLE.getBytes("UTF-8");
70         } catch (UnsupportedEncodingException e) {
71             // Ignore.
72         }
73         JPEG_EXIF_SEGMENT_PREAMBLE_BYTES = bytes;
74     }
75 
ImageHeaderParser(InputStream is)76     public ImageHeaderParser(InputStream is) {
77         streamReader = new StreamReader(is);
78     }
79 
80     // 0xD0A3C68 -> <htm
81     // 0xCAFEBABE -> <!DOCTYPE...
hasAlpha()82     public boolean hasAlpha() throws IOException {
83         return getType().hasAlpha();
84     }
85 
getType()86     public ImageType getType() throws IOException {
87         int firstByte = streamReader.getUInt8();
88 
89         // JPEG.
90         if (firstByte == EXIF_MAGIC_NUMBER >> 8) {
91             return JPEG;
92         }
93 
94         final int firstTwoBytes = firstByte << 8 & 0xFF00 | streamReader.getUInt8() & 0xFF;
95         final int firstFourBytes = firstTwoBytes << 16 & 0xFFFF0000 | streamReader.getUInt16() & 0xFFFF;
96         // PNG.
97         if (firstFourBytes == PNG_HEADER) {
98             // See: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha-color-type
99             streamReader.skip(25 - 4);
100             int alpha = streamReader.getByte();
101             // A RGB indexed PNG can also have transparency. Better safe than sorry!
102             return alpha >= 3 ? PNG_A : PNG;
103         }
104 
105         // GIF from first 3 bytes.
106         if (firstFourBytes >> 8 == GIF_HEADER) {
107             return GIF;
108         }
109 
110         return UNKNOWN;
111     }
112 
113     /**
114      * Parse the orientation from the image header. If it doesn't handle this image type (or this is not an image)
115      * it will return a default value rather than throwing an exception.
116      *
117      * @return The exif orientation if present or -1 if the header couldn't be parsed or doesn't contain an orientation
118      * @throws IOException
119      */
getOrientation()120     public int getOrientation() throws IOException {
121         final int magicNumber = streamReader.getUInt16();
122 
123         if (!handles(magicNumber)) {
124             return -1;
125         } else {
126             byte[] exifData = getExifSegment();
127             boolean hasJpegExifPreamble = exifData != null
128                     && exifData.length >= JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length;
129 
130             if (hasJpegExifPreamble) {
131                 for (int i = 0; i < JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length; i++) {
132                     if (exifData[i] != JPEG_EXIF_SEGMENT_PREAMBLE_BYTES[i]) {
133                         hasJpegExifPreamble = false;
134                         break;
135                     }
136                 }
137             }
138 
139             if (hasJpegExifPreamble) {
140                 return parseExifSegment(new RandomAccessReader(exifData));
141             } else {
142                 return -1;
143             }
144         }
145     }
146 
getExifSegment()147     private byte[] getExifSegment() throws IOException {
148         short segmentId, segmentType;
149         int segmentLength;
150         while (true) {
151             segmentId = streamReader.getUInt8();
152 
153             if (segmentId != SEGMENT_START_ID) {
154                 if (Log.isLoggable(TAG, Log.DEBUG)) {
155                     Log.d(TAG, "Unknown segmentId=" + segmentId);
156                 }
157                 return null;
158             }
159 
160             segmentType = streamReader.getUInt8();
161 
162             if (segmentType == SEGMENT_SOS) {
163                 return null;
164             } else if (segmentType == MARKER_EOI) {
165                 if (Log.isLoggable(TAG, Log.DEBUG)) {
166                     Log.d(TAG, "Found MARKER_EOI in exif segment");
167                 }
168                 return null;
169             }
170 
171             // Segment length includes bytes for segment length.
172             segmentLength = streamReader.getUInt16() - 2;
173 
174             if (segmentType != EXIF_SEGMENT_TYPE) {
175                 if (segmentLength != streamReader.skip(segmentLength)) {
176                     if (Log.isLoggable(TAG, Log.DEBUG)) {
177                         Log.d(TAG, "Unable to skip enough data for type=" + segmentType);
178                     }
179                     return null;
180                 }
181             } else {
182                 byte[] segmentData = new byte[segmentLength];
183 
184                 if (segmentLength != streamReader.read(segmentData)) {
185                     if (Log.isLoggable(TAG, Log.DEBUG)) {
186                         Log.d(TAG, "Unable to read segment data for type=" + segmentType + " length=" + segmentLength);
187                     }
188                     return null;
189                 } else {
190                     return segmentData;
191                 }
192             }
193         }
194     }
195 
parseExifSegment(RandomAccessReader segmentData)196     private static int parseExifSegment(RandomAccessReader segmentData) {
197         final int headerOffsetSize = JPEG_EXIF_SEGMENT_PREAMBLE.length();
198 
199         short byteOrderIdentifier = segmentData.getInt16(headerOffsetSize);
200         final ByteOrder byteOrder;
201         if (byteOrderIdentifier == MOTOROLA_TIFF_MAGIC_NUMBER) {
202             byteOrder = ByteOrder.BIG_ENDIAN;
203         } else if (byteOrderIdentifier == INTEL_TIFF_MAGIC_NUMBER) {
204             byteOrder = ByteOrder.LITTLE_ENDIAN;
205         } else {
206             if (Log.isLoggable(TAG, Log.DEBUG)) {
207                 Log.d(TAG, "Unknown endianness = " + byteOrderIdentifier);
208             }
209             byteOrder = ByteOrder.BIG_ENDIAN;
210         }
211 
212         segmentData.order(byteOrder);
213 
214         int firstIfdOffset = segmentData.getInt32(headerOffsetSize + 4) + headerOffsetSize;
215         int tagCount = segmentData.getInt16(firstIfdOffset);
216 
217         int tagOffset, tagType, formatCode, componentCount;
218         for (int i = 0; i < tagCount; i++) {
219             tagOffset = calcTagOffset(firstIfdOffset, i);
220 
221             tagType = segmentData.getInt16(tagOffset);
222 
223             // We only want orientation.
224             if (tagType != ORIENTATION_TAG_TYPE) {
225                 continue;
226             }
227 
228             formatCode = segmentData.getInt16(tagOffset + 2);
229 
230             // 12 is max format code.
231             if (formatCode < 1 || formatCode > 12) {
232                 if (Log.isLoggable(TAG, Log.DEBUG)) {
233                     Log.d(TAG, "Got invalid format code = " + formatCode);
234                 }
235                 continue;
236             }
237 
238             componentCount = segmentData.getInt32(tagOffset + 4);
239 
240             if (componentCount < 0) {
241                 if (Log.isLoggable(TAG, Log.DEBUG)) {
242                     Log.d(TAG, "Negative tiff component count");
243                 }
244                 continue;
245             }
246 
247             if (Log.isLoggable(TAG, Log.DEBUG)) {
248                 Log.d(TAG, "Got tagIndex=" + i + " tagType=" + tagType + " formatCode =" + formatCode
249                         + " componentCount=" + componentCount);
250             }
251 
252             final int byteCount = componentCount + BYTES_PER_FORMAT[formatCode];
253 
254             if (byteCount > 4) {
255                 if (Log.isLoggable(TAG, Log.DEBUG)) {
256                     Log.d(TAG, "Got byte count > 4, not orientation, continuing, formatCode=" + formatCode);
257                 }
258                 continue;
259             }
260 
261             final int tagValueOffset = tagOffset + 8;
262 
263             if (tagValueOffset < 0 || tagValueOffset > segmentData.length()) {
264                 if (Log.isLoggable(TAG, Log.DEBUG)) {
265                     Log.d(TAG, "Illegal tagValueOffset=" + tagValueOffset + " tagType=" + tagType);
266                 }
267                 continue;
268             }
269 
270             if (byteCount < 0 || tagValueOffset + byteCount > segmentData.length()) {
271                 if (Log.isLoggable(TAG, Log.DEBUG)) {
272                     Log.d(TAG, "Illegal number of bytes for TI tag data tagType=" + tagType);
273                 }
274                 continue;
275             }
276 
277             //assume componentCount == 1 && fmtCode == 3
278             return segmentData.getInt16(tagValueOffset);
279         }
280 
281         return -1;
282     }
283 
calcTagOffset(int ifdOffset, int tagIndex)284     private static int calcTagOffset(int ifdOffset, int tagIndex) {
285         return ifdOffset + 2 + 12 * tagIndex;
286     }
287 
handles(int imageMagicNumber)288     private static boolean handles(int imageMagicNumber) {
289         return (imageMagicNumber & EXIF_MAGIC_NUMBER) == EXIF_MAGIC_NUMBER
290                 || imageMagicNumber == MOTOROLA_TIFF_MAGIC_NUMBER
291                 || imageMagicNumber == INTEL_TIFF_MAGIC_NUMBER;
292     }
293 
294     private static class RandomAccessReader {
295         private final ByteBuffer data;
296 
RandomAccessReader(byte[] data)297         public RandomAccessReader(byte[] data) {
298             this.data = ByteBuffer.wrap(data);
299             this.data.order(ByteOrder.BIG_ENDIAN);
300         }
301 
order(ByteOrder byteOrder)302         public void order(ByteOrder byteOrder) {
303             this.data.order(byteOrder);
304         }
305 
length()306         public int length() {
307             return data.array().length;
308         }
309 
getInt32(int offset)310         public int getInt32(int offset) {
311             return data.getInt(offset);
312         }
313 
getInt16(int offset)314         public short getInt16(int offset) {
315             return data.getShort(offset);
316         }
317     }
318 
319     private static class StreamReader {
320         private final InputStream is;
321         //motorola / big endian byte order
322 
StreamReader(InputStream is)323         public StreamReader(InputStream is) {
324             this.is = is;
325         }
326 
getUInt16()327         public int getUInt16() throws IOException {
328             return  (is.read() << 8 & 0xFF00) | (is.read() & 0xFF);
329         }
330 
getUInt8()331         public short getUInt8() throws IOException {
332             return (short) (is.read() & 0xFF);
333         }
334 
skip(long total)335         public long skip(long total) throws IOException {
336             return is.skip(total);
337         }
338 
read(byte[] buffer)339         public int read(byte[] buffer) throws IOException {
340             return is.read(buffer);
341         }
342 
getByte()343         public int getByte() throws IOException {
344             return is.read();
345         }
346     }
347 }
348 
349