Using imageIO, I usually have the problem of transforming an image file, and after overwriting it, it loses all of its EXIF data. Is there any way to preserve it without first extracting it, caching it, and then resetting it?
Asked
Active
Viewed 8,735 times
17

Ed Randall
- 6,887
- 2
- 50
- 45

Preslav Rachev
- 3,983
- 6
- 39
- 63
-
2Store it elsewhere then overwrite new image with an old exif meta? http://www.screaming-penguin.com/node/7485 – Sergey Benner Jan 23 '12 at 13:30
-
This is precisely what I want to avoid – Preslav Rachev Jan 23 '12 at 13:34
-
2what's the problem with copying the meta? here's another example http://nucleussystems.com/blog/java-copy-exif-data – Sergey Benner Jan 23 '12 at 13:49
-
The libraries I've found so far either read metadata or read the image. Not both. You'd have to read it twice. If it's being read from a stream, that means you need to save it in a byte[]. Which might require too much additional memory. – Ed Randall May 16 '14 at 19:44
2 Answers
22
ImageIO do have this functionality itself, but instead of ImageIO.read you will need to use ImageReader:
ImageReader reader = ImageIO.getImageReadersBySuffix("jpg").next();
(you may want to also check if such reader exists). Then you need to set the input:
reader.setInput(ImageIO.createImageInputStream(your_imput_stream));
Now you may save your metadata:
IIOMetadata metadata = reader.getImageMetadata(0);
// As far as I understand you should provide
// index as tiff images could have multiple pages
And then read the image:
BufferedImage bi = reader.read(0);
When you want to save new image, you should use ImageWriter:
// I'm writing to byte array in memory, but you may use any other stream
ByteArrayOutputStream os = new ByteArrayOutputStream(255);
ImageOutputStream ios = ImageIO.createImageOutputStream(os);
Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");
ImageWriter writer = iter.next();
writer.setOutput(ios);
//You may want also to alter jpeg quality
ImageWriteParam iwParam = writer.getDefaultWriteParam();
iwParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwParam.setCompressionQuality(.95f);
//Note: we're using metadata we've already saved.
writer.write(null, new IIOImage(bi, null, metadata), iwParam);
writer.dispose();
//ImageIO.write(bi, "jpg", ios); <- This was initially in the code but actually it was only adding image again at the end of the file.
As it's old topic, I guess this answer is a bit too late, but may help others as this topic is still googlable.

Rigeborod
- 356
- 2
- 6
-
This looks substantially more memory-efficient than my solution, I guess one copy of the image is still in-memory but can't really avoid that. – Ed Randall May 20 '16 at 12:04
-
Just came across this and have tried to use it... not sure if the last line, `ImageIO.write(...)` is supposed to be there as it seems to rewrite the image without the metadata. Just adding in case anyone stumbles across it. – cfnz Feb 26 '20 at 03:59
-
Well, you're right and wrong at the same time. It will indeed write the image without metadata... but at the end of the stream. Effectively doubling file size, but keeping the correct image with metadata... – Rigeborod Jun 17 '20 at 15:45
11
Here is my solution using a combination of ImageIO, Imgscalr and Apache commons-imaging. It's a pity there's (May 2016) no single library which combines reading the image with its metadata , making this probably rather excessive on memory usage; Improvements welcome.
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.imageio.ImageIO;
import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.ImageWriteException;
import org.apache.commons.imaging.Imaging;
import org.apache.commons.imaging.common.IImageMetadata;
import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter;
import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
import org.apache.commons.io.IOUtils;
import org.imgscalr.Scalr;
public class ImageData {
private byte[] imageData;
public ImageData(InputStream instream) throws IOException {
imageData = IOUtils.toByteArray(instream);
instream.close();
}
public synchronized void resize(int maxDimension) throws IOException, ImageReadException, ImageWriteException {
// Resize the image if necessary
BufferedImage image = readImage(imageData);
if (image.getWidth() > maxDimension || image.getHeight() > maxDimension) {
// Save existing metadata, if any
TiffImageMetadata metadata = readExifMetadata(imageData);
imageData = null; // allow immediate GC
// resize
image = Scalr.resize(image, maxDimension);
// rewrite resized image as byte[]
byte[] resizedData = writeJPEG(image);
image = null; // allow immediate GC
// Re-code resizedData + metadata to imageData
if (metadata != null) {
this.imageData = writeExifMetadata(metadata, resizedData);
} else {
this.imageData = resizedData;
}
}
}
private TiffImageMetadata readExifMetadata(byte[] jpegData) throws ImageReadException, IOException {
IImageMetadata imageMetadata = Imaging.getMetadata(jpegData);
if (imageMetadata == null) {
return null;
}
JpegImageMetadata jpegMetadata = (JpegImageMetadata)imageMetadata;
TiffImageMetadata exif = jpegMetadata.getExif();
if (exif == null) {
return null;
}
return exif;
}
private byte[] writeExifMetadata(TiffImageMetadata metadata, byte[] jpegData)
throws ImageReadException, ImageWriteException, IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
new ExifRewriter().updateExifMetadataLossless(jpegData, out, metadata.getOutputSet());
out.close();
return out.toByteArray();
}
private BufferedImage readImage(byte[] data) throws IOException {
return ImageIO.read(new ByteArrayInputStream(data));
}
private byte[] writeJPEG(BufferedImage image) throws IOException {
ByteArrayOutputStream jpegOut = new ByteArrayOutputStream();
ImageIO.write(image, "JPEG", jpegOut);
jpegOut.close();
return jpegOut.toByteArray();
}
public synchronized void writeJPEG(OutputStream outstream) throws IOException {
IOUtils.write(imageData, outstream);
}
public synchronized byte[] getJPEGData() {
return imageData;
}
}

Ed Randall
- 6,887
- 2
- 50
- 45
-
Thanks a lot. It worked nicely. The only thing is that apparently `IImageMetadata ` is named `ImageMetadata ` in the current repo for Apache Commons Imaging – m.hashemian Aug 30 '16 at 17:05
-
Do also check the other solution from @Rigeborod which looks a bit more efficient – Ed Randall Sep 06 '17 at 15:43