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 androidx.camera.core.Logger; 20 21 import org.jspecify.annotations.NonNull; 22 import org.jspecify.annotations.Nullable; 23 24 import java.io.IOException; 25 import java.nio.ByteBuffer; 26 import java.nio.ByteOrder; 27 import java.nio.charset.Charset; 28 import java.nio.charset.StandardCharsets; 29 30 /** 31 * A class for indicating EXIF attribute. 32 * 33 * This class was pulled from the {@link androidx.exifinterface.media.ExifInterface} class. 34 */ 35 final class ExifAttribute { 36 private static final String TAG = "ExifAttribute"; 37 public static final long BYTES_OFFSET_UNKNOWN = -1; 38 39 // See JPEG File Interchange Format Version 1.02. 40 // The following values are defined for handling JPEG streams. In this implementation, we are 41 // not only getting information from EXIF but also from some JPEG special segments such as 42 // MARKER_COM for user comment and MARKER_SOFx for image width and height. 43 @SuppressWarnings("WeakerAccess") /* synthetic access */ 44 static final Charset ASCII = StandardCharsets.US_ASCII; 45 46 // Formats for the value in IFD entry (See TIFF 6.0 Section 2, "Image File Directory".) 47 static final int IFD_FORMAT_BYTE = 1; 48 static final int IFD_FORMAT_STRING = 2; 49 static final int IFD_FORMAT_USHORT = 3; 50 static final int IFD_FORMAT_ULONG = 4; 51 static final int IFD_FORMAT_URATIONAL = 5; 52 static final int IFD_FORMAT_SBYTE = 6; 53 static final int IFD_FORMAT_UNDEFINED = 7; 54 static final int IFD_FORMAT_SSHORT = 8; 55 static final int IFD_FORMAT_SLONG = 9; 56 static final int IFD_FORMAT_SRATIONAL = 10; 57 static final int IFD_FORMAT_SINGLE = 11; 58 static final int IFD_FORMAT_DOUBLE = 12; 59 // Names for the data formats for debugging purpose. 60 static final String[] IFD_FORMAT_NAMES = new String[] { 61 "", "BYTE", "STRING", "USHORT", "ULONG", "URATIONAL", "SBYTE", "UNDEFINED", "SSHORT", 62 "SLONG", "SRATIONAL", "SINGLE", "DOUBLE", "IFD" 63 }; 64 // Sizes of the components of each IFD value format 65 static final int[] IFD_FORMAT_BYTES_PER_FORMAT = new int[] { 66 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 1 67 }; 68 69 @SuppressWarnings("WeakerAccess") /* synthetic access */ 70 static final byte[] EXIF_ASCII_PREFIX = new byte[] { 71 0x41, 0x53, 0x43, 0x49, 0x49, 0x0, 0x0, 0x0 72 }; 73 74 public final int format; 75 public final int numberOfComponents; 76 public final long bytesOffset; 77 public final byte[] bytes; 78 79 @SuppressWarnings("WeakerAccess") /* synthetic access */ ExifAttribute(int format, int numberOfComponents, byte[] bytes)80 ExifAttribute(int format, int numberOfComponents, byte[] bytes) { 81 this(format, numberOfComponents, BYTES_OFFSET_UNKNOWN, bytes); 82 } 83 84 @SuppressWarnings("WeakerAccess") /* synthetic access */ ExifAttribute(int format, int numberOfComponents, long bytesOffset, byte[] bytes)85 ExifAttribute(int format, int numberOfComponents, long bytesOffset, byte[] bytes) { 86 this.format = format; 87 this.numberOfComponents = numberOfComponents; 88 this.bytesOffset = bytesOffset; 89 this.bytes = bytes; 90 } 91 createUShort(int @NonNull [] values, @NonNull ByteOrder byteOrder)92 public static @NonNull ExifAttribute createUShort(int @NonNull [] values, 93 @NonNull ByteOrder byteOrder) { 94 final ByteBuffer buffer = ByteBuffer.wrap( 95 new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_USHORT] * values.length]); 96 buffer.order(byteOrder); 97 for (int value : values) { 98 buffer.putShort((short) value); 99 } 100 return new ExifAttribute(IFD_FORMAT_USHORT, values.length, buffer.array()); 101 } 102 createUShort(int value, @NonNull ByteOrder byteOrder)103 public static @NonNull ExifAttribute createUShort(int value, @NonNull ByteOrder byteOrder) { 104 return createUShort(new int[] {value}, byteOrder); 105 } 106 createULong(long @NonNull [] values, @NonNull ByteOrder byteOrder)107 public static @NonNull ExifAttribute createULong(long @NonNull [] values, 108 @NonNull ByteOrder byteOrder) { 109 final ByteBuffer buffer = ByteBuffer.wrap( 110 new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_ULONG] * values.length]); 111 buffer.order(byteOrder); 112 for (long value : values) { 113 buffer.putInt((int) value); 114 } 115 return new ExifAttribute(IFD_FORMAT_ULONG, values.length, buffer.array()); 116 } 117 createULong(long value, @NonNull ByteOrder byteOrder)118 public static @NonNull ExifAttribute createULong(long value, @NonNull ByteOrder byteOrder) { 119 return createULong(new long[] {value}, byteOrder); 120 } 121 createSLong(int @NonNull [] values, @NonNull ByteOrder byteOrder)122 public static @NonNull ExifAttribute createSLong(int @NonNull [] values, 123 @NonNull ByteOrder byteOrder) { 124 final ByteBuffer buffer = ByteBuffer.wrap( 125 new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_SLONG] * values.length]); 126 buffer.order(byteOrder); 127 for (int value : values) { 128 buffer.putInt(value); 129 } 130 return new ExifAttribute(IFD_FORMAT_SLONG, values.length, buffer.array()); 131 } 132 createSLong(int value, @NonNull ByteOrder byteOrder)133 public static @NonNull ExifAttribute createSLong(int value, @NonNull ByteOrder byteOrder) { 134 return createSLong(new int[] {value}, byteOrder); 135 } 136 createByte(@onNull String value)137 public static @NonNull ExifAttribute createByte(@NonNull String value) { 138 // Exception for GPSAltitudeRef tag 139 if (value.length() == 1 && value.charAt(0) >= '0' && value.charAt(0) <= '1') { 140 final byte[] bytes = new byte[] { (byte) (value.charAt(0) - '0') }; 141 return new ExifAttribute(IFD_FORMAT_BYTE, bytes.length, bytes); 142 } 143 final byte[] ascii = value.getBytes(ASCII); 144 return new ExifAttribute(IFD_FORMAT_BYTE, ascii.length, ascii); 145 } 146 createString(@onNull String value)147 public static @NonNull ExifAttribute createString(@NonNull String value) { 148 final byte[] ascii = (value + '\0').getBytes(ASCII); 149 return new ExifAttribute(IFD_FORMAT_STRING, ascii.length, ascii); 150 } 151 createURational(LongRational @onNull [] values, @NonNull ByteOrder byteOrder)152 public static @NonNull ExifAttribute createURational(LongRational @NonNull [] values, 153 @NonNull ByteOrder byteOrder) { 154 final ByteBuffer buffer = ByteBuffer.wrap( 155 new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_URATIONAL] * values.length]); 156 buffer.order(byteOrder); 157 for (LongRational value : values) { 158 buffer.putInt((int) value.getNumerator()); 159 buffer.putInt((int) value.getDenominator()); 160 } 161 return new ExifAttribute(IFD_FORMAT_URATIONAL, values.length, buffer.array()); 162 } 163 createURational(@onNull LongRational value, @NonNull ByteOrder byteOrder)164 public static @NonNull ExifAttribute createURational(@NonNull LongRational value, 165 @NonNull ByteOrder byteOrder) { 166 return createURational(new LongRational[] {value}, byteOrder); 167 } 168 createSRational(LongRational @onNull [] values, @NonNull ByteOrder byteOrder)169 public static @NonNull ExifAttribute createSRational(LongRational @NonNull [] values, 170 @NonNull ByteOrder byteOrder) { 171 final ByteBuffer buffer = ByteBuffer.wrap( 172 new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_SRATIONAL] * values.length]); 173 buffer.order(byteOrder); 174 for (LongRational value : values) { 175 buffer.putInt((int) value.getNumerator()); 176 buffer.putInt((int) value.getDenominator()); 177 } 178 return new ExifAttribute(IFD_FORMAT_SRATIONAL, values.length, buffer.array()); 179 } 180 createSRational(@onNull LongRational value, @NonNull ByteOrder byteOrder)181 public static @NonNull ExifAttribute createSRational(@NonNull LongRational value, 182 @NonNull ByteOrder byteOrder) { 183 return createSRational(new LongRational[] {value}, byteOrder); 184 } 185 createDouble(double @NonNull [] values, @NonNull ByteOrder byteOrder)186 public static @NonNull ExifAttribute createDouble(double @NonNull [] values, 187 @NonNull ByteOrder byteOrder) { 188 final ByteBuffer buffer = ByteBuffer.wrap( 189 new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_DOUBLE] * values.length]); 190 buffer.order(byteOrder); 191 for (double value : values) { 192 buffer.putDouble(value); 193 } 194 return new ExifAttribute(IFD_FORMAT_DOUBLE, values.length, buffer.array()); 195 } 196 createDouble(double value, @NonNull ByteOrder byteOrder)197 public static @NonNull ExifAttribute createDouble(double value, @NonNull ByteOrder byteOrder) { 198 return createDouble(new double[] {value}, byteOrder); 199 } 200 201 @Override toString()202 public String toString() { 203 return "(" + IFD_FORMAT_NAMES[format] + ", data length:" + bytes.length + ")"; 204 } 205 206 @SuppressWarnings("WeakerAccess") /* synthetic access */ getValue(ByteOrder byteOrder)207 Object getValue(ByteOrder byteOrder) { 208 ByteOrderedDataInputStream inputStream = null; 209 try { 210 inputStream = new ByteOrderedDataInputStream(bytes); 211 inputStream.setByteOrder(byteOrder); 212 switch (format) { 213 case IFD_FORMAT_BYTE: 214 case IFD_FORMAT_SBYTE: { 215 // Exception for GPSAltitudeRef tag 216 if (bytes.length == 1 && bytes[0] >= 0 && bytes[0] <= 1) { 217 return new String(new char[] { (char) (bytes[0] + '0') }); 218 } 219 return new String(bytes, ASCII); 220 } 221 case IFD_FORMAT_UNDEFINED: 222 case IFD_FORMAT_STRING: { 223 int index = 0; 224 if (numberOfComponents >= EXIF_ASCII_PREFIX.length) { 225 boolean same = true; 226 for (int i = 0; i < EXIF_ASCII_PREFIX.length; ++i) { 227 if (bytes[i] != EXIF_ASCII_PREFIX[i]) { 228 same = false; 229 break; 230 } 231 } 232 if (same) { 233 index = EXIF_ASCII_PREFIX.length; 234 } 235 } 236 237 StringBuilder stringBuilder = new StringBuilder(); 238 while (index < numberOfComponents) { 239 int ch = bytes[index]; 240 if (ch == 0) { 241 break; 242 } 243 if (ch >= 32) { 244 stringBuilder.append((char) ch); 245 } else { 246 stringBuilder.append('?'); 247 } 248 ++index; 249 } 250 return stringBuilder.toString(); 251 } 252 case IFD_FORMAT_USHORT: { 253 final int[] values = new int[numberOfComponents]; 254 for (int i = 0; i < numberOfComponents; ++i) { 255 values[i] = inputStream.readUnsignedShort(); 256 } 257 return values; 258 } 259 case IFD_FORMAT_ULONG: { 260 final long[] values = new long[numberOfComponents]; 261 for (int i = 0; i < numberOfComponents; ++i) { 262 values[i] = inputStream.readUnsignedInt(); 263 } 264 return values; 265 } 266 case IFD_FORMAT_URATIONAL: { 267 final LongRational[] values = new LongRational[numberOfComponents]; 268 for (int i = 0; i < numberOfComponents; ++i) { 269 final long numerator = inputStream.readUnsignedInt(); 270 final long denominator = inputStream.readUnsignedInt(); 271 values[i] = new LongRational(numerator, denominator); 272 } 273 return values; 274 } 275 case IFD_FORMAT_SSHORT: { 276 final int[] values = new int[numberOfComponents]; 277 for (int i = 0; i < numberOfComponents; ++i) { 278 values[i] = inputStream.readShort(); 279 } 280 return values; 281 } 282 case IFD_FORMAT_SLONG: { 283 final int[] values = new int[numberOfComponents]; 284 for (int i = 0; i < numberOfComponents; ++i) { 285 values[i] = inputStream.readInt(); 286 } 287 return values; 288 } 289 case IFD_FORMAT_SRATIONAL: { 290 final LongRational[] values = new LongRational[numberOfComponents]; 291 for (int i = 0; i < numberOfComponents; ++i) { 292 final long numerator = inputStream.readInt(); 293 final long denominator = inputStream.readInt(); 294 values[i] = new LongRational(numerator, denominator); 295 } 296 return values; 297 } 298 case IFD_FORMAT_SINGLE: { 299 final double[] values = new double[numberOfComponents]; 300 for (int i = 0; i < numberOfComponents; ++i) { 301 values[i] = inputStream.readFloat(); 302 } 303 return values; 304 } 305 case IFD_FORMAT_DOUBLE: { 306 final double[] values = new double[numberOfComponents]; 307 for (int i = 0; i < numberOfComponents; ++i) { 308 values[i] = inputStream.readDouble(); 309 } 310 return values; 311 } 312 default: 313 return null; 314 } 315 } catch (IOException e) { 316 Logger.w(TAG, "IOException occurred during reading a value", e); 317 return null; 318 } finally { 319 if (inputStream != null) { 320 try { 321 inputStream.close(); 322 } catch (IOException e) { 323 Logger.e(TAG, "IOException occurred while closing InputStream", e); 324 } 325 } 326 } 327 } 328 getDoubleValue(@onNull ByteOrder byteOrder)329 public double getDoubleValue(@NonNull ByteOrder byteOrder) { 330 Object value = getValue(byteOrder); 331 if (value == null) { 332 throw new NumberFormatException("NULL can't be converted to a double value"); 333 } 334 if (value instanceof String) { 335 return Double.parseDouble((String) value); 336 } 337 if (value instanceof long[]) { 338 long[] array = (long[]) value; 339 if (array.length == 1) { 340 return array[0]; 341 } 342 throw new NumberFormatException("There are more than one component"); 343 } 344 if (value instanceof int[]) { 345 int[] array = (int[]) value; 346 if (array.length == 1) { 347 return array[0]; 348 } 349 throw new NumberFormatException("There are more than one component"); 350 } 351 if (value instanceof double[]) { 352 double[] array = (double[]) value; 353 if (array.length == 1) { 354 return array[0]; 355 } 356 throw new NumberFormatException("There are more than one component"); 357 } 358 if (value instanceof LongRational[]) { 359 LongRational[] array = (LongRational[]) value; 360 if (array.length == 1) { 361 return array[0].toDouble(); 362 } 363 throw new NumberFormatException("There are more than one component"); 364 } 365 throw new NumberFormatException("Couldn't find a double value"); 366 } 367 getIntValue(@onNull ByteOrder byteOrder)368 public int getIntValue(@NonNull ByteOrder byteOrder) { 369 Object value = getValue(byteOrder); 370 if (value == null) { 371 throw new NumberFormatException("NULL can't be converted to a integer value"); 372 } 373 if (value instanceof String) { 374 return Integer.parseInt((String) value); 375 } 376 if (value instanceof long[]) { 377 long[] array = (long[]) value; 378 if (array.length == 1) { 379 return (int) array[0]; 380 } 381 throw new NumberFormatException("There are more than one component"); 382 } 383 if (value instanceof int[]) { 384 int[] array = (int[]) value; 385 if (array.length == 1) { 386 return array[0]; 387 } 388 throw new NumberFormatException("There are more than one component"); 389 } 390 throw new NumberFormatException("Couldn't find a integer value"); 391 } 392 getStringValue(@onNull ByteOrder byteOrder)393 public @Nullable String getStringValue(@NonNull ByteOrder byteOrder) { 394 Object value = getValue(byteOrder); 395 if (value == null) { 396 return null; 397 } 398 if (value instanceof String) { 399 return (String) value; 400 } 401 402 final StringBuilder stringBuilder = new StringBuilder(); 403 if (value instanceof long[]) { 404 long[] array = (long[]) value; 405 for (int i = 0; i < array.length; ++i) { 406 stringBuilder.append(array[i]); 407 if (i + 1 != array.length) { 408 stringBuilder.append(","); 409 } 410 } 411 return stringBuilder.toString(); 412 } 413 if (value instanceof int[]) { 414 int[] array = (int[]) value; 415 for (int i = 0; i < array.length; ++i) { 416 stringBuilder.append(array[i]); 417 if (i + 1 != array.length) { 418 stringBuilder.append(","); 419 } 420 } 421 return stringBuilder.toString(); 422 } 423 if (value instanceof double[]) { 424 double[] array = (double[]) value; 425 for (int i = 0; i < array.length; ++i) { 426 stringBuilder.append(array[i]); 427 if (i + 1 != array.length) { 428 stringBuilder.append(","); 429 } 430 } 431 return stringBuilder.toString(); 432 } 433 if (value instanceof LongRational[]) { 434 LongRational[] array = (LongRational[]) value; 435 for (int i = 0; i < array.length; ++i) { 436 stringBuilder.append(array[i].getNumerator()); 437 stringBuilder.append('/'); 438 stringBuilder.append(array[i].getDenominator()); 439 if (i + 1 != array.length) { 440 stringBuilder.append(","); 441 } 442 } 443 return stringBuilder.toString(); 444 } 445 return null; 446 } 447 size()448 public int size() { 449 return IFD_FORMAT_BYTES_PER_FORMAT[format] * numberOfComponents; 450 } 451 } 452