1bcf4a0dae04a4ad14287eeb34069a97c96fe9bb1Sam Juddpackage com.bumptech.glide.load.resource.bitmap;
2d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
3f7a6d65cf7c1a41908dd48e0dab68ee5b881387eSam Juddimport static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.GIF;
4f7a6d65cf7c1a41908dd48e0dab68ee5b881387eSam Juddimport static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.JPEG;
5f7a6d65cf7c1a41908dd48e0dab68ee5b881387eSam Juddimport static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.PNG;
6f7a6d65cf7c1a41908dd48e0dab68ee5b881387eSam Juddimport static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.PNG_A;
7f7a6d65cf7c1a41908dd48e0dab68ee5b881387eSam Juddimport static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.UNKNOWN;
8f7a6d65cf7c1a41908dd48e0dab68ee5b881387eSam Judd
9e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Juddimport android.util.Log;
10d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
11d7efd9c46ebd8872640bb4b4efb564968905507dSam Juddimport java.io.IOException;
12d7efd9c46ebd8872640bb4b4efb564968905507dSam Juddimport java.io.InputStream;
1391289b40e72aee1f4cfce66e536a141e91b39c50Sam Juddimport java.io.UnsupportedEncodingException;
14d7efd9c46ebd8872640bb4b4efb564968905507dSam Juddimport java.nio.ByteBuffer;
15d7efd9c46ebd8872640bb4b4efb564968905507dSam Juddimport java.nio.ByteOrder;
16d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
17d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd/**
185f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd * A class for parsing the exif orientation and other data from an image header.
19d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd */
20f243e2db2fc16ab84ec68995520544f6a469b90dSam Juddpublic class ImageHeaderParser {
21e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd    private static final String TAG = "ImageHeaderParser";
222310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis
23b5419dc08eb0a0f82821d774435720e5a31bc936Sam Judd    /**
24b5419dc08eb0a0f82821d774435720e5a31bc936Sam Judd     * The format of the image data including whether or not the image may include transparent pixels.
25b5419dc08eb0a0f82821d774435720e5a31bc936Sam Judd     */
262310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis    public static enum ImageType {
27fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd        /** GIF type. */
28c24284108320cf3d613497ddd67ba4e1b232ce74Savvas Dalkitsis        GIF(true),
29fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd        /** JPG type. */
30c24284108320cf3d613497ddd67ba4e1b232ce74Savvas Dalkitsis        JPEG(false),
31fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd        /** PNG type with alpha. */
32c24284108320cf3d613497ddd67ba4e1b232ce74Savvas Dalkitsis        PNG_A(true),
33fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd        /** PNG type without alpha. */
34c24284108320cf3d613497ddd67ba4e1b232ce74Savvas Dalkitsis        PNG(false),
35fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd        /** Unrecognized type. */
36c24284108320cf3d613497ddd67ba4e1b232ce74Savvas Dalkitsis        UNKNOWN(false);
372310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis        private final boolean hasAlpha;
382310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis
392310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis        ImageType(boolean hasAlpha) {
402310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis            this.hasAlpha = hasAlpha;
412310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis        }
422310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis
432310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis        public boolean hasAlpha() {
442310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis            return hasAlpha;
452310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis        }
462310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis    }
472310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis
48525d50359e27ca73eeeba96a994155c684d05292Sam Judd    private static final int GIF_HEADER = 0x474946;
49525d50359e27ca73eeeba96a994155c684d05292Sam Judd    private static final int PNG_HEADER = 0x89504E47;
50d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    private static final int EXIF_MAGIC_NUMBER = 0xFFD8;
51fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd    // "MM".
52fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd    private static final int MOTOROLA_TIFF_MAGIC_NUMBER = 0x4D4D;
53fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd    // "II".
54fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd    private static final int INTEL_TIFF_MAGIC_NUMBER = 0x4949;
55d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0";
5691289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd    private static final byte[] JPEG_EXIF_SEGMENT_PREAMBLE_BYTES;
575ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd    private static final int SEGMENT_SOS = 0xDA;
585ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd    private static final int MARKER_EOI = 0xD9;
595ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd    private static final int SEGMENT_START_ID = 0xFF;
605ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd    private static final int EXIF_SEGMENT_TYPE = 0xE1;
615ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd    private static final int ORIENTATION_TAG_TYPE = 0x0112;
625ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd    private static final int[] BYTES_PER_FORMAT = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 };
635ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd
645ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd    private final StreamReader streamReader;
6591289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd
6691289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd    static {
6791289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd        byte[] bytes = new byte[0];
6891289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd        try {
6991289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd            bytes = JPEG_EXIF_SEGMENT_PREAMBLE.getBytes("UTF-8");
7091289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd        } catch (UnsupportedEncodingException e) {
7191289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd            // Ignore.
7291289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd        }
7391289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd        JPEG_EXIF_SEGMENT_PREAMBLE_BYTES = bytes;
7491289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd    }
75d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
76f243e2db2fc16ab84ec68995520544f6a469b90dSam Judd    public ImageHeaderParser(InputStream is) {
77d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        streamReader = new StreamReader(is);
78d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    }
79d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
80525d50359e27ca73eeeba96a994155c684d05292Sam Judd    // 0xD0A3C68 -> <htm
81525d50359e27ca73eeeba96a994155c684d05292Sam Judd    // 0xCAFEBABE -> <!DOCTYPE...
82525d50359e27ca73eeeba96a994155c684d05292Sam Judd    public boolean hasAlpha() throws IOException {
832310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis        return getType().hasAlpha();
842310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis    }
852310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis
862310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis    public ImageType getType() throws IOException {
87525d50359e27ca73eeeba96a994155c684d05292Sam Judd        int firstByte = streamReader.getUInt8();
88525d50359e27ca73eeeba96a994155c684d05292Sam Judd
89fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd        // JPEG.
90fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd        if (firstByte == EXIF_MAGIC_NUMBER >> 8) {
91c24284108320cf3d613497ddd67ba4e1b232ce74Savvas Dalkitsis            return JPEG;
92525d50359e27ca73eeeba96a994155c684d05292Sam Judd        }
93525d50359e27ca73eeeba96a994155c684d05292Sam Judd
94525d50359e27ca73eeeba96a994155c684d05292Sam Judd        final int firstTwoBytes = firstByte << 8 & 0xFF00 | streamReader.getUInt8() & 0xFF;
95525d50359e27ca73eeeba96a994155c684d05292Sam Judd        final int firstFourBytes = firstTwoBytes << 16 & 0xFFFF0000 | streamReader.getUInt16() & 0xFFFF;
96fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd        // PNG.
97fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd        if (firstFourBytes == PNG_HEADER) {
98fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd            // See: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha-color-type
99525d50359e27ca73eeeba96a994155c684d05292Sam Judd            streamReader.skip(25 - 4);
100525d50359e27ca73eeeba96a994155c684d05292Sam Judd            int alpha = streamReader.getByte();
10140c6d302321db0784619297ddb41a48a119ba899Carlos Sobrinho            // A RGB indexed PNG can also have transparency. Better safe than sorry!
102c24284108320cf3d613497ddd67ba4e1b232ce74Savvas Dalkitsis            return alpha >= 3 ? PNG_A : PNG;
103525d50359e27ca73eeeba96a994155c684d05292Sam Judd        }
104525d50359e27ca73eeeba96a994155c684d05292Sam Judd
105fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd        // GIF from first 3 bytes.
106fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd        if (firstFourBytes >> 8 == GIF_HEADER) {
107c24284108320cf3d613497ddd67ba4e1b232ce74Savvas Dalkitsis            return GIF;
108525d50359e27ca73eeeba96a994155c684d05292Sam Judd        }
109525d50359e27ca73eeeba96a994155c684d05292Sam Judd
110c24284108320cf3d613497ddd67ba4e1b232ce74Savvas Dalkitsis        return UNKNOWN;
111525d50359e27ca73eeeba96a994155c684d05292Sam Judd    }
112525d50359e27ca73eeeba96a994155c684d05292Sam Judd
113d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    /**
114d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd     * Parse the orientation from the image header. If it doesn't handle this image type (or this is not an image)
115d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd     * it will return a default value rather than throwing an exception.
116d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd     *
117d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd     * @return The exif orientation if present or -1 if the header couldn't be parsed or doesn't contain an orientation
118d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd     * @throws IOException
119d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd     */
120d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    public int getOrientation() throws IOException {
121d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        final int magicNumber = streamReader.getUInt16();
122d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
123d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        if (!handles(magicNumber)) {
124d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            return -1;
125d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        } else {
126d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            byte[] exifData = getExifSegment();
12791289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd            boolean hasJpegExifPreamble = exifData != null
12891289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd                    && exifData.length >= JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length;
12991289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd
13091289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd            if (hasJpegExifPreamble) {
13191289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd                for (int i = 0; i < JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length; i++) {
13291289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd                    if (exifData[i] != JPEG_EXIF_SEGMENT_PREAMBLE_BYTES[i]) {
13391289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd                        hasJpegExifPreamble = false;
13491289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd                        break;
13591289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd                    }
13691289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd                }
13791289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd            }
13891289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd
13991289b40e72aee1f4cfce66e536a141e91b39c50Sam Judd            if (hasJpegExifPreamble) {
140d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                return parseExifSegment(new RandomAccessReader(exifData));
141d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            } else {
142d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                return -1;
143d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            }
144d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
145d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    }
146d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
147d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    private byte[] getExifSegment() throws IOException {
148d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        short segmentId, segmentType;
149d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        int segmentLength;
150d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        while (true) {
151d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            segmentId = streamReader.getUInt8();
152d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
153d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            if (segmentId != SEGMENT_START_ID) {
154e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                if (Log.isLoggable(TAG, Log.DEBUG)) {
155e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                    Log.d(TAG, "Unknown segmentId=" + segmentId);
156e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                }
157d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                return null;
158d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            }
159d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
160d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            segmentType = streamReader.getUInt8();
161d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
162d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            if (segmentType == SEGMENT_SOS) {
163d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                return null;
164d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            } else if (segmentType == MARKER_EOI) {
165e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                if (Log.isLoggable(TAG, Log.DEBUG)) {
166e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                    Log.d(TAG, "Found MARKER_EOI in exif segment");
167e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                }
168d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                return null;
169d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            }
170d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
171fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd            // Segment length includes bytes for segment length.
172fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd            segmentLength = streamReader.getUInt16() - 2;
173d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
174d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            if (segmentType != EXIF_SEGMENT_TYPE) {
175d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                if (segmentLength != streamReader.skip(segmentLength)) {
176e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                    if (Log.isLoggable(TAG, Log.DEBUG)) {
177e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                        Log.d(TAG, "Unable to skip enough data for type=" + segmentType);
178e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                    }
179d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                    return null;
180d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                }
181d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            } else {
182d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                byte[] segmentData = new byte[segmentLength];
183d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
184d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                if (segmentLength != streamReader.read(segmentData)) {
185e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                    if (Log.isLoggable(TAG, Log.DEBUG)) {
186e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                        Log.d(TAG, "Unable to read segment data for type=" + segmentType + " length=" + segmentLength);
187e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                    }
188d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                    return null;
189d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                } else {
190d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                    return segmentData;
191d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                }
192d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            }
193d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
194d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    }
195d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
196ead25618fdd9d1befa76a2ea1dbd66f9741c9af6Robert Papp    private static int parseExifSegment(RandomAccessReader segmentData) {
197d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        final int headerOffsetSize = JPEG_EXIF_SEGMENT_PREAMBLE.length();
198d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
199d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        short byteOrderIdentifier = segmentData.getInt16(headerOffsetSize);
200d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        final ByteOrder byteOrder;
201fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd        if (byteOrderIdentifier == MOTOROLA_TIFF_MAGIC_NUMBER) {
202d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            byteOrder = ByteOrder.BIG_ENDIAN;
203d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        } else if (byteOrderIdentifier == INTEL_TIFF_MAGIC_NUMBER) {
204d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            byteOrder = ByteOrder.LITTLE_ENDIAN;
205d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        } else {
206e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd            if (Log.isLoggable(TAG, Log.DEBUG)) {
207e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                Log.d(TAG, "Unknown endianness = " + byteOrderIdentifier);
208e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd            }
209d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            byteOrder = ByteOrder.BIG_ENDIAN;
210d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
211d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
212d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        segmentData.order(byteOrder);
213d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
214d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        int firstIfdOffset = segmentData.getInt32(headerOffsetSize + 4) + headerOffsetSize;
215d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        int tagCount = segmentData.getInt16(firstIfdOffset);
216d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
217d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        int tagOffset, tagType, formatCode, componentCount;
218d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        for (int i = 0; i < tagCount; i++) {
219d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            tagOffset = calcTagOffset(firstIfdOffset, i);
220d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
221d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            tagType = segmentData.getInt16(tagOffset);
222d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
223fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd            // We only want orientation.
224fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd            if (tagType != ORIENTATION_TAG_TYPE) {
225d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                continue;
226d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            }
227d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
228d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            formatCode = segmentData.getInt16(tagOffset + 2);
229d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
230fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd            // 12 is max format code.
231fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd            if (formatCode < 1 || formatCode > 12) {
232e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                if (Log.isLoggable(TAG, Log.DEBUG)) {
233e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                    Log.d(TAG, "Got invalid format code = " + formatCode);
234e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                }
235d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                continue;
236d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            }
237d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
238d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            componentCount = segmentData.getInt32(tagOffset + 4);
239d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
240d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            if (componentCount < 0) {
241e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                if (Log.isLoggable(TAG, Log.DEBUG)) {
242e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                    Log.d(TAG, "Negative tiff component count");
243e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                }
244d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                continue;
245d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            }
246d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
247e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd            if (Log.isLoggable(TAG, Log.DEBUG)) {
248e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                Log.d(TAG, "Got tagIndex=" + i + " tagType=" + tagType + " formatCode =" + formatCode
249e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                        + " componentCount=" + componentCount);
250e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd            }
251d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
252d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            final int byteCount = componentCount + BYTES_PER_FORMAT[formatCode];
253d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
254d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            if (byteCount > 4) {
255e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                if (Log.isLoggable(TAG, Log.DEBUG)) {
256e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                    Log.d(TAG, "Got byte count > 4, not orientation, continuing, formatCode=" + formatCode);
257e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                }
258d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                continue;
259d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            }
260d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
261d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            final int tagValueOffset = tagOffset + 8;
262d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
263d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            if (tagValueOffset < 0 || tagValueOffset > segmentData.length()) {
264e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                if (Log.isLoggable(TAG, Log.DEBUG)) {
265e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                    Log.d(TAG, "Illegal tagValueOffset=" + tagValueOffset + " tagType=" + tagType);
266e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                }
267d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                continue;
268d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            }
269d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
270d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            if (byteCount < 0 || tagValueOffset + byteCount > segmentData.length()) {
271e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                if (Log.isLoggable(TAG, Log.DEBUG)) {
272e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                    Log.d(TAG, "Illegal number of bytes for TI tag data tagType=" + tagType);
273e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd                }
274d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd                continue;
275d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            }
276d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
277d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            //assume componentCount == 1 && fmtCode == 3
278d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            return segmentData.getInt16(tagValueOffset);
279d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
280d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
281d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        return -1;
282d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    }
283d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
284d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    private static int calcTagOffset(int ifdOffset, int tagIndex) {
2855ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd        return ifdOffset + 2 + 12 * tagIndex;
286d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    }
287d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
288ead25618fdd9d1befa76a2ea1dbd66f9741c9af6Robert Papp    private static boolean handles(int imageMagicNumber) {
289fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd        return (imageMagicNumber & EXIF_MAGIC_NUMBER) == EXIF_MAGIC_NUMBER
290fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd                || imageMagicNumber == MOTOROLA_TIFF_MAGIC_NUMBER
291fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd                || imageMagicNumber == INTEL_TIFF_MAGIC_NUMBER;
292d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    }
293d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
294d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    private static class RandomAccessReader {
295d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        private final ByteBuffer data;
296d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
297d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        public RandomAccessReader(byte[] data) {
298d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            this.data = ByteBuffer.wrap(data);
299d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            this.data.order(ByteOrder.BIG_ENDIAN);
300d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
301d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
302d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        public void order(ByteOrder byteOrder) {
303d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            this.data.order(byteOrder);
304d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
305d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
306d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        public int length() {
307d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            return data.array().length;
308d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
309d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
310d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        public int getInt32(int offset) {
311d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            return data.getInt(offset);
312d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
313d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
314d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        public short getInt16(int offset) {
315d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            return data.getShort(offset);
316d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
317d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    }
318d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
319d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    private static class StreamReader {
320d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        private final InputStream is;
321d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        //motorola / big endian byte order
322d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
323d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        public StreamReader(InputStream is) {
324d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            this.is = is;
325d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
326d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
327d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        public int getUInt16() throws IOException {
328d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            return  (is.read() << 8 & 0xFF00) | (is.read() & 0xFF);
329d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
330d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
331d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        public short getUInt8() throws IOException {
332d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            return (short) (is.read() & 0xFF);
333d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
334d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
335d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        public long skip(long total) throws IOException {
336d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            return is.skip(total);
337d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
338d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
339d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        public int read(byte[] buffer) throws IOException {
340d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd            return is.read(buffer);
341d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd        }
342525d50359e27ca73eeeba96a994155c684d05292Sam Judd
343525d50359e27ca73eeeba96a994155c684d05292Sam Judd        public int getByte() throws IOException {
344525d50359e27ca73eeeba96a994155c684d05292Sam Judd            return is.read();
345525d50359e27ca73eeeba96a994155c684d05292Sam Judd        }
346d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd    }
347d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd}
348d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd
349