1 /* 2 * Copyright 2020 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 androidx.camera.core.impl.utils; 18 19 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_BYTE; 20 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_DOUBLE; 21 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_SLONG; 22 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_SRATIONAL; 23 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_STRING; 24 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_ULONG; 25 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_UNDEFINED; 26 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_URATIONAL; 27 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_USHORT; 28 import static androidx.exifinterface.media.ExifInterface.CONTRAST_NORMAL; 29 import static androidx.exifinterface.media.ExifInterface.EXPOSURE_PROGRAM_NOT_DEFINED; 30 import static androidx.exifinterface.media.ExifInterface.FILE_SOURCE_DSC; 31 import static androidx.exifinterface.media.ExifInterface.FLAG_FLASH_FIRED; 32 import static androidx.exifinterface.media.ExifInterface.FLAG_FLASH_NO_FLASH_FUNCTION; 33 import static androidx.exifinterface.media.ExifInterface.GPS_DIRECTION_TRUE; 34 import static androidx.exifinterface.media.ExifInterface.GPS_DISTANCE_KILOMETERS; 35 import static androidx.exifinterface.media.ExifInterface.GPS_SPEED_KILOMETERS_PER_HOUR; 36 import static androidx.exifinterface.media.ExifInterface.LIGHT_SOURCE_FLASH; 37 import static androidx.exifinterface.media.ExifInterface.LIGHT_SOURCE_UNKNOWN; 38 import static androidx.exifinterface.media.ExifInterface.METERING_MODE_UNKNOWN; 39 import static androidx.exifinterface.media.ExifInterface.ORIENTATION_NORMAL; 40 import static androidx.exifinterface.media.ExifInterface.RENDERED_PROCESS_NORMAL; 41 import static androidx.exifinterface.media.ExifInterface.RESOLUTION_UNIT_INCHES; 42 import static androidx.exifinterface.media.ExifInterface.SATURATION_NORMAL; 43 import static androidx.exifinterface.media.ExifInterface.SCENE_CAPTURE_TYPE_STANDARD; 44 import static androidx.exifinterface.media.ExifInterface.SCENE_TYPE_DIRECTLY_PHOTOGRAPHED; 45 import static androidx.exifinterface.media.ExifInterface.SENSITIVITY_TYPE_ISO_SPEED; 46 import static androidx.exifinterface.media.ExifInterface.SHARPNESS_NORMAL; 47 import static androidx.exifinterface.media.ExifInterface.TAG_APERTURE_VALUE; 48 import static androidx.exifinterface.media.ExifInterface.TAG_BRIGHTNESS_VALUE; 49 import static androidx.exifinterface.media.ExifInterface.TAG_COLOR_SPACE; 50 import static androidx.exifinterface.media.ExifInterface.TAG_COMPONENTS_CONFIGURATION; 51 import static androidx.exifinterface.media.ExifInterface.TAG_CONTRAST; 52 import static androidx.exifinterface.media.ExifInterface.TAG_CUSTOM_RENDERED; 53 import static androidx.exifinterface.media.ExifInterface.TAG_DATETIME; 54 import static androidx.exifinterface.media.ExifInterface.TAG_DATETIME_DIGITIZED; 55 import static androidx.exifinterface.media.ExifInterface.TAG_DATETIME_ORIGINAL; 56 import static androidx.exifinterface.media.ExifInterface.TAG_EXIF_VERSION; 57 import static androidx.exifinterface.media.ExifInterface.TAG_EXPOSURE_BIAS_VALUE; 58 import static androidx.exifinterface.media.ExifInterface.TAG_EXPOSURE_MODE; 59 import static androidx.exifinterface.media.ExifInterface.TAG_EXPOSURE_PROGRAM; 60 import static androidx.exifinterface.media.ExifInterface.TAG_EXPOSURE_TIME; 61 import static androidx.exifinterface.media.ExifInterface.TAG_FILE_SOURCE; 62 import static androidx.exifinterface.media.ExifInterface.TAG_FLASH; 63 import static androidx.exifinterface.media.ExifInterface.TAG_FLASHPIX_VERSION; 64 import static androidx.exifinterface.media.ExifInterface.TAG_FOCAL_LENGTH; 65 import static androidx.exifinterface.media.ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT; 66 import static androidx.exifinterface.media.ExifInterface.TAG_F_NUMBER; 67 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_ALTITUDE; 68 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_ALTITUDE_REF; 69 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_DEST_BEARING_REF; 70 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_DEST_DISTANCE_REF; 71 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_IMG_DIRECTION_REF; 72 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_LATITUDE; 73 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_LATITUDE_REF; 74 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_LONGITUDE; 75 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_LONGITUDE_REF; 76 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_SPEED_REF; 77 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_TIMESTAMP; 78 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_TRACK_REF; 79 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_VERSION_ID; 80 import static androidx.exifinterface.media.ExifInterface.TAG_IMAGE_LENGTH; 81 import static androidx.exifinterface.media.ExifInterface.TAG_IMAGE_WIDTH; 82 import static androidx.exifinterface.media.ExifInterface.TAG_INTEROPERABILITY_INDEX; 83 import static androidx.exifinterface.media.ExifInterface.TAG_ISO_SPEED_RATINGS; 84 import static androidx.exifinterface.media.ExifInterface.TAG_LIGHT_SOURCE; 85 import static androidx.exifinterface.media.ExifInterface.TAG_MAKE; 86 import static androidx.exifinterface.media.ExifInterface.TAG_MAX_APERTURE_VALUE; 87 import static androidx.exifinterface.media.ExifInterface.TAG_METERING_MODE; 88 import static androidx.exifinterface.media.ExifInterface.TAG_MODEL; 89 import static androidx.exifinterface.media.ExifInterface.TAG_ORIENTATION; 90 import static androidx.exifinterface.media.ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY; 91 import static androidx.exifinterface.media.ExifInterface.TAG_PIXEL_X_DIMENSION; 92 import static androidx.exifinterface.media.ExifInterface.TAG_PIXEL_Y_DIMENSION; 93 import static androidx.exifinterface.media.ExifInterface.TAG_RESOLUTION_UNIT; 94 import static androidx.exifinterface.media.ExifInterface.TAG_SATURATION; 95 import static androidx.exifinterface.media.ExifInterface.TAG_SCENE_CAPTURE_TYPE; 96 import static androidx.exifinterface.media.ExifInterface.TAG_SCENE_TYPE; 97 import static androidx.exifinterface.media.ExifInterface.TAG_SENSING_METHOD; 98 import static androidx.exifinterface.media.ExifInterface.TAG_SENSITIVITY_TYPE; 99 import static androidx.exifinterface.media.ExifInterface.TAG_SHARPNESS; 100 import static androidx.exifinterface.media.ExifInterface.TAG_SHUTTER_SPEED_VALUE; 101 import static androidx.exifinterface.media.ExifInterface.TAG_SOFTWARE; 102 import static androidx.exifinterface.media.ExifInterface.TAG_SUBSEC_TIME; 103 import static androidx.exifinterface.media.ExifInterface.TAG_SUBSEC_TIME_DIGITIZED; 104 import static androidx.exifinterface.media.ExifInterface.TAG_SUBSEC_TIME_ORIGINAL; 105 import static androidx.exifinterface.media.ExifInterface.TAG_WHITE_BALANCE; 106 import static androidx.exifinterface.media.ExifInterface.TAG_X_RESOLUTION; 107 import static androidx.exifinterface.media.ExifInterface.TAG_Y_CB_CR_POSITIONING; 108 import static androidx.exifinterface.media.ExifInterface.TAG_Y_RESOLUTION; 109 import static androidx.exifinterface.media.ExifInterface.WHITE_BALANCE_AUTO; 110 import static androidx.exifinterface.media.ExifInterface.WHITE_BALANCE_MANUAL; 111 import static androidx.exifinterface.media.ExifInterface.Y_CB_CR_POSITIONING_CENTERED; 112 113 import android.os.Build; 114 import android.util.Pair; 115 116 import androidx.camera.core.ImageInfo; 117 import androidx.camera.core.ImageProxy; 118 import androidx.camera.core.Logger; 119 import androidx.camera.core.impl.CameraCaptureMetaData; 120 import androidx.camera.core.impl.ImageOutputConfig; 121 import androidx.core.util.Preconditions; 122 import androidx.exifinterface.media.ExifInterface; 123 124 import org.jspecify.annotations.NonNull; 125 import org.jspecify.annotations.Nullable; 126 127 import java.nio.ByteOrder; 128 import java.nio.charset.StandardCharsets; 129 import java.util.Arrays; 130 import java.util.Collections; 131 import java.util.Enumeration; 132 import java.util.HashMap; 133 import java.util.HashSet; 134 import java.util.List; 135 import java.util.Locale; 136 import java.util.Map; 137 import java.util.concurrent.TimeUnit; 138 import java.util.regex.Matcher; 139 import java.util.regex.Pattern; 140 141 /** 142 * This class stores the EXIF header in IFDs according to the JPEG specification. 143 */ 144 // Note: This class is adapted from {@link androidx.exifinterface.media.ExifInterface}, and is 145 // currently expected to be used for writing a subset of Exif values. Support for other mime 146 // types besides JPEG have been removed. Support for thumbnails/strips has been removed along 147 // with many exif tags. If more tags are required, the source code for ExifInterface should be 148 // referenced and can be adapted to this class. 149 public class ExifData { 150 private static final String TAG = "ExifData"; 151 private static final boolean DEBUG = false; 152 153 /** 154 * Enum representing the white balance mode. 155 */ 156 public enum WhiteBalanceMode { 157 /** AWB is turned on. */ 158 AUTO, 159 /** AWB is turned off. */ 160 MANUAL 161 } 162 163 // Names for the data formats for debugging purpose. 164 static final String[] IFD_FORMAT_NAMES = new String[]{ 165 "", "BYTE", "STRING", "USHORT", "ULONG", "URATIONAL", "SBYTE", "UNDEFINED", "SSHORT", 166 "SLONG", "SRATIONAL", "SINGLE", "DOUBLE", "IFD" 167 }; 168 169 /** 170 * Private tags used for pointing the other IFD offsets. 171 * The types of the following tags are int. 172 * See JEITA CP-3451C Section 4.6.3: Exif-specific IFD. 173 * For SubIFD, see Note 1 of Adobe PageMaker® 6.0 TIFF Technical Notes. 174 */ 175 static final String TAG_EXIF_IFD_POINTER = "ExifIFDPointer"; 176 static final String TAG_GPS_INFO_IFD_POINTER = "GPSInfoIFDPointer"; 177 static final String TAG_INTEROPERABILITY_IFD_POINTER = "InteroperabilityIFDPointer"; 178 static final String TAG_SUB_IFD_POINTER = "SubIFDPointer"; 179 180 // Primary image IFD TIFF tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) 181 // This is only a subset of the tags defined in ExifInterface 182 private static final ExifTag[] IFD_TIFF_TAGS = new ExifTag[]{ 183 // For below two, see TIFF 6.0 Spec Section 3: Bilevel Images. 184 new ExifTag(TAG_IMAGE_WIDTH, 256, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 185 new ExifTag(TAG_IMAGE_LENGTH, 257, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 186 new ExifTag(TAG_MAKE, 271, IFD_FORMAT_STRING), 187 new ExifTag(TAG_MODEL, 272, IFD_FORMAT_STRING), 188 new ExifTag(TAG_ORIENTATION, 274, IFD_FORMAT_USHORT), 189 new ExifTag(TAG_X_RESOLUTION, 282, IFD_FORMAT_URATIONAL), 190 new ExifTag(TAG_Y_RESOLUTION, 283, IFD_FORMAT_URATIONAL), 191 new ExifTag(TAG_RESOLUTION_UNIT, 296, IFD_FORMAT_USHORT), 192 new ExifTag(TAG_SOFTWARE, 305, IFD_FORMAT_STRING), 193 new ExifTag(TAG_DATETIME, 306, IFD_FORMAT_STRING), 194 new ExifTag(TAG_Y_CB_CR_POSITIONING, 531, IFD_FORMAT_USHORT), 195 new ExifTag(TAG_SUB_IFD_POINTER, 330, IFD_FORMAT_ULONG), 196 new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG), 197 new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG), 198 }; 199 200 // Primary image IFD Exif Private tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) 201 // This is only a subset of the tags defined in ExifInterface 202 private static final ExifTag[] IFD_EXIF_TAGS = new ExifTag[]{ 203 new ExifTag(TAG_EXPOSURE_TIME, 33434, IFD_FORMAT_URATIONAL), 204 new ExifTag(TAG_F_NUMBER, 33437, IFD_FORMAT_URATIONAL), 205 new ExifTag(TAG_EXPOSURE_PROGRAM, 34850, IFD_FORMAT_USHORT), 206 new ExifTag(TAG_PHOTOGRAPHIC_SENSITIVITY, 34855, IFD_FORMAT_USHORT), 207 new ExifTag(TAG_SENSITIVITY_TYPE, 34864, IFD_FORMAT_USHORT), 208 new ExifTag(TAG_EXIF_VERSION, 36864, IFD_FORMAT_STRING), 209 new ExifTag(TAG_DATETIME_ORIGINAL, 36867, IFD_FORMAT_STRING), 210 new ExifTag(TAG_DATETIME_DIGITIZED, 36868, IFD_FORMAT_STRING), 211 new ExifTag(TAG_COMPONENTS_CONFIGURATION, 37121, IFD_FORMAT_UNDEFINED), 212 new ExifTag(TAG_SHUTTER_SPEED_VALUE, 37377, IFD_FORMAT_SRATIONAL), 213 new ExifTag(TAG_APERTURE_VALUE, 37378, IFD_FORMAT_URATIONAL), 214 new ExifTag(TAG_BRIGHTNESS_VALUE, 37379, IFD_FORMAT_SRATIONAL), 215 new ExifTag(TAG_EXPOSURE_BIAS_VALUE, 37380, IFD_FORMAT_SRATIONAL), 216 new ExifTag(TAG_MAX_APERTURE_VALUE, 37381, IFD_FORMAT_URATIONAL), 217 new ExifTag(TAG_METERING_MODE, 37383, IFD_FORMAT_USHORT), 218 new ExifTag(TAG_LIGHT_SOURCE, 37384, IFD_FORMAT_USHORT), 219 new ExifTag(TAG_FLASH, 37385, IFD_FORMAT_USHORT), 220 new ExifTag(TAG_FOCAL_LENGTH, 37386, IFD_FORMAT_URATIONAL), 221 new ExifTag(TAG_SUBSEC_TIME, 37520, IFD_FORMAT_STRING), 222 new ExifTag(TAG_SUBSEC_TIME_ORIGINAL, 37521, IFD_FORMAT_STRING), 223 new ExifTag(TAG_SUBSEC_TIME_DIGITIZED, 37522, IFD_FORMAT_STRING), 224 new ExifTag(TAG_FLASHPIX_VERSION, 40960, IFD_FORMAT_UNDEFINED), 225 new ExifTag(TAG_COLOR_SPACE, 40961, IFD_FORMAT_USHORT), 226 new ExifTag(TAG_PIXEL_X_DIMENSION, 40962, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 227 new ExifTag(TAG_PIXEL_Y_DIMENSION, 40963, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 228 new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG), 229 new ExifTag(TAG_FOCAL_PLANE_RESOLUTION_UNIT, 41488, IFD_FORMAT_USHORT), 230 new ExifTag(TAG_SENSING_METHOD, 41495, IFD_FORMAT_USHORT), 231 new ExifTag(TAG_FILE_SOURCE, 41728, IFD_FORMAT_UNDEFINED), 232 new ExifTag(TAG_SCENE_TYPE, 41729, IFD_FORMAT_UNDEFINED), 233 new ExifTag(TAG_CUSTOM_RENDERED, 41985, IFD_FORMAT_USHORT), 234 new ExifTag(TAG_EXPOSURE_MODE, 41986, IFD_FORMAT_USHORT), 235 new ExifTag(TAG_WHITE_BALANCE, 41987, IFD_FORMAT_USHORT), 236 new ExifTag(TAG_SCENE_CAPTURE_TYPE, 41990, IFD_FORMAT_USHORT), 237 new ExifTag(TAG_CONTRAST, 41992, IFD_FORMAT_USHORT), 238 new ExifTag(TAG_SATURATION, 41993, IFD_FORMAT_USHORT), 239 new ExifTag(TAG_SHARPNESS, 41994, IFD_FORMAT_USHORT) 240 }; 241 242 // Primary image IFD GPS Info tags (See JEITA CP-3451C Section 4.6.6 Tag Support Levels) 243 // This is only a subset of the tags defined in ExifInterface 244 private static final ExifTag[] IFD_GPS_TAGS = new ExifTag[]{ 245 new ExifTag(TAG_GPS_VERSION_ID, 0, IFD_FORMAT_BYTE), 246 new ExifTag(TAG_GPS_LATITUDE_REF, 1, IFD_FORMAT_STRING), 247 // Allow SRATIONAL to be compatible with apps using wrong format and 248 // even if it is negative, it may be valid latitude / longitude. 249 new ExifTag(TAG_GPS_LATITUDE, 2, IFD_FORMAT_URATIONAL, IFD_FORMAT_SRATIONAL), 250 new ExifTag(TAG_GPS_LONGITUDE_REF, 3, IFD_FORMAT_STRING), 251 new ExifTag(TAG_GPS_LONGITUDE, 4, IFD_FORMAT_URATIONAL, IFD_FORMAT_SRATIONAL), 252 new ExifTag(TAG_GPS_ALTITUDE_REF, 5, IFD_FORMAT_BYTE), 253 new ExifTag(TAG_GPS_ALTITUDE, 6, IFD_FORMAT_URATIONAL), 254 new ExifTag(TAG_GPS_TIMESTAMP, 7, IFD_FORMAT_URATIONAL), 255 new ExifTag(TAG_GPS_SPEED_REF, 12, IFD_FORMAT_STRING), 256 new ExifTag(TAG_GPS_TRACK_REF, 14, IFD_FORMAT_STRING), 257 new ExifTag(TAG_GPS_IMG_DIRECTION_REF, 16, IFD_FORMAT_STRING), 258 new ExifTag(TAG_GPS_DEST_BEARING_REF, 23, IFD_FORMAT_STRING), 259 new ExifTag(TAG_GPS_DEST_DISTANCE_REF, 25, IFD_FORMAT_STRING) 260 }; 261 262 // List of tags for pointing to the other image file directory offset. 263 static final ExifTag[] EXIF_POINTER_TAGS = new ExifTag[]{ 264 new ExifTag(TAG_SUB_IFD_POINTER, 330, IFD_FORMAT_ULONG), 265 new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG), 266 new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG), 267 new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG), 268 }; 269 270 // Primary image IFD Interoperability tag (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) 271 private static final ExifTag[] IFD_INTEROPERABILITY_TAGS = new ExifTag[]{ 272 new ExifTag(TAG_INTEROPERABILITY_INDEX, 1, IFD_FORMAT_STRING) 273 }; 274 275 // List of Exif tag groups 276 static final ExifTag[][] EXIF_TAGS = new ExifTag[][]{ 277 IFD_TIFF_TAGS, IFD_EXIF_TAGS, IFD_GPS_TAGS, IFD_INTEROPERABILITY_TAGS 278 }; 279 280 // Indices for the above tags. Note these must stay in sync with the order of EXIF_TAGS. 281 static final int IFD_TYPE_PRIMARY = 0; 282 static final int IFD_TYPE_EXIF = 1; 283 static final int IFD_TYPE_GPS = 2; 284 static final int IFD_TYPE_INTEROPERABILITY = 3; 285 286 // NOTE: This is a subset of the tags from ExifInterface. Only supports tags in this class. 287 static final HashSet<String> sTagSetForCompatibility = new HashSet<>(Arrays.asList( 288 TAG_F_NUMBER, TAG_EXPOSURE_TIME, TAG_GPS_TIMESTAMP)); 289 290 private static final int MM_IN_MICRONS = 1000; 291 private static final String COMPONENTS_CONFIGURATION_YCBCR = new String(new byte[]{1, 2, 3, 0}, 292 StandardCharsets.UTF_8); 293 294 private final List<Map<String, ExifAttribute>> mAttributes; 295 private final ByteOrder mByteOrder; 296 ExifData(ByteOrder order, List<Map<String, ExifAttribute>> attributes)297 ExifData(ByteOrder order, List<Map<String, ExifAttribute>> attributes) { 298 Preconditions.checkState(attributes.size() == EXIF_TAGS.length, "Malformed attributes " 299 + "list. Number of IFDs mismatch."); 300 mByteOrder = order; 301 mAttributes = attributes; 302 } 303 304 /** 305 * Creates a {@link ExifData} from {@link ImageProxy} and rotation degrees. 306 * 307 * @param rotationDegrees overwrites the rotation degrees in the {@link ImageInfo}. 308 */ create(@onNull ImageProxy imageProxy, @ImageOutputConfig.RotationDegreesValue int rotationDegrees)309 public static @NonNull ExifData create(@NonNull ImageProxy imageProxy, 310 @ImageOutputConfig.RotationDegreesValue int rotationDegrees) { 311 ExifData.Builder builder = ExifData.builderForDevice(); 312 if (imageProxy.getImageInfo() != null) { 313 imageProxy.getImageInfo().populateExifData(builder); 314 } 315 316 // Overwrites the orientation degrees value of the output image because the capture 317 // results might not have correct value when capturing image in YUV_420_888 format. See 318 // b/204375890. 319 builder.setOrientationDegrees(rotationDegrees); 320 321 return builder.setImageWidth(imageProxy.getWidth()) 322 .setImageHeight(imageProxy.getHeight()) 323 .build(); 324 } 325 326 /** 327 * Gets the byte order. 328 */ getByteOrder()329 public @NonNull ByteOrder getByteOrder() { 330 return mByteOrder; 331 } 332 getAttributes(int ifdIndex)333 @NonNull Map<String, ExifAttribute> getAttributes(int ifdIndex) { 334 Preconditions.checkArgumentInRange(ifdIndex, 0, EXIF_TAGS.length, 335 "Invalid IFD index: " + ifdIndex + ". Index should be between [0, EXIF_TAGS" 336 + ".length] "); 337 return mAttributes.get(ifdIndex); 338 } 339 340 /** 341 * Returns the value of the specified tag or {@code null} if there 342 * is no such tag in the image file. 343 * 344 * @param tag the name of the tag. 345 */ getAttribute(@onNull String tag)346 public @Nullable String getAttribute(@NonNull String tag) { 347 ExifAttribute attribute = getExifAttribute(tag); 348 if (attribute != null) { 349 if (!sTagSetForCompatibility.contains(tag)) { 350 return attribute.getStringValue(mByteOrder); 351 } 352 if (tag.equals(TAG_GPS_TIMESTAMP)) { 353 // Convert the rational values to the custom formats for backwards compatibility. 354 if (attribute.format != IFD_FORMAT_URATIONAL 355 && attribute.format != IFD_FORMAT_SRATIONAL) { 356 Logger.w(TAG, 357 "GPS Timestamp format is not rational. format=" + attribute.format); 358 return null; 359 } 360 LongRational[] array = 361 (LongRational[]) attribute.getValue(mByteOrder); 362 if (array == null || array.length != 3) { 363 Logger.w(TAG, "Invalid GPS Timestamp array. array=" + Arrays.toString(array)); 364 return null; 365 } 366 return String.format(Locale.US, "%02d:%02d:%02d", 367 (int) ((float) array[0].getNumerator() / array[0].getDenominator()), 368 (int) ((float) array[1].getNumerator() / array[1].getDenominator()), 369 (int) ((float) array[2].getNumerator() / array[2].getDenominator())); 370 } 371 try { 372 return Double.toString(attribute.getDoubleValue(mByteOrder)); 373 } catch (NumberFormatException e) { 374 return null; 375 } 376 } 377 return null; 378 } 379 380 /** 381 * Returns the EXIF attribute of the specified tag or {@code null} if there is no such tag. 382 * 383 * @param tag the name of the tag. 384 */ 385 @SuppressWarnings("deprecation") getExifAttribute(@onNull String tag)386 private @Nullable ExifAttribute getExifAttribute(@NonNull String tag) { 387 // Maintain compatibility. 388 if (TAG_ISO_SPEED_RATINGS.equals(tag)) { 389 if (DEBUG) { 390 Logger.d(TAG, "getExifAttribute: Replacing TAG_ISO_SPEED_RATINGS with " 391 + "TAG_PHOTOGRAPHIC_SENSITIVITY."); 392 } 393 tag = TAG_PHOTOGRAPHIC_SENSITIVITY; 394 } 395 // Retrieves all tag groups. The value from primary image tag group has a higher priority 396 // than the value from the thumbnail tag group if there are more than one candidates. 397 for (int i = 0; i < EXIF_TAGS.length; ++i) { 398 ExifAttribute value = mAttributes.get(i).get(tag); 399 if (value != null) { 400 return value; 401 } 402 } 403 return null; 404 } 405 406 /** 407 * Generates an empty builder suitable for generating ExifData for JPEG from the current device. 408 */ builderForDevice()409 public static @NonNull Builder builderForDevice() { 410 // Add PRIMARY defaults. EXIF and GPS defaults will be added in build() 411 return new Builder(ByteOrder.BIG_ENDIAN) 412 .setAttribute(TAG_ORIENTATION, String.valueOf(ORIENTATION_NORMAL)) 413 .setAttribute(TAG_X_RESOLUTION, "72/1") 414 .setAttribute(TAG_Y_RESOLUTION, "72/1") 415 .setAttribute(TAG_RESOLUTION_UNIT, String.valueOf(RESOLUTION_UNIT_INCHES)) 416 .setAttribute(TAG_Y_CB_CR_POSITIONING, 417 String.valueOf(Y_CB_CR_POSITIONING_CENTERED)) 418 // Defaults derived from device 419 .setAttribute(TAG_MAKE, Build.MANUFACTURER) 420 .setAttribute(TAG_MODEL, Build.MODEL); 421 } 422 423 /** 424 * Builder for the {@link ExifData} class. 425 */ 426 public static final class Builder { 427 // Pattern to check gps timestamp 428 private static final Pattern GPS_TIMESTAMP_PATTERN = 429 Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2})$"); 430 // Pattern to check date time primary format (e.g. 2020:01:01 00:00:00) 431 private static final Pattern DATETIME_PRIMARY_FORMAT_PATTERN = 432 Pattern.compile("^(\\d{4}):(\\d{2}):(\\d{2})\\s(\\d{2}):(\\d{2}):(\\d{2})$"); 433 // Pattern to check date time secondary format (e.g. 2020-01-01 00:00:00) 434 private static final Pattern DATETIME_SECONDARY_FORMAT_PATTERN = 435 Pattern.compile("^(\\d{4})-(\\d{2})-(\\d{2})\\s(\\d{2}):(\\d{2}):(\\d{2})$"); 436 private static final int DATETIME_VALUE_STRING_LENGTH = 19; 437 438 // Mappings from tag name to tag number and each item represents one IFD tag group. 439 static final List<HashMap<String, ExifTag>> sExifTagMapsForWriting = 440 Collections.list(new Enumeration<HashMap<String, ExifTag>>() { 441 int mIfdIndex = 0; 442 443 @Override 444 public boolean hasMoreElements() { 445 return mIfdIndex < EXIF_TAGS.length; 446 } 447 448 @Override 449 public HashMap<String, ExifTag> nextElement() { 450 // Build up the hash tables to look up Exif tags for writing Exif tags. 451 HashMap<String, ExifTag> map = new HashMap<>(); 452 for (ExifTag tag : EXIF_TAGS[mIfdIndex]) { 453 map.put(tag.name, tag); 454 } 455 mIfdIndex++; 456 return map; 457 } 458 }); 459 460 final List<Map<String, ExifAttribute>> mAttributes = Collections.list( 461 new Enumeration<Map<String, ExifAttribute>>() { 462 int mIfdIndex = 0; 463 464 @Override 465 public boolean hasMoreElements() { 466 return mIfdIndex < EXIF_TAGS.length; 467 } 468 469 @Override 470 public Map<String, ExifAttribute> nextElement() { 471 mIfdIndex++; 472 return new HashMap<>(); 473 } 474 }); 475 private final ByteOrder mByteOrder; 476 Builder(@onNull ByteOrder byteOrder)477 Builder(@NonNull ByteOrder byteOrder) { 478 mByteOrder = byteOrder; 479 } 480 481 /** 482 * Sets the width of the image. 483 * 484 * @param width the width of the image. 485 */ setImageWidth(int width)486 public @NonNull Builder setImageWidth(int width) { 487 return setAttribute(TAG_IMAGE_WIDTH, String.valueOf(width)); 488 } 489 490 /** 491 * Sets the height of the image. 492 * 493 * @param height the height of the image. 494 */ setImageHeight(int height)495 public @NonNull Builder setImageHeight(int height) { 496 return setAttribute(TAG_IMAGE_LENGTH, String.valueOf(height)); 497 } 498 499 /** 500 * Sets the orientation of the image in degrees. 501 * 502 * @param orientationDegrees the orientation in degrees. Can be one of (0, 90, 180, 270) 503 */ setOrientationDegrees(int orientationDegrees)504 public @NonNull Builder setOrientationDegrees(int orientationDegrees) { 505 int orientationEnum; 506 switch (orientationDegrees) { 507 case 0: 508 orientationEnum = ExifInterface.ORIENTATION_NORMAL; 509 break; 510 case 90: 511 orientationEnum = ExifInterface.ORIENTATION_ROTATE_90; 512 break; 513 case 180: 514 orientationEnum = ExifInterface.ORIENTATION_ROTATE_180; 515 break; 516 case 270: 517 orientationEnum = ExifInterface.ORIENTATION_ROTATE_270; 518 break; 519 default: 520 Logger.w(TAG, 521 "Unexpected orientation value: " + orientationDegrees 522 + ". Must be one of 0, 90, 180, 270."); 523 orientationEnum = ExifInterface.ORIENTATION_UNDEFINED; 524 break; 525 } 526 return setAttribute(TAG_ORIENTATION, String.valueOf(orientationEnum)); 527 } 528 529 /** 530 * Sets the flash information from 531 * {@link androidx.camera.core.impl.CameraCaptureMetaData.FlashState}. 532 * 533 * @param flashState the state of the flash at capture time. 534 */ setFlashState( CameraCaptureMetaData.@onNull FlashState flashState)535 public @NonNull Builder setFlashState( 536 CameraCaptureMetaData.@NonNull FlashState flashState) { 537 if (flashState == CameraCaptureMetaData.FlashState.UNKNOWN) { 538 // Cannot set flash state information 539 return this; 540 } 541 542 short value; 543 switch (flashState) { 544 case READY: 545 value = 0; 546 break; 547 case NONE: 548 value = FLAG_FLASH_NO_FLASH_FUNCTION; 549 break; 550 case FIRED: 551 value = FLAG_FLASH_FIRED; 552 break; 553 default: 554 Logger.w(TAG, "Unknown flash state: " + flashState); 555 return this; 556 } 557 558 if ((value & FLAG_FLASH_FIRED) == FLAG_FLASH_FIRED) { 559 // Set light source to flash 560 setAttribute(TAG_LIGHT_SOURCE, String.valueOf(LIGHT_SOURCE_FLASH)); 561 } 562 563 564 return setAttribute(TAG_FLASH, String.valueOf(value)); 565 } 566 567 /** 568 * Sets the amount of time the sensor was exposed for, in nanoseconds. 569 * 570 * @param exposureTimeNs The exposure time in nanoseconds. 571 */ setExposureTimeNanos(long exposureTimeNs)572 public @NonNull Builder setExposureTimeNanos(long exposureTimeNs) { 573 return setAttribute(TAG_EXPOSURE_TIME, 574 String.valueOf(exposureTimeNs / (double) TimeUnit.SECONDS.toNanos(1))); 575 } 576 577 /** 578 * Sets the lens f-number. 579 * 580 * <p>The lens f-number has precision 1.xx, for example, 1.80. 581 * 582 * @param fNumber The f-number. 583 */ setLensFNumber(float fNumber)584 public @NonNull Builder setLensFNumber(float fNumber) { 585 return setAttribute(TAG_F_NUMBER, String.valueOf(fNumber)); 586 } 587 588 /** 589 * Sets the ISO. 590 * 591 * @param iso the standard ISO sensitivity value, as defined in ISO 12232:2006. 592 */ setIso(int iso)593 public @NonNull Builder setIso(int iso) { 594 return setAttribute(TAG_SENSITIVITY_TYPE, String.valueOf(SENSITIVITY_TYPE_ISO_SPEED)) 595 .setAttribute(TAG_PHOTOGRAPHIC_SENSITIVITY, String.valueOf(Math.min(65535, 596 iso))); 597 } 598 599 /** 600 * Sets lens focal length, in millimeters. 601 * 602 * @param focalLength The lens focal length in millimeters. 603 */ setFocalLength(float focalLength)604 public @NonNull Builder setFocalLength(float focalLength) { 605 LongRational focalLengthRational = 606 new LongRational((long) (focalLength * MM_IN_MICRONS), MM_IN_MICRONS); 607 return setAttribute(TAG_FOCAL_LENGTH, focalLengthRational.toString()); 608 } 609 610 /** 611 * Sets the white balance mode. 612 * 613 * @param whiteBalanceMode The white balance mode. One of {@link WhiteBalanceMode#AUTO} 614 * or {@link WhiteBalanceMode#MANUAL}. 615 */ setWhiteBalanceMode(@onNull WhiteBalanceMode whiteBalanceMode)616 public @NonNull Builder setWhiteBalanceMode(@NonNull WhiteBalanceMode whiteBalanceMode) { 617 String wbString = null; 618 switch (whiteBalanceMode) { 619 case AUTO: 620 wbString = String.valueOf(WHITE_BALANCE_AUTO); 621 break; 622 case MANUAL: 623 wbString = String.valueOf(WHITE_BALANCE_MANUAL); 624 break; 625 } 626 return setAttribute(TAG_WHITE_BALANCE, wbString); 627 } 628 629 /** 630 * Sets the value of the specified tag. 631 * 632 * @param tag the name of the tag. 633 * @param value the value of the tag. 634 */ setAttribute(@onNull String tag, @NonNull String value)635 public @NonNull Builder setAttribute(@NonNull String tag, @NonNull String value) { 636 setAttributeInternal(tag, value, mAttributes); 637 return this; 638 } 639 640 /** 641 * Removes the attribute with the given tag. 642 * 643 * @param tag the name of the tag. 644 */ removeAttribute(@onNull String tag)645 public @NonNull Builder removeAttribute(@NonNull String tag) { 646 setAttributeInternal(tag, null, mAttributes); 647 return this; 648 } 649 setAttributeIfMissing(@onNull String tag, @NonNull String value, @NonNull List<Map<String, ExifAttribute>> attributes)650 private void setAttributeIfMissing(@NonNull String tag, @NonNull String value, 651 @NonNull List<Map<String, ExifAttribute>> attributes) { 652 for (Map<String, ExifAttribute> attrs : attributes) { 653 if (attrs.containsKey(tag)) { 654 // Attr already exists 655 return; 656 } 657 } 658 659 // Add missing attribute. 660 setAttributeInternal(tag, value, attributes); 661 } 662 663 @SuppressWarnings("deprecation") 664 // Allows null values to remove attributes setAttributeInternal(@onNull String tag, @Nullable String value, @NonNull List<Map<String, ExifAttribute>> attributes)665 private void setAttributeInternal(@NonNull String tag, @Nullable String value, 666 @NonNull List<Map<String, ExifAttribute>> attributes) { 667 // Validate and convert if necessary. 668 if (TAG_DATETIME.equals(tag) || TAG_DATETIME_ORIGINAL.equals(tag) 669 || TAG_DATETIME_DIGITIZED.equals(tag)) { 670 if (value != null) { 671 boolean isPrimaryFormat = DATETIME_PRIMARY_FORMAT_PATTERN.matcher(value).find(); 672 boolean isSecondaryFormat = DATETIME_SECONDARY_FORMAT_PATTERN.matcher( 673 value).find(); 674 // Validate 675 if (value.length() != DATETIME_VALUE_STRING_LENGTH 676 || (!isPrimaryFormat && !isSecondaryFormat)) { 677 Logger.w(TAG, "Invalid value for " + tag + " : " + value); 678 return; 679 } 680 // If datetime value has secondary format (e.g. 2020-01-01 00:00:00), convert it 681 // to primary format (e.g. 2020:01:01 00:00:00) since it is the format in the 682 // official documentation. 683 // See JEITA CP-3451C Section 4.6.4. D. Other Tags, DateTime 684 if (isSecondaryFormat) { 685 // Replace "-" with ":" to match the primary format. 686 value = value.replaceAll("-", ":"); 687 } 688 } 689 } 690 // Maintain compatibility. 691 if (TAG_ISO_SPEED_RATINGS.equals(tag)) { 692 if (DEBUG) { 693 Logger.d(TAG, "setAttribute: Replacing TAG_ISO_SPEED_RATINGS with " 694 + "TAG_PHOTOGRAPHIC_SENSITIVITY."); 695 } 696 tag = TAG_PHOTOGRAPHIC_SENSITIVITY; 697 } 698 // Convert the given value to rational values for backwards compatibility. 699 if (value != null && sTagSetForCompatibility.contains(tag)) { 700 if (tag.equals(TAG_GPS_TIMESTAMP)) { 701 Matcher m = GPS_TIMESTAMP_PATTERN.matcher(value); 702 if (!m.find()) { 703 Logger.w(TAG, "Invalid value for " + tag + " : " + value); 704 return; 705 } 706 value = Integer.parseInt(Preconditions.checkNotNull(m.group(1))) + "/1," 707 + Integer.parseInt(Preconditions.checkNotNull(m.group(2))) + "/1," 708 + Integer.parseInt(Preconditions.checkNotNull(m.group(3))) + "/1"; 709 } else { 710 try { 711 double doubleValue = Double.parseDouble(value); 712 value = new LongRational(doubleValue).toString(); 713 } catch (NumberFormatException e) { 714 Logger.w(TAG, "Invalid value for " + tag + " : " + value, e); 715 return; 716 } 717 } 718 } 719 720 for (int i = 0; i < EXIF_TAGS.length; ++i) { 721 final ExifTag exifTag = sExifTagMapsForWriting.get(i).get(tag); 722 if (exifTag != null) { 723 if (value == null) { 724 attributes.get(i).remove(tag); 725 continue; 726 } 727 Pair<Integer, Integer> guess = guessDataFormat(value); 728 int dataFormat; 729 if (exifTag.primaryFormat == guess.first 730 || exifTag.primaryFormat == guess.second) { 731 dataFormat = exifTag.primaryFormat; 732 } else if (exifTag.secondaryFormat != -1 && ( 733 exifTag.secondaryFormat == guess.first 734 || exifTag.secondaryFormat == guess.second)) { 735 dataFormat = exifTag.secondaryFormat; 736 } else if (exifTag.primaryFormat == IFD_FORMAT_BYTE 737 || exifTag.primaryFormat == IFD_FORMAT_UNDEFINED 738 || exifTag.primaryFormat == IFD_FORMAT_STRING) { 739 dataFormat = exifTag.primaryFormat; 740 } else { 741 if (DEBUG) { 742 Logger.d(TAG, "Given tag (" + tag 743 + ") value didn't match with one of expected " 744 + "formats: " + IFD_FORMAT_NAMES[exifTag.primaryFormat] 745 + (exifTag.secondaryFormat == -1 ? "" : ", " 746 + IFD_FORMAT_NAMES[exifTag.secondaryFormat]) + " (guess: " 747 + IFD_FORMAT_NAMES[guess.first] + (guess.second == -1 ? "" 748 : ", " 749 + IFD_FORMAT_NAMES[guess.second]) + ")"); 750 } 751 continue; 752 } 753 switch (dataFormat) { 754 case IFD_FORMAT_BYTE: { 755 attributes.get(i).put(tag, ExifAttribute.createByte(value)); 756 break; 757 } 758 case IFD_FORMAT_UNDEFINED: 759 case IFD_FORMAT_STRING: { 760 attributes.get(i).put(tag, ExifAttribute.createString(value)); 761 break; 762 } 763 case IFD_FORMAT_USHORT: { 764 final String[] values = value.split(",", -1); 765 final int[] intArray = new int[values.length]; 766 for (int j = 0; j < values.length; ++j) { 767 intArray[j] = Integer.parseInt(values[j]); 768 } 769 attributes.get(i).put(tag, 770 ExifAttribute.createUShort(intArray, mByteOrder)); 771 break; 772 } 773 case IFD_FORMAT_SLONG: { 774 final String[] values = value.split(",", -1); 775 final int[] intArray = new int[values.length]; 776 for (int j = 0; j < values.length; ++j) { 777 intArray[j] = Integer.parseInt(values[j]); 778 } 779 attributes.get(i).put(tag, 780 ExifAttribute.createSLong(intArray, mByteOrder)); 781 break; 782 } 783 case IFD_FORMAT_ULONG: { 784 final String[] values = value.split(",", -1); 785 final long[] longArray = new long[values.length]; 786 for (int j = 0; j < values.length; ++j) { 787 longArray[j] = Long.parseLong(values[j]); 788 } 789 attributes.get(i).put(tag, 790 ExifAttribute.createULong(longArray, mByteOrder)); 791 break; 792 } 793 case IFD_FORMAT_URATIONAL: { 794 final String[] values = value.split(",", -1); 795 final LongRational[] rationalArray = new LongRational[values.length]; 796 for (int j = 0; j < values.length; ++j) { 797 final String[] numbers = values[j].split("/", -1); 798 rationalArray[j] = new LongRational( 799 (long) Double.parseDouble(numbers[0]), 800 (long) Double.parseDouble(numbers[1])); 801 } 802 attributes.get(i).put(tag, 803 ExifAttribute.createURational(rationalArray, mByteOrder)); 804 break; 805 } 806 case IFD_FORMAT_SRATIONAL: { 807 final String[] values = value.split(",", -1); 808 final LongRational[] rationalArray = new LongRational[values.length]; 809 for (int j = 0; j < values.length; ++j) { 810 final String[] numbers = values[j].split("/", -1); 811 rationalArray[j] = new LongRational( 812 (long) Double.parseDouble(numbers[0]), 813 (long) Double.parseDouble(numbers[1])); 814 } 815 attributes.get(i).put(tag, 816 ExifAttribute.createSRational(rationalArray, mByteOrder)); 817 break; 818 } 819 case IFD_FORMAT_DOUBLE: { 820 final String[] values = value.split(",", -1); 821 final double[] doubleArray = new double[values.length]; 822 for (int j = 0; j < values.length; ++j) { 823 doubleArray[j] = Double.parseDouble(values[j]); 824 } 825 attributes.get(i).put(tag, 826 ExifAttribute.createDouble(doubleArray, mByteOrder)); 827 break; 828 } 829 default: 830 if (DEBUG) { 831 Logger.d(TAG, 832 "Data format isn't one of expected formats: " + dataFormat); 833 } 834 } 835 } 836 } 837 } 838 839 /** 840 * Builds an {@link ExifData} from the current state of the builder. 841 */ build()842 public @NonNull ExifData build() { 843 // Create a read-only copy of all attributes. This needs to be a deep copy since 844 // build() can be called multiple times. We'll remove null values as well. 845 List<Map<String, ExifAttribute>> attributes = Collections.list( 846 new Enumeration<Map<String, ExifAttribute>>() { 847 final Enumeration<Map<String, ExifAttribute>> mMapEnumeration = 848 Collections.enumeration(mAttributes); 849 850 @Override 851 public boolean hasMoreElements() { 852 return mMapEnumeration.hasMoreElements(); 853 } 854 855 @Override 856 public Map<String, ExifAttribute> nextElement() { 857 return new HashMap<>(mMapEnumeration.nextElement()); 858 } 859 }); 860 // Add EXIF defaults if needed 861 if (!attributes.get(IFD_TYPE_EXIF).isEmpty()) { 862 setAttributeIfMissing(TAG_EXPOSURE_PROGRAM, 863 String.valueOf(EXPOSURE_PROGRAM_NOT_DEFINED), attributes); 864 setAttributeIfMissing(TAG_EXIF_VERSION, "0230", attributes); 865 // Default is for YCbCr components 866 setAttributeIfMissing(TAG_COMPONENTS_CONFIGURATION, COMPONENTS_CONFIGURATION_YCBCR, 867 attributes); 868 setAttributeIfMissing(TAG_METERING_MODE, String.valueOf(METERING_MODE_UNKNOWN), 869 attributes); 870 setAttributeIfMissing(TAG_LIGHT_SOURCE, String.valueOf(LIGHT_SOURCE_UNKNOWN), 871 attributes); 872 setAttributeIfMissing(TAG_FLASHPIX_VERSION, "0100", attributes); 873 setAttributeIfMissing(TAG_FOCAL_PLANE_RESOLUTION_UNIT, 874 String.valueOf(RESOLUTION_UNIT_INCHES), attributes); 875 setAttributeIfMissing(TAG_FILE_SOURCE, String.valueOf(FILE_SOURCE_DSC), attributes); 876 setAttributeIfMissing(TAG_SCENE_TYPE, 877 String.valueOf(SCENE_TYPE_DIRECTLY_PHOTOGRAPHED), attributes); 878 setAttributeIfMissing(TAG_CUSTOM_RENDERED, String.valueOf(RENDERED_PROCESS_NORMAL), 879 attributes); 880 setAttributeIfMissing(TAG_SCENE_CAPTURE_TYPE, 881 String.valueOf(SCENE_CAPTURE_TYPE_STANDARD), attributes); 882 setAttributeIfMissing(TAG_CONTRAST, String.valueOf(CONTRAST_NORMAL), attributes); 883 setAttributeIfMissing(TAG_SATURATION, String.valueOf(SATURATION_NORMAL), 884 attributes); 885 setAttributeIfMissing(TAG_SHARPNESS, String.valueOf(SHARPNESS_NORMAL), attributes); 886 } 887 // Add GPS defaults if needed 888 if (!attributes.get(IFD_TYPE_GPS).isEmpty()) { 889 setAttributeIfMissing(TAG_GPS_VERSION_ID, "2300", attributes); 890 setAttributeIfMissing(TAG_GPS_SPEED_REF, GPS_SPEED_KILOMETERS_PER_HOUR, attributes); 891 setAttributeIfMissing(TAG_GPS_TRACK_REF, GPS_DIRECTION_TRUE, attributes); 892 setAttributeIfMissing(TAG_GPS_IMG_DIRECTION_REF, GPS_DIRECTION_TRUE, attributes); 893 setAttributeIfMissing(TAG_GPS_DEST_BEARING_REF, GPS_DIRECTION_TRUE, attributes); 894 setAttributeIfMissing(TAG_GPS_DEST_DISTANCE_REF, GPS_DISTANCE_KILOMETERS, 895 attributes); 896 } 897 return new ExifData(mByteOrder, attributes); 898 } 899 900 /** 901 * Determines the data format of EXIF entry value. 902 * 903 * @param entryValue The value to be determined. 904 * @return Returns two data formats guessed as a pair in integer. If there is no two 905 * candidate 906 * data formats for the given entry value, returns {@code -1} in the second of the pair. 907 */ guessDataFormat(String entryValue)908 private static Pair<Integer, Integer> guessDataFormat(String entryValue) { 909 // See TIFF 6.0 Section 2, "Image File Directory". 910 // Take the first component if there are more than one component. 911 if (entryValue.contains(",")) { 912 String[] entryValues = entryValue.split(",", -1); 913 Pair<Integer, Integer> dataFormat = guessDataFormat(entryValues[0]); 914 if (dataFormat.first == IFD_FORMAT_STRING) { 915 return dataFormat; 916 } 917 for (int i = 1; i < entryValues.length; ++i) { 918 final Pair<Integer, Integer> guessDataFormat = guessDataFormat(entryValues[i]); 919 int first = -1, second = -1; 920 if (guessDataFormat.first.equals(dataFormat.first) 921 || guessDataFormat.second.equals(dataFormat.first)) { 922 first = dataFormat.first; 923 } 924 if (dataFormat.second != -1 && (guessDataFormat.first.equals(dataFormat.second) 925 || guessDataFormat.second.equals(dataFormat.second))) { 926 second = dataFormat.second; 927 } 928 if (first == -1 && second == -1) { 929 return new Pair<>(IFD_FORMAT_STRING, -1); 930 } 931 if (first == -1) { 932 dataFormat = new Pair<>(second, -1); 933 continue; 934 } 935 if (second == -1) { 936 dataFormat = new Pair<>(first, -1); 937 } 938 } 939 return dataFormat; 940 } 941 942 if (entryValue.contains("/")) { 943 String[] rationalNumber = entryValue.split("/", -1); 944 if (rationalNumber.length == 2) { 945 try { 946 long numerator = (long) Double.parseDouble(rationalNumber[0]); 947 long denominator = (long) Double.parseDouble(rationalNumber[1]); 948 if (numerator < 0L || denominator < 0L) { 949 return new Pair<>(IFD_FORMAT_SRATIONAL, -1); 950 } 951 if (numerator > Integer.MAX_VALUE || denominator > Integer.MAX_VALUE) { 952 return new Pair<>(IFD_FORMAT_URATIONAL, -1); 953 } 954 return new Pair<>(IFD_FORMAT_SRATIONAL, IFD_FORMAT_URATIONAL); 955 } catch (NumberFormatException e) { 956 // Ignored 957 } 958 } 959 return new Pair<>(IFD_FORMAT_STRING, -1); 960 } 961 try { 962 long longValue = Long.parseLong(entryValue); 963 if (longValue >= 0 && longValue <= 65535) { 964 return new Pair<>(IFD_FORMAT_USHORT, IFD_FORMAT_ULONG); 965 } 966 if (longValue < 0) { 967 return new Pair<>(IFD_FORMAT_SLONG, -1); 968 } 969 return new Pair<>(IFD_FORMAT_ULONG, -1); 970 } catch (NumberFormatException e) { 971 // Ignored 972 } 973 try { 974 Double.parseDouble(entryValue); 975 return new Pair<>(IFD_FORMAT_DOUBLE, -1); 976 } catch (NumberFormatException e) { 977 // Ignored 978 } 979 return new Pair<>(IFD_FORMAT_STRING, -1); 980 } 981 } 982 } 983