1/**
2 * Copyright (C) 2015 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 */
16package com.android.gallery3d.common;
17
18import android.content.Context;
19import android.media.ExifInterface;
20import android.util.Log;
21
22import java.io.DataInputStream;
23import java.io.DataOutputStream;
24import java.io.File;
25import java.io.FileOutputStream;
26import java.io.IOException;
27import java.io.InputStream;
28
29public class ExifOrientation {
30    private static final String TAG = "ExifOrientation";
31    private static final boolean DEBUG = false;
32
33    private static final short SOI =  (short) 0xFFD8;   // start of input
34    private static final short APP0 = (short) 0xFFE0;
35    private static final short APPF = (short) 0xFFEF;
36    private static final short APP1 = (short) 0xFFE1;
37    private static final short SOS = (short) 0xFFDA;    // start of stream
38    private static final short EOI = (short) 0xFFD9;    // end of input
39
40    // The header is available in first 64 bytes, so reading upto 128 bytes
41    // should be more than enough.
42    private static final int MAX_BYTES_TO_READ = 128 * 1024;
43
44    /**
45     * Parses the rotation of the JPEG image from the input stream.
46     */
47    public static final int readRotation(InputStream in, Context context) {
48        // Since the platform implementation only takes file input, create a temporary file
49        // with just the image header.
50        File tempFile = null;
51        DataOutputStream tempOut = null;
52
53        try {
54        DataInputStream din = new DataInputStream(in);
55            int pos = 0;
56            if (din.readShort() == SOI) {
57                pos += 2;
58
59                short marker = din.readShort();
60                pos += 2;
61
62                while ((marker >= APP0 && marker <= APPF) && pos < MAX_BYTES_TO_READ) {
63                    int length = din.readUnsignedShort();
64                    if (length < 2) {
65                        throw new IOException("Invalid header size");
66                    }
67
68                    // We only want APP1 headers
69                    if (length > 2) {
70                        if (marker == APP1) {
71                            // Copy the header
72                            if (tempFile == null) {
73                                tempFile = File.createTempFile(TAG, ".jpg", context.getCacheDir());
74                                tempOut = new DataOutputStream(new FileOutputStream(tempFile));
75                                tempOut.writeShort(SOI);
76                            }
77
78                            tempOut.writeShort(marker);
79                            tempOut.writeShort(length);
80
81                            byte[] header = new byte[length - 2];
82                            din.read(header);
83                            tempOut.write(header);
84                        } else {
85                            din.skip(length - 2);
86                        }
87                    }
88                    pos += length;
89
90                    marker = din.readShort();
91                    pos += 2;
92                }
93
94                if (tempOut != null) {
95                    // Write empty image data.
96                    tempOut.writeShort(SOS);
97                    // Write the frame size as 2. Since this includes the size bytes as well
98                    // (short = 2 bytes), it implies there is 0 byte of image data.
99                    tempOut.writeShort(2);
100
101                    // End of input
102                    tempOut.writeShort(EOI);
103                    tempOut.close();
104
105                    return readRotation(tempFile.getAbsolutePath());
106                }
107            }
108        } catch (IOException e) {
109            if (DEBUG) {
110                Log.d(TAG, "Error parsing input stream", e);
111            }
112        } finally {
113            Utils.closeSilently(in);
114            Utils.closeSilently(tempOut);
115            if (tempFile != null) {
116                tempFile.delete();
117            }
118        }
119        return 0;
120    }
121
122    /**
123     * Parses the rotation of the JPEG image.
124     */
125    public static final int readRotation(String filePath) {
126        try {
127            ExifInterface exif = new ExifInterface(filePath);
128            switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) {
129                case ExifInterface.ORIENTATION_ROTATE_90:
130                    return 90;
131                case ExifInterface.ORIENTATION_ROTATE_270:
132                    return 270;
133                case ExifInterface.ORIENTATION_ROTATE_180:
134                    return 180;
135                default:
136                    return 0;
137            }
138        } catch (IOException e) {
139            if (DEBUG) {
140                Log.d(TAG, "Error reading file", e);
141            }
142        }
143        return 0;
144    }
145}
146