Copying EXIF metadata using Sanselan

Dec 8, 2012   //   by Theo   //   Blog  //  1 Comment

The Challenge

After resizing, rotating, or otherwise modifing an image, you typically want to retain all the EXIF metadata from the original image. Unfortunately the EXIF metadata is not automatically copied for you, so you need to explicitly copy it from the original.

Review of Existing Solutions

There are several solutions that currently address this, but most of them fall short in one way or another.

Some solutions attempt to copy the data using Android’s built-in ExifInterface. Unfortunately,  ExifInterface has EXIF data corruption issues on some Android OS versions. In addition, ExifInterface is only able to read and write a limited number of EXIF fields, meaning that these solutions are not able to preserve all EXIF fields.

Better solutions are based on the Sanselan Android library, which is a superior choice for writing EXIF data. Unfortunately the one solution we have seen that uses Sanselan does not work as intended: in addition to copying the EXIF metadata it also overwrites the image data, effectively undoing the image transformation work.
http://kb.trisugar.com/node/22096

Our Solution

Our solution extends the Sanselan solution above. To work around an apparent bug in Sanselan that others have noted, we do not apply the previous EXIF data set to the new image. Instead we create a new EXIF data set, loop through existing fields in the old data set and copy each old field to the new data set.

public static boolean copyExifData(File sourceFile, File destFile, List<TagInfo> excludedFields)
{
    String tempFileName = destFile.getAbsolutePath() + ".tmp";
    File tempFile = null;
    OutputStream tempStream = null;

    try
    {
        tempFile = new File (tempFileName);

        TiffOutputSet sourceSet = getSanselanOutputSet(sourceFile, TiffConstants.DEFAULT_TIFF_BYTE_ORDER);
        TiffOutputSet destSet = getSanselanOutputSet(destFile, sourceSet.byteOrder);

        // If the EXIF data endianess of the source and destination files
        // differ then fail. This only happens if the source and
        // destination images were created on different devices. It's
        // technically possible to copy this data by changing the byte
        // order of the data, but handling this case is outside the scope
        // of this implementation
        if (sourceSet.byteOrder != destSet.byteOrder) return false;

        destSet.getOrCreateExifDirectory();

        // Go through the source directories
        List<?> sourceDirectories = sourceSet.getDirectories();
        for (int i=0; i<sourceDirectories.size(); i++)
        {
            TiffOutputDirectory sourceDirectory = (TiffOutputDirectory)sourceDirectories.get(i);
            TiffOutputDirectory destinationDirectory = getOrCreateExifDirectory(destSet, sourceDirectory);

            if (destinationDirectory == null) continue; // failed to create

            // Loop the fields
            List<?> sourceFields = sourceDirectory.getFields();
            for (int j=0; j<sourceFields.size(); j++)
            {
                // Get the source field
                TiffOutputField sourceField = (TiffOutputField) sourceFields.get(j);

                // Check exclusion list
                if (excludedFields != null && excludedFields.contains(sourceField.tagInfo))
                {
                    destinationDirectory.removeField(sourceField.tagInfo);
                    continue;
                }

                // Remove any existing field
                destinationDirectory.removeField(sourceField.tagInfo);

                // Add field
                destinationDirectory.add(sourceField);
            }
        }

        // Save data to destination
        tempStream = new BufferedOutputStream(new FileOutputStream(tempFile));
        new ExifRewriter().updateExifMetadataLossless(destFile, tempStream, destSet);
        tempStream.close();

        // Replace file
        if (destFile.delete())
        {
            tempFile.renameTo(destFile);
        }

        return true;
    }
    catch (ImageReadException exception)
    {
        exception.printStackTrace();
    }
    catch (ImageWriteException exception)
    {
        exception.printStackTrace();
    }
    catch (IOException exception)
    {
        exception.printStackTrace();
    }
    finally
    {
        if (tempStream != null)
        {
            try
            {
                tempStream.close();
            }
            catch (IOException e)
            {
            }
        }

        if (tempFile != null)
        {
            if (tempFile.exists()) tempFile.delete();
        }
    }

    return false;
}

private static TiffOutputSet getSanselanOutputSet(File jpegImageFile, int defaultByteOrder)
        throws IOException, ImageReadException, ImageWriteException
{
    TiffImageMetadata exif = null;
    TiffOutputSet outputSet = null;

    IImageMetadata metadata = Sanselan.getMetadata(jpegImageFile);
    JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
    if (jpegMetadata != null)
    {
        exif = jpegMetadata.getExif();

        if (exif != null)
        {
            outputSet = exif.getOutputSet();
        }
    }

    // If JPEG file contains no EXIF metadata, create an empty set
    // of EXIF metadata. Otherwise, use existing EXIF metadata to
    // keep all other existing tags
    if (outputSet == null)
        outputSet = new TiffOutputSet(exif==null?defaultByteOrder:exif.contents.header.byteOrder);

    return outputSet;
}

private static TiffOutputDirectory getOrCreateExifDirectory(TiffOutputSet outputSet, TiffOutputDirectory outputDirectory)
{
    TiffOutputDirectory result = outputSet.findDirectory(outputDirectory.type);
    if (result != null)
        return result;
    result = new TiffOutputDirectory(outputDirectory.type);
    try
    {
        outputSet.addDirectory(result);
    }
    catch (ImageWriteException e)
    {
        return null;
    }
    return result;
}

Note that we used the lossless Sanselan function (updateExifMetadataLossless), so Sanselan can copy all existing EXIF metadata intact, regardless of whether Sanselan “knows” about custom EXIF fields or marker notes that may appear in the data. You can use the lossy Sanselan function (updateExifMetadataLossy) if you need Sanselan to drop custom fields and marker notes.

Conclusion

Hopefully this solution is useful to you when faced with situations where you need to copy EXIF metadata.  Let us know about any questions or problems in the comments.

One comment on “Copying EXIF metadata using Sanselan

  1. You may get the library from the following link https://github.com/gaborbiro/sanselanandroid

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>