summaryrefslogtreecommitdiffstats
path: root/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
diff options
context:
space:
mode:
Diffstat (limited to 'gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java')
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java256
1 files changed, 165 insertions, 91 deletions
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
index f34321b82..6ea655431 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
@@ -16,6 +16,8 @@
package com.android.gallery3d.exif;
+import android.util.Log;
+
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -26,16 +28,15 @@ import java.nio.ByteOrder;
* This class provides a way to replace the Exif header of a JPEG image.
* <p>
* Below is an example of writing EXIF data into a file
+ *
* <pre>
* public static void writeExif(byte[] jpeg, ExifData exif, String path) {
* OutputStream os = null;
* try {
* os = new FileOutputStream(path);
* ExifOutputStream eos = new ExifOutputStream(os);
- *
* // Set the exif header
* eos.setExifData(exif);
- *
* // Write the original jpeg out, the header will be add into the file.
* eos.write(jpeg);
* } catch (FileNotFoundException e) {
@@ -54,8 +55,9 @@ import java.nio.ByteOrder;
* }
* </pre>
*/
-public class ExifOutputStream extends FilterOutputStream {
+class ExifOutputStream extends FilterOutputStream {
private static final String TAG = "ExifOutputStream";
+ private static final boolean DEBUG = false;
private static final int STATE_SOI = 0;
private static final int STATE_FRAME_HEADER = 1;
@@ -67,29 +69,32 @@ public class ExifOutputStream extends FilterOutputStream {
private static final short TIFF_LITTLE_ENDIAN = 0x4949;
private static final short TAG_SIZE = 12;
private static final short TIFF_HEADER_SIZE = 8;
+ private static final int MAX_EXIF_SIZE = 65535;
private ExifData mExifData;
private int mState = STATE_SOI;
private int mByteToSkip;
private int mByteToCopy;
private ByteBuffer mBuffer = ByteBuffer.allocate(4);
+ private final ExifInterface mInterface;
- public ExifOutputStream(OutputStream ou) {
+ protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
super(ou);
+ mInterface = iRef;
}
/**
- * Sets the ExifData to be written into the JPEG file. Should be called before writing image
- * data.
+ * Sets the ExifData to be written into the JPEG file. Should be called
+ * before writing image data.
*/
- public void setExifData(ExifData exifData) {
+ protected void setExifData(ExifData exifData) {
mExifData = exifData;
}
/**
* Gets the Exif header to be written into the JPEF file.
*/
- public ExifData getExifData() {
+ protected ExifData getExifData() {
return mExifData;
}
@@ -102,12 +107,12 @@ public class ExifOutputStream extends FilterOutputStream {
}
/**
- * 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.
+ * 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)
+ while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
&& length > 0) {
if (mByteToSkip > 0) {
int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
@@ -122,22 +127,29 @@ public class ExifOutputStream extends FilterOutputStream {
mByteToCopy -= byteToProcess;
offset += byteToProcess;
}
- if (length == 0) return;
+ 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;
+ if (mBuffer.position() < 2) {
+ return;
+ }
mBuffer.rewind();
- assert(mBuffer.getShort() == JpegHeader.SOI);
- out.write(mBuffer.array(), 0 ,2);
+ if (mBuffer.getShort() != JpegHeader.SOI) {
+ throw new IOException("Not a valid jpeg image, cannot write exif");
+ }
+ out.write(mBuffer.array(), 0, 2);
mState = STATE_FRAME_HEADER;
mBuffer.rewind();
writeExifData();
break;
case STATE_FRAME_HEADER:
- // We ignore the APP1 segment and copy all other segments until SOF tag.
+ // We ignore the APP1 segment and copy all other segments
+ // until SOF tag.
byteRead = requestByteToBuffer(4, buffer, offset, length);
offset += byteRead;
length -= byteRead;
@@ -149,15 +161,17 @@ public class ExifOutputStream extends FilterOutputStream {
mBuffer.rewind();
}
}
- if (mBuffer.position() < 4) return;
+ if (mBuffer.position() < 4) {
+ return;
+ }
mBuffer.rewind();
short marker = mBuffer.getShort();
if (marker == JpegHeader.APP1) {
- mByteToSkip = (mBuffer.getShort() & 0xff) - 2;
+ mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2;
mState = STATE_JPEG_DATA;
} else if (!JpegHeader.isSofMarker(marker)) {
out.write(mBuffer.array(), 0, 4);
- mByteToCopy = (mBuffer.getShort() & 0xff) - 2;
+ mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
} else {
out.write(mBuffer.array(), 0, 4);
mState = STATE_JPEG_DATA;
@@ -171,12 +185,14 @@ public class ExifOutputStream extends FilterOutputStream {
}
/**
- * 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.
+ * 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 {
- byte[] buf = new byte[] {(byte) (0xff & oneByte)};
+ byte[] buf = new byte[] {
+ (byte) (0xff & oneByte)
+ };
write(buf);
}
@@ -189,9 +205,17 @@ public class ExifOutputStream extends FilterOutputStream {
}
private void writeExifData() throws IOException {
- if (mExifData == null) return;
+ if (mExifData == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Writing exif data...");
+ }
createRequiredIfdAndTag();
int exifSize = calculateAllOffset();
+ if (exifSize + 8 > 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.APP1);
@@ -241,70 +265,34 @@ public class ExifOutputStream extends FilterOutputStream {
throws IOException {
ExifTag[] tags = ifd.getAllTags();
dataOutputStream.writeShort((short) tags.length);
- for (ExifTag tag: tags) {
+ for (ExifTag 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 {
- writeTagValue(tag, dataOutputStream);
+ ExifOutputStream.writeTagValue(tag, dataOutputStream);
for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
dataOutputStream.write(0);
}
}
}
dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
- for (ExifTag tag: tags) {
+ for (ExifTag tag : tags) {
if (tag.getDataSize() > 4) {
- writeTagValue(tag, dataOutputStream);
+ ExifOutputStream.writeTagValue(tag, dataOutputStream);
}
}
}
- private void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
- throws IOException {
- switch (tag.getDataType()) {
- case ExifTag.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 ExifTag.TYPE_LONG:
- case ExifTag.TYPE_UNSIGNED_LONG:
- for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- dataOutputStream.writeInt((int) tag.getValueAt(i));
- }
- break;
- case ExifTag.TYPE_RATIONAL:
- case ExifTag.TYPE_UNSIGNED_RATIONAL:
- for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- dataOutputStream.writeRational(tag.getRational(i));
- }
- break;
- case ExifTag.TYPE_UNDEFINED:
- case ExifTag.TYPE_UNSIGNED_BYTE:
- buf = new byte[tag.getComponentCount()];
- tag.getBytes(buf);
- dataOutputStream.write(buf);
- break;
- case ExifTag.TYPE_UNSIGNED_SHORT:
- for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- dataOutputStream.writeShort((short) tag.getValueAt(i));
- }
- break;
- }
- }
-
private int calculateOffsetOfIfd(IfdData ifd, int offset) {
offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
ExifTag[] tags = ifd.getAllTags();
- for(ExifTag tag: tags) {
+ for (ExifTag tag : tags) {
if (tag.getDataSize() > 4) {
tag.setOffset(offset);
offset += tag.getDataSize();
@@ -313,18 +301,21 @@ public class ExifOutputStream extends FilterOutputStream {
return offset;
}
- private void createRequiredIfdAndTag() {
+ private void createRequiredIfdAndTag() throws IOException {
// IFD0 is required for all file
IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
if (ifd0 == null) {
ifd0 = new IfdData(IfdId.TYPE_IFD_0);
mExifData.addIfdData(ifd0);
}
- ExifTag exifOffsetTag = new ExifTag(ExifTag.TAG_EXIF_IFD,
- ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0);
+ ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD);
+ if (exifOffsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_EXIF_IFD);
+ }
ifd0.setTag(exifOffsetTag);
- // Exif IFD is required for all file.
+ // Exif IFD is required for all files.
IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
if (exifIfd == null) {
exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
@@ -334,16 +325,23 @@ public class ExifOutputStream extends FilterOutputStream {
// GPS IFD
IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
if (gpsIfd != null) {
- ExifTag gpsOffsetTag = new ExifTag(ExifTag.TAG_GPS_IFD,
- ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0);
+ ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD);
+ if (gpsOffsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_GPS_IFD);
+ }
ifd0.setTag(gpsOffsetTag);
}
// Interoperability IFD
IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
if (interIfd != null) {
- ExifTag interOffsetTag = new ExifTag(ExifTag.TAG_INTEROPERABILITY_IFD,
- ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_EXIF);
+ ExifTag interOffsetTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD);
+ if (interOffsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_INTEROPERABILITY_IFD);
+ }
exifIfd.setTag(interOffsetTag);
}
@@ -351,27 +349,50 @@ public class ExifOutputStream extends FilterOutputStream {
// thumbnail
if (mExifData.hasCompressedThumbnail()) {
+
if (ifd1 == null) {
ifd1 = new IfdData(IfdId.TYPE_IFD_1);
mExifData.addIfdData(ifd1);
}
- ExifTag offsetTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT,
- ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1);
+
+ ExifTag offsetTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+ if (offsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+ }
+
ifd1.setTag(offsetTag);
- ExifTag lengthTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
- ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1);
+ ExifTag lengthTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+ if (lengthTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+ }
+
lengthTag.setValue(mExifData.getCompressedThumbnail().length);
ifd1.setTag(lengthTag);
- } else if (mExifData.hasUncompressedStrip()){
+
+ // Get rid of tags for uncompressed if they exist.
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+ } else if (mExifData.hasUncompressedStrip()) {
if (ifd1 == null) {
ifd1 = new IfdData(IfdId.TYPE_IFD_1);
mExifData.addIfdData(ifd1);
}
int stripCount = mExifData.getStripCount();
- ExifTag offsetTag = new ExifTag(ExifTag.TAG_STRIP_OFFSETS,
- ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1);
- ExifTag lengthTag = new ExifTag(ExifTag.TAG_STRIP_BYTE_COUNTS,
- ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1);
+ ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS);
+ if (offsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_STRIP_OFFSETS);
+ }
+ ExifTag lengthTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS);
+ if (lengthTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_STRIP_BYTE_COUNTS);
+ }
long[] lengths = new long[stripCount];
for (int i = 0; i < mExifData.getStripCount(); i++) {
lengths[i] = mExifData.getStrip(i).length;
@@ -379,6 +400,17 @@ public class ExifOutputStream extends FilterOutputStream {
lengthTag.setValue(lengths);
ifd1.setTag(offsetTag);
ifd1.setTag(lengthTag);
+ // Get rid of tags for compressed if they exist.
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+ ifd1.removeTag(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+ } else {
+ // Get rid of offset and length tags if there is no thumbnail.
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+ ifd1.removeTag(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
}
}
@@ -386,20 +418,21 @@ public class ExifOutputStream extends FilterOutputStream {
int offset = TIFF_HEADER_SIZE;
IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
offset = calculateOffsetOfIfd(ifd0, offset);
- ifd0.getTag(ExifTag.TAG_EXIF_IFD).setValue(offset);
+ ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset);
IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
offset = calculateOffsetOfIfd(exifIfd, offset);
IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
if (interIfd != null) {
- exifIfd.getTag(ExifTag.TAG_INTEROPERABILITY_IFD).setValue(offset);
+ exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
+ .setValue(offset);
offset = calculateOffsetOfIfd(interIfd, offset);
}
IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
if (gpsIfd != null) {
- ifd0.getTag(ExifTag.TAG_GPS_IFD).setValue(offset);
+ ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset);
offset = calculateOffsetOfIfd(gpsIfd, offset);
}
@@ -411,17 +444,58 @@ public class ExifOutputStream extends FilterOutputStream {
// thumbnail
if (mExifData.hasCompressedThumbnail()) {
- ifd1.getTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT).setValue(offset);
+ ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
+ .setValue(offset);
offset += mExifData.getCompressedThumbnail().length;
- } else if (mExifData.hasUncompressedStrip()){
+ } else if (mExifData.hasUncompressedStrip()) {
int stripCount = mExifData.getStripCount();
long[] offsets = new long[stripCount];
for (int i = 0; i < mExifData.getStripCount(); i++) {
offsets[i] = offset;
offset += mExifData.getStrip(i).length;
}
- ifd1.getTag(ExifTag.TAG_STRIP_OFFSETS).setValue(offsets);
+ ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue(
+ offsets);
}
return offset;
}
-} \ No newline at end of file
+
+ static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ switch (tag.getDataType()) {
+ case ExifTag.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 ExifTag.TYPE_LONG:
+ case ExifTag.TYPE_UNSIGNED_LONG:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeInt((int) tag.getValueAt(i));
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL:
+ case ExifTag.TYPE_UNSIGNED_RATIONAL:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeRational(tag.getRational(i));
+ }
+ break;
+ case ExifTag.TYPE_UNDEFINED:
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+ dataOutputStream.write(buf);
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeShort((short) tag.getValueAt(i));
+ }
+ break;
+ }
+ }
+}