Quantcast
Channel: Bricolsoft Consulting
Viewing all articles
Browse latest Browse all 11

Copying EXIF metadata using Sanselan

$
0
0

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.

private static void 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);
        TiffOutputSet destSet = getSanselanOutputSet(destFile);

        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.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);
        }
    }
    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();
        }
    }
}

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

    // note that metadata might be null if no metadata is found.
    IImageMetadata metadata = Sanselan.getMetadata(jpegImageFile);
    JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
    if (jpegMetadata != null)
    {
        // note that exif might be null if no Exif metadata is found.
        TiffImageMetadata exif = jpegMetadata.getExif();

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

    // if file does not contain any exif metadata, we create an empty
    // set of exif metadata. Otherwise, we keep all of the other
    // existing tags.
    if (outputSet == null)
        outputSet = new TiffOutputSet();

    // Return
    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.


Viewing all articles
Browse latest Browse all 11

Trending Articles