diff options
Diffstat (limited to 'tests/src/com/android/gallery3d/exif')
11 files changed, 2259 insertions, 0 deletions
diff --git a/tests/src/com/android/gallery3d/exif/ExifDataTest.java b/tests/src/com/android/gallery3d/exif/ExifDataTest.java new file mode 100644 index 000000000..142cc6bf6 --- /dev/null +++ b/tests/src/com/android/gallery3d/exif/ExifDataTest.java @@ -0,0 +1,154 @@ +/* + * 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.gallery3d.exif; + +import android.test.suitebuilder.annotation.SmallTest; +import junit.framework.TestCase; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ExifDataTest extends TestCase { + Map<Integer, ExifTag> mTestTags; + ExifInterface mInterface; + private ExifTag mVersionTag; + private ExifTag mGpsVersionTag; + private ExifTag mModelTag; + private ExifTag mDateTimeTag; + private ExifTag mCompressionTag; + private ExifTag mThumbnailFormatTag; + private ExifTag mLongitudeTag; + private ExifTag mShutterTag; + private ExifTag mInteropIndex; + + @Override + public void setUp() throws Exception { + super.setUp(); + + mInterface = new ExifInterface(); + + // TYPE_UNDEFINED with 4 components + mVersionTag = mInterface.buildTag(ExifInterface.TAG_EXIF_VERSION, new byte[] { + 5, 4, 3, 2 + }); + // TYPE_UNSIGNED_BYTE with 4 components + mGpsVersionTag = mInterface.buildTag(ExifInterface.TAG_GPS_VERSION_ID, new byte[] { + 6, 7, 8, 9 + }); + // TYPE ASCII with arbitrary length + mModelTag = mInterface.buildTag(ExifInterface.TAG_MODEL, "helloworld"); + // TYPE_ASCII with 20 components + mDateTimeTag = mInterface.buildTag(ExifInterface.TAG_DATE_TIME, "2013:02:11 20:20:20"); + // TYPE_UNSIGNED_SHORT with 1 components + mCompressionTag = mInterface.buildTag(ExifInterface.TAG_COMPRESSION, 100); + // TYPE_UNSIGNED_LONG with 1 components + mThumbnailFormatTag = + mInterface.buildTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 100); + // TYPE_UNSIGNED_RATIONAL with 3 components + mLongitudeTag = mInterface.buildTag(ExifInterface.TAG_GPS_LONGITUDE, new Rational[] { + new Rational(2, 2), new Rational(11, 11), + new Rational(102, 102) + }); + // TYPE_RATIONAL with 1 components + mShutterTag = mInterface + .buildTag(ExifInterface.TAG_SHUTTER_SPEED_VALUE, new Rational(4, 6)); + // TYPE_ASCII with arbitrary length + mInteropIndex = mInterface.buildTag(ExifInterface.TAG_INTEROPERABILITY_INDEX, "foo"); + + mTestTags = new HashMap<Integer, ExifTag>(); + + mTestTags.put(ExifInterface.TAG_EXIF_VERSION, mVersionTag); + mTestTags.put(ExifInterface.TAG_GPS_VERSION_ID, mGpsVersionTag); + mTestTags.put(ExifInterface.TAG_MODEL, mModelTag); + mTestTags.put(ExifInterface.TAG_DATE_TIME, mDateTimeTag); + mTestTags.put(ExifInterface.TAG_COMPRESSION, mCompressionTag); + mTestTags.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, mThumbnailFormatTag); + mTestTags.put(ExifInterface.TAG_GPS_LONGITUDE, mLongitudeTag); + mTestTags.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, mShutterTag); + mTestTags.put(ExifInterface.TAG_INTEROPERABILITY_INDEX, mInteropIndex); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + mInterface = null; + mTestTags = null; + } + + @SmallTest + public void testAddTag() { + ExifData exifData = new ExifData(ByteOrder.BIG_ENDIAN); + + // Add all test tags + for (ExifTag t : mTestTags.values()) { + assertTrue(exifData.addTag(t) == null); + } + + // Make sure no initial thumbnails + assertFalse(exifData.hasCompressedThumbnail()); + assertFalse(exifData.hasUncompressedStrip()); + + // Check that we can set thumbnails + exifData.setStripBytes(3, new byte[] { + 1, 2, 3, 4, 5 + }); + assertTrue(exifData.hasUncompressedStrip()); + exifData.setCompressedThumbnail(new byte[] { + 1 + }); + assertTrue(exifData.hasCompressedThumbnail()); + + // Check that we can clear thumbnails + exifData.clearThumbnailAndStrips(); + assertFalse(exifData.hasCompressedThumbnail()); + assertFalse(exifData.hasUncompressedStrip()); + + // Make sure ifds exist + for (int i : IfdData.getIfds()) { + assertTrue(exifData.getIfdData(i) != null); + } + + // Get all test tags + List<ExifTag> allTags = exifData.getAllTags(); + assertTrue(allTags != null); + + // Make sure all test tags are in data + for (ExifTag t : mTestTags.values()) { + boolean check = false; + for (ExifTag i : allTags) { + if (t.equals(i)) { + check = true; + break; + } + } + assertTrue(check); + } + + // Check if getting tags for a tid works + List<ExifTag> tidTags = exifData.getAllTagsForTagId(ExifInterface + .getTrueTagKey(ExifInterface.TAG_SHUTTER_SPEED_VALUE)); + assertTrue(tidTags.size() == 1); + assertTrue(tidTags.get(0).equals(mShutterTag)); + + // Check if getting tags for an ifd works + List<ExifTag> ifdTags = exifData.getAllTagsForIfd(IfdId.TYPE_IFD_INTEROPERABILITY); + assertTrue(ifdTags.size() == 1); + assertTrue(ifdTags.get(0).equals(mInteropIndex)); + + } +} diff --git a/tests/src/com/android/gallery3d/exif/ExifInterfaceTest.java b/tests/src/com/android/gallery3d/exif/ExifInterfaceTest.java new file mode 100644 index 000000000..01b2a323e --- /dev/null +++ b/tests/src/com/android/gallery3d/exif/ExifInterfaceTest.java @@ -0,0 +1,533 @@ +/* + * 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.gallery3d.exif; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import android.test.suitebuilder.annotation.MediumTest; + +import java.io.ByteArrayInputStream; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; + +import java.io.IOException; +import java.io.InputStream; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ExifInterfaceTest extends ExifXmlDataTestCase { + + private File mTmpFile; + private List<Map<Short, List<String>>> mGroundTruth; + private ExifInterface mInterface; + private ExifTag mVersionTag; + private ExifTag mGpsVersionTag; + private ExifTag mModelTag; + private ExifTag mDateTimeTag; + private ExifTag mCompressionTag; + private ExifTag mThumbnailFormatTag; + private ExifTag mLongitudeTag; + private ExifTag mShutterTag; + Map<Integer, ExifTag> mTestTags; + Map<Integer, Integer> mTagDefinitions; + + public ExifInterfaceTest(int imageRes, int xmlRes) { + super(imageRes, xmlRes); + } + + public ExifInterfaceTest(String imagePath, String xmlPath) { + super(imagePath, xmlPath); + } + + @MediumTest + public void testInterface() throws Exception { + + InputStream imageInputStream = null; + try { + // Basic checks + + // Check if bitmap is valid + byte[] imgData = Util.readToByteArray(getImageInputStream()); + imageInputStream = new ByteArrayInputStream(imgData); + checkBitmap(imageInputStream); + + // Check defines + int tag = ExifInterface.defineTag(1, (short) 0x0100); + assertTrue(getImageTitle(), tag == 0x00010100); + int tagDef = mInterface.getTagDefinition((short) 0x0100, IfdId.TYPE_IFD_0); + assertTrue(getImageTitle(), tagDef == 0x03040001); + int[] allowed = ExifInterface.getAllowedIfdsFromInfo(mInterface.getTagInfo().get( + ExifInterface.TAG_IMAGE_WIDTH)); + assertTrue(getImageTitle(), allowed.length == 2 && allowed[0] == IfdId.TYPE_IFD_0 + && allowed[1] == IfdId.TYPE_IFD_1); + + // Check if there are any initial tags + assertTrue(getImageTitle(), mInterface.getAllTags() == null); + + // ///////// Basic read/write testing + + // Make sure we can read + imageInputStream = new ByteArrayInputStream(imgData); + mInterface.readExif(imageInputStream); + + // Check tags against ground truth + checkTagsAgainstXml(mInterface.getAllTags()); + + // Make sure clearing Exif works + mInterface.clearExif(); + assertTrue(getImageTitle(), mInterface.getAllTags() == null); + + // Make sure setting tags works + mInterface.setTags(mTestTags.values()); + checkTagsAgainstHash(mInterface.getAllTags(), mTestTags); + + // Try writing over bitmap exif + ByteArrayOutputStream imgModified = new ByteArrayOutputStream(); + mInterface.writeExif(imgData, imgModified); + + // Check if bitmap is valid + byte[] imgData2 = imgModified.toByteArray(); + imageInputStream = new ByteArrayInputStream(imgData2); + checkBitmap(imageInputStream); + + // Make sure we get the same tags out + imageInputStream = new ByteArrayInputStream(imgData2); + mInterface.readExif(imageInputStream); + checkTagsAgainstHash(mInterface.getAllTags(), mTestTags); + + // Reread original image + imageInputStream = new ByteArrayInputStream(imgData); + mInterface.readExif(imageInputStream); + + // Write out with original exif + imgModified = new ByteArrayOutputStream(); + mInterface.writeExif(imgData2, imgModified); + + // Read back in exif and check tags + imgData2 = imgModified.toByteArray(); + imageInputStream = new ByteArrayInputStream(imgData2); + mInterface.readExif(imageInputStream); + checkTagsAgainstXml(mInterface.getAllTags()); + + // Check if bitmap is valid + imageInputStream = new ByteArrayInputStream(imgData2); + checkBitmap(imageInputStream); + + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } finally { + Util.closeSilently(imageInputStream); + } + } + + @MediumTest + public void testInterfaceModify() throws Exception { + + // TODO: This test is dependent on galaxy_nexus jpeg/xml file. + InputStream imageInputStream = null; + try { + // Check if bitmap is valid + byte[] imgData = Util.readToByteArray(getImageInputStream()); + imageInputStream = new ByteArrayInputStream(imgData); + checkBitmap(imageInputStream); + + // ///////// Exif modifier testing. + + // Read exif and write to temp file + imageInputStream = new ByteArrayInputStream(imgData); + mInterface.readExif(imageInputStream); + mInterface.writeExif(imgData, mTmpFile.getPath()); + + // Check if bitmap is valid + imageInputStream = new FileInputStream(mTmpFile); + checkBitmap(imageInputStream); + + // Create some tags to overwrite with + ArrayList<ExifTag> tags = new ArrayList<ExifTag>(); + tags.add(mInterface.buildTag(ExifInterface.TAG_ORIENTATION, + ExifInterface.Orientation.RIGHT_TOP)); + tags.add(mInterface.buildTag(ExifInterface.TAG_USER_COMMENT, "goooooooooooooooooogle")); + + // Attempt to rewrite tags + assertTrue(getImageTitle(), mInterface.rewriteExif(mTmpFile.getPath(), tags)); + + imageInputStream.close(); + // Check if bitmap is valid + imageInputStream = new FileInputStream(mTmpFile); + checkBitmap(imageInputStream); + + // Read tags and check against xml + mInterface.readExif(mTmpFile.getPath()); + for (ExifTag t : mInterface.getAllTags()) { + short tid = t.getTagId(); + if (tid != ExifInterface.getTrueTagKey(ExifInterface.TAG_ORIENTATION) + && tid != ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT)) { + checkTagAgainstXml(t); + } + } + assertTrue(getImageTitle(), mInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION) + .shortValue() == ExifInterface.Orientation.RIGHT_TOP); + String valString = mInterface.getTagStringValue(ExifInterface.TAG_USER_COMMENT); + assertTrue(getImageTitle(), valString.equals("goooooooooooooooooogle")); + + // Test forced modify + + // Create some tags to overwrite with + tags = new ArrayList<ExifTag>(); + tags.add(mInterface.buildTag(ExifInterface.TAG_SOFTWARE, "magic super photomaker pro")); + tags.add(mInterface.buildTag(ExifInterface.TAG_USER_COMMENT, "noodles")); + tags.add(mInterface.buildTag(ExifInterface.TAG_ORIENTATION, + ExifInterface.Orientation.TOP_LEFT)); + + // Force rewrite tags + mInterface.forceRewriteExif(mTmpFile.getPath(), tags); + + imageInputStream.close(); + // Check if bitmap is valid + imageInputStream = new FileInputStream(mTmpFile); + checkBitmap(imageInputStream); + + // Read tags and check against xml + mInterface.readExif(mTmpFile.getPath()); + for (ExifTag t : mInterface.getAllTags()) { + short tid = t.getTagId(); + if (!ExifInterface.isOffsetTag(tid) + && tid != ExifInterface.getTrueTagKey(ExifInterface.TAG_SOFTWARE) + && tid != ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT)) { + checkTagAgainstXml(t); + } + } + valString = mInterface.getTagStringValue(ExifInterface.TAG_SOFTWARE); + String compareString = "magic super photomaker pro\0"; + assertTrue(getImageTitle(), valString.equals(compareString)); + valString = mInterface.getTagStringValue(ExifInterface.TAG_USER_COMMENT); + assertTrue(getImageTitle(), valString.equals("noodles")); + + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } finally { + Util.closeSilently(imageInputStream); + } + } + + @MediumTest + public void testInterfaceDefines() throws Exception { + + InputStream imageInputStream = null; + try { + // Check if bitmap is valid + byte[] imgData = Util.readToByteArray(getImageInputStream()); + imageInputStream = new ByteArrayInputStream(imgData); + checkBitmap(imageInputStream); + + // Set some tags. + mInterface.setTags(mTestTags.values()); + + // Check tag definitions against default + for (Integer i : mTestTags.keySet()) { + int check = mTagDefinitions.get(i).intValue(); + int actual = mInterface.getTagInfo().get(i); + assertTrue(check == actual); + } + + // Check defines + int tag1 = ExifInterface.defineTag(IfdId.TYPE_IFD_1, (short) 42); + int tag2 = ExifInterface.defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 43); + assertTrue(tag1 == 0x0001002a); + assertTrue(tag2 == 0x0003002b); + + // Define some non-standard tags + assertTrue(mInterface.setTagDefinition((short) 42, IfdId.TYPE_IFD_1, + ExifTag.TYPE_UNSIGNED_BYTE, (short) 16, new int[] { + IfdId.TYPE_IFD_1 + }) == tag1); + assertTrue(mInterface.getTagInfo().get(tag1) == 0x02010010); + assertTrue(mInterface.setTagDefinition((short) 43, IfdId.TYPE_IFD_INTEROPERABILITY, + ExifTag.TYPE_ASCII, (short) 5, new int[] { + IfdId.TYPE_IFD_GPS, IfdId.TYPE_IFD_INTEROPERABILITY + }) == tag2); + assertTrue(mInterface.getTagInfo().get(tag2) == 0x18020005); + + // Make sure these don't work + assertTrue(mInterface.setTagDefinition((short) 42, IfdId.TYPE_IFD_1, + ExifTag.TYPE_UNSIGNED_BYTE, (short) 16, new int[] { + IfdId.TYPE_IFD_0 + }) == ExifInterface.TAG_NULL); + assertTrue(mInterface.setTagDefinition((short) 42, IfdId.TYPE_IFD_1, (short) 0, + (short) 16, new int[] { + IfdId.TYPE_IFD_1 + }) == ExifInterface.TAG_NULL); + assertTrue(mInterface.setTagDefinition((short) 42, 5, ExifTag.TYPE_UNSIGNED_BYTE, + (short) 16, new int[] { + 5 + }) == ExifInterface.TAG_NULL); + assertTrue(mInterface.setTagDefinition((short) 42, IfdId.TYPE_IFD_1, + ExifTag.TYPE_UNSIGNED_BYTE, (short) 16, new int[] { + -1 + }) == ExifInterface.TAG_NULL); + assertTrue(mInterface.setTagDefinition((short) 43, IfdId.TYPE_IFD_GPS, + ExifTag.TYPE_ASCII, (short) 5, new int[] { + IfdId.TYPE_IFD_GPS + }) == ExifInterface.TAG_NULL); + assertTrue(mInterface.setTagDefinition((short) 43, IfdId.TYPE_IFD_0, + ExifTag.TYPE_ASCII, (short) 5, new int[] { + IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_GPS + }) == ExifInterface.TAG_NULL); + + // Set some tags + mInterface.setTags(mTestTags.values()); + checkTagsAgainstHash(mInterface.getAllTags(), mTestTags); + + // Make some tags using new defines + ExifTag defTag0 = mInterface.buildTag(tag1, new byte[] { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 + }); + assertTrue(defTag0 != null); + ExifTag defTag1 = mInterface.buildTag(tag2, "hihi"); + assertTrue(defTag1 != null); + ExifTag defTag2 = mInterface.buildTag(tag2, IfdId.TYPE_IFD_GPS, "byte"); + assertTrue(defTag2 != null); + + // Make sure these don't work + ExifTag badTag = mInterface.buildTag(tag1, new byte[] { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + }); + assertTrue(badTag == null); + badTag = mInterface.buildTag(tag1, IfdId.TYPE_IFD_0, new byte[] { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 + }); + assertTrue(badTag == null); + badTag = mInterface.buildTag(0x0002002a, new byte[] { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 + }); + assertTrue(badTag == null); + badTag = mInterface.buildTag(tag2, new byte[] { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 + }); + assertTrue(badTag == null); + + // Set the tags + assertTrue(mInterface.setTag(defTag0) == null); + assertTrue(mInterface.setTag(defTag1) == null); + assertTrue(mInterface.setTag(defTag2) == null); + assertTrue(mInterface.setTag(defTag0).equals(defTag0)); + assertTrue(mInterface.setTag(null) == null); + assertTrue(mInterface.setTagValue(tag2, "yoyo") == true); + assertTrue(mInterface.setTagValue(tag2, "yaaarggg") == false); + assertTrue(mInterface.getTagStringValue(tag2).equals("yoyo\0")); + + // Try writing over bitmap exif + ByteArrayOutputStream imgModified = new ByteArrayOutputStream(); + mInterface.writeExif(imgData, imgModified); + + // Check if bitmap is valid + byte[] imgData2 = imgModified.toByteArray(); + imageInputStream = new ByteArrayInputStream(imgData2); + checkBitmap(imageInputStream); + + // Read back in the tags + mInterface.readExif(imgData2); + + // Check tags + for (ExifTag t : mInterface.getAllTags()) { + int tid = t.getTagId(); + if (tid != ExifInterface.getTrueTagKey(tag1) + && tid != ExifInterface.getTrueTagKey(tag2)) { + checkTagAgainstHash(t, mTestTags); + } + } + assertTrue(Arrays.equals(mInterface.getTagByteValues(tag1), new byte[] { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 + })); + assertTrue(mInterface.getTagStringValue(tag2).equals("yoyo\0")); + assertTrue(mInterface.getTagStringValue(tag2, IfdId.TYPE_IFD_GPS).equals("byte\0")); + + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } finally { + Util.closeSilently(imageInputStream); + } + } + + @MediumTest + public void testInterfaceThumbnails() throws Exception { + + InputStream imageInputStream = null; + try { + // Check if bitmap is valid + byte[] imgData = Util.readToByteArray(getImageInputStream()); + imageInputStream = new ByteArrayInputStream(imgData); + checkBitmap(imageInputStream); + + // Check thumbnails + mInterface.readExif(imgData); + Bitmap bmap = mInterface.getThumbnailBitmap(); + assertTrue(getImageTitle(), bmap != null); + + // Make a new thumbnail and set it + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inSampleSize = 16; + Bitmap thumb = BitmapFactory.decodeByteArray(imgData, 0, imgData.length, opts); + assertTrue(getImageTitle(), thumb != null); + assertTrue(getImageTitle(), mInterface.setCompressedThumbnail(thumb) == true); + + // Write out image + ByteArrayOutputStream outData = new ByteArrayOutputStream(); + mInterface.writeExif(imgData, outData); + + // Make sure bitmap is still valid + byte[] imgData2 = outData.toByteArray(); + imageInputStream = new ByteArrayInputStream(imgData2); + checkBitmap(imageInputStream); + + // Read in bitmap and make sure thumbnail is still valid + mInterface.readExif(imgData2); + bmap = mInterface.getThumbnailBitmap(); + assertTrue(getImageTitle(), bmap != null); + + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } finally { + Util.closeSilently(imageInputStream); + } + } + + @Override + public void setUp() throws Exception { + super.setUp(); + mTmpFile = File.createTempFile("exif_test", ".jpg"); + mGroundTruth = ExifXmlReader.readXml(getXmlParser()); + + mInterface = new ExifInterface(); + + // TYPE_UNDEFINED with 4 components + mVersionTag = mInterface.buildTag(ExifInterface.TAG_EXIF_VERSION, new byte[] { + 5, 4, 3, 2 + }); + // TYPE_UNSIGNED_BYTE with 4 components + mGpsVersionTag = mInterface.buildTag(ExifInterface.TAG_GPS_VERSION_ID, new byte[] { + 6, 7, 8, 9 + }); + // TYPE ASCII with arbitary length + mModelTag = mInterface.buildTag(ExifInterface.TAG_MODEL, "helloworld"); + // TYPE_ASCII with 20 components + mDateTimeTag = mInterface.buildTag(ExifInterface.TAG_DATE_TIME, "2013:02:11 20:20:20"); + // TYPE_UNSIGNED_SHORT with 1 components + mCompressionTag = mInterface.buildTag(ExifInterface.TAG_COMPRESSION, 100); + // TYPE_UNSIGNED_LONG with 1 components + mThumbnailFormatTag = + mInterface.buildTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 100); + // TYPE_UNSIGNED_RATIONAL with 3 components + mLongitudeTag = mInterface.buildTag(ExifInterface.TAG_GPS_LONGITUDE, new Rational[] { + new Rational(2, 2), new Rational(11, 11), + new Rational(102, 102) + }); + // TYPE_RATIONAL with 1 components + mShutterTag = mInterface + .buildTag(ExifInterface.TAG_SHUTTER_SPEED_VALUE, new Rational(4, 6)); + + mTestTags = new HashMap<Integer, ExifTag>(); + + mTestTags.put(ExifInterface.TAG_EXIF_VERSION, mVersionTag); + mTestTags.put(ExifInterface.TAG_GPS_VERSION_ID, mGpsVersionTag); + mTestTags.put(ExifInterface.TAG_MODEL, mModelTag); + mTestTags.put(ExifInterface.TAG_DATE_TIME, mDateTimeTag); + mTestTags.put(ExifInterface.TAG_COMPRESSION, mCompressionTag); + mTestTags.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, mThumbnailFormatTag); + mTestTags.put(ExifInterface.TAG_GPS_LONGITUDE, mLongitudeTag); + mTestTags.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, mShutterTag); + + mTagDefinitions = new HashMap<Integer, Integer>(); + mTagDefinitions.put(ExifInterface.TAG_EXIF_VERSION, 0x04070004); + mTagDefinitions.put(ExifInterface.TAG_GPS_VERSION_ID, 0x10010004); + mTagDefinitions.put(ExifInterface.TAG_MODEL, 0x03020000); + mTagDefinitions.put(ExifInterface.TAG_DATE_TIME, 0x03020014); + mTagDefinitions.put(ExifInterface.TAG_COMPRESSION, 0x03030001); + mTagDefinitions.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 0x02040001); + mTagDefinitions.put(ExifInterface.TAG_GPS_LONGITUDE, 0x100a0003); + mTagDefinitions.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, 0x040a0001); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + mTmpFile.delete(); + } + + // Helper functions + + private void checkTagAgainstXml(ExifTag tag) { + List<String> truth = mGroundTruth.get(tag.getIfd()).get(tag.getTagId()); + + if (truth == null) { + fail(String.format("Unknown Tag %02x", tag.getTagId()) + ", " + getImageTitle()); + } + + // No value from exiftool. + if (truth.contains(null)) + return; + + String dataString = Util.tagValueToString(tag).trim(); + assertTrue(String.format("Tag %02x", tag.getTagId()) + ", " + getImageTitle() + + ": " + dataString, + truth.contains(dataString)); + } + + private void checkTagsAgainstXml(List<ExifTag> tags) { + for (ExifTag t : tags) { + checkTagAgainstXml(t); + } + } + + private void checkTagAgainstHash(ExifTag tag, Map<Integer, ExifTag> testTags) { + int tagdef = mInterface.getTagDefinitionForTag(tag); + assertTrue(getImageTitle(), tagdef != ExifInterface.TAG_NULL); + ExifTag t = testTags.get(tagdef); + // Ignore offset tags & other special tags + if (!ExifInterface.sBannedDefines.contains(tag.getTagId())) { + assertTrue(getImageTitle(), t != null); + } else { + return; + } + if (t == tag) + return; + assertTrue(getImageTitle(), tag.equals(t)); + assertTrue(getImageTitle(), tag.getDataType() == t.getDataType()); + assertTrue(getImageTitle(), tag.getTagId() == t.getTagId()); + assertTrue(getImageTitle(), tag.getIfd() == t.getIfd()); + assertTrue(getImageTitle(), tag.getComponentCount() == t.getComponentCount()); + } + + private void checkTagsAgainstHash(List<ExifTag> tags, Map<Integer, ExifTag> testTags) { + for (ExifTag t : tags) { + checkTagAgainstHash(t, testTags); + } + } + + private void checkBitmap(InputStream inputStream) throws IOException { + Bitmap bmp = BitmapFactory.decodeStream(inputStream); + assertTrue(getImageTitle(), bmp != null); + } + +} diff --git a/tests/src/com/android/gallery3d/exif/ExifModifierTest.java b/tests/src/com/android/gallery3d/exif/ExifModifierTest.java new file mode 100644 index 000000000..96f405ef6 --- /dev/null +++ b/tests/src/com/android/gallery3d/exif/ExifModifierTest.java @@ -0,0 +1,184 @@ +/* + * 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.gallery3d.exif; + +import android.test.suitebuilder.annotation.MediumTest; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel.MapMode; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ExifModifierTest extends ExifXmlDataTestCase { + + private File mTmpFile; + private List<Map<Short, List<String>>> mGroundTruth; + private ExifInterface mInterface; + private Map<Short, ExifTag> mTestTags; + ExifTag mVersionTag; + ExifTag mGpsVersionTag; + ExifTag mModelTag; + ExifTag mDateTimeTag; + ExifTag mCompressionTag; + ExifTag mThumbnailFormatTag; + ExifTag mLongitudeTag; + ExifTag mShutterTag; + + @Override + public void setUp() throws Exception { + super.setUp(); + mGroundTruth = ExifXmlReader.readXml(getXmlParser()); + mTmpFile = File.createTempFile("exif_test", ".jpg"); + FileOutputStream os = null; + InputStream is = getImageInputStream(); + try { + os = new FileOutputStream(mTmpFile); + byte[] buf = new byte[1024]; + int n; + while ((n = is.read(buf)) > 0) { + os.write(buf, 0, n); + } + } finally { + Util.closeSilently(os); + } + + // TYPE_UNDEFINED with 4 components + mVersionTag = mInterface.buildTag(ExifInterface.TAG_EXIF_VERSION, new byte[] { + 1, 2, 3, 4 + }); + // TYPE_UNSIGNED_BYTE with 4 components + mGpsVersionTag = mInterface.buildTag(ExifInterface.TAG_GPS_VERSION_ID, new byte[] { + 4, 3, 2, 1 + }); + // TYPE ASCII with arbitary length + mModelTag = mInterface.buildTag(ExifInterface.TAG_MODEL, "end-of-the-world"); + // TYPE_ASCII with 20 components + mDateTimeTag = mInterface.buildTag(ExifInterface.TAG_DATE_TIME, "2012:12:31 23:59:59"); + // TYPE_UNSIGNED_SHORT with 1 components + mCompressionTag = mInterface.buildTag(ExifInterface.TAG_COMPRESSION, 100); + // TYPE_UNSIGNED_LONG with 1 components + mThumbnailFormatTag = + mInterface.buildTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 100); + // TYPE_UNSIGNED_RATIONAL with 3 components + mLongitudeTag = mInterface.buildTag(ExifInterface.TAG_GPS_LONGITUDE, new Rational[] { + new Rational(1, 1), new Rational(10, 10), + new Rational(100, 100) + }); + // TYPE_RATIONAL with 1 components + mShutterTag = mInterface + .buildTag(ExifInterface.TAG_SHUTTER_SPEED_VALUE, new Rational(1, 1)); + + mTestTags = new HashMap<Short, ExifTag>(); + + mTestTags.put(mVersionTag.getTagId(), mVersionTag); + mTestTags.put(mGpsVersionTag.getTagId(), mGpsVersionTag); + mTestTags.put(mModelTag.getTagId(), mModelTag); + mTestTags.put(mDateTimeTag.getTagId(), mDateTimeTag); + mTestTags.put(mCompressionTag.getTagId(), mCompressionTag); + mTestTags.put(mThumbnailFormatTag.getTagId(), mThumbnailFormatTag); + mTestTags.put(mLongitudeTag.getTagId(), mLongitudeTag); + mTestTags.put(mShutterTag.getTagId(), mShutterTag); + } + + public ExifModifierTest(int imageRes, int xmlRes) { + super(imageRes, xmlRes); + mInterface = new ExifInterface(); + } + + public ExifModifierTest(String imagePath, String xmlPath) { + super(imagePath, xmlPath); + mInterface = new ExifInterface(); + } + + @MediumTest + public void testModify() throws Exception { + Map<Short, Boolean> results = new HashMap<Short, Boolean>(); + + RandomAccessFile file = null; + try { + file = new RandomAccessFile(mTmpFile, "rw"); + MappedByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, file.length()); + for (ExifTag tag : mTestTags.values()) { + ExifModifier modifier = new ExifModifier(buf, mInterface); + modifier.modifyTag(tag); + boolean result = modifier.commit(); + results.put(tag.getTagId(), result); + buf.force(); + buf.position(0); + + if (!result) { + List<String> value = mGroundTruth.get(tag.getIfd()).get(tag.getTagId()); + assertTrue(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(), + value == null || tag.getTagId() == ExifInterface.TAG_MODEL); + } + } + } finally { + Util.closeSilently(file); + } + + // Parse the new file and check the result + InputStream is = null; + try { + is = new FileInputStream(mTmpFile); + ExifData data = new ExifReader(mInterface).read(is); + for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { + checkIfd(data.getIfdData(i), mGroundTruth.get(i), results); + } + } finally { + Util.closeSilently(is); + } + + } + + private void checkIfd(IfdData ifd, Map<Short, List<String>> ifdValue, + Map<Short, Boolean> results) { + if (ifd == null) { + assertEquals(getImageTitle(), 0, ifdValue.size()); + return; + } + ExifTag[] tags = ifd.getAllTags(); + for (ExifTag tag : tags) { + List<String> truth = ifdValue.get(tag.getTagId()); + assertNotNull(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(), truth); + if (truth.contains(null)) { + continue; + } + + ExifTag newTag = mTestTags.get(tag.getTagId()); + if (newTag != null + && results.get(tag.getTagId())) { + assertEquals(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(), + Util.tagValueToString(newTag), Util.tagValueToString(tag)); + } else { + assertTrue(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(), + truth.contains(Util.tagValueToString(tag).trim())); + } + } + assertEquals(getImageTitle(), ifdValue.size(), tags.length); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + mTmpFile.delete(); + } +} diff --git a/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java b/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java new file mode 100644 index 000000000..151bdbc99 --- /dev/null +++ b/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java @@ -0,0 +1,198 @@ +/* + * 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.gallery3d.exif; + +import android.test.suitebuilder.annotation.MediumTest; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +public class ExifOutputStreamTest extends ExifXmlDataTestCase { + + private File mTmpFile; + + private ExifInterface mInterface; + + @Override + public void setUp() throws Exception { + super.setUp(); + mTmpFile = File.createTempFile("exif_test", ".jpg"); + } + + public ExifOutputStreamTest(int imgRes, int xmlRes) { + super(imgRes, xmlRes); + mInterface = new ExifInterface(); + } + + public ExifOutputStreamTest(String imgPath, String xmlPath) { + super(imgPath, xmlPath); + mInterface = new ExifInterface(); + } + + @MediumTest + public void testExifOutputStream() throws Exception { + InputStream imageInputStream = null; + InputStream exifInputStream = null; + FileInputStream reDecodeInputStream = null; + FileInputStream reParseInputStream = null; + + InputStream dangerInputStream = null; + OutputStream dangerOutputStream = null; + try { + try { + byte[] imgData = Util.readToByteArray(getImageInputStream()); + imageInputStream = new ByteArrayInputStream(imgData); + exifInputStream = new ByteArrayInputStream(imgData); + + // Read the image data + Bitmap bmp = BitmapFactory.decodeStream(imageInputStream); + // The image is invalid + if (bmp == null) { + return; + } + + // Read exif data + ExifData exifData = new ExifReader(mInterface).read(exifInputStream); + + // Encode the image with the exif data + FileOutputStream outputStream = new FileOutputStream(mTmpFile); + ExifOutputStream exifOutputStream = new ExifOutputStream(outputStream, mInterface); + exifOutputStream.setExifData(exifData); + bmp.compress(Bitmap.CompressFormat.JPEG, 90, exifOutputStream); + exifOutputStream.close(); + exifOutputStream = null; + + // Re-decode the temp file and check the data. + reDecodeInputStream = new FileInputStream(mTmpFile); + Bitmap decodedBmp = BitmapFactory.decodeStream(reDecodeInputStream); + assertNotNull(getImageTitle(), decodedBmp); + reDecodeInputStream.close(); + + // Re-parse the temp file the check EXIF tag + reParseInputStream = new FileInputStream(mTmpFile); + ExifData reExifData = new ExifReader(mInterface).read(reParseInputStream); + assertEquals(getImageTitle(), exifData, reExifData); + reParseInputStream.close(); + + // Try writing exif to file with existing exif. + dangerOutputStream = (OutputStream) new FileOutputStream(mTmpFile); + exifOutputStream = new ExifOutputStream(dangerOutputStream, mInterface); + exifOutputStream.setExifData(exifData); + exifOutputStream.write(imgData); + // exifOutputStream.write(strippedImgData); + exifOutputStream.close(); + exifOutputStream = null; + + // Make sure it still can be parsed into a bitmap. + dangerInputStream = (InputStream) new FileInputStream(mTmpFile); + decodedBmp = null; + decodedBmp = BitmapFactory.decodeStream(dangerInputStream); + assertNotNull(getImageTitle(), decodedBmp); + dangerInputStream.close(); + dangerInputStream = null; + + // Make sure exif is still well-formatted. + dangerInputStream = (InputStream) new FileInputStream(mTmpFile); + reExifData = null; + reExifData = new ExifReader(mInterface).read(dangerInputStream); + assertEquals(getImageTitle(), exifData, reExifData); + dangerInputStream.close(); + dangerInputStream = null; + + } finally { + Util.closeSilently(imageInputStream); + Util.closeSilently(exifInputStream); + Util.closeSilently(reDecodeInputStream); + Util.closeSilently(reParseInputStream); + + Util.closeSilently(dangerInputStream); + Util.closeSilently(dangerOutputStream); + } + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } + } + + @MediumTest + public void testOutputSpeed() throws Exception { + final String LOGTAG = "testOutputSpeed"; + InputStream imageInputStream = null; + OutputStream imageOutputStream = null; + try { + try { + imageInputStream = getImageInputStream(); + // Read the image data + Bitmap bmp = BitmapFactory.decodeStream(imageInputStream); + // The image is invalid + if (bmp == null) { + return; + } + imageInputStream.close(); + int nLoops = 20; + long totalReadDuration = 0; + long totalWriteDuration = 0; + for (int i = 0; i < nLoops; i++) { + imageInputStream = reopenFileStream(); + // Read exif data + long startTime = System.nanoTime(); + ExifData exifData = new ExifReader(mInterface).read(imageInputStream); + long endTime = System.nanoTime(); + long duration = endTime - startTime; + totalReadDuration += duration; + Log.v(LOGTAG, " read time: " + duration); + imageInputStream.close(); + + // Encode the image with the exif data + imageOutputStream = (OutputStream) new FileOutputStream(mTmpFile); + ExifOutputStream exifOutputStream = new ExifOutputStream(imageOutputStream, + mInterface); + exifOutputStream.setExifData(exifData); + startTime = System.nanoTime(); + bmp.compress(Bitmap.CompressFormat.JPEG, 90, exifOutputStream); + endTime = System.nanoTime(); + duration = endTime - startTime; + totalWriteDuration += duration; + Log.v(LOGTAG, " write time: " + duration); + exifOutputStream.close(); + } + Log.v(LOGTAG, "======================= normal"); + Log.v(LOGTAG, "avg read time: " + totalReadDuration / nLoops); + Log.v(LOGTAG, "avg write time: " + totalWriteDuration / nLoops); + Log.v(LOGTAG, "======================="); + } finally { + Util.closeSilently(imageInputStream); + Util.closeSilently(imageOutputStream); + } + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + mTmpFile.delete(); + } +} diff --git a/tests/src/com/android/gallery3d/exif/ExifParserTest.java b/tests/src/com/android/gallery3d/exif/ExifParserTest.java new file mode 100644 index 000000000..247ea022b --- /dev/null +++ b/tests/src/com/android/gallery3d/exif/ExifParserTest.java @@ -0,0 +1,243 @@ +/* + * 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.gallery3d.exif; + +import android.test.suitebuilder.annotation.MediumTest; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import java.util.List; +import java.util.Map; + +public class ExifParserTest extends ExifXmlDataTestCase { + private static final String TAG = "ExifParserTest"; + + private ExifInterface mInterface; + + public ExifParserTest(int imgRes, int xmlRes) { + super(imgRes, xmlRes); + mInterface = new ExifInterface(); + } + + public ExifParserTest(String imgPath, String xmlPath) { + super(imgPath, xmlPath); + mInterface = new ExifInterface(); + } + + private List<Map<Short, List<String>>> mGroundTruth; + + @Override + public void setUp() throws Exception { + super.setUp(); + mGroundTruth = ExifXmlReader.readXml(getXmlParser()); + } + + @MediumTest + public void testParse() throws Exception { + try { + ExifParser parser = ExifParser.parse(getImageInputStream(), mInterface); + int event = parser.next(); + while (event != ExifParser.EVENT_END) { + switch (event) { + case ExifParser.EVENT_START_OF_IFD: + break; + case ExifParser.EVENT_NEW_TAG: + ExifTag tag = parser.getTag(); + if (!tag.hasValue()) { + parser.registerForTagValue(tag); + } else { + checkTag(tag); + } + break; + case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: + tag = parser.getTag(); + if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) { + byte[] buf = new byte[tag.getComponentCount()]; + parser.read(buf); + assertTrue(TAG, tag.setValue(buf)); + } + checkTag(tag); + break; + } + event = parser.next(); + } + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } + } + + private void checkTag(ExifTag tag) { + List<String> truth = mGroundTruth.get(tag.getIfd()).get(tag.getTagId()); + + if (truth == null) { + fail(String.format("Unknown Tag %02x", tag.getTagId()) + ", " + getImageTitle()); + } + + // No value from exiftool. + if (truth.contains(null)) { + return; + } + + String dataString = Util.tagValueToString(tag).trim(); + assertTrue(String.format("Tag %02x", tag.getTagId()) + ", " + getImageTitle() + + ": " + dataString, + truth.contains(dataString)); + } + + private void parseOneIfd(int ifd, int options) throws Exception { + try { + Map<Short, List<String>> expectedResult = mGroundTruth.get(ifd); + int numOfTag = 0; + ExifParser parser = ExifParser.parse(getImageInputStream(), options, mInterface); + int event = parser.next(); + while (event != ExifParser.EVENT_END) { + switch (event) { + case ExifParser.EVENT_START_OF_IFD: + assertEquals(getImageTitle(), ifd, parser.getCurrentIfd()); + break; + case ExifParser.EVENT_NEW_TAG: + ExifTag tag = parser.getTag(); + numOfTag++; + if (tag.hasValue()) { + checkTag(tag); + } else { + parser.registerForTagValue(tag); + } + break; + case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: + tag = parser.getTag(); + if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) { + byte[] buf = new byte[tag.getComponentCount()]; + parser.read(buf); + tag.setValue(buf); + } + checkTag(tag); + break; + case ExifParser.EVENT_COMPRESSED_IMAGE: + case ExifParser.EVENT_UNCOMPRESSED_STRIP: + fail("Invalid Event type: " + event + ", " + getImageTitle()); + break; + } + event = parser.next(); + } + assertEquals(getImageTitle(), ExifXmlReader.getTrueTagNumber(expectedResult), numOfTag); + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } + } + + @MediumTest + public void testOnlyExifIfd() throws Exception { + parseOneIfd(IfdId.TYPE_IFD_EXIF, ExifParser.OPTION_IFD_EXIF); + } + + @MediumTest + public void testOnlyIfd0() throws Exception { + parseOneIfd(IfdId.TYPE_IFD_0, ExifParser.OPTION_IFD_0); + } + + @MediumTest + public void testOnlyIfd1() throws Exception { + parseOneIfd(IfdId.TYPE_IFD_1, ExifParser.OPTION_IFD_1); + } + + @MediumTest + public void testOnlyInteroperabilityIfd() throws Exception { + parseOneIfd(IfdId.TYPE_IFD_INTEROPERABILITY, ExifParser.OPTION_IFD_INTEROPERABILITY); + } + + @MediumTest + public void testOnlyReadSomeTag() throws Exception { + // Do not do this test if there is no model tag. + if (mGroundTruth.get(IfdId.TYPE_IFD_0).get(ExifInterface.TAG_MODEL) == null) { + return; + } + + try { + ExifParser parser = ExifParser.parse(getImageInputStream(), ExifParser.OPTION_IFD_0, + mInterface); + int event = parser.next(); + boolean isTagFound = false; + while (event != ExifParser.EVENT_END) { + switch (event) { + case ExifParser.EVENT_START_OF_IFD: + assertEquals(getImageTitle(), IfdId.TYPE_IFD_0, parser.getCurrentIfd()); + break; + case ExifParser.EVENT_NEW_TAG: + ExifTag tag = parser.getTag(); + if (tag.getTagId() == ExifInterface.TAG_MODEL) { + if (tag.hasValue()) { + isTagFound = true; + checkTag(tag); + } else { + parser.registerForTagValue(tag); + } + parser.skipRemainingTagsInCurrentIfd(); + } + break; + case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: + tag = parser.getTag(); + assertEquals(getImageTitle(), ExifInterface.TAG_MODEL, tag.getTagId()); + checkTag(tag); + isTagFound = true; + break; + } + event = parser.next(); + } + assertTrue(getImageTitle(), isTagFound); + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } + } + + @MediumTest + public void testReadThumbnail() throws Exception { + try { + ExifParser parser = ExifParser.parse(getImageInputStream(), + ExifParser.OPTION_IFD_1 | ExifParser.OPTION_THUMBNAIL, mInterface); + + int event = parser.next(); + Bitmap bmp = null; + boolean mIsContainCompressedImage = false; + while (event != ExifParser.EVENT_END) { + switch (event) { + case ExifParser.EVENT_NEW_TAG: + ExifTag tag = parser.getTag(); + if (tag.getTagId() == ExifInterface.TAG_COMPRESSION) { + if (tag.getValueAt(0) == ExifInterface.Compression.JPEG) { + mIsContainCompressedImage = true; + } + } + break; + case ExifParser.EVENT_COMPRESSED_IMAGE: + int imageSize = parser.getCompressedImageSize(); + byte buf[] = new byte[imageSize]; + parser.read(buf); + bmp = BitmapFactory.decodeByteArray(buf, 0, imageSize); + break; + } + event = parser.next(); + } + if (mIsContainCompressedImage) { + assertNotNull(getImageTitle(), bmp); + } + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } + } +} diff --git a/tests/src/com/android/gallery3d/exif/ExifReaderTest.java b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java new file mode 100644 index 000000000..4b5c02941 --- /dev/null +++ b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java @@ -0,0 +1,165 @@ +/* + * 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.gallery3d.exif; + +import android.test.suitebuilder.annotation.MediumTest; + +import android.graphics.BitmapFactory; + +import java.util.List; +import java.util.Map; + +public class ExifReaderTest extends ExifXmlDataTestCase { + private static final String TAG = "ExifReaderTest"; + + private ExifInterface mInterface; + private List<Map<Short, List<String>>> mGroundTruth; + + @Override + public void setUp() throws Exception { + super.setUp(); + mGroundTruth = ExifXmlReader.readXml(getXmlParser()); + } + + public ExifReaderTest(int imgRes, int xmlRes) { + super(imgRes, xmlRes); + mInterface = new ExifInterface(); + } + + public ExifReaderTest(String imgPath, String xmlPath) { + super(imgPath, xmlPath); + mInterface = new ExifInterface(); + } + + @MediumTest + public void testRead() throws Exception { + try { + ExifReader reader = new ExifReader(mInterface); + ExifData exifData = reader.read(getImageInputStream()); + for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { + checkIfd(exifData.getIfdData(i), mGroundTruth.get(i)); + } + checkThumbnail(exifData); + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } + } + + private void checkThumbnail(ExifData exifData) { + Map<Short, List<String>> ifd1Truth = mGroundTruth.get(IfdId.TYPE_IFD_1); + + List<String> typeTagValue = ifd1Truth.get(ExifInterface.TAG_COMPRESSION); + if (typeTagValue == null) + return; + + IfdData ifd1 = exifData.getIfdData(IfdId.TYPE_IFD_1); + if (ifd1 == null) + fail(getImageTitle() + ": failed to find IFD1"); + + String typeTagTruth = typeTagValue.get(0); + + int type = (int) ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_COMPRESSION)) + .getValueAt(0); + + if (String.valueOf(ExifInterface.Compression.JPEG).equals(typeTagTruth)) { + assertTrue(getImageTitle(), type == ExifInterface.Compression.JPEG); + assertTrue(getImageTitle(), exifData.hasCompressedThumbnail()); + byte[] thumbnail = exifData.getCompressedThumbnail(); + assertTrue(getImageTitle(), + BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length) != null); + } else if (String.valueOf(ExifInterface.Compression.UNCOMPRESSION).equals(typeTagTruth)) { + assertTrue(getImageTitle(), type == ExifInterface.Compression.UNCOMPRESSION); + // Try to check the strip count with the formula provided by EXIF spec. + int planarType = ExifInterface.PlanarConfiguration.CHUNKY; + ExifTag planarTag = ifd1.getTag(ExifInterface + .getTrueTagKey(ExifInterface.TAG_PLANAR_CONFIGURATION)); + if (planarTag != null) { + planarType = (int) planarTag.getValueAt(0); + } + + if (!ifd1Truth.containsKey(ExifInterface.TAG_IMAGE_LENGTH) || + !ifd1Truth.containsKey(ExifInterface.TAG_ROWS_PER_STRIP)) { + return; + } + + ExifTag heightTag = ifd1.getTag(ExifInterface + .getTrueTagKey(ExifInterface.TAG_IMAGE_LENGTH)); + ExifTag rowPerStripTag = ifd1.getTag(ExifInterface + .getTrueTagKey(ExifInterface.TAG_ROWS_PER_STRIP)); + + // Fail the test if required tags are missing + if (heightTag == null || rowPerStripTag == null) { + fail(getImageTitle()); + } + + int imageLength = (int) heightTag.getValueAt(0); + int rowsPerStrip = (int) rowPerStripTag.getValueAt(0); + int stripCount = ifd1.getTag( + ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)) + .getComponentCount(); + + if (planarType == ExifInterface.PlanarConfiguration.CHUNKY) { + assertTrue(getImageTitle(), + stripCount == (imageLength + rowsPerStrip - 1) / rowsPerStrip); + } else { + if (!ifd1Truth.containsKey(ExifInterface.TAG_SAMPLES_PER_PIXEL)) { + return; + } + ExifTag samplePerPixelTag = ifd1.getTag(ExifInterface + .getTrueTagKey(ExifInterface.TAG_SAMPLES_PER_PIXEL)); + int samplePerPixel = (int) samplePerPixelTag.getValueAt(0); + assertTrue(getImageTitle(), + stripCount == + (imageLength + rowsPerStrip - 1) / rowsPerStrip * samplePerPixel); + } + + if (!ifd1Truth.containsKey(ExifInterface.TAG_STRIP_BYTE_COUNTS)) { + return; + } + ExifTag byteCountTag = ifd1.getTag(ExifInterface + .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS)); + short byteCountDataType = byteCountTag.getDataType(); + for (int i = 0; i < stripCount; i++) { + if (byteCountDataType == ExifTag.TYPE_UNSIGNED_SHORT) { + assertEquals(getImageTitle(), + byteCountTag.getValueAt(i), exifData.getStrip(i).length); + } else { + assertEquals(getImageTitle(), + byteCountTag.getValueAt(i), exifData.getStrip(i).length); + } + } + } + } + + private void checkIfd(IfdData ifd, Map<Short, List<String>> ifdValue) { + if (ifd == null) { + assertEquals(getImageTitle(), 0, ifdValue.size()); + return; + } + ExifTag[] tags = ifd.getAllTags(); + for (ExifTag tag : tags) { + List<String> truth = ifdValue.get(tag.getTagId()); + assertNotNull(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(), truth); + if (truth.contains(null)) { + continue; + } + assertTrue(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(), + truth.contains(Util.tagValueToString(tag).trim())); + } + assertEquals(getImageTitle(), ifdValue.size(), tags.length); + } +} diff --git a/tests/src/com/android/gallery3d/exif/ExifTagTest.java b/tests/src/com/android/gallery3d/exif/ExifTagTest.java new file mode 100644 index 000000000..e6a41ecfa --- /dev/null +++ b/tests/src/com/android/gallery3d/exif/ExifTagTest.java @@ -0,0 +1,219 @@ +/* + * 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.gallery3d.exif; + +import android.test.suitebuilder.annotation.SmallTest; + +import junit.framework.TestCase; + +import java.util.HashMap; +import java.util.Map; + +public class ExifTagTest extends TestCase { + + private static long MAX_UNSIGNED_LONG = (1L << 32) - 1; + private static int MAX_LONG = Integer.MAX_VALUE; + private static int MIN_LONG = Integer.MIN_VALUE; + + Map<Integer, ExifTag> mTestTags; + ExifInterface mInterface; + private ExifTag mVersionTag; + private ExifTag mGpsVersionTag; + private ExifTag mModelTag; + private ExifTag mDateTimeTag; + private ExifTag mCompressionTag; + private ExifTag mThumbnailFormatTag; + private ExifTag mLongitudeTag; + private ExifTag mShutterTag; + private ExifTag mInteropIndex; + + @Override + public void setUp() throws Exception { + super.setUp(); + mInterface = new ExifInterface(); + + // TYPE_UNDEFINED with 4 components + mVersionTag = mInterface.buildTag(ExifInterface.TAG_EXIF_VERSION, new byte[] { + 5, 4, 3, 2 + }); + // TYPE_UNSIGNED_BYTE with 4 components + mGpsVersionTag = mInterface.buildTag(ExifInterface.TAG_GPS_VERSION_ID, new byte[] { + 6, 7, 8, 9 + }); + // TYPE ASCII with arbitrary length + mModelTag = mInterface.buildTag(ExifInterface.TAG_MODEL, "helloworld"); + // TYPE_ASCII with 20 components + mDateTimeTag = mInterface.buildTag(ExifInterface.TAG_DATE_TIME, "2013:02:11 20:20:20"); + // TYPE_UNSIGNED_SHORT with 1 components + mCompressionTag = mInterface.buildTag(ExifInterface.TAG_COMPRESSION, 100); + // TYPE_UNSIGNED_LONG with 1 components + mThumbnailFormatTag = + mInterface.buildTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 100); + // TYPE_UNSIGNED_RATIONAL with 3 components + mLongitudeTag = mInterface.buildTag(ExifInterface.TAG_GPS_LONGITUDE, new Rational[] { + new Rational(2, 2), new Rational(11, 11), + new Rational(102, 102) + }); + // TYPE_RATIONAL with 1 components + mShutterTag = mInterface + .buildTag(ExifInterface.TAG_SHUTTER_SPEED_VALUE, new Rational(4, 6)); + // TYPE_ASCII with arbitrary length + mInteropIndex = mInterface.buildTag(ExifInterface.TAG_INTEROPERABILITY_INDEX, "foo"); + + mTestTags = new HashMap<Integer, ExifTag>(); + + mTestTags.put(ExifInterface.TAG_EXIF_VERSION, mVersionTag); + mTestTags.put(ExifInterface.TAG_GPS_VERSION_ID, mGpsVersionTag); + mTestTags.put(ExifInterface.TAG_MODEL, mModelTag); + mTestTags.put(ExifInterface.TAG_DATE_TIME, mDateTimeTag); + mTestTags.put(ExifInterface.TAG_COMPRESSION, mCompressionTag); + mTestTags.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, mThumbnailFormatTag); + mTestTags.put(ExifInterface.TAG_GPS_LONGITUDE, mLongitudeTag); + mTestTags.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, mShutterTag); + mTestTags.put(ExifInterface.TAG_INTEROPERABILITY_INDEX, mInteropIndex); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + mInterface = null; + mTestTags = null; + } + + @SmallTest + public void testValueType() { + for (ExifTag tag : mTestTags.values()) { + assertTrue(tag != null); + int count = tag.getComponentCount(); + int intBuf[] = new int[count]; + long longBuf[] = new long[count]; + byte byteBuf[] = new byte[count]; + Rational rationalBuf[] = new Rational[count]; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < count; i++) { + intBuf[i] = 0; + longBuf[i] = 0; + byteBuf[i] = 0; + rationalBuf[i] = new Rational(0, 0); + // The string size should equal to component count - 1 + if (i != count - 1) { + sb.append("*"); + } else { + sb.append("\0"); + } + } + String strBuf = sb.toString(); + + checkTypeByte(tag, byteBuf); + checkTypeAscii(tag, strBuf); + checkTypeUnsignedShort(tag, intBuf); + checkTypeUnsignedLong(tag, intBuf, longBuf); + checkTypeLong(tag, intBuf); + checkTypeRational(tag, rationalBuf); + checkTypeUnsignedRational(tag, rationalBuf); + } + } + + private void checkTypeByte(ExifTag tag, byte[] buf) { + short type = tag.getDataType(); + assertFalse("\nTag: " + tag.toString(), tag.setValue(buf) + ^ (type == ExifTag.TYPE_UNDEFINED || type == ExifTag.TYPE_UNSIGNED_BYTE)); + } + + private void checkTypeAscii(ExifTag tag, String str) { + short type = tag.getDataType(); + assertFalse("\nTag: " + tag.toString(), tag.setValue(str) + ^ (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED)); + } + + private void checkTypeUnsignedShort(ExifTag tag, int[] intBuf) { + short type = tag.getDataType(); + assertFalse("\nTag: " + tag.toString(), + tag.setValue(intBuf) + ^ (type == ExifTag.TYPE_UNSIGNED_SHORT + || type == ExifTag.TYPE_UNSIGNED_LONG + || type == ExifTag.TYPE_LONG)); + } + + private void checkTypeUnsignedLong(ExifTag tag, int[] intBuf, long[] longBuf) { + + // Test value only for unsigned long. + int count = intBuf.length; + intBuf[count - 1] = MAX_LONG; + tag.setValue(intBuf); + longBuf[count - 1] = MAX_UNSIGNED_LONG; + + assertFalse("\nTag: " + tag.toString(), tag.setValue(longBuf) + ^ (tag.getDataType() == ExifTag.TYPE_UNSIGNED_LONG)); + + intBuf[count - 1] = 0; + // Test invalid value for all type. + longBuf[count - 1] = MAX_UNSIGNED_LONG + 1; + assertFalse(tag.setValue(longBuf)); + longBuf[count - 1] = 0; + } + + private void checkTypeLong(ExifTag tag, int[] intBuf) { + int count = intBuf.length; + intBuf[count - 1] = MAX_LONG; + tag.setValue(intBuf); + intBuf[count - 1] = MIN_LONG; + + assertFalse("\nTag: " + tag.toString(), tag.setValue(intBuf) + ^ (tag.getDataType() == ExifTag.TYPE_LONG)); + intBuf[count - 1] = 0; + } + + private void checkTypeRational(ExifTag tag, Rational rationalBuf[]) { + int count = rationalBuf.length; + Rational r = rationalBuf[count - 1]; + rationalBuf[count - 1] = new Rational(MAX_LONG, MIN_LONG); + + assertFalse("\nTag: " + tag.toString(), tag.setValue(rationalBuf) + ^ (tag.getDataType() == ExifTag.TYPE_RATIONAL)); + + if (tag.getDataType() == ExifTag.TYPE_RATIONAL) { + // check overflow + + rationalBuf[count - 1] = new Rational(MAX_LONG + 1L, MIN_LONG); + assertFalse(tag.setValue(rationalBuf)); + + rationalBuf[count - 1] = new Rational(MAX_LONG, MIN_LONG - 1L); + assertFalse(tag.setValue(rationalBuf)); + } + rationalBuf[count - 1] = r; + } + + private void checkTypeUnsignedRational(ExifTag tag, Rational rationalBuf[]) { + int count = rationalBuf.length; + Rational r = rationalBuf[count - 1]; + rationalBuf[count - 1] = new Rational(MAX_UNSIGNED_LONG, MAX_UNSIGNED_LONG); + + assertFalse("\nTag: " + tag.toString(), tag.setValue(rationalBuf) + ^ (tag.getDataType() == ExifTag.TYPE_UNSIGNED_RATIONAL)); + + if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_RATIONAL) { + // check overflow + rationalBuf[count - 1] = new Rational(MAX_UNSIGNED_LONG + 1, 0); + assertFalse(tag.setValue(rationalBuf)); + + rationalBuf[count - 1] = new Rational(MAX_UNSIGNED_LONG, -1); + assertFalse(tag.setValue(rationalBuf)); + } + rationalBuf[count - 1] = r; + } +} diff --git a/tests/src/com/android/gallery3d/exif/ExifTestRunner.java b/tests/src/com/android/gallery3d/exif/ExifTestRunner.java new file mode 100644 index 000000000..162baea29 --- /dev/null +++ b/tests/src/com/android/gallery3d/exif/ExifTestRunner.java @@ -0,0 +1,135 @@ +/* + * 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.gallery3d.exif; + +import android.content.Context; +import android.os.Environment; +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; +import android.util.Log; + +import com.android.gallery3d.tests.R; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +public class ExifTestRunner extends InstrumentationTestRunner { + private static final String TAG = "ExifTestRunner"; + + private static final int[] IMG_RESOURCE = { + R.raw.galaxy_nexus + }; + + private static final int[] EXIF_DATA_RESOURCE = { + R.xml.galaxy_nexus + }; + + private static List<String> mTestImgPath = new ArrayList<String>(); + private static List<String> mTestXmlPath = new ArrayList<String>(); + + @Override + public TestSuite getAllTests() { + getTestImagePath(); + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(ExifDataTest.class); + suite.addTestSuite(ExifTagTest.class); + addAllTestsFromExifTestCase(ExifParserTest.class, suite); + addAllTestsFromExifTestCase(ExifReaderTest.class, suite); + addAllTestsFromExifTestCase(ExifOutputStreamTest.class, suite); + addAllTestsFromExifTestCase(ExifModifierTest.class, suite); + addAllTestsFromExifTestCase(ExifInterfaceTest.class, suite); + return suite; + } + + private void getTestImagePath() { + Context context = getContext(); + File imgDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES); + File xmlDir = new File(context.getExternalFilesDir(null).getPath(), "Xml"); + + if (imgDir != null && xmlDir != null) { + String[] imgs = imgDir.list(); + if (imgs == null) { + return; + } + for (String imgName : imgs) { + String xmlName = imgName.substring(0, imgName.lastIndexOf('.')) + ".xml"; + File xmlFile = new File(xmlDir, xmlName); + if (xmlFile.exists()) { + mTestImgPath.add(new File(imgDir, imgName).getAbsolutePath()); + mTestXmlPath.add(xmlFile.getAbsolutePath()); + } + } + } + } + + private void addAllTestsFromExifTestCase(Class<? extends ExifXmlDataTestCase> testClass, + TestSuite suite) { + for (Method method : testClass.getDeclaredMethods()) { + if (method.getName().startsWith("test") && method.getParameterTypes().length == 0) { + for (int i = 0; i < IMG_RESOURCE.length; i++) { + TestCase test; + try { + test = testClass.getDeclaredConstructor(int.class, int.class). + newInstance(IMG_RESOURCE[i], EXIF_DATA_RESOURCE[i]); + test.setName(method.getName()); + suite.addTest(test); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Failed to create test case", e); + } catch (InstantiationException e) { + Log.e(TAG, "Failed to create test case", e); + } catch (IllegalAccessException e) { + Log.e(TAG, "Failed to create test case", e); + } catch (InvocationTargetException e) { + Log.e(TAG, "Failed to create test case", e); + } catch (NoSuchMethodException e) { + Log.e(TAG, "Failed to create test case", e); + } + } + for (int i = 0, n = mTestImgPath.size(); i < n; i++) { + TestCase test; + try { + test = testClass.getDeclaredConstructor(String.class, String.class). + newInstance(mTestImgPath.get(i), mTestXmlPath.get(i)); + test.setName(method.getName()); + suite.addTest(test); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Failed to create test case", e); + } catch (InstantiationException e) { + Log.e(TAG, "Failed to create test case", e); + } catch (IllegalAccessException e) { + Log.e(TAG, "Failed to create test case", e); + } catch (InvocationTargetException e) { + Log.e(TAG, "Failed to create test case", e); + } catch (NoSuchMethodException e) { + Log.e(TAG, "Failed to create test case", e); + } + } + } + } + } + + @Override + public ClassLoader getLoader() { + return ExifTestRunner.class.getClassLoader(); + } +} diff --git a/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java b/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java new file mode 100644 index 000000000..da860208b --- /dev/null +++ b/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java @@ -0,0 +1,108 @@ +/* + * 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.gallery3d.exif; + +import android.content.res.Resources; +import android.test.InstrumentationTestCase; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; + +public class ExifXmlDataTestCase extends InstrumentationTestCase { + + private static final String RES_ID_TITLE = "Resource ID: %x"; + + private InputStream mImageInputStream; + private InputStream mXmlInputStream; + private XmlPullParser mXmlParser; + private final String mImagePath; + private final String mXmlPath; + private final int mImageResourceId; + private final int mXmlResourceId; + + public ExifXmlDataTestCase(int imageRes, int xmlRes) { + mImagePath = null; + mXmlPath = null; + mImageResourceId = imageRes; + mXmlResourceId = xmlRes; + } + + public ExifXmlDataTestCase(String imagePath, String xmlPath) { + mImagePath = imagePath; + mXmlPath = xmlPath; + mImageResourceId = 0; + mXmlResourceId = 0; + } + + protected InputStream getImageInputStream() { + return mImageInputStream; + } + + protected XmlPullParser getXmlParser() { + return mXmlParser; + } + + @Override + public void setUp() throws Exception { + try { + if (mImagePath != null) { + mImageInputStream = new FileInputStream(mImagePath); + mXmlInputStream = new FileInputStream(mXmlPath); + mXmlParser = Xml.newPullParser(); + mXmlParser.setInput(new InputStreamReader(mXmlInputStream)); + } else { + Resources res = getInstrumentation().getContext().getResources(); + mImageInputStream = res.openRawResource(mImageResourceId); + mXmlParser = res.getXml(mXmlResourceId); + } + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } + } + + @Override + public void tearDown() throws Exception { + Util.closeSilently(mImageInputStream); + Util.closeSilently(mXmlInputStream); + mXmlParser = null; + } + + protected String getImageTitle() { + if (mImagePath != null) { + return mImagePath; + } else { + return String.format(RES_ID_TITLE, mImageResourceId); + } + } + + protected InputStream reopenFileStream() throws Exception { + try { + if (mImagePath != null) { + return new FileInputStream(mImagePath); + } else { + Resources res = getInstrumentation().getContext().getResources(); + return res.openRawResource(mImageResourceId); + } + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } + } +} diff --git a/tests/src/com/android/gallery3d/exif/ExifXmlReader.java b/tests/src/com/android/gallery3d/exif/ExifXmlReader.java new file mode 100644 index 000000000..12e9cf77e --- /dev/null +++ b/tests/src/com/android/gallery3d/exif/ExifXmlReader.java @@ -0,0 +1,125 @@ +/* + * 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.gallery3d.exif; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ExifXmlReader { + private static final String TAG_EXIF = "exif"; + private static final String TAG_TAG = "tag"; + + private static final String IFD0 = "IFD0"; + private static final String EXIF_IFD = "ExifIFD"; + private static final String GPS_IFD = "GPS"; + private static final String IFD1 = "IFD1"; + private static final String INTEROP_IFD = "InteropIFD"; + + private static final String ATTR_ID = "id"; + private static final String ATTR_IFD = "ifd"; + + private static final String NO_VALUE = "NO_VALUE"; + + /** + * This function read the ground truth XML. + * + * @throws XmlPullParserException + * @throws IOException + */ + static public List<Map<Short, List<String>>> readXml(XmlPullParser parser) + throws XmlPullParserException, IOException { + + List<Map<Short, List<String>>> exifData = + new ArrayList<Map<Short, List<String>>>(IfdId.TYPE_IFD_COUNT); + for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { + exifData.add(new HashMap<Short, List<String>>()); + } + + while (parser.next() != XmlPullParser.END_DOCUMENT) { + if (parser.getEventType() == XmlPullParser.START_TAG) { + break; + } + } + parser.require(XmlPullParser.START_TAG, null, TAG_EXIF); + + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + + parser.require(XmlPullParser.START_TAG, null, TAG_TAG); + + int ifdId = getIfdIdFromString(parser.getAttributeValue(null, ATTR_IFD)); + short id = Integer.decode(parser.getAttributeValue(null, ATTR_ID)).shortValue(); + + String value = ""; + if (parser.next() == XmlPullParser.TEXT) { + value = parser.getText(); + parser.next(); + } + + if (ifdId < 0) { + // TODO: the MarkerNote segment. + } else { + List<String> tagData = exifData.get(ifdId).get(id); + if (tagData == null) { + tagData = new ArrayList<String>(); + exifData.get(ifdId).put(id, tagData); + } + if (NO_VALUE.equals(value)) { + tagData.add(null); + } else { + tagData.add(value.trim()); + } + } + + parser.require(XmlPullParser.END_TAG, null, null); + } + return exifData; + } + + static private int getIfdIdFromString(String prefix) { + if (IFD0.equals(prefix)) { + return IfdId.TYPE_IFD_0; + } else if (EXIF_IFD.equals(prefix)) { + return IfdId.TYPE_IFD_EXIF; + } else if (GPS_IFD.equals(prefix)) { + return IfdId.TYPE_IFD_GPS; + } else if (IFD1.equals(prefix)) { + return IfdId.TYPE_IFD_1; + } else if (INTEROP_IFD.equals(prefix)) { + return IfdId.TYPE_IFD_INTEROPERABILITY; + } else { + assert (false); + return -1; + } + } + + static public int getTrueTagNumber(Map<Short, List<String>> ifdData) { + int size = 0; + for (List<String> tag : ifdData.values()) { + size += tag.size(); + } + return size; + } +} diff --git a/tests/src/com/android/gallery3d/exif/Util.java b/tests/src/com/android/gallery3d/exif/Util.java new file mode 100644 index 000000000..15de00714 --- /dev/null +++ b/tests/src/com/android/gallery3d/exif/Util.java @@ -0,0 +1,195 @@ +/* + * 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.gallery3d.exif; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +class Util { + public static boolean equals(Object a, Object b) { + return (a == b) || (a == null ? false : a.equals(b)); + } + + public static void closeSilently(Closeable c) { + if (c == null) + return; + try { + c.close(); + } catch (Throwable t) { + // do nothing + } + } + + public static byte[] readToByteArray(InputStream is) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int len; + byte[] buf = new byte[1024]; + while ((len = is.read(buf)) > -1) { + bos.write(buf, 0, len); + } + bos.flush(); + return bos.toByteArray(); + } + + /** + * Tags that are not defined in the spec. + */ + static final short TAG_XP_TITLE = (short) 0x9c9b; + static final short TAG_XP_COMMENT = (short) 0x9c9c; + static final short TAG_XP_AUTHOR = (short) 0x9c9d; + static final short TAG_XP_KEYWORDS = (short) 0x9c9e; + static final short TAG_XP_SUBJECT = (short) 0x9c9f; + + private static String tagUndefinedTypeValueToString(ExifTag tag) { + StringBuilder sbuilder = new StringBuilder(); + byte[] buf = new byte[tag.getComponentCount()]; + tag.getBytes(buf); + short tagId = tag.getTagId(); + if (tagId == ExifInterface.getTrueTagKey(ExifInterface.TAG_COMPONENTS_CONFIGURATION)) { + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + if (i != 0) { + sbuilder.append(" "); + } + sbuilder.append(buf[i]); + } + } else { + if (buf.length == 1) { + sbuilder.append(buf[0]); + } else { + for (int i = 0, n = buf.length; i < n; i++) { + byte code = buf[i]; + if (code == 0) { + continue; + } + if (code > 31 && code < 127) { + sbuilder.append((char) code); + } else { + sbuilder.append('.'); + } + } + } + } + return sbuilder.toString(); + } + + /** + * Returns a string representation of the value of this tag. + */ + public static String tagValueToString(ExifTag tag) { + StringBuilder sbuilder = new StringBuilder(); + short id = tag.getTagId(); + switch (tag.getDataType()) { + case ExifTag.TYPE_UNDEFINED: + sbuilder.append(tagUndefinedTypeValueToString(tag)); + break; + case ExifTag.TYPE_UNSIGNED_BYTE: + if (id == ExifInterface.TAG_MAKER_NOTE || id == TAG_XP_TITLE || + id == TAG_XP_COMMENT || id == TAG_XP_AUTHOR || + id == TAG_XP_KEYWORDS || id == TAG_XP_SUBJECT) { + sbuilder.append(tagUndefinedTypeValueToString(tag)); + } else { + byte[] buf = new byte[tag.getComponentCount()]; + tag.getBytes(buf); + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + if (i != 0) + sbuilder.append(" "); + sbuilder.append(buf[i]); + } + } + break; + case ExifTag.TYPE_ASCII: + byte[] buf = tag.getStringByte(); + for (int i = 0, n = buf.length; i < n; i++) { + byte code = buf[i]; + if (code == 0) { + // Treat some tag as undefined type data. + if (id == ExifInterface.TAG_COPYRIGHT + || id == ExifInterface.TAG_GPS_DATE_STAMP) { + continue; + } else { + break; + } + } + if (code > 31 && code < 127) { + sbuilder.append((char) code); + } else { + sbuilder.append('.'); + } + } + break; + case ExifTag.TYPE_UNSIGNED_LONG: + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + if (i != 0) { + sbuilder.append(" "); + } + sbuilder.append(tag.getValueAt(i)); + } + break; + case ExifTag.TYPE_RATIONAL: + case ExifTag.TYPE_UNSIGNED_RATIONAL: + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + Rational r = tag.getRational(i); + if (i != 0) { + sbuilder.append(" "); + } + sbuilder.append(r.getNumerator()).append("/").append(r.getDenominator()); + } + break; + case ExifTag.TYPE_UNSIGNED_SHORT: + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + if (i != 0) { + sbuilder.append(" "); + } + sbuilder.append((int) tag.getValueAt(i)); + } + break; + case ExifTag.TYPE_LONG: + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + if (i != 0) { + sbuilder.append(" "); + } + sbuilder.append((int) tag.getValueAt(i)); + } + break; + } + return sbuilder.toString(); + } + + public static String valueToString(Object obj) { + if (obj instanceof int[]) { + return Arrays.toString((int[]) obj); + } else if (obj instanceof Integer[]) { + return Arrays.toString((Integer[]) obj); + } else if (obj instanceof long[]) { + return Arrays.toString((long[]) obj); + } else if (obj instanceof Long[]) { + return Arrays.toString((Long[]) obj); + } else if (obj instanceof Rational) { + return ((Rational) obj).toString(); + } else if (obj instanceof Rational[]) { + return Arrays.toString((Rational[]) obj); + } else if (obj instanceof byte[]) { + return Arrays.toString((byte[]) obj); + } else if (obj != null) { + return obj.toString(); + } + return ""; + } +} |