diff options
| author | 2019-01-31 17:51:32 -0700 | |
|---|---|---|
| committer | 2019-02-05 18:24:17 -0700 | |
| commit | d95d0d2d9e9466a7e03d6d54d7a02c17186680cd (patch) | |
| tree | a9a8d61a7414b2fdff7643dc2edb785bd3ff59e5 | |
| parent | 40c96fbc42c5a3a92dab9859bc77e0098b7b51a2 (diff) | |
Add support for XMP tags.
These Extensible Metadata Platform (XMP) tags contain XML data which
is used to communicate rich metadata information beyond EXIF.
TIFF: Tag 700
JPEG: Application segment 1 (0xFFE1) with segment header
"http://ns.adobe.com/xap/1.0/\x00"
Bug: 120791890
Test: atest cts/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
Change-Id: Iacccbf9b516b3204d99a760eaeb19a41554c3f97
| -rw-r--r-- | api/current.txt | 2 | ||||
| -rw-r--r-- | core/java/com/android/internal/util/ArrayUtils.java | 9 | ||||
| -rw-r--r-- | media/java/android/media/ExifInterface.java | 83 |
3 files changed, 62 insertions, 32 deletions
diff --git a/api/current.txt b/api/current.txt index 131237a77277..e914fd160fa5 100644 --- a/api/current.txt +++ b/api/current.txt @@ -23747,6 +23747,7 @@ package android.media { ctor public ExifInterface(@NonNull java.io.InputStream) throws java.io.IOException; method public double getAltitude(double); method @Nullable public String getAttribute(@NonNull String); + method @Nullable public byte[] getAttributeBytes(@NonNull String); method public double getAttributeDouble(@NonNull String, double); method public int getAttributeInt(@NonNull String, int); method @Nullable public long[] getAttributeRange(@NonNull String); @@ -23902,6 +23903,7 @@ package android.media { field public static final String TAG_USER_COMMENT = "UserComment"; field public static final String TAG_WHITE_BALANCE = "WhiteBalance"; field public static final String TAG_WHITE_POINT = "WhitePoint"; + field public static final String TAG_XMP = "Xmp"; field public static final String TAG_X_RESOLUTION = "XResolution"; field public static final String TAG_Y_CB_CR_COEFFICIENTS = "YCbCrCoefficients"; field public static final String TAG_Y_CB_CR_POSITIONING = "YCbCrPositioning"; diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 397df56a809c..b04ebec77a92 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -691,6 +691,15 @@ public class ArrayUtils { return result; } + public static boolean startsWith(byte[] cur, byte[] val) { + if (cur == null || val == null) return false; + if (cur.length < val.length) return false; + for (int i = 0; i < val.length; i++) { + if (cur[i] != val[i]) return false; + } + return true; + } + /** * Returns the first element from the array for which * condition {@code predicate} is true, or null if there is no such element diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index b9088d417dad..31d22327c79f 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -31,6 +31,8 @@ import android.system.OsConstants; import android.util.Log; import android.util.Pair; +import com.android.internal.util.ArrayUtils; + import libcore.io.IoUtils; import libcore.io.Streams; @@ -395,6 +397,12 @@ public class ExifInterface { * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html */ public static final String TAG_RW2_JPG_FROM_RAW = "JpgFromRaw"; + /** + * Type is byte[]. See <a href= + * "https://en.wikipedia.org/wiki/Extensible_Metadata_Platform">Extensible + * Metadata Platform (XMP)</a> for details on contents. + */ + public static final String TAG_XMP = "Xmp"; /** * Private tags used for pointing the other IFD offsets. @@ -1012,7 +1020,8 @@ public class ExifInterface { new ExifTag(TAG_RW2_SENSOR_BOTTOM_BORDER, 6, IFD_FORMAT_ULONG), new ExifTag(TAG_RW2_SENSOR_RIGHT_BORDER, 7, IFD_FORMAT_ULONG), new ExifTag(TAG_RW2_ISO, 23, IFD_FORMAT_USHORT), - new ExifTag(TAG_RW2_JPG_FROM_RAW, 46, IFD_FORMAT_UNDEFINED) + new ExifTag(TAG_RW2_JPG_FROM_RAW, 46, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_XMP, 700, IFD_FORMAT_BYTE), }; // Primary image IFD Exif Private tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) @@ -1243,6 +1252,8 @@ public class ExifInterface { private static final Charset ASCII = Charset.forName("US-ASCII"); // Identifier for EXIF APP1 segment in JPEG private static final byte[] IDENTIFIER_EXIF_APP1 = "Exif\0\0".getBytes(ASCII); + // Identifier for XMP APP1 segment in JPEG + private static final byte[] IDENTIFIER_XMP_APP1 = "http://ns.adobe.com/xap/1.0/\0".getBytes(ASCII); // JPEG segment markers, that each marker consumes two bytes beginning with 0xff and ending with // the indicator. There is no SOF4, SOF8, SOF16 markers in JPEG and SOFx markers indicates start // of frame(baseline DCT) and the image size info exists in its beginning part. @@ -2046,6 +2057,22 @@ public class ExifInterface { } /** + * Returns the raw bytes for the value of the requested tag inside the image + * file, or {@code null} if the tag is not contained. + * + * @return raw bytes for the value of the requested tag, or {@code null} if + * no tag was found. + */ + public @Nullable byte[] getAttributeBytes(@NonNull String tag) { + final ExifAttribute attribute = getExifAttribute(tag); + if (attribute != null) { + return attribute.bytes; + } else { + return null; + } + } + + /** * Stores the latitude and longitude value in a float array. The first element is * the latitude, and the second element is the longitude. Returns false if the * Exif tags are not available. @@ -2432,40 +2459,32 @@ public class ExifInterface { } switch (marker) { case MARKER_APP1: { - if (DEBUG) { - Log.d(TAG, "MARKER_APP1"); - } - if (length < 6) { - // Skip if it's not an EXIF APP1 segment. - break; - } - byte[] identifier = new byte[6]; - if (in.read(identifier) != 6) { - throw new IOException("Invalid exif"); - } - bytesRead += 6; - length -= 6; - if (!Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) { - // Skip if it's not an EXIF APP1 segment. - break; - } - if (length <= 0) { - throw new IOException("Invalid exif"); - } - if (DEBUG) { - Log.d(TAG, "readExifSegment with a byte array (length: " + length + ")"); - } - // Save offset values for createJpegThumbnailBitmap() function - mExifOffset = bytesRead; - - byte[] bytes = new byte[length]; - if (in.read(bytes) != length) { - throw new IOException("Invalid exif"); - } + final int start = bytesRead; + final byte[] bytes = new byte[length]; + in.readFully(bytes); bytesRead += length; length = 0; - readExifSegment(bytes, imageType); + if (ArrayUtils.startsWith(bytes, IDENTIFIER_EXIF_APP1)) { + final long offset = start + IDENTIFIER_EXIF_APP1.length; + final byte[] value = Arrays.copyOfRange(bytes, + IDENTIFIER_EXIF_APP1.length, bytes.length); + + readExifSegment(value, imageType); + + // Save offset values for createJpegThumbnailBitmap() function + mExifOffset = (int) offset; + } else if (ArrayUtils.startsWith(bytes, IDENTIFIER_XMP_APP1)) { + // See XMP Specification Part 3: Storage in Files, 1.1.3 JPEG, Table 6 + final long offset = start + IDENTIFIER_XMP_APP1.length; + final byte[] value = Arrays.copyOfRange(bytes, + IDENTIFIER_XMP_APP1.length, bytes.length); + + if (getAttribute(TAG_XMP) == null) { + mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, new ExifAttribute( + IFD_FORMAT_BYTE, value.length, offset, value)); + } + } break; } |