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