summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJay Wang <jaywang@codeaurora.org>2016-02-22 16:38:13 -0800
committerSteve Kondik <steve@cyngn.com>2016-08-03 15:45:46 -0700
commit16ac0c5d1326a232624804a9d5697274186fcce4 (patch)
treea34dc6f2241382370ef469d8bfa9c086b4ae6ebe /src
parente5d300a3dc13848fe8a8920bafb5474c29b44213 (diff)
downloadandroid_packages_apps_Snap-16ac0c5d1326a232624804a9d5697274186fcce4.tar.gz
android_packages_apps_Snap-16ac0c5d1326a232624804a9d5697274186fcce4.tar.bz2
android_packages_apps_Snap-16ac0c5d1326a232624804a9d5697274186fcce4.zip
SnapdragonCamera: Add support for outputting MPO format files
Add support for generating MPO formatted files from the application layer. CRs-Fixed: 993611 Change-Id: I9a78d33e1d80b7da748f9bc75446f49172342078
Diffstat (limited to 'src')
-rw-r--r--src/com/android/camera/exif/ExifTag.java20
-rw-r--r--src/com/android/camera/exif/JpegHeader.java3
-rw-r--r--src/com/android/camera/exif/OrderedDataOutputStream.java2
-rw-r--r--src/com/android/camera/mpo/MpoData.java184
-rw-r--r--src/com/android/camera/mpo/MpoIfdData.java126
-rw-r--r--src/com/android/camera/mpo/MpoImageData.java248
-rw-r--r--src/com/android/camera/mpo/MpoInterface.java143
-rw-r--r--src/com/android/camera/mpo/MpoOutputStream.java333
-rw-r--r--src/com/android/camera/mpo/MpoTag.java163
9 files changed, 1210 insertions, 12 deletions
diff --git a/src/com/android/camera/exif/ExifTag.java b/src/com/android/camera/exif/ExifTag.java
index 1d50316dd..fcf0f5c29 100644
--- a/src/com/android/camera/exif/ExifTag.java
+++ b/src/com/android/camera/exif/ExifTag.java
@@ -85,7 +85,7 @@ public class ExifTag {
TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
}
- static final int SIZE_UNDEFINED = 0;
+ public static final int SIZE_UNDEFINED = 0;
// Exif TagId
private final short mTagId;
@@ -124,7 +124,7 @@ public class ExifTag {
}
// Use builtTag in ExifInterface instead of constructor.
- ExifTag(short tagId, short type, int componentCount, int ifd,
+ public ExifTag(short tagId, short type, int componentCount, int ifd,
boolean hasDefinedComponentCount) {
mTagId = tagId;
mDataType = type;
@@ -163,7 +163,7 @@ public class ExifTag {
return mIfd;
}
- protected void setIfd(int ifdId) {
+ public void setIfd(int ifdId) {
mIfd = ifdId;
}
@@ -785,7 +785,7 @@ public class ExifTag {
* @exception IllegalArgumentException if the data type is
* {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
*/
- protected long getValueAt(int index) {
+ public long getValueAt(int index) {
if (mValue instanceof long[]) {
return ((long[]) mValue)[index];
} else if (mValue instanceof byte[]) {
@@ -812,7 +812,7 @@ public class ExifTag {
/*
* Get the converted ascii byte. Used by ExifOutputStream.
*/
- protected byte[] getStringByte() {
+ public byte[] getStringByte() {
return (byte[]) mValue;
}
@@ -822,7 +822,7 @@ public class ExifTag {
* @exception IllegalArgumentException If the type is NOT
* {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
*/
- protected Rational getRational(int index) {
+ public Rational getRational(int index) {
if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) {
throw new IllegalArgumentException("Cannot get RATIONAL value from "
+ convertTypeToString(mDataType));
@@ -833,7 +833,7 @@ public class ExifTag {
/**
* Equivalent to getBytes(buffer, 0, buffer.length).
*/
- protected void getBytes(byte[] buf) {
+ public void getBytes(byte[] buf) {
getBytes(buf, 0, buf.length);
}
@@ -847,7 +847,7 @@ public class ExifTag {
* @exception IllegalArgumentException If the type is NOT
* {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
*/
- protected void getBytes(byte[] buf, int offset, int length) {
+ public void getBytes(byte[] buf, int offset, int length) {
if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) {
throw new IllegalArgumentException("Cannot get BYTE value from "
+ convertTypeToString(mDataType));
@@ -860,14 +860,14 @@ public class ExifTag {
* Gets the offset of this tag. This is only valid if this data size > 4 and
* contains an offset to the location of the actual value.
*/
- protected int getOffset() {
+ public int getOffset() {
return mOffset;
}
/**
* Sets the offset of this tag.
*/
- protected void setOffset(int offset) {
+ public void setOffset(int offset) {
mOffset = offset;
}
diff --git a/src/com/android/camera/exif/JpegHeader.java b/src/com/android/camera/exif/JpegHeader.java
index 383617af4..6fc46ca18 100644
--- a/src/com/android/camera/exif/JpegHeader.java
+++ b/src/com/android/camera/exif/JpegHeader.java
@@ -16,8 +16,9 @@
package com.android.camera.exif;
-class JpegHeader {
+public class JpegHeader {
public static final short SOI = (short) 0xFFD8;
+ public static final short APP2 = (short) 0xFFE2;
public static final short APP1 = (short) 0xFFE1;
public static final short APP0 = (short) 0xFFE0;
public static final short EOI = (short) 0xFFD9;
diff --git a/src/com/android/camera/exif/OrderedDataOutputStream.java b/src/com/android/camera/exif/OrderedDataOutputStream.java
index abc0a6eb1..3206d72b0 100644
--- a/src/com/android/camera/exif/OrderedDataOutputStream.java
+++ b/src/com/android/camera/exif/OrderedDataOutputStream.java
@@ -22,7 +22,7 @@ import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-class OrderedDataOutputStream extends FilterOutputStream {
+public class OrderedDataOutputStream extends FilterOutputStream {
private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4);
private int mSize = 0;
diff --git a/src/com/android/camera/mpo/MpoData.java b/src/com/android/camera/mpo/MpoData.java
new file mode 100644
index 000000000..7cd431ebe
--- /dev/null
+++ b/src/com/android/camera/mpo/MpoData.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.camera.mpo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.android.camera.mpo.MpoTag.MpEntry;
+
+public class MpoData {
+
+ private MpoImageData mPrimaryMpoImage;
+ private ArrayList<MpoImageData> mAuxiliaryImages = new ArrayList<MpoImageData>();
+
+ public MpoData() {
+ }
+
+ public void setPrimaryMpoImage(MpoImageData image) {
+ mPrimaryMpoImage = image;
+ addDefaultAttribIfdTags(mPrimaryMpoImage, 1);
+ addDefaultIndexIfdTags();
+ }
+
+ public void addAuxiliaryMpoImage(MpoImageData image) {
+ mAuxiliaryImages.add(image);
+ int imageNum = getAuxiliaryImageCount() + ((mPrimaryMpoImage == null) ? 0 : 1);
+ addDefaultAttribIfdTags(image, imageNum);
+ }
+
+ public boolean removeAuxiliaryMpoImage(MpoImageData image) {
+ boolean ret = mAuxiliaryImages.remove(image);
+ return ret;
+ }
+
+ public MpoImageData getPrimaryMpoImage() {
+ return mPrimaryMpoImage;
+ }
+
+ public List<MpoImageData> getAuxiliaryMpoImages() {
+ return mAuxiliaryImages;
+ }
+
+ public int getAuxiliaryImageCount() {
+ return mAuxiliaryImages.size();
+ }
+
+ public void addDefaultAttribIfdTags(MpoImageData image, int imageNum) {
+ MpoTag mpFormatVersionTag = new MpoTag((short) MpoInterface.TAG_MP_FORMAT_VERSION,
+ MpoTag.TYPE_UNDEFINED, 4, MpoIfdData.TYPE_MP_ATTRIB_IFD, true);
+ mpFormatVersionTag.setValue(MpoIfdData.MP_FORMAT_VER_VALUE);
+ image.addTag(mpFormatVersionTag);
+
+ MpoTag imageNumTag = new MpoTag((short) MpoInterface.TAG_IMAGE_NUMBER,
+ MpoTag.TYPE_UNSIGNED_LONG, 1, MpoIfdData.TYPE_MP_ATTRIB_IFD, false);
+ imageNumTag.setValue(imageNum);
+ image.addTag(imageNumTag);
+ }
+
+ public void addDefaultIndexIfdTags() {
+ if (mPrimaryMpoImage == null)
+ throw new IllegalArgumentException("Primary Mpo Image has not been set");
+ if (getAuxiliaryImageCount() == 0)
+ throw new IllegalArgumentException("No auxiliary images have been added");
+
+ MpoTag mpFormatVersionTag = mPrimaryMpoImage.getTag(
+ (short) MpoInterface.TAG_MP_FORMAT_VERSION, MpoIfdData.TYPE_MP_INDEX_IFD);
+ if (mpFormatVersionTag == null) {
+ mpFormatVersionTag = new MpoTag((short) MpoInterface.TAG_MP_FORMAT_VERSION,
+ MpoTag.TYPE_UNDEFINED, 4, MpoIfdData.TYPE_MP_INDEX_IFD, true);
+ mpFormatVersionTag.setValue(MpoIfdData.MP_FORMAT_VER_VALUE);
+ mPrimaryMpoImage.addTag(mpFormatVersionTag);
+ }
+
+ MpoTag numImagesTag = mPrimaryMpoImage.getTag((short) MpoInterface.TAG_NUM_IMAGES,
+ MpoIfdData.TYPE_MP_INDEX_IFD);
+ if (numImagesTag == null) {
+ numImagesTag = new MpoTag((short) MpoInterface.TAG_NUM_IMAGES,
+ MpoTag.TYPE_UNSIGNED_LONG, 1, MpoIfdData.TYPE_MP_INDEX_IFD, false);
+ }
+ numImagesTag.setValue(getAuxiliaryImageCount() + 1);
+ mPrimaryMpoImage.addTag(numImagesTag);
+
+ // check, create and add required tags
+ MpoTag mpEntryTag = new MpoTag((short) MpoInterface.TAG_MP_ENTRY, MpoTag.TYPE_UNDEFINED,
+ MpoTag.SIZE_UNDEFINED, MpoIfdData.TYPE_MP_INDEX_IFD, false);
+ ArrayList<MpEntry> mpEntries = new ArrayList<MpEntry>(getAuxiliaryImageCount() + 1);
+ mpEntries.add(new MpEntry()); // primary image
+ for (int i = 0; i < getAuxiliaryImageCount(); i++) {
+ mpEntries.add(new MpEntry()); // aux images
+ }
+ mpEntryTag.setValue(mpEntries);
+ mPrimaryMpoImage.addTag(mpEntryTag);
+ }
+
+ public void updateAllTags() {
+ updateAttribIfdTags();
+ updateIndexIfdTags();
+ }
+
+ private void updateIndexIfdTags() {
+ if (mPrimaryMpoImage == null)
+ throw new IllegalArgumentException("Primary Mpo Image has not been set");
+ if (getAuxiliaryImageCount() == 0)
+ throw new IllegalArgumentException("No auxiliary images have been added");
+
+ MpoTag numImagesTag = mPrimaryMpoImage.getTag((short) MpoInterface.TAG_NUM_IMAGES,
+ MpoIfdData.TYPE_MP_INDEX_IFD);
+ if (numImagesTag == null) {
+ numImagesTag = new MpoTag((short) MpoInterface.TAG_NUM_IMAGES,
+ MpoTag.TYPE_UNSIGNED_LONG, 1, MpoIfdData.TYPE_MP_INDEX_IFD, false);
+ }
+ numImagesTag.setValue(getAuxiliaryImageCount() + 1);
+ mPrimaryMpoImage.addTag(numImagesTag);
+
+ // check, create and add required tags
+ MpoTag mpEntryTag = new MpoTag((short) MpoInterface.TAG_MP_ENTRY, MpoTag.TYPE_UNDEFINED,
+ MpoTag.SIZE_UNDEFINED, MpoIfdData.TYPE_MP_INDEX_IFD, false);
+ ArrayList<MpEntry> mpEntries = new ArrayList<MpEntry>(getAuxiliaryImageCount() + 1);
+
+ int imgOffset = 0;
+ // primary image
+ MpEntry entry = new MpEntry(1 << 29, mPrimaryMpoImage.calculateImageSize(), imgOffset);
+ mpEntries.add(entry);
+ imgOffset += mPrimaryMpoImage.calculateImageSize();
+
+ for (MpoImageData image : getAuxiliaryMpoImages()) {
+ int imageSize = image.calculateImageSize();
+ entry = new MpEntry(0x020002, imageSize, imgOffset);
+ mpEntries.add(entry); // aux images
+ imgOffset += imageSize;
+ }
+ mpEntryTag.setValue(mpEntries);
+ mPrimaryMpoImage.addTag(mpEntryTag);
+ }
+
+ private void updateAttribIfdTags() {
+ if (mPrimaryMpoImage == null)
+ throw new IllegalArgumentException("Primary Mpo Image has not been set");
+ if (getAuxiliaryImageCount() == 0)
+ throw new IllegalArgumentException("No auxiliary images have been added");
+
+ int imageNum = 1;
+ MpoTag imageNumTag = null;
+
+ imageNumTag = new MpoTag((short) MpoInterface.TAG_IMAGE_NUMBER, MpoTag.TYPE_UNSIGNED_LONG,
+ 1, MpoIfdData.TYPE_MP_ATTRIB_IFD, false);
+ imageNumTag.setValue(0xFFFFFFFFL);
+ mPrimaryMpoImage.addTag(imageNumTag);
+
+ for (MpoImageData image : getAuxiliaryMpoImages()) {
+ imageNumTag = new MpoTag((short) MpoInterface.TAG_IMAGE_NUMBER,
+ MpoTag.TYPE_UNSIGNED_LONG, 1, MpoIfdData.TYPE_MP_ATTRIB_IFD, false);
+ imageNumTag.setValue(imageNum++);
+ image.addTag(imageNumTag);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/camera/mpo/MpoIfdData.java b/src/com/android/camera/mpo/MpoIfdData.java
new file mode 100644
index 000000000..8e422944a
--- /dev/null
+++ b/src/com/android/camera/mpo/MpoIfdData.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ * Not a contribution.
+ *
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.mpo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class stores all the tags in an MP Index IFD.
+ */
+public class MpoIfdData {
+ public static final int TYPE_MP_INDEX_IFD = 1;
+ public static final int TYPE_MP_ATTRIB_IFD = 2;
+ public static final byte[] MP_FORMAT_VER_VALUE = { 0x30, 0x31, 0x30, 0x30 };
+
+ private final int mIfdId;
+ private final Map<Short, MpoTag> mTags = new HashMap<Short, MpoTag>();
+ private int mOffsetToNextIfd = 0;
+
+ /**
+ * Creates an empty MpIndexIfdData
+ */
+ public MpoIfdData(int ifdId) {
+ mIfdId = ifdId;
+ }
+
+ /**
+ * Get a array the contains all {@link MpoTag} in this IFD.
+ */
+ protected MpoTag[] getAllTags() {
+ return mTags.values().toArray(new MpoTag[mTags.size()]);
+ }
+
+ /**
+ * Gets the {@link MpoTag} with given tag id. Return null if there is no
+ * such tag.
+ */
+ protected MpoTag getTag(short tagId) {
+ return mTags.get(tagId);
+ }
+
+ /**
+ * Adds or replaces a {@link MpoTag}.
+ */
+ protected MpoTag setTag(MpoTag tag) {
+ tag.setIfd(mIfdId);
+ return mTags.put(tag.getTagId(), tag);
+ }
+
+ protected boolean checkCollision(short tagId) {
+ return mTags.get(tagId) != null;
+ }
+
+ /**
+ * Removes the tag of the given ID
+ */
+ protected void removeTag(short tagId) {
+ mTags.remove(tagId);
+ }
+
+ /**
+ * Gets the tags count in the IFD.
+ */
+ protected int getTagCount() {
+ return mTags.size();
+ }
+
+ /**
+ * Sets the offset of next IFD.
+ */
+ protected void setOffsetToNextIfd(int offset) {
+ mOffsetToNextIfd = offset;
+ }
+
+ /**
+ * Gets the offset of next IFD.
+ */
+ protected int getOffsetToNextIfd() {
+ return mOffsetToNextIfd;
+ }
+
+ /**
+ * Returns true if all tags in this two IFDs are equal. Note that tags of
+ * IFD offset will be ignored.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof MpoIfdData) {
+ MpoIfdData data = (MpoIfdData) obj;
+ if (data.getTagCount() == getTagCount()) {
+ MpoTag[] tags = data.getAllTags();
+ for (MpoTag tag : tags) {
+ MpoTag tag2 = mTags.get(tag.getTagId());
+ if (!tag.equals(tag2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/camera/mpo/MpoImageData.java b/src/com/android/camera/mpo/MpoImageData.java
new file mode 100644
index 000000000..84d2d0f6e
--- /dev/null
+++ b/src/com/android/camera/mpo/MpoImageData.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ * Not a contribution.
+ *
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.mpo;
+
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class stores the MPO header in IFDs according to the MPO specification.
+ */
+
+public class MpoImageData {
+ private static final String TAG = "MpoImageData";
+ static final int OFFSET_TO_FIRST_IFD = 8;
+ static final int MP_FORMAT_IDENTIFIER = 0x4D504600; // 'M' 'P' 'F' 'NULL'
+ static final int MP_HEADER_SIZE = 8;
+ static final int APP_HEADER_SIZE = 6;
+
+ private final MpoIfdData mMpIndexIfdData = new MpoIfdData(MpoIfdData.TYPE_MP_INDEX_IFD);
+ private final MpoIfdData mMpAttribIfdData = new MpoIfdData(MpoIfdData.TYPE_MP_ATTRIB_IFD);
+ private final byte[] mJpegData;
+ private final ByteOrder mByteOrder;
+
+ public MpoImageData(byte[] jpegData, ByteOrder byteOrder) {
+ mJpegData = jpegData;
+ mByteOrder = byteOrder;
+ }
+
+ /**
+ * Gets the jpeg data.
+ */
+ protected byte[] getJpegData() {
+ return mJpegData;
+ }
+
+ /**
+ * Gets the byte order.
+ */
+ protected ByteOrder getByteOrder() {
+ return mByteOrder;
+ }
+
+ /**
+ * Returns the {@link mMpAttribIfdData} object if it exists or null.
+ */
+ protected MpoIfdData getAttribIfdData() {
+ return mMpAttribIfdData;
+ }
+
+ /**
+ * Returns the {@link mMpIndexIfdData} object if it exists or null.
+ */
+ protected MpoIfdData getIndexIfdData() {
+ return mMpIndexIfdData;
+ }
+
+ /**
+ * Returns the {@link MpoIfdData} object corresponding to a given IFD.
+ */
+ protected MpoIfdData getMpIfdData(int ifdId) {
+ return (ifdId == MpoIfdData.TYPE_MP_INDEX_IFD) ? mMpIndexIfdData : mMpAttribIfdData;
+ }
+
+ /**
+ * Returns the tag with a given tag ID in the given IFD if the tag exists.
+ * Otherwise returns null.
+ */
+ protected MpoTag getTag(short tag, int ifd) {
+ MpoIfdData mpIfdData = getMpIfdData(ifd);
+ return mpIfdData.getTag(tag);
+ }
+
+ /**
+ * Adds the given MpoTag to its default IFD and returns an existing MpoTag
+ * with the same TID or null if none exist.
+ */
+ protected MpoTag addTag(MpoTag tag) {
+ if (tag != null) {
+ int ifd = tag.getIfd();
+ return addTag(tag, ifd);
+ }
+ return null;
+ }
+
+ /**
+ * Adds the given MpoTag to the given IFD and returns an existing MpoTag
+ * with the same tag ID or null if none exist.
+ */
+ protected MpoTag addTag(MpoTag tag, int ifdId) {
+ if (tag != null && MpoTag.isValidIfd(ifdId)) {
+ return getMpIfdData(ifdId).setTag(tag);
+ }
+ return null;
+ }
+
+ /**
+ * Removes the tag with a given tag ID and IFD.
+ */
+ protected void removeTag(short tagId, int ifdId) {
+ getMpIfdData(ifdId).removeTag(tagId);
+ }
+
+ /**
+ * Returns a list of all {@link MpoTag}s in the ExifData or null if there
+ * are none.
+ */
+ protected List<MpoTag> getAllTags() {
+ ArrayList<MpoTag> ret = new ArrayList<MpoTag>();
+ MpoTag[] tags = mMpIndexIfdData.getAllTags();
+ if (tags != null) {
+ for (MpoTag t : tags) {
+ ret.add(t);
+ }
+ }
+
+ tags = mMpAttribIfdData.getAllTags();
+ if (tags != null) {
+ for (MpoTag t : tags) {
+ ret.add(t);
+ }
+ }
+
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a list of all {@link MpoTag}s in a given IFD or null if there are
+ * none.
+ */
+ protected List<MpoTag> getAllTagsForIfd(int ifd) {
+ MpoTag[] tags = getMpIfdData(ifd).getAllTags();
+ if (tags == null) {
+ return null;
+ }
+ ArrayList<MpoTag> ret = new ArrayList<MpoTag>(tags.length);
+ for (MpoTag t : tags) {
+ ret.add(t);
+ }
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a list of all {@link MpoTag}s with a given TID or null if there
+ * are none.
+ */
+ protected List<MpoTag> getAllTagsForTagId(short tag) {
+ ArrayList<MpoTag> ret = new ArrayList<MpoTag>();
+ MpoTag t = mMpIndexIfdData.getTag(tag);
+ if (t != null) {
+ ret.add(t);
+ }
+
+ t = mMpAttribIfdData.getTag(tag);
+ if (t != null) {
+ ret.add(t);
+ }
+
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof MpoImageData) {
+ MpoImageData data = (MpoImageData) obj;
+ if (data.mByteOrder != mByteOrder) {
+ return false;
+ }
+
+ MpoIfdData indexIfd1 = data.getMpIfdData(MpoIfdData.TYPE_MP_INDEX_IFD);
+ MpoIfdData indexIfd2 = getMpIfdData(MpoIfdData.TYPE_MP_INDEX_IFD);
+ if (indexIfd1 != indexIfd2 && indexIfd1 != null && !indexIfd1.equals(indexIfd2)) {
+ return false;
+ }
+
+ MpoIfdData attribIfd1 = data.getMpIfdData(MpoIfdData.TYPE_MP_ATTRIB_IFD);
+ MpoIfdData attribIfd2 = getMpIfdData(MpoIfdData.TYPE_MP_ATTRIB_IFD);
+ if (attribIfd1 != attribIfd2 && attribIfd1 != null && !attribIfd1.equals(attribIfd2)) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private int calculateOffsetOfIfd(MpoIfdData ifd, int offset) {
+ offset += 2 + ifd.getTagCount() * MpoTag.TAG_SIZE + 4;
+ MpoTag[] tags = ifd.getAllTags();
+ for (MpoTag tag : tags) {
+ if (tag.getDataSize() > 4) {
+ tag.setOffset(offset);
+ offset += tag.getDataSize();
+ }
+ }
+ return offset;
+ }
+
+ public int calculateAllIfdOffsets() {
+ int offset = MP_HEADER_SIZE;
+ MpoIfdData indexIfd = getIndexIfdData();
+ if (indexIfd.getTagCount() > 0)
+ offset = calculateOffsetOfIfd(indexIfd, offset);
+
+ MpoIfdData attribIfd = getAttribIfdData();
+ if (attribIfd.getTagCount() > 0) {
+ indexIfd.setOffsetToNextIfd(offset);
+ offset = calculateOffsetOfIfd(attribIfd, offset);
+ }
+
+ return offset;
+ }
+
+ public int calculateImageSize() {
+ return 2 + APP_HEADER_SIZE + calculateAllIfdOffsets() + mJpegData.length;
+ }
+}
diff --git a/src/com/android/camera/mpo/MpoInterface.java b/src/com/android/camera/mpo/MpoInterface.java
new file mode 100644
index 000000000..3e2d9a4e2
--- /dev/null
+++ b/src/com/android/camera/mpo/MpoInterface.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ * Not a contribution.
+ *
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.mpo;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import android.util.Log;
+
+import com.android.camera.exif.ExifInterface;
+import com.android.camera.util.CameraUtil;
+
+public class MpoInterface {
+ private static final String TAG = "MpoInterface";
+ private static final String NULL_ARGUMENT_STRING = "Argument is null";
+
+ // Index IFD
+ public static final int TAG_MP_FORMAT_VERSION = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_INDEX_IFD + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB000);
+ public static final int TAG_NUM_IMAGES = ExifInterface.defineTag(MpoIfdData.TYPE_MP_INDEX_IFD,
+ (short) 0xB001);
+ public static final int TAG_MP_ENTRY = ExifInterface.defineTag(MpoIfdData.TYPE_MP_INDEX_IFD,
+ (short) 0xB002);
+ public static final int TAG_IMAGE_UNIQUE_ID_LIST = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_INDEX_IFD, (short) 0xB003);
+ public static final int TAG_NUM_CAPTURED_FRAMES = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_INDEX_IFD, (short) 0xB004);
+
+ // Attrib IFD
+ public static final int TAG_IMAGE_NUMBER = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB101);
+ public static final int TAG_PAN_ORIENTATION = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB201);
+ public static final int TAG_PAN_OVERLAP_H = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB202);
+ public static final int TAG_PAN_OVERLAP_V = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB203);
+ public static final int TAG_BASE_VIEWPOINT_NUM = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB204);
+ public static final int TAG_CONVERGE_ANGLE = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB205);
+ public static final int TAG_BASELINE_LEN = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB206);
+ public static final int TAG_DIVERGE_ANGLE = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB207);
+ public static final int TAG_AXIS_DISTANCE_X = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB208);
+ public static final int TAG_AXIS_DISTANCE_Y = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB209);
+ public static final int TAG_AXIS_DISTANCE_Z = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB20A);
+ public static final int TAG_YAW_ANGLE = ExifInterface.defineTag(MpoIfdData.TYPE_MP_ATTRIB_IFD,
+ (short) 0xB20B);
+ public static final int TAG_PITCH_ANGLE = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB20C);
+ public static final int TAG_ROLL_ANGLE = ExifInterface.defineTag(MpoIfdData.TYPE_MP_ATTRIB_IFD,
+ (short) 0xB20D);
+
+ public static int writeMpo(MpoData mpo, OutputStream out) {
+ if (mpo == null || out == null)
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+
+ MpoOutputStream s = getMpoWriterStream(out);
+ s.setMpoData(mpo);
+
+ // check and write mpo file
+ try {
+ s.writeMpoFile();
+ } catch (IOException e) {
+ CameraUtil.closeSilently(s);
+ Log.w(TAG, "IO Exception when writing mpo image");
+ return -1;
+ }
+
+ // close stream
+ CameraUtil.closeSilently(s);
+ return s.size();
+ }
+
+ public static int writeMpo(MpoData mpo, String outFilename) {
+ if (mpo == null || outFilename == null)
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+
+ return writeMpo(mpo, getFileWriterStream(outFilename));
+ }
+
+ /**
+ * Wraps an OutputStream object with an MpoOutputStream.
+ *
+ * @param outStream
+ * an OutputStream to wrap.
+ * @return an MpoOutputStream that wraps the outStream parameter, and adds
+ * mpo metadata. A jpeg image should be written to this stream.
+ */
+ private static MpoOutputStream getMpoWriterStream(OutputStream outStream) {
+ if (outStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ MpoOutputStream mos = new MpoOutputStream(outStream);
+ return mos;
+ }
+
+ /**
+ * Returns an FileOutputStream object that writes to a file.
+ *
+ * @param outFileName
+ * an String containing a filepath for a file.
+ * @return an FileOutputStream that writes to the outFileName file.
+ * @throws FileNotFoundException
+ */
+ private static OutputStream getFileWriterStream(String outFileName) {
+ if (outFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream out = null;
+ try {
+ out = new FileOutputStream(outFileName);
+ } catch (FileNotFoundException e) {
+ CameraUtil.closeSilently(out);
+ Log.w(TAG, "File not found");
+ }
+ return out;
+ }
+}
diff --git a/src/com/android/camera/mpo/MpoOutputStream.java b/src/com/android/camera/mpo/MpoOutputStream.java
new file mode 100644
index 000000000..6e8e72fd9
--- /dev/null
+++ b/src/com/android/camera/mpo/MpoOutputStream.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ * Not a contribution/
+ *
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.mpo;
+
+import java.io.BufferedOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.List;
+
+import android.util.Log;
+
+import com.android.camera.exif.JpegHeader;
+import com.android.camera.exif.OrderedDataOutputStream;
+import com.android.camera.mpo.MpoTag.MpEntry;
+
+class MpoOutputStream extends FilterOutputStream {
+ private static final String TAG = "MpoOutputStream";
+ private static final boolean DEBUG = true;
+ private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
+
+ private static final int STATE_SOI = 0;
+ private static final int STATE_FRAME_HEADER = 1;
+ private static final int STATE_JPEG_DATA = 3;
+
+ private static final short TIFF_HEADER = 0x002A;
+ private static final short TIFF_BIG_ENDIAN = 0x4d4d;
+ private static final short TIFF_LITTLE_ENDIAN = 0x4949;
+ private static final int MAX_EXIF_SIZE = 65535;
+
+ private MpoData mMpoData;
+ private MpoImageData mCurrentImageData;
+ private int mState = STATE_SOI;
+ private int mByteToSkip;
+ private int mByteToCopy;
+ private byte[] mSingleByteArray = new byte[1];
+ private ByteBuffer mBuffer = ByteBuffer.allocate(4);
+ private int mMpoOffsetStart = -1;
+ private int mSize = 0;
+
+ protected MpoOutputStream(OutputStream ou) {
+ super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
+ }
+
+ /**
+ * Sets the ExifData to be written into the JPEG file. Should be called
+ * before writing image data.
+ */
+ protected void setMpoData(MpoData mpoData) {
+ mMpoData = mpoData;
+ mMpoData.updateAllTags();
+ }
+
+ private void resetStates() {
+ mState = STATE_SOI;
+ mByteToSkip = 0;
+ mByteToCopy = 0;
+ mBuffer.rewind();
+ }
+
+ private int requestByteToBuffer(int requestByteCount, byte[] buffer, int offset, int length) {
+ int byteNeeded = requestByteCount - mBuffer.position();
+ int byteToRead = length > byteNeeded ? byteNeeded : length;
+ mBuffer.put(buffer, offset, byteToRead);
+ return byteToRead;
+ }
+
+ void writeMpoFile() throws IOException {
+ // check and write primary image
+ mCurrentImageData = mMpoData.getPrimaryMpoImage();
+ write(mCurrentImageData.getJpegData());
+ flush();
+
+ // check and write auxiliary images
+ for (MpoImageData image : mMpoData.getAuxiliaryMpoImages()) {
+ resetStates();
+ mCurrentImageData = image;
+ write(mCurrentImageData.getJpegData());
+ flush();
+ }
+ }
+
+ /**
+ * Writes the image out. The input data should be a valid JPEG format. After
+ * writing, it's Exif header will be replaced by the given header.
+ */
+ @Override
+ public void write(byte[] buffer, int offset, int length) throws IOException {
+ while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA) && length > 0) {
+ if (mByteToSkip > 0) {
+ int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
+ length -= byteToProcess;
+ mByteToSkip -= byteToProcess;
+ offset += byteToProcess;
+ }
+ if (mByteToCopy > 0) {
+ int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
+ out.write(buffer, offset, byteToProcess);
+ mSize += byteToProcess;
+ length -= byteToProcess;
+ mByteToCopy -= byteToProcess;
+ offset += byteToProcess;
+ }
+ if (length == 0) {
+ return;
+ }
+ switch (mState) {
+ case STATE_SOI:
+ int byteRead = requestByteToBuffer(2, buffer, offset, length);
+ offset += byteRead;
+ length -= byteRead;
+ if (mBuffer.position() < 2) {
+ return;
+ }
+ mBuffer.rewind();
+ if (mBuffer.getShort() != JpegHeader.SOI) {
+ throw new IOException("Not a valid jpeg image, cannot write exif");
+ }
+ out.write(mBuffer.array(), 0, 2);
+ mSize += 2;
+ mState = STATE_FRAME_HEADER;
+ mBuffer.rewind();
+ break;
+ case STATE_FRAME_HEADER:
+ // Copy APP1 if it exists
+ // Insert MPO data
+ // Copy remainder of image
+ byteRead = requestByteToBuffer(4, buffer, offset, length);
+ offset += byteRead;
+ length -= byteRead;
+ // Check if this image data doesn't contain SOF.
+ if (mBuffer.position() == 2) {
+ short tag = mBuffer.getShort();
+ if (tag == JpegHeader.EOI) {
+ out.write(mBuffer.array(), 0, 2);
+ mSize += 2;
+ mBuffer.rewind();
+ }
+ }
+ if (mBuffer.position() < 4) {
+ return;
+ }
+ mBuffer.rewind();
+ short marker = mBuffer.getShort();
+ if (marker == JpegHeader.APP1 || marker == JpegHeader.APP0) {
+ out.write(mBuffer.array(), 0, 4);
+ mSize += 4;
+ mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
+ } else {
+ writeMpoData();
+ out.write(mBuffer.array(), 0, 4);
+ mSize += 4;
+ mState = STATE_JPEG_DATA;
+ }
+ mBuffer.rewind();
+ break;
+ }
+ }
+ if (length > 0) {
+ out.write(buffer, offset, length);
+ mSize += length;
+ }
+ }
+
+ /**
+ * Writes the one bytes out. The input data should be a valid JPEG format.
+ * After writing, it's Exif header will be replaced by the given header.
+ */
+ @Override
+ public void write(int oneByte) throws IOException {
+ mSingleByteArray[0] = (byte) (0xff & oneByte);
+ write(mSingleByteArray);
+ }
+
+ /**
+ * Equivalent to calling write(buffer, 0, buffer.length).
+ */
+ @Override
+ public void write(byte[] buffer) throws IOException {
+ write(buffer, 0, buffer.length);
+ }
+
+ private void writeMpoData() throws IOException {
+ if (mMpoData == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Writing mpo data...");
+ }
+ int exifSize = mCurrentImageData.calculateAllIfdOffsets() + MpoImageData.APP_HEADER_SIZE;
+ if (exifSize > MAX_EXIF_SIZE) {
+ throw new IOException("Exif header is too large (>64Kb)");
+ }
+ OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
+ dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+ dataOutputStream.writeShort(JpegHeader.APP2);
+ dataOutputStream.writeShort((short) (exifSize));
+ dataOutputStream.writeInt(MpoImageData.MP_FORMAT_IDENTIFIER);
+ if (mMpoOffsetStart == -1) {
+ mMpoOffsetStart = mSize + dataOutputStream.size();
+ }
+ if (mCurrentImageData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+ dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
+ } else {
+ dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
+ }
+ dataOutputStream.setByteOrder(mCurrentImageData.getByteOrder());
+ dataOutputStream.writeShort(TIFF_HEADER);
+ if (exifSize > MpoImageData.MP_HEADER_SIZE + MpoImageData.APP_HEADER_SIZE) {
+ dataOutputStream.writeInt(MpoImageData.OFFSET_TO_FIRST_IFD);
+ writeAllTags(dataOutputStream);
+ } else
+ dataOutputStream.writeInt(0);
+
+ mSize += dataOutputStream.size();
+ }
+
+ private void updateIndexIfdOffsets(MpoIfdData indexIfd, int mpoOffset) {
+ // update offsets
+ MpoTag mpEntryTag = mMpoData.getPrimaryMpoImage().getTag((short) MpoInterface.TAG_MP_ENTRY,
+ MpoIfdData.TYPE_MP_INDEX_IFD);
+ List<MpEntry> mpEntries = mpEntryTag.getMpEntryValue();
+ for (int i = 1; i < mpEntries.size(); i++) { // primary offset is always
+ // 0
+ MpEntry entry = mpEntries.get(i);
+ entry.setImageOffset(entry.getImageOffset() - mpoOffset);
+ }
+
+ mpEntryTag.setValue(mpEntries);
+ }
+
+ private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException {
+ MpoIfdData indexIfd = mCurrentImageData.getIndexIfdData();
+ if (indexIfd.getTagCount() > 0) {
+ updateIndexIfdOffsets(indexIfd, mMpoOffsetStart);
+ writeIfd(indexIfd, dataOutputStream);
+ }
+
+ MpoIfdData attribIfd = mCurrentImageData.getAttribIfdData();
+ if (attribIfd.getTagCount() > 0)
+ writeIfd(attribIfd, dataOutputStream);
+ }
+
+ private void writeIfd(MpoIfdData ifd, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ MpoTag[] tags = ifd.getAllTags();
+ dataOutputStream.writeShort((short) tags.length);
+ for (MpoTag tag : tags) {
+ dataOutputStream.writeShort(tag.getTagId());
+ dataOutputStream.writeShort(tag.getDataType());
+ dataOutputStream.writeInt(tag.getComponentCount());
+ if (DEBUG) {
+ Log.v(TAG, "\n" + tag.toString());
+ }
+ if (tag.getDataSize() > 4) {
+ dataOutputStream.writeInt(tag.getOffset());
+ } else {
+ MpoOutputStream.writeTagValue(tag, dataOutputStream);
+ for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
+ dataOutputStream.write(0);
+ }
+ }
+ }
+ dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
+ for (MpoTag tag : tags) {
+ if (tag.getDataSize() > 4) {
+ MpoOutputStream.writeTagValue(tag, dataOutputStream);
+ }
+ }
+ }
+
+ static void writeTagValue(MpoTag tag, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ switch (tag.getDataType()) {
+ case MpoTag.TYPE_ASCII:
+ byte buf[] = tag.getStringByte();
+ if (buf.length == tag.getComponentCount()) {
+ buf[buf.length - 1] = 0;
+ dataOutputStream.write(buf);
+ } else {
+ dataOutputStream.write(buf);
+ dataOutputStream.write(0);
+ }
+ break;
+ case MpoTag.TYPE_LONG:
+ case MpoTag.TYPE_UNSIGNED_LONG:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeInt((int) tag.getValueAt(i));
+ }
+ break;
+ case MpoTag.TYPE_RATIONAL:
+ case MpoTag.TYPE_UNSIGNED_RATIONAL:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeRational(tag.getRational(i));
+ }
+ break;
+ case MpoTag.TYPE_UNDEFINED:
+ case MpoTag.TYPE_UNSIGNED_BYTE:
+ buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+ dataOutputStream.write(buf);
+ break;
+ case MpoTag.TYPE_UNSIGNED_SHORT:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeShort((short) tag.getValueAt(i));
+ }
+ break;
+ }
+ }
+
+ int size() {
+ return mSize;
+ }
+}
diff --git a/src/com/android/camera/mpo/MpoTag.java b/src/com/android/camera/mpo/MpoTag.java
new file mode 100644
index 000000000..bc0e6ce30
--- /dev/null
+++ b/src/com/android/camera/mpo/MpoTag.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.camera.mpo;
+
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.util.Log;
+
+import com.android.camera.exif.ExifTag;
+
+public class MpoTag extends ExifTag {
+ private static final String TAG = "MpoTag";
+ static final int TAG_SIZE = 12;
+
+ MpoTag(short tagId, short type, int componentCount, int ifd, boolean hasDefinedComponentCount) {
+ super(tagId, type, componentCount, ifd, hasDefinedComponentCount);
+ }
+
+ public boolean setValue(List<MpEntry> entries) {
+ if (getTagId() != (short) MpoInterface.TAG_MP_ENTRY) {
+ return false;
+ }
+
+ byte[] bytes = new byte[entries.size() * MpEntry.SIZE];
+ for (int i = 0; i < entries.size(); i++) {
+ MpEntry entry = entries.get(i);
+ entry.getBytes(ByteBuffer.wrap(bytes, i * MpEntry.SIZE, MpEntry.SIZE));
+ }
+ return setValue(bytes);
+ }
+
+ public List<MpEntry> getMpEntryValue() {
+ if (getTagId() != (short) MpoInterface.TAG_MP_ENTRY) {
+ return null;
+ }
+
+ byte[] bytes = getValueAsBytes();
+ List<MpEntry> entries = new ArrayList<MpEntry>(bytes.length / MpEntry.SIZE);
+ for (int i = 0; i < bytes.length; i += MpEntry.SIZE) {
+ entries.add(new MpEntry(ByteBuffer.wrap(bytes, i, MpEntry.SIZE)));
+ }
+ return entries;
+ }
+
+ static class MpEntry {
+ static final int SIZE = 16;
+ private int mImageAttrib;
+ private int mImageSize;
+ private int mImageOffset;
+ private short mDependantImage1;
+ private short mDependantImage2;
+
+ public MpEntry() {
+ this(0, 0, 0, (short) 0, (short) 0);
+ }
+
+ public MpEntry(int imageAttrib, int imageSize, int imageOffset) {
+ this(imageAttrib, imageSize, imageOffset, (short) 0, (short) 0);
+ }
+
+ public MpEntry(int imageAttrib, int imageSize, int imageOffset, short dependantImage1,
+ short dependantImage2) {
+ mImageAttrib = imageAttrib;
+ mImageSize = imageSize;
+ mImageOffset = imageOffset;
+ mDependantImage1 = dependantImage1;
+ mDependantImage2 = dependantImage2;
+ }
+
+ public MpEntry(ByteBuffer buffer) {
+ mImageAttrib = buffer.getInt();
+ mImageSize = buffer.getInt();
+ mImageOffset = buffer.getInt();
+ mDependantImage1 = buffer.getShort();
+ mDependantImage2 = buffer.getShort();
+ }
+
+ public int getImageAttrib() {
+ return mImageAttrib;
+ }
+
+ public int getImageSize() {
+ return mImageSize;
+ }
+
+ public int getImageOffset() {
+ return mImageOffset;
+ }
+
+ public short getDependantImage1() {
+ return mDependantImage1;
+ }
+
+ public short getDependantImage2() {
+ return mDependantImage2;
+ }
+
+ public void setImageAttrib(int imageAttrib) {
+ mImageAttrib = imageAttrib;
+ }
+
+ public void setImageSize(int imageSize) {
+ mImageSize = imageSize;
+ }
+
+ public void setImageOffset(int imageOffset) {
+ mImageOffset = imageOffset;
+ }
+
+ public void setDependantImage1(short depImage1) {
+ mDependantImage1 = depImage1;
+ }
+
+ public void setDependantImage2(short depImage2) {
+ mDependantImage2 = depImage2;
+ }
+
+ public boolean getBytes(ByteBuffer buffer) {
+ try {
+ buffer.putInt(mImageAttrib);
+ buffer.putInt(mImageSize);
+ buffer.putInt(mImageOffset);
+ buffer.putShort(mDependantImage1);
+ buffer.putShort(mDependantImage2);
+ } catch (BufferOverflowException e) {
+ Log.w(TAG, "Buffer size too small");
+ return false;
+ }
+
+ return true;
+ }
+ }
+} \ No newline at end of file