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