1 /* 2 * Copyright (C) 2016 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 */ 16 17 package com.android.dialer.callcomposer.camera.exif; 18 19 import android.annotation.SuppressLint; 20 import android.graphics.Bitmap; 21 import android.util.SparseIntArray; 22 import java.io.ByteArrayInputStream; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.io.OutputStream; 26 import java.text.DateFormat; 27 import java.text.SimpleDateFormat; 28 import java.util.HashSet; 29 import java.util.TimeZone; 30 31 /** 32 * This class provides methods and constants for reading and writing jpeg file metadata. It contains 33 * a collection of ExifTags, and a collection of definitions for creating valid ExifTags. The 34 * collection of ExifTags can be updated by: reading new ones from a file, deleting or adding 35 * existing ones, or building new ExifTags from a tag definition. These ExifTags can be written to a 36 * valid jpeg image as exif metadata. 37 * 38 * <p>Each ExifTag has a tag ID (TID) and is stored in a specific image file directory (IFD) as 39 * specified by the exif standard. A tag definition can be looked up with a constant that is a 40 * combination of TID and IFD. This definition has information about the type, number of components, 41 * and valid IFDs for a tag. 42 * 43 * @see ExifTag 44 */ 45 public class ExifInterface { 46 private static final int IFD_NULL = -1; 47 static final int DEFINITION_NULL = 0; 48 49 /** Tag constants for Jeita EXIF 2.2 */ 50 // IFD 0 51 public static final int TAG_ORIENTATION = defineTag(IfdId.TYPE_IFD_0, (short) 0x0112); 52 53 static final int TAG_EXIF_IFD = defineTag(IfdId.TYPE_IFD_0, (short) 0x8769); 54 static final int TAG_GPS_IFD = defineTag(IfdId.TYPE_IFD_0, (short) 0x8825); 55 static final int TAG_STRIP_OFFSETS = defineTag(IfdId.TYPE_IFD_0, (short) 0x0111); 56 static final int TAG_STRIP_BYTE_COUNTS = defineTag(IfdId.TYPE_IFD_0, (short) 0x0117); 57 // IFD 1 58 static final int TAG_JPEG_INTERCHANGE_FORMAT = defineTag(IfdId.TYPE_IFD_1, (short) 0x0201); 59 static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = defineTag(IfdId.TYPE_IFD_1, (short) 0x0202); 60 // IFD Exif Tags 61 static final int TAG_INTEROPERABILITY_IFD = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005); 62 63 /** Tags that contain offset markers. These are included in the banned defines. */ 64 private static HashSet<Short> offsetTags = new HashSet<>(); 65 66 static { getTrueTagKey(TAG_GPS_IFD)67 offsetTags.add(getTrueTagKey(TAG_GPS_IFD)); getTrueTagKey(TAG_EXIF_IFD)68 offsetTags.add(getTrueTagKey(TAG_EXIF_IFD)); getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT)69 offsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT)); getTrueTagKey(TAG_INTEROPERABILITY_IFD)70 offsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD)); getTrueTagKey(TAG_STRIP_OFFSETS)71 offsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS)); 72 } 73 74 private static final String NULL_ARGUMENT_STRING = "Argument is null"; 75 76 private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd"; 77 78 private ExifData data = new ExifData(); 79 80 @SuppressLint("SimpleDateFormat") ExifInterface()81 public ExifInterface() { 82 DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR); 83 mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 84 } 85 86 /** 87 * Reads the exif tags from a byte array, clearing this ExifInterface object's existing exif tags. 88 * 89 * @param jpeg a byte array containing a jpeg compressed image. 90 * @throws java.io.IOException 91 */ readExif(byte[] jpeg)92 public void readExif(byte[] jpeg) throws IOException { 93 readExif(new ByteArrayInputStream(jpeg)); 94 } 95 96 /** 97 * Reads the exif tags from an InputStream, clearing this ExifInterface object's existing exif 98 * tags. 99 * 100 * @param inStream an InputStream containing a jpeg compressed image. 101 * @throws java.io.IOException 102 */ readExif(InputStream inStream)103 private void readExif(InputStream inStream) throws IOException { 104 if (inStream == null) { 105 throw new IllegalArgumentException(NULL_ARGUMENT_STRING); 106 } 107 ExifData d; 108 try { 109 d = new ExifReader(this).read(inStream); 110 } catch (ExifInvalidFormatException e) { 111 throw new IOException("Invalid exif format : " + e); 112 } 113 data = d; 114 } 115 116 /** Returns the TID for a tag constant. */ getTrueTagKey(int tag)117 static short getTrueTagKey(int tag) { 118 // Truncate 119 return (short) tag; 120 } 121 122 /** Returns the constant representing a tag with a given TID and default IFD. */ defineTag(int ifdId, short tagId)123 private static int defineTag(int ifdId, short tagId) { 124 return (tagId & 0x0000ffff) | (ifdId << 16); 125 } 126 isIfdAllowed(int info, int ifd)127 static boolean isIfdAllowed(int info, int ifd) { 128 int[] ifds = IfdData.getIfds(); 129 int ifdFlags = getAllowedIfdFlagsFromInfo(info); 130 for (int i = 0; i < ifds.length; i++) { 131 if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) { 132 return true; 133 } 134 } 135 return false; 136 } 137 getAllowedIfdFlagsFromInfo(int info)138 private static int getAllowedIfdFlagsFromInfo(int info) { 139 return info >>> 24; 140 } 141 142 /** 143 * Returns true if tag TID is one of the following: {@code TAG_EXIF_IFD}, {@code TAG_GPS_IFD}, 144 * {@code TAG_JPEG_INTERCHANGE_FORMAT}, {@code TAG_STRIP_OFFSETS}, {@code 145 * TAG_INTEROPERABILITY_IFD} 146 * 147 * <p>Note: defining tags with these TID's is disallowed. 148 * 149 * @param tag a tag's TID (can be obtained from a defined tag constant with {@link 150 * #getTrueTagKey}). 151 * @return true if the TID is that of an offset tag. 152 */ isOffsetTag(short tag)153 static boolean isOffsetTag(short tag) { 154 return offsetTags.contains(tag); 155 } 156 157 private SparseIntArray tagInfo = null; 158 getTagInfo()159 SparseIntArray getTagInfo() { 160 if (tagInfo == null) { 161 tagInfo = new SparseIntArray(); 162 initTagInfo(); 163 } 164 return tagInfo; 165 } 166 initTagInfo()167 private void initTagInfo() { 168 /** 169 * We put tag information in a 4-bytes integer. The first byte a bitmask representing the 170 * allowed IFDs of the tag, the second byte is the data type, and the last two byte are a short 171 * value indicating the default component count of this tag. 172 */ 173 // IFD0 tags 174 int[] ifdAllowedIfds = {IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1}; 175 int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24; 176 tagInfo.put(ExifInterface.TAG_STRIP_OFFSETS, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16); 177 tagInfo.put(ExifInterface.TAG_EXIF_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); 178 tagInfo.put(ExifInterface.TAG_GPS_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); 179 tagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); 180 tagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16); 181 // IFD1 tags 182 int[] ifd1AllowedIfds = {IfdId.TYPE_IFD_1}; 183 int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24; 184 tagInfo.put( 185 ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 186 ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); 187 tagInfo.put( 188 ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 189 ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); 190 // Exif tags 191 int[] exifAllowedIfds = {IfdId.TYPE_IFD_EXIF}; 192 int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24; 193 tagInfo.put( 194 ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); 195 } 196 getFlagsFromAllowedIfds(int[] allowedIfds)197 private static int getFlagsFromAllowedIfds(int[] allowedIfds) { 198 if (allowedIfds == null || allowedIfds.length == 0) { 199 return 0; 200 } 201 int flags = 0; 202 int[] ifds = IfdData.getIfds(); 203 for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { 204 for (int j : allowedIfds) { 205 if (ifds[i] == j) { 206 flags |= 1 << i; 207 break; 208 } 209 } 210 } 211 return flags; 212 } 213 getTagIntValue(int tagId, int ifdId)214 private Integer getTagIntValue(int tagId, int ifdId) { 215 int[] l = getTagIntValues(tagId, ifdId); 216 if (l == null || l.length <= 0) { 217 return null; 218 } 219 return l[0]; 220 } 221 getTagIntValues(int tagId, int ifdId)222 private int[] getTagIntValues(int tagId, int ifdId) { 223 ExifTag t = getTag(tagId, ifdId); 224 if (t == null) { 225 return null; 226 } 227 return t.getValueAsInts(); 228 } 229 230 /** Gets an ExifTag for an IFD other than the tag's default. */ getTag(int tagId, int ifdId)231 public ExifTag getTag(int tagId, int ifdId) { 232 if (!ExifTag.isValidIfd(ifdId)) { 233 return null; 234 } 235 return data.getTag(getTrueTagKey(tagId), ifdId); 236 } 237 getTagIntValue(int tagId)238 public Integer getTagIntValue(int tagId) { 239 int ifdId = getDefinedTagDefaultIfd(tagId); 240 return getTagIntValue(tagId, ifdId); 241 } 242 243 /** 244 * Gets the default IFD for a tag. 245 * 246 * @param tagId a defined tag constant, e.g. {@link #TAG_EXIF_IFD}. 247 * @return the default IFD for a tag definition or {@link #IFD_NULL} if no definition exists. 248 */ getDefinedTagDefaultIfd(int tagId)249 private int getDefinedTagDefaultIfd(int tagId) { 250 int info = getTagInfo().get(tagId); 251 if (info == DEFINITION_NULL) { 252 return IFD_NULL; 253 } 254 return getTrueIfd(tagId); 255 } 256 257 /** Returns the default IFD for a tag constant. */ getTrueIfd(int tag)258 private static int getTrueIfd(int tag) { 259 return tag >>> 16; 260 } 261 262 /** 263 * Constants for {@code TAG_ORIENTATION}. They can be interpreted as follows: 264 * 265 * <ul> 266 * <li>TOP_LEFT is the normal orientation. 267 * <li>TOP_RIGHT is a left-right mirror. 268 * <li>BOTTOM_LEFT is a 180 degree rotation. 269 * <li>BOTTOM_RIGHT is a top-bottom mirror. 270 * <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis. 271 * <li>RIGHT_TOP is a 90 degree clockwise rotation. 272 * <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis. 273 * <li>RIGHT_BOTTOM is a 270 degree clockwise rotation. 274 * </ul> 275 */ 276 interface Orientation { 277 short TOP_LEFT = 1; 278 short TOP_RIGHT = 2; 279 short BOTTOM_LEFT = 3; 280 short BOTTOM_RIGHT = 4; 281 short LEFT_TOP = 5; 282 short RIGHT_TOP = 6; 283 short LEFT_BOTTOM = 7; 284 short RIGHT_BOTTOM = 8; 285 } 286 287 /** Wrapper class to define some orientation parameters. */ 288 public static class OrientationParams { 289 public int rotation = 0; 290 int scaleX = 1; 291 int scaleY = 1; 292 public boolean invertDimensions = false; 293 } 294 getOrientationParams(int orientation)295 public static OrientationParams getOrientationParams(int orientation) { 296 OrientationParams params = new OrientationParams(); 297 switch (orientation) { 298 case Orientation.TOP_RIGHT: // Flip horizontal 299 params.scaleX = -1; 300 break; 301 case Orientation.BOTTOM_RIGHT: // Flip vertical 302 params.scaleY = -1; 303 break; 304 case Orientation.BOTTOM_LEFT: // Rotate 180 305 params.rotation = 180; 306 break; 307 case Orientation.RIGHT_BOTTOM: // Rotate 270 308 params.rotation = 270; 309 params.invertDimensions = true; 310 break; 311 case Orientation.RIGHT_TOP: // Rotate 90 312 params.rotation = 90; 313 params.invertDimensions = true; 314 break; 315 case Orientation.LEFT_TOP: // Transpose 316 params.rotation = 90; 317 params.scaleX = -1; 318 params.invertDimensions = true; 319 break; 320 case Orientation.LEFT_BOTTOM: // Transverse 321 params.rotation = 270; 322 params.scaleX = -1; 323 params.invertDimensions = true; 324 break; 325 } 326 return params; 327 } 328 329 /** Clears this ExifInterface object's existing exif tags. */ clearExif()330 public void clearExif() { 331 data = new ExifData(); 332 } 333 334 /** 335 * Puts an ExifTag into this ExifInterface object's tags, removing a previous ExifTag with the 336 * same TID and IFD. The IFD it is put into will be the one the tag was created with in {@link 337 * #buildTag}. 338 * 339 * @param tag an ExifTag to put into this ExifInterface's tags. 340 * @return the previous ExifTag with the same TID and IFD or null if none exists. 341 */ setTag(ExifTag tag)342 public ExifTag setTag(ExifTag tag) { 343 return data.addTag(tag); 344 } 345 346 /** 347 * Returns the ExifTag in that tag's default IFD for a defined tag constant or null if none 348 * exists. 349 * 350 * @param tagId a defined tag constant, e.g. {@link #TAG_EXIF_IFD}. 351 * @return an {@link ExifTag} or null if none exists. 352 */ getTag(int tagId)353 public ExifTag getTag(int tagId) { 354 int ifdId = getDefinedTagDefaultIfd(tagId); 355 return getTag(tagId, ifdId); 356 } 357 358 /** 359 * Writes the tags from this ExifInterface object into a jpeg compressed bitmap, removing prior 360 * exif tags. 361 * 362 * @param bmap a bitmap to compress and write exif into. 363 * @param exifOutStream the OutputStream to which the jpeg image with added exif tags will be 364 * written. 365 * @throws java.io.IOException 366 */ writeExif(Bitmap bmap, OutputStream exifOutStream)367 public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException { 368 if (bmap == null || exifOutStream == null) { 369 throw new IllegalArgumentException(NULL_ARGUMENT_STRING); 370 } 371 bmap.compress(Bitmap.CompressFormat.JPEG, 90, exifOutStream); 372 exifOutStream.flush(); 373 } 374 } 375