Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5f195074 authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Updating the ExifParsing code > Using system implementation of...

Merge "Updating the ExifParsing code   > Using system implementation of ExifInterface to read orientation   > For inputstream, creating a temporary file with just the header     since the system API only supports file input" into ub-launcher3-master
parents 08efde7e 2563b3b9
Loading
Loading
Loading
Loading
+7 −21
Original line number Diff line number Diff line
@@ -21,8 +21,6 @@ import android.content.res.Resources;
import android.net.Uri;
import android.util.Log;

import com.android.gallery3d.exif.ExifInterface;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -42,38 +40,26 @@ public class BitmapUtils {
    }

    public static int getRotationFromExif(Context context, Uri uri) {
        return BitmapUtils.getRotationFromExifHelper(null, 0, context, uri);
        return BitmapUtils.getRotationFromExifHelper(null, 0, uri, context);
    }

    public static int getRotationFromExif(Resources res, int resId) {
        return BitmapUtils.getRotationFromExifHelper(res, resId, null, null);
    public static int getRotationFromExif(Resources res, int resId, Context context) {
        return BitmapUtils.getRotationFromExifHelper(res, resId, null, context);
    }

    private static int getRotationFromExifHelper(Resources res, int resId, Context context, Uri uri) {
        ExifInterface ei = new ExifInterface();
    private static int getRotationFromExifHelper(Resources res, int resId,
            Uri uri, Context context) {
        InputStream is = null;
        BufferedInputStream bis = null;
        try {
            if (uri != null) {
                is = context.getContentResolver().openInputStream(uri);
                bis = new BufferedInputStream(is);
                ei.readExif(bis);
            } else {
                is = res.openRawResource(resId);
                bis = new BufferedInputStream(is);
                ei.readExif(bis);
            }
            Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
            if (ori != null) {
                return ExifInterface.getRotationForOrientationValue(ori.shortValue());
            }
        } catch (IOException e) {
            Log.w(TAG, "Getting exif data failed", e);
        } catch (NullPointerException e) {
            // Sometimes the ExifInterface has an internal NPE if Exif data isn't valid
            return ExifOrientation.readRotation(new BufferedInputStream(is), context);
        } catch (IOException | NullPointerException e) {
            Log.w(TAG, "Getting exif data failed", e);
        } finally {
            Utils.closeSilently(bis);
            Utils.closeSilently(is);
        }
        return 0;
+145 −0
Original line number Diff line number Diff line
/**
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.gallery3d.common;

import android.content.Context;
import android.media.ExifInterface;
import android.util.Log;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class ExifOrientation {
    private static final String TAG = "ExifOrientation";
    private static final boolean DEBUG = false;

    private static final short SOI =  (short) 0xFFD8;   // start of input
    private static final short APP0 = (short) 0xFFE0;
    private static final short APPF = (short) 0xFFEF;
    private static final short APP1 = (short) 0xFFE1;
    private static final short SOS = (short) 0xFFDA;    // start of stream
    private static final short EOI = (short) 0xFFD9;    // end of input

    // The header is available in first 64 bytes, so reading upto 128 bytes
    // should be more than enough.
    private static final int MAX_BYTES_TO_READ = 128 * 1024;

    /**
     * Parses the rotation of the JPEG image from the input stream.
     */
    public static final int readRotation(InputStream in, Context context) {
        // Since the platform implementation only takes file input, create a temporary file
        // with just the image header.
        File tempFile = null;
        DataOutputStream tempOut = null;

        try {
        DataInputStream din = new DataInputStream(in);
            int pos = 0;
            if (din.readShort() == SOI) {
                pos += 2;

                short marker = din.readShort();
                pos += 2;

                while ((marker >= APP0 && marker <= APPF) && pos < MAX_BYTES_TO_READ) {
                    int length = din.readUnsignedShort();
                    if (length < 2) {
                        throw new IOException("Invalid header size");
                    }

                    // We only want APP1 headers
                    if (length > 2) {
                        if (marker == APP1) {
                            // Copy the header
                            if (tempFile == null) {
                                tempFile = File.createTempFile(TAG, ".jpg", context.getCacheDir());
                                tempOut = new DataOutputStream(new FileOutputStream(tempFile));
                                tempOut.writeShort(SOI);
                            }

                            tempOut.writeShort(marker);
                            tempOut.writeShort(length);

                            byte[] header = new byte[length - 2];
                            din.read(header);
                            tempOut.write(header);
                        } else {
                            din.skip(length - 2);
                        }
                    }
                    pos += length;

                    marker = din.readShort();
                    pos += 2;
                }

                if (tempOut != null) {
                    // Write empty image data.
                    tempOut.writeShort(SOS);
                    // Write the frame size as 2. Since this includes the size bytes as well
                    // (short = 2 bytes), it implies there is 0 byte of image data.
                    tempOut.writeShort(2);

                    // End of input
                    tempOut.writeShort(EOI);
                    tempOut.close();

                    return readRotation(tempFile.getAbsolutePath());
                }
            }
        } catch (IOException e) {
            if (DEBUG) {
                Log.d(TAG, "Error parsing input stream", e);
            }
        } finally {
            Utils.closeSilently(in);
            Utils.closeSilently(tempOut);
            if (tempFile != null) {
                tempFile.delete();
            }
        }
        return 0;
    }

    /**
     * Parses the rotation of the JPEG image.
     */
    public static final int readRotation(String filePath) {
        try {
            ExifInterface exif = new ExifInterface(filePath);
            switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    return 90;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    return 270;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    return 180;
                default:
                    return 0;
            }
        } catch (IOException e) {
            if (DEBUG) {
                Log.d(TAG, "Error reading file", e);
            }
        }
        return 0;
    }
}
+0 −48
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.gallery3d.exif;

import java.io.InputStream;
import java.nio.ByteBuffer;

class ByteBufferInputStream extends InputStream {

    private ByteBuffer mBuf;

    public ByteBufferInputStream(ByteBuffer buf) {
        mBuf = buf;
    }

    @Override
    public int read() {
        if (!mBuf.hasRemaining()) {
            return -1;
        }
        return mBuf.get() & 0xFF;
    }

    @Override
    public int read(byte[] bytes, int off, int len) {
        if (!mBuf.hasRemaining()) {
            return -1;
        }

        len = Math.min(len, mBuf.remaining());
        mBuf.get(bytes, off, len);
        return len;
    }
}
+0 −136
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.gallery3d.exif;

import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;

class CountedDataInputStream extends FilterInputStream {

    private int mCount = 0;

    // allocate a byte buffer for a long value;
    private final byte mByteArray[] = new byte[8];
    private final ByteBuffer mByteBuffer = ByteBuffer.wrap(mByteArray);

    protected CountedDataInputStream(InputStream in) {
        super(in);
    }

    public int getReadByteCount() {
        return mCount;
    }

    @Override
    public int read(byte[] b) throws IOException {
        int r = in.read(b);
        mCount += (r >= 0) ? r : 0;
        return r;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int r = in.read(b, off, len);
        mCount += (r >= 0) ? r : 0;
        return r;
    }

    @Override
    public int read() throws IOException {
        int r = in.read();
        mCount += (r >= 0) ? 1 : 0;
        return r;
    }

    @Override
    public long skip(long length) throws IOException {
        long skip = in.skip(length);
        mCount += skip;
        return skip;
    }

    public void skipOrThrow(long length) throws IOException {
        if (skip(length) != length) throw new EOFException();
    }

    public void skipTo(long target) throws IOException {
        long cur = mCount;
        long diff = target - cur;
        assert(diff >= 0);
        skipOrThrow(diff);
    }

    public void readOrThrow(byte[] b, int off, int len) throws IOException {
        int r = read(b, off, len);
        if (r != len) throw new EOFException();
    }

    public void readOrThrow(byte[] b) throws IOException {
        readOrThrow(b, 0, b.length);
    }

    public void setByteOrder(ByteOrder order) {
        mByteBuffer.order(order);
    }

    public ByteOrder getByteOrder() {
        return mByteBuffer.order();
    }

    public short readShort() throws IOException {
        readOrThrow(mByteArray, 0 ,2);
        mByteBuffer.rewind();
        return mByteBuffer.getShort();
    }

    public int readUnsignedShort() throws IOException {
        return readShort() & 0xffff;
    }

    public int readInt() throws IOException {
        readOrThrow(mByteArray, 0 , 4);
        mByteBuffer.rewind();
        return mByteBuffer.getInt();
    }

    public long readUnsignedInt() throws IOException {
        return readInt() & 0xffffffffL;
    }

    public long readLong() throws IOException {
        readOrThrow(mByteArray, 0 , 8);
        mByteBuffer.rewind();
        return mByteBuffer.getLong();
    }

    public String readString(int n) throws IOException {
        byte buf[] = new byte[n];
        readOrThrow(buf);
        return new String(buf, "UTF8");
    }

    public String readString(int n, Charset charset) throws IOException {
        byte buf[] = new byte[n];
        readOrThrow(buf);
        return new String(buf, charset);
    }
}
 No newline at end of file
+0 −348
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.gallery3d.exif;

import android.util.Log;

import java.io.UnsupportedEncodingException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * This class stores the EXIF header in IFDs according to the JPEG
 * specification. It is the result produced by {@link ExifReader}.
 *
 * @see ExifReader
 * @see IfdData
 */
class ExifData {
    private static final String TAG = "ExifData";
    private static final byte[] USER_COMMENT_ASCII = {
            0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00
    };
    private static final byte[] USER_COMMENT_JIS = {
            0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
    };
    private static final byte[] USER_COMMENT_UNICODE = {
            0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00
    };

    private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
    private byte[] mThumbnail;
    private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
    private final ByteOrder mByteOrder;

    ExifData(ByteOrder order) {
        mByteOrder = order;
    }

    /**
     * Gets the compressed thumbnail. Returns null if there is no compressed
     * thumbnail.
     *
     * @see #hasCompressedThumbnail()
     */
    protected byte[] getCompressedThumbnail() {
        return mThumbnail;
    }

    /**
     * Sets the compressed thumbnail.
     */
    protected void setCompressedThumbnail(byte[] thumbnail) {
        mThumbnail = thumbnail;
    }

    /**
     * Returns true it this header contains a compressed thumbnail.
     */
    protected boolean hasCompressedThumbnail() {
        return mThumbnail != null;
    }

    /**
     * Adds an uncompressed strip.
     */
    protected void setStripBytes(int index, byte[] strip) {
        if (index < mStripBytes.size()) {
            mStripBytes.set(index, strip);
        } else {
            for (int i = mStripBytes.size(); i < index; i++) {
                mStripBytes.add(null);
            }
            mStripBytes.add(strip);
        }
    }

    /**
     * Gets the strip count.
     */
    protected int getStripCount() {
        return mStripBytes.size();
    }

    /**
     * Gets the strip at the specified index.
     *
     * @exceptions #IndexOutOfBoundException
     */
    protected byte[] getStrip(int index) {
        return mStripBytes.get(index);
    }

    /**
     * Returns true if this header contains uncompressed strip.
     */
    protected boolean hasUncompressedStrip() {
        return mStripBytes.size() != 0;
    }

    /**
     * Gets the byte order.
     */
    protected ByteOrder getByteOrder() {
        return mByteOrder;
    }

    /**
     * Returns the {@link IfdData} object corresponding to a given IFD if it
     * exists or null.
     */
    protected IfdData getIfdData(int ifdId) {
        if (ExifTag.isValidIfd(ifdId)) {
            return mIfdDatas[ifdId];
        }
        return null;
    }

    /**
     * Adds IFD data. If IFD data of the same type already exists, it will be
     * replaced by the new data.
     */
    protected void addIfdData(IfdData data) {
        mIfdDatas[data.getId()] = data;
    }

    /**
     * Returns the {@link IfdData} object corresponding to a given IFD or
     * generates one if none exist.
     */
    protected IfdData getOrCreateIfdData(int ifdId) {
        IfdData ifdData = mIfdDatas[ifdId];
        if (ifdData == null) {
            ifdData = new IfdData(ifdId);
            mIfdDatas[ifdId] = ifdData;
        }
        return ifdData;
    }

    /**
     * Returns the tag with a given TID in the given IFD if the tag exists.
     * Otherwise returns null.
     */
    protected ExifTag getTag(short tag, int ifd) {
        IfdData ifdData = mIfdDatas[ifd];
        return (ifdData == null) ? null : ifdData.getTag(tag);
    }

    /**
     * Adds the given ExifTag to its default IFD and returns an existing ExifTag
     * with the same TID or null if none exist.
     */
    protected ExifTag addTag(ExifTag tag) {
        if (tag != null) {
            int ifd = tag.getIfd();
            return addTag(tag, ifd);
        }
        return null;
    }

    /**
     * Adds the given ExifTag to the given IFD and returns an existing ExifTag
     * with the same TID or null if none exist.
     */
    protected ExifTag addTag(ExifTag tag, int ifdId) {
        if (tag != null && ExifTag.isValidIfd(ifdId)) {
            IfdData ifdData = getOrCreateIfdData(ifdId);
            return ifdData.setTag(tag);
        }
        return null;
    }

    protected void clearThumbnailAndStrips() {
        mThumbnail = null;
        mStripBytes.clear();
    }

    /**
     * Removes the thumbnail and its related tags. IFD1 will be removed.
     */
    protected void removeThumbnailData() {
        clearThumbnailAndStrips();
        mIfdDatas[IfdId.TYPE_IFD_1] = null;
    }

    /**
     * Removes the tag with a given TID and IFD.
     */
    protected void removeTag(short tagId, int ifdId) {
        IfdData ifdData = mIfdDatas[ifdId];
        if (ifdData == null) {
            return;
        }
        ifdData.removeTag(tagId);
    }

    /**
     * Decodes the user comment tag into string as specified in the EXIF
     * standard. Returns null if decoding failed.
     */
    protected String getUserComment() {
        IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
        if (ifdData == null) {
            return null;
        }
        ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT));
        if (tag == null) {
            return null;
        }
        if (tag.getComponentCount() < 8) {
            return null;
        }

        byte[] buf = new byte[tag.getComponentCount()];
        tag.getBytes(buf);

        byte[] code = new byte[8];
        System.arraycopy(buf, 0, code, 0, 8);

        try {
            if (Arrays.equals(code, USER_COMMENT_ASCII)) {
                return new String(buf, 8, buf.length - 8, "US-ASCII");
            } else if (Arrays.equals(code, USER_COMMENT_JIS)) {
                return new String(buf, 8, buf.length - 8, "EUC-JP");
            } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) {
                return new String(buf, 8, buf.length - 8, "UTF-16");
            } else {
                return null;
            }
        } catch (UnsupportedEncodingException e) {
            Log.w(TAG, "Failed to decode the user comment");
            return null;
        }
    }

    /**
     * Returns a list of all {@link ExifTag}s in the ExifData or null if there
     * are none.
     */
    protected List<ExifTag> getAllTags() {
        ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
        for (IfdData d : mIfdDatas) {
            if (d != null) {
                ExifTag[] tags = d.getAllTags();
                if (tags != null) {
                    for (ExifTag t : tags) {
                        ret.add(t);
                    }
                }
            }
        }
        if (ret.size() == 0) {
            return null;
        }
        return ret;
    }

    /**
     * Returns a list of all {@link ExifTag}s in a given IFD or null if there
     * are none.
     */
    protected List<ExifTag> getAllTagsForIfd(int ifd) {
        IfdData d = mIfdDatas[ifd];
        if (d == null) {
            return null;
        }
        ExifTag[] tags = d.getAllTags();
        if (tags == null) {
            return null;
        }
        ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
        for (ExifTag t : tags) {
            ret.add(t);
        }
        if (ret.size() == 0) {
            return null;
        }
        return ret;
    }

    /**
     * Returns a list of all {@link ExifTag}s with a given TID or null if there
     * are none.
     */
    protected List<ExifTag> getAllTagsForTagId(short tag) {
        ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
        for (IfdData d : mIfdDatas) {
            if (d != null) {
                ExifTag t = d.getTag(tag);
                if (t != null) {
                    ret.add(t);
                }
            }
        }
        if (ret.size() == 0) {
            return null;
        }
        return ret;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (obj instanceof ExifData) {
            ExifData data = (ExifData) obj;
            if (data.mByteOrder != mByteOrder ||
                    data.mStripBytes.size() != mStripBytes.size() ||
                    !Arrays.equals(data.mThumbnail, mThumbnail)) {
                return false;
            }
            for (int i = 0; i < mStripBytes.size(); i++) {
                if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
                    return false;
                }
            }
            for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
                IfdData ifd1 = data.getIfdData(i);
                IfdData ifd2 = getIfdData(i);
                if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

}
Loading