summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeff Sharkey <jsharkey@android.com> 2019-01-31 17:51:32 -0700
committer Jeff Sharkey <jsharkey@android.com> 2019-02-05 18:24:17 -0700
commitd95d0d2d9e9466a7e03d6d54d7a02c17186680cd (patch)
treea9a8d61a7414b2fdff7643dc2edb785bd3ff59e5
parent40c96fbc42c5a3a92dab9859bc77e0098b7b51a2 (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.txt2
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java9
-rw-r--r--media/java/android/media/ExifInterface.java83
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;
}