summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/callcomposer/camera/exif/ExifInterface.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/callcomposer/camera/exif/ExifInterface.java')
-rw-r--r--java/com/android/dialer/callcomposer/camera/exif/ExifInterface.java374
1 files changed, 374 insertions, 0 deletions
diff --git a/java/com/android/dialer/callcomposer/camera/exif/ExifInterface.java b/java/com/android/dialer/callcomposer/camera/exif/ExifInterface.java
new file mode 100644
index 000000000..92dee1c94
--- /dev/null
+++ b/java/com/android/dialer/callcomposer/camera/exif/ExifInterface.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2016 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.dialer.callcomposer.camera.exif;
+
+import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
+import android.util.SparseIntArray;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.HashSet;
+import java.util.TimeZone;
+
+/**
+ * This class provides methods and constants for reading and writing jpeg file metadata. It contains
+ * a collection of ExifTags, and a collection of definitions for creating valid ExifTags. The
+ * collection of ExifTags can be updated by: reading new ones from a file, deleting or adding
+ * existing ones, or building new ExifTags from a tag definition. These ExifTags can be written to a
+ * valid jpeg image as exif metadata.
+ *
+ * <p>Each ExifTag has a tag ID (TID) and is stored in a specific image file directory (IFD) as
+ * specified by the exif standard. A tag definition can be looked up with a constant that is a
+ * combination of TID and IFD. This definition has information about the type, number of components,
+ * and valid IFDs for a tag.
+ *
+ * @see ExifTag
+ */
+public class ExifInterface {
+ private static final int IFD_NULL = -1;
+ static final int DEFINITION_NULL = 0;
+
+ /** Tag constants for Jeita EXIF 2.2 */
+ // IFD 0
+ public static final int TAG_ORIENTATION = defineTag(IfdId.TYPE_IFD_0, (short) 0x0112);
+
+ static final int TAG_EXIF_IFD = defineTag(IfdId.TYPE_IFD_0, (short) 0x8769);
+ static final int TAG_GPS_IFD = defineTag(IfdId.TYPE_IFD_0, (short) 0x8825);
+ static final int TAG_STRIP_OFFSETS = defineTag(IfdId.TYPE_IFD_0, (short) 0x0111);
+ static final int TAG_STRIP_BYTE_COUNTS = defineTag(IfdId.TYPE_IFD_0, (short) 0x0117);
+ // IFD 1
+ static final int TAG_JPEG_INTERCHANGE_FORMAT = defineTag(IfdId.TYPE_IFD_1, (short) 0x0201);
+ static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = defineTag(IfdId.TYPE_IFD_1, (short) 0x0202);
+ // IFD Exif Tags
+ static final int TAG_INTEROPERABILITY_IFD = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005);
+
+ /** Tags that contain offset markers. These are included in the banned defines. */
+ private static HashSet<Short> sOffsetTags = new HashSet<>();
+
+ static {
+ sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD));
+ sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD));
+ sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT));
+ sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD));
+ sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS));
+ }
+
+ private static final String NULL_ARGUMENT_STRING = "Argument is null";
+
+ private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
+
+ private ExifData mData = new ExifData();
+
+ @SuppressLint("SimpleDateFormat")
+ public ExifInterface() {
+ DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
+ mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+ /**
+ * Reads the exif tags from a byte array, clearing this ExifInterface object's existing exif tags.
+ *
+ * @param jpeg a byte array containing a jpeg compressed image.
+ * @throws java.io.IOException
+ */
+ public void readExif(byte[] jpeg) throws IOException {
+ readExif(new ByteArrayInputStream(jpeg));
+ }
+
+ /**
+ * Reads the exif tags from an InputStream, clearing this ExifInterface object's existing exif
+ * tags.
+ *
+ * @param inStream an InputStream containing a jpeg compressed image.
+ * @throws java.io.IOException
+ */
+ private void readExif(InputStream inStream) throws IOException {
+ if (inStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ ExifData d;
+ try {
+ d = new ExifReader(this).read(inStream);
+ } catch (ExifInvalidFormatException e) {
+ throw new IOException("Invalid exif format : " + e);
+ }
+ mData = d;
+ }
+
+ /** Returns the TID for a tag constant. */
+ static short getTrueTagKey(int tag) {
+ // Truncate
+ return (short) tag;
+ }
+
+ /** Returns the constant representing a tag with a given TID and default IFD. */
+ private static int defineTag(int ifdId, short tagId) {
+ return (tagId & 0x0000ffff) | (ifdId << 16);
+ }
+
+ static boolean isIfdAllowed(int info, int ifd) {
+ int[] ifds = IfdData.getIfds();
+ int ifdFlags = getAllowedIfdFlagsFromInfo(info);
+ for (int i = 0; i < ifds.length; i++) {
+ if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static int getAllowedIfdFlagsFromInfo(int info) {
+ return info >>> 24;
+ }
+
+ /**
+ * Returns true if tag TID is one of the following: {@code TAG_EXIF_IFD}, {@code TAG_GPS_IFD},
+ * {@code TAG_JPEG_INTERCHANGE_FORMAT}, {@code TAG_STRIP_OFFSETS}, {@code
+ * TAG_INTEROPERABILITY_IFD}
+ *
+ * <p>Note: defining tags with these TID's is disallowed.
+ *
+ * @param tag a tag's TID (can be obtained from a defined tag constant with {@link
+ * #getTrueTagKey}).
+ * @return true if the TID is that of an offset tag.
+ */
+ static boolean isOffsetTag(short tag) {
+ return sOffsetTags.contains(tag);
+ }
+
+ private SparseIntArray mTagInfo = null;
+
+ SparseIntArray getTagInfo() {
+ if (mTagInfo == null) {
+ mTagInfo = new SparseIntArray();
+ initTagInfo();
+ }
+ return mTagInfo;
+ }
+
+ private void initTagInfo() {
+ /**
+ * We put tag information in a 4-bytes integer. The first byte a bitmask representing the
+ * allowed IFDs of the tag, the second byte is the data type, and the last two byte are a short
+ * value indicating the default component count of this tag.
+ */
+ // IFD0 tags
+ int[] ifdAllowedIfds = {IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1};
+ int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24;
+ mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16);
+ mTagInfo.put(ExifInterface.TAG_EXIF_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16);
+ // IFD1 tags
+ int[] ifd1AllowedIfds = {IfdId.TYPE_IFD_1};
+ int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24;
+ mTagInfo.put(
+ ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
+ ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(
+ ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+ ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ // Exif tags
+ int[] exifAllowedIfds = {IfdId.TYPE_IFD_EXIF};
+ int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24;
+ mTagInfo.put(
+ ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ }
+
+ private static int getFlagsFromAllowedIfds(int[] allowedIfds) {
+ if (allowedIfds == null || allowedIfds.length == 0) {
+ return 0;
+ }
+ int flags = 0;
+ int[] ifds = IfdData.getIfds();
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ for (int j : allowedIfds) {
+ if (ifds[i] == j) {
+ flags |= 1 << i;
+ break;
+ }
+ }
+ }
+ return flags;
+ }
+
+ private Integer getTagIntValue(int tagId, int ifdId) {
+ int[] l = getTagIntValues(tagId, ifdId);
+ if (l == null || l.length <= 0) {
+ return null;
+ }
+ return l[0];
+ }
+
+ private int[] getTagIntValues(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsInts();
+ }
+
+ /** Gets an ExifTag for an IFD other than the tag's default. */
+ public ExifTag getTag(int tagId, int ifdId) {
+ if (!ExifTag.isValidIfd(ifdId)) {
+ return null;
+ }
+ return mData.getTag(getTrueTagKey(tagId), ifdId);
+ }
+
+ public Integer getTagIntValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagIntValue(tagId, ifdId);
+ }
+
+ /**
+ * Gets the default IFD for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_EXIF_IFD}.
+ * @return the default IFD for a tag definition or {@link #IFD_NULL} if no definition exists.
+ */
+ private int getDefinedTagDefaultIfd(int tagId) {
+ int info = getTagInfo().get(tagId);
+ if (info == DEFINITION_NULL) {
+ return IFD_NULL;
+ }
+ return getTrueIfd(tagId);
+ }
+
+ /** Returns the default IFD for a tag constant. */
+ private static int getTrueIfd(int tag) {
+ return tag >>> 16;
+ }
+
+ /**
+ * Constants for {@code TAG_ORIENTATION}. They can be interpreted as follows:
+ *
+ * <ul>
+ * <li>TOP_LEFT is the normal orientation.
+ * <li>TOP_RIGHT is a left-right mirror.
+ * <li>BOTTOM_LEFT is a 180 degree rotation.
+ * <li>BOTTOM_RIGHT is a top-bottom mirror.
+ * <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis.
+ * <li>RIGHT_TOP is a 90 degree clockwise rotation.
+ * <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.
+ * <li>RIGHT_BOTTOM is a 270 degree clockwise rotation.
+ * </ul>
+ */
+ interface Orientation {
+ short TOP_LEFT = 1;
+ short TOP_RIGHT = 2;
+ short BOTTOM_LEFT = 3;
+ short BOTTOM_RIGHT = 4;
+ short LEFT_TOP = 5;
+ short RIGHT_TOP = 6;
+ short LEFT_BOTTOM = 7;
+ short RIGHT_BOTTOM = 8;
+ }
+
+ /** Wrapper class to define some orientation parameters. */
+ public static class OrientationParams {
+ int rotation = 0;
+ int scaleX = 1;
+ int scaleY = 1;
+ public boolean invertDimensions = false;
+ }
+
+ public static OrientationParams getOrientationParams(int orientation) {
+ OrientationParams params = new OrientationParams();
+ switch (orientation) {
+ case Orientation.TOP_RIGHT: // Flip horizontal
+ params.scaleX = -1;
+ break;
+ case Orientation.BOTTOM_RIGHT: // Flip vertical
+ params.scaleY = -1;
+ break;
+ case Orientation.BOTTOM_LEFT: // Rotate 180
+ params.rotation = 180;
+ break;
+ case Orientation.RIGHT_BOTTOM: // Rotate 270
+ params.rotation = 270;
+ params.invertDimensions = true;
+ break;
+ case Orientation.RIGHT_TOP: // Rotate 90
+ params.rotation = 90;
+ params.invertDimensions = true;
+ break;
+ case Orientation.LEFT_TOP: // Transpose
+ params.rotation = 90;
+ params.scaleX = -1;
+ params.invertDimensions = true;
+ break;
+ case Orientation.LEFT_BOTTOM: // Transverse
+ params.rotation = 270;
+ params.scaleX = -1;
+ params.invertDimensions = true;
+ break;
+ }
+ return params;
+ }
+
+ /** Clears this ExifInterface object's existing exif tags. */
+ public void clearExif() {
+ mData = new ExifData();
+ }
+
+ /**
+ * Puts an ExifTag into this ExifInterface object's tags, removing a previous ExifTag with the
+ * same TID and IFD. The IFD it is put into will be the one the tag was created with in {@link
+ * #buildTag}.
+ *
+ * @param tag an ExifTag to put into this ExifInterface's tags.
+ * @return the previous ExifTag with the same TID and IFD or null if none exists.
+ */
+ public ExifTag setTag(ExifTag tag) {
+ return mData.addTag(tag);
+ }
+
+ /**
+ * Returns the ExifTag in that tag's default IFD for a defined tag constant or null if none
+ * exists.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_EXIF_IFD}.
+ * @return an {@link ExifTag} or null if none exists.
+ */
+ public ExifTag getTag(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTag(tagId, ifdId);
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg compressed bitmap, removing prior
+ * exif tags.
+ *
+ * @param bmap a bitmap to compress and write exif into.
+ * @param exifOutStream the OutputStream to which the jpeg image with added exif tags will be
+ * written.
+ * @throws java.io.IOException
+ */
+ public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException {
+ if (bmap == null || exifOutStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ bmap.compress(Bitmap.CompressFormat.JPEG, 90, exifOutStream);
+ exifOutStream.flush();
+ }
+}