blob: f0ca33fed0bbefb6de7aa8d1c6b1b2b410887574 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.commons.imaging.formats.jpeg.exif;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.ImageWriteException;
import org.apache.commons.imaging.Sanselan;
import org.apache.commons.imaging.common.bytesource.ByteSource;
import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
import org.apache.commons.imaging.formats.jpeg.JpegUtils;
import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter;
import org.apache.commons.imaging.formats.tiff.TiffField;
import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
import org.apache.commons.imaging.formats.tiff.constants.AllTagConstants;
import org.apache.commons.imaging.formats.tiff.constants.TiffFieldTypeConstants;
import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
import org.apache.commons.imaging.util.Debug;
import org.apache.commons.imaging.util.IoUtils;
public class ExifRewriteTest extends ExifBaseTest implements AllTagConstants
{
// public ExifRewriteTest(String name)
// {
// super(name);
// }
public void testRemove() throws Exception
{
List images = getImagesWithExifData();
for (int i = 0; i < images.size(); i++)
{
if(i%10==0)
Debug.purgeMemory();
File imageFile = (File) images.get(i);
Debug.debug("imageFile", imageFile);
boolean ignoreImageData = isPhilHarveyTestImage(imageFile);
if (ignoreImageData)
continue;
ByteSource byteSource = new ByteSourceFile(imageFile);
Debug.debug("Source Segments:");
new JpegUtils().dumpJFIF(byteSource);
{
JpegImageMetadata metadata = (JpegImageMetadata) Sanselan
.getMetadata(imageFile);
// assertNotNull(metadata.getExif());
}
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new ExifRewriter().removeExifMetadata(byteSource, baos);
byte bytes[] = baos.toByteArray();
File tempFile = createTempFile("test", ".jpg");
Debug.debug("tempFile", tempFile);
IoUtils.writeToFile(bytes, tempFile);
Debug.debug("Output Segments:");
new JpegUtils().dumpJFIF(new ByteSourceArray(bytes));
assertTrue(!hasExifData(tempFile));
}
}
}
public void testInsert() throws Exception
{
List images = getImagesWithExifData();
for (int i = 0; i < images.size(); i++)
{
if(i%10==0)
Debug.purgeMemory();
File imageFile = (File) images.get(i);
Debug.debug("imageFile", imageFile);
boolean ignoreImageData = isPhilHarveyTestImage(imageFile);
if (ignoreImageData)
continue;
ByteSource byteSource = new ByteSourceFile(imageFile);
Debug.debug("Source Segments:");
new JpegUtils().dumpJFIF(byteSource);
JpegImageMetadata originalMetadata = (JpegImageMetadata) Sanselan
.getMetadata(imageFile);
assertNotNull(originalMetadata);
TiffImageMetadata oldExifMetadata = originalMetadata.getExif();
assertNotNull(oldExifMetadata);
ByteSource stripped;
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new ExifRewriter().removeExifMetadata(byteSource, baos);
byte bytes[] = baos.toByteArray();
File tempFile = createTempFile("removed", ".jpg");
Debug.debug("tempFile", tempFile);
IoUtils.writeToFile(bytes, tempFile);
Debug.debug("Output Segments:");
stripped = new ByteSourceArray(bytes);
new JpegUtils().dumpJFIF(stripped);
assertTrue(!hasExifData(tempFile));
}
{
TiffOutputSet outputSet = oldExifMetadata.getOutputSet();
// outputSet.dump();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new ExifRewriter().updateExifMetadataLossy(stripped, baos,
outputSet);
byte bytes[] = baos.toByteArray();
File tempFile = createTempFile("inserted" + "_", ".jpg");
Debug.debug("tempFile", tempFile);
IoUtils.writeToFile(bytes, tempFile);
Debug.debug("Output Segments:");
new JpegUtils().dumpJFIF(new ByteSourceArray(bytes));
// assertTrue(!hasExifData(tempFile));
JpegImageMetadata newMetadata = (JpegImageMetadata) Sanselan
.getMetadata(tempFile);
assertNotNull(newMetadata);
TiffImageMetadata newExifMetadata = newMetadata.getExif();
assertNotNull(newExifMetadata);
// newMetadata.dump();
compare(imageFile, oldExifMetadata, newExifMetadata);
}
}
}
private interface Rewriter
{
public void rewrite(ByteSource byteSource, OutputStream os,
TiffOutputSet outputSet) throws ImageReadException,
IOException, ImageWriteException;
}
private void rewrite(Rewriter rewriter, String name) throws IOException,
ImageReadException, ImageWriteException
{
List images = getImagesWithExifData();
for (int i = 0; i < images.size(); i++)
{
if(i%10==0)
Debug.purgeMemory();
File imageFile = (File) images.get(i);
try
{
Debug.debug("imageFile", imageFile);
boolean ignoreImageData = isPhilHarveyTestImage(imageFile);
if (ignoreImageData)
continue;
ByteSource byteSource = new ByteSourceFile(imageFile);
Debug.debug("Source Segments:");
new JpegUtils().dumpJFIF(byteSource);
JpegImageMetadata oldMetadata = (JpegImageMetadata) Sanselan
.getMetadata(imageFile);
if (null == oldMetadata)
continue;
assertNotNull(oldMetadata);
TiffImageMetadata oldExifMetadata = oldMetadata.getExif();
if (null == oldExifMetadata)
continue;
assertNotNull(oldExifMetadata);
oldMetadata.dump();
// TiffImageMetadata tiffImageMetadata = metadata.getExif();
// Photoshop photoshop = metadata.getPhotoshop();
TiffOutputSet outputSet = oldExifMetadata.getOutputSet();
// outputSet.dump();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
rewriter.rewrite(byteSource, baos, outputSet);
byte bytes[] = baos.toByteArray();
File tempFile = createTempFile(name + "_", ".jpg");
Debug.debug("tempFile", tempFile);
IoUtils.writeToFile(bytes, tempFile);
Debug.debug("Output Segments:");
new JpegUtils().dumpJFIF(new ByteSourceArray(bytes));
// assertTrue(!hasExifData(tempFile));
JpegImageMetadata newMetadata = (JpegImageMetadata) Sanselan
.getMetadata(tempFile);
assertNotNull(newMetadata);
TiffImageMetadata newExifMetadata = newMetadata.getExif();
assertNotNull(newExifMetadata);
// newMetadata.dump();
compare(imageFile, oldExifMetadata, newExifMetadata);
}
catch (IOException e)
{
Debug.debug("imageFile", imageFile.getAbsoluteFile());
Debug.debug(e);
throw e;
}
catch (ImageReadException e)
{
Debug.debug("imageFile", imageFile.getAbsoluteFile());
Debug.debug(e);
throw e;
}
catch (ImageWriteException e)
{
Debug.debug("imageFile", imageFile.getAbsoluteFile());
Debug.debug(e);
// FIXME: this image has 28kB of Maker Notes, causing the APP1 segment
// to go beyond 64kB, so ignore the exception this throws.
if (!imageFile.getName().equalsIgnoreCase("Nikon D50 - 2007.12.19.n.DSC_3656.JPG")) {
throw e;
}
}
}
}
public void testRewriteLossy() throws Exception
{
Rewriter rewriter = new Rewriter()
{
public void rewrite(ByteSource byteSource, OutputStream os,
TiffOutputSet outputSet) throws ImageReadException,
IOException, ImageWriteException
{
new ExifRewriter().updateExifMetadataLossy(byteSource, os,
outputSet);
}
};
rewrite(rewriter, "lossy");
}
public void testRewriteLossless() throws Exception
{
Rewriter rewriter = new Rewriter()
{
public void rewrite(ByteSource byteSource, OutputStream os,
TiffOutputSet outputSet) throws ImageReadException,
IOException, ImageWriteException
{
new ExifRewriter().updateExifMetadataLossless(byteSource, os,
outputSet);
}
};
rewrite(rewriter, "lossless");
}
private Hashtable makeDirectoryMap(List directories)
{
Hashtable directoryMap = new Hashtable();
for (int i = 0; i < directories.size(); i++)
{
TiffImageMetadata.Directory directory = (TiffImageMetadata.Directory) directories
.get(i);
directoryMap.put(new Integer(directory.type), directory);
}
return directoryMap;
}
private Hashtable makeFieldMap(List items)
{
Hashtable fieldMap = new Hashtable();
for (int i = 0; i < items.size(); i++)
{
TiffImageMetadata.Item item = (TiffImageMetadata.Item) items.get(i);
TiffField field = item.getTiffField();
Object key = new Integer(field.tag);
if (!fieldMap.containsKey(key))
fieldMap.put(key, field);
}
return fieldMap;
}
private void compare(File imageFile, TiffImageMetadata oldExifMetadata,
TiffImageMetadata newExifMetadata) throws ImageReadException
{
assertNotNull(oldExifMetadata);
assertNotNull(newExifMetadata);
List oldDirectories = oldExifMetadata.getDirectories();
List newDirectories = newExifMetadata.getDirectories();
assertTrue(oldDirectories.size() == newDirectories.size());
Hashtable oldDirectoryMap = makeDirectoryMap(oldDirectories);
Hashtable newDirectoryMap = makeDirectoryMap(newDirectories);
assertEquals(oldDirectories.size(), oldDirectoryMap.keySet().size());
List oldDirectoryTypes = new ArrayList(oldDirectoryMap.keySet());
Collections.sort(oldDirectoryTypes);
List newDirectoryTypes = new ArrayList(newDirectoryMap.keySet());
Collections.sort(newDirectoryTypes);
assertEquals(oldDirectoryTypes, newDirectoryTypes);
for (int i = 0; i < oldDirectoryTypes.size(); i++)
{
Integer dirType = (Integer) oldDirectoryTypes.get(i);
// Debug.debug("dirType", dirType);
TiffImageMetadata.Directory oldDirectory = (TiffImageMetadata.Directory) oldDirectoryMap
.get(dirType);
TiffImageMetadata.Directory newDirectory = (TiffImageMetadata.Directory) newDirectoryMap
.get(dirType);
assertNotNull(oldDirectory);
assertNotNull(newDirectory);
List oldItems = oldDirectory.getItems();
List newItems = newDirectory.getItems();
// Debug.debug("oldItems.size()", oldItems.size());
// Debug.debug("newItems.size()", newItems.size());
// dump("oldItems", oldItems);
// dump("newItems", newItems);
// if (oldItems.size() != newItems.size())
// ;
// {
// dump("oldItems", oldItems);
// dump("newItems", newItems);
// }
// assertTrue(oldItems.size() == newItems.size());
Hashtable oldFieldMap = makeFieldMap(oldItems);
Hashtable newFieldMap = makeFieldMap(newItems);
Set missingInNew = new HashSet(oldFieldMap.keySet());
missingInNew.removeAll(newFieldMap.keySet());
Set missingInOld = new HashSet(newFieldMap.keySet());
missingInOld.removeAll(oldFieldMap.keySet());
// dump("missingInNew", missingInNew);
// dump("missingInOld", missingInOld);
// dump("newFieldMap.keySet()", newFieldMap.keySet());
// dump("oldFieldMap.keySet()", oldFieldMap.keySet());
assertTrue(missingInNew.size() == 0);
assertTrue(missingInOld.size() == 0);
// Debug.debug("oldItems.size()", oldItems.size());
// Debug.debug("oldFieldMap.keySet().size()", oldFieldMap.keySet().size());
// assertEquals(oldItems.size(), oldFieldMap.keySet().size());
// assertEquals(oldFieldMap.keySet(), newFieldMap.keySet());
// assertEquals(oldFieldMap.keySet(), newFieldMap.keySet());
List oldFieldTags = new ArrayList(oldFieldMap.keySet());
Collections.sort(oldFieldTags);
List newFieldTags = new ArrayList(newFieldMap.keySet());
Collections.sort(newFieldTags);
assertEquals(oldFieldTags, newFieldTags);
for (int j = 0; j < oldFieldTags.size(); j++)
{
Integer fieldTag = (Integer) oldFieldTags.get(j);
TiffField oldField = (TiffField) oldFieldMap.get(fieldTag);
TiffField newField = (TiffField) newFieldMap.get(fieldTag);
// Debug.debug("fieldTag", fieldTag);
// Debug.debug("oldField", oldField);
// Debug.debug("newField", newField);
// fieldTag.
assertNotNull(oldField);
assertNotNull(newField);
assertEquals(oldField.tag, newField.tag);
assertEquals(dirType.intValue(), newField.directoryType);
assertEquals(oldField.directoryType, newField.directoryType);
assertEquals(oldField.length, newField.length);
assertEquals(oldField.isLocalValue(), newField.isLocalValue());
if (oldField.tag == 0x202)
{
// ignore "jpg from raw length" value. may have off-by-one bug in certain cameras.
// i.e. Sony DCR-PC110
continue;
}
if (oldField.fieldType == TiffFieldTypeConstants.FIELD_TYPE_ASCII)
{
// Sanselan currently doesn't correctly rewrite
// strings if any byte had the highest bit set,
// so if the source had that, all bets are off.
byte[] rawBytes = oldField.fieldType.getRawBytes(oldField);
boolean hasInvalidByte = false;
for (int k = 0; k < rawBytes.length; k++)
{
if ((rawBytes[k] & 0x80) != 0)
{
hasInvalidByte = true;
break;
}
}
if (hasInvalidByte)
continue;
}
if (!oldField.tagInfo.isOffset())
{
if (oldField.tagInfo.isText())
{ /* do nothing */ }
else if (oldField.isLocalValue())
{
// Debug.debug("oldField.tag", oldField.tag);
// Debug.debug("newField.tag", newField.tag);
// Debug.debug("oldField.tagInfo", oldField.tagInfo);
// Debug.debug("newField.tagInfo", newField.tagInfo);
// Debug.debug("oldField.fieldType", oldField.fieldType);
// Debug.debug("newField.fieldType", newField.fieldType);
// Debug.debug("oldField.getBytesLength", oldField
// .getBytesLength());
// Debug.debug("newField.getBytesLength", newField
// .getBytesLength());
//
// Debug.debug("oldField.valueOffsetBytes",
// oldField.valueOffsetBytes);
// Debug.debug("newField.valueOffsetBytes",
// newField.valueOffsetBytes);
String label = imageFile.getName() +
", dirType[" + i + "]=" + dirType +
", fieldTag[" + j + "]=" + fieldTag;
if (oldField.tag == 0x116 || oldField.tag == 0x117)
compare(label, oldField, newField);
else {
compare(label, oldField.valueOffsetBytes,
newField.valueOffsetBytes,
oldField.getBytesLength(),
newField.getBytesLength());
}
}
else
{
// Debug.debug("oldField.tagInfo", oldField.tagInfo);
// Debug.debug("oldField.fieldType", oldField.fieldType);
// Debug.debug("newField.fieldType", newField.fieldType);
// Debug.debug("oldField.getBytesLength", oldField
// .getBytesLength());
// Debug.debug("newField.getBytesLength", newField
// .getBytesLength());
// Debug.debug("oldField.oversizeValue", oldField.oversizeValue);
// Debug.debug("newField.oversizeValue", newField.oversizeValue);
compare(oldField.oversizeValue, newField.oversizeValue);
}
}
}
// Debug.debug();
}
}
private void compare(String label, byte a[], byte b[], int aLength, int bLength)
{
// Debug.debug("c0 a", a);
// Debug.debug("c0 b", b);
assertEquals(aLength, bLength);
assertTrue(a.length >= aLength);
assertTrue(b.length >= bLength);
assertNotNull(a);
assertNotNull(b);
assertEquals(a.length, b.length);
int length = aLength;
for (int i = 0; i < length; i++)
{
// byte ba = a[i];
// byte bb = b[i];
// boolean eq = ba == bb;
// Debug.debug("i: " + i + ", a[i]: " + ba + ", b[i]: " + bb + " = "
// + (ba == bb) + " " + eq);
// if(ba != bb)
// assertFalse(true);
//
// Debug.debug("i: " + i + ", a[i]: " + ba + ", b[i]: " + bb + " = "
// + (ba == bb) + " " + eq);
// assertTrue(eq == true);
assertEquals(label + ", byte[" + i + "]", a[i], b[i]);
// Debug.debug("c");
// assertTrue((0xff & a[i]) == (0xff & b[i]));
}
}
private void compare(String label, TiffField a, TiffField b) throws ImageReadException
{
Object v1 = a.getValue();
Object v2 = b.getValue();
// Debug.debug("v1", v1 + " (" + Debug.getType(v1) + ")");
// Debug.debug("v2", v2 + " (" + Debug.getType(v2) + ")");
assertEquals(label, v1, v2);
}
private void compare(byte a[], byte b[])
{
// Debug.debug("c1 a", a);
// Debug.debug("c1 b", b);
assertNotNull(a);
assertNotNull(b);
assertEquals(a.length, b.length);
for (int i = 0; i < a.length; i++)
assertEquals(a[i], b[i]);
}
}