/* * 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. * *

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 offsetTags = new HashSet<>(); static { offsetTags.add(getTrueTagKey(TAG_GPS_IFD)); offsetTags.add(getTrueTagKey(TAG_EXIF_IFD)); offsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT)); offsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD)); offsetTags.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 data = 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); } data = 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} * *

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 offsetTags.contains(tag); } private SparseIntArray tagInfo = null; SparseIntArray getTagInfo() { if (tagInfo == null) { tagInfo = new SparseIntArray(); initTagInfo(); } return tagInfo; } 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; tagInfo.put(ExifInterface.TAG_STRIP_OFFSETS, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16); tagInfo.put(ExifInterface.TAG_EXIF_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); tagInfo.put(ExifInterface.TAG_GPS_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); tagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); tagInfo.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; tagInfo.put( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); tagInfo.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; tagInfo.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 data.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: * *

*/ 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 { public 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() { data = 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 data.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(); } }