I want to add some custom metadata to a multipage tiff for further processing steps, like
My idea was to update (see code/TODO below)
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
public class TiffMetadataExample {
public static void addMetadata(File tiff, File out, Object metadata2Add)
throws FileNotFoundException, IOException {
try (FileInputStream fis = new FileInputStream(tiff);
FileOutputStream fos = new FileOutputStream(out)) {
addMetadata(fis, fos, metadata2Add);
}
}
public static void addMetadata(InputStream inputImage, OutputStream out, Object metadata2Add)
throws IOException {
List<IIOMetadata> metadata = new ArrayList<>();
List<BufferedImage> images = getImages(inputImage, metadata);
if (metadata.size() != images.size()) {
throw new IllegalStateException();
}
// Obtain a TIFF writer
ImageWriter writer = ImageIO.getImageWritersByFormatName("TIFF").next();
try (ImageOutputStream output = ImageIO.createImageOutputStream(out)) {
writer.setOutput(output);
ImageWriteParam params = writer.getDefaultWriteParam();
params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
// Compression: None, PackBits, ZLib, Deflate, LZW, JPEG and CCITT variants allowed
// (different plugins may use a different set of compression type names)
params.setCompressionType("Deflate");
// streamMetadata is null here
IIOMetadata streamMetadata = writer.getDefaultStreamMetadata(params);
// TODO: add custom metadata fields [option 1]
writer.prepareWriteSequence(streamMetadata);
for (int i = 0; i < images.size(); i++) {
BufferedImage image = images.get(i);
IIOMetadata imageMetadata = metadata.get(i);
// TODO: add custom metadata fields [option 2]
writer.writeToSequence(new IIOImage(image, null, imageMetadata), params);
}
writer.endWriteSequence();
} finally {
writer.dispose();
}
}
private static List<BufferedImage> getImages(final InputStream inputImage,
final List<IIOMetadata> metadata) throws IOException {
List<BufferedImage> images = new ArrayList<>();
ImageReader reader = null;
try (ImageInputStream is = ImageIO.createImageInputStream(inputImage)) {
Iterator<ImageReader> iterator = ImageIO.getImageReaders(is);
reader = iterator.next();
reader.setInput(is);
int numPages = reader.getNumImages(true);
for (int numPage = 0; numPage < numPages; numPage++) {
BufferedImage pageImage = reader.read(numPage);
IIOMetadata imageMetadata = reader.getImageMetadata(numPage);
metadata.add(imageMetadata);
images.add(pageImage);
}
return images;
} finally {
if (reader != null) {
reader.dispose();
}
}
}
}
Try to update imageMetadata [option 2] with following code does not work. What is wrong here?
IIOMetadataNode textEntry = new IIOMetadataNode("tEXtEntry");
textEntry.setAttribute("keyword", "aaaaaa");
textEntry.setAttribute("value", "bbb");
IIOMetadataNode text = new IIOMetadataNode("tEXt");
text.appendChild(textEntry);
Node root = meta.getAsTree(formatName);
root.appendChild(text);
//e.g. formatName = "javax_imageio_1.0"
imageMetadata.setFromTree(imageMetadata.getNativeMetadataFormatName(), root);
Or is there a nicer/other way to store some further processing informations within the tiff?
This is my working solution.
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import org.apache.commons.imaging.common.RationalNumber;
import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoAscii;
import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoBytes;
import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDouble;
import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloat;
import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLong;
import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRational;
import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShort;
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
public class TiffMetadataExample {
public static final int TIFF_TAG_XMP = 0x2BC;
public static final String TIFF_TAG_XMP_NAME = "XMP";
private static final String SUN_TIFF_FORMAT = "com_sun_media_imageio_plugins_tiff_image_1.0";
private static final String SUN_TIFF_STREAM_FORMAT =
"com_sun_media_imageio_plugins_tiff_stream_1.0";
private static final String TAG_SET_CLASS_NAME =
"com.sun.media.imageio.plugins.tiff.BaselineTIFFTagSet";
public static void setMetaData(File in, File out, Metadata metaData) throws IOException {
try (FileInputStream fis = new FileInputStream(in);
FileOutputStream fos = new FileOutputStream(out)) {
setMetaData(fis, fos, metaData);
}
UserDefinedFileAttributeView userDefView =
Files.getFileAttributeView(out.toPath(), UserDefinedFileAttributeView.class);
for (Entry<String, String> fileAttEntry : metaData.getfileAtt().entrySet()) {
userDefView.write(fileAttEntry.getKey(),
Charset.defaultCharset().encode(fileAttEntry.getValue()));
}
}
public static void setMetaData(InputStream inputImage, OutputStream out, Metadata metdaData2Add)
throws IOException {
List<IIOMetadata> metadataList = new ArrayList<>();
List<BufferedImage> images = getImages(inputImage, metadataList);
// Obtain a TIFF writer
ImageWriter writer = ImageIO.getImageWritersByFormatName("TIFF").next();
try (ImageOutputStream output = ImageIO.createImageOutputStream(out)) {
writer.setOutput(output);
ImageWriteParam params = writer.getDefaultWriteParam();
params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
// Compression: None, PackBits, ZLib, Deflate, LZW, JPEG and CCITT variants allowed
// (different plugins may use a different set of compression type names)
params.setCompressionType("Deflate");
IIOMetadata streamMetadata = writer.getDefaultStreamMetadata(params);
writer.prepareWriteSequence(streamMetadata);
for (int i = 0; i < images.size(); i++) {
BufferedImage image = images.get(i);
IIOMetadata imageMetadata = metadataList.get(i);
updateMetadata(imageMetadata, metdaData2Add.get());
writer.writeToSequence(new IIOImage(image, null, imageMetadata), params);
}
writer.endWriteSequence();
} finally {
writer.dispose();
}
}
private static void updateMetadata(IIOMetadata metadata, List<IIOMetadataNode> metdaData2AddList)
throws IOException {
if (SUN_TIFF_FORMAT.equals(metadata.getNativeMetadataFormatName())
|| SUN_TIFF_STREAM_FORMAT.equals(metadata.getNativeMetadataFormatName())) {
// wanted format
} else {
throw new IllegalArgumentException(
"Could not write tiff metadata, wrong format: " + metadata.getNativeMetadataFormatName());
}
IIOMetadataNode root = new IIOMetadataNode(metadata.getNativeMetadataFormatName());
IIOMetadataNode ifd;
if (root.getElementsByTagName("TIFFIFD").getLength() == 0) {
ifd = new IIOMetadataNode("TIFFIFD");
ifd.setAttribute("tagSets", TAG_SET_CLASS_NAME);
root.appendChild(ifd);
} else {
ifd = (IIOMetadataNode) root.getElementsByTagName("TIFFIFD").item(0);
}
for (IIOMetadataNode metdaData2Add : metdaData2AddList) {
ifd.appendChild(metdaData2Add);
}
metadata.mergeTree(metadata.getNativeMetadataFormatName(), root);
}
private static List<BufferedImage> getImages(final InputStream inputImage,
final List<IIOMetadata> metadata) throws IOException {
List<BufferedImage> images = new ArrayList<>();
ImageReader reader = null;
try (ImageInputStream is = ImageIO.createImageInputStream(inputImage)) {
Iterator<ImageReader> iterator = ImageIO.getImageReaders(is);
reader = iterator.next();
reader.setInput(is);
int numPages = reader.getNumImages(true);
for (int numPage = 0; numPage < numPages; numPage++) {
BufferedImage pageImage = reader.read(numPage);
IIOMetadata meta = reader.getImageMetadata(numPage);
metadata.add(meta);
images.add(pageImage);
}
return images;
} finally {
if (reader != null) {
reader.dispose();
}
}
}
public static class Metadata {
private final List<IIOMetadataNode> addList = new ArrayList<>();
private final Map<String, String> fileAtt = new TreeMap<>();
public Metadata() {}
private List<IIOMetadataNode> get() {
return addList;
}
private Map<String, String> getfileAtt() {
return fileAtt;
}
public void add(int exifTag, String exifTagName, Object val) {
IIOMetadataNode md;
if (val instanceof byte[]) {
md = createBytesField(exifTag, exifTagName, (byte[]) val);
} else if (val instanceof String) {
md = createAsciiField(exifTag, exifTagName, (String) val);
fileAtt.put(exifTagName, String.valueOf(val));
} else if (val instanceof Short) {
md = createShortField(exifTag, exifTagName, ((Short) val).intValue());
fileAtt.put(exifTagName, String.valueOf(val));
} else if (val instanceof Integer) {
md = createShortField(exifTag, exifTagName, ((Integer) val).intValue());
fileAtt.put(exifTagName, String.valueOf(val));
} else if (val instanceof Long) {
md = createLongField(exifTag, exifTagName, ((Long) val).longValue());
fileAtt.put(exifTagName, String.valueOf(val));
} else if (val instanceof Float) {
md = createFloatField(exifTag, exifTagName, ((Float) val).floatValue());
fileAtt.put(exifTagName, String.valueOf(val));
} else if (val instanceof Double) {
md = createDoubleField(exifTag, exifTagName, ((Double) val).doubleValue());
fileAtt.put(exifTagName, String.valueOf(val));
} else if (val instanceof Rational) {
md = createRationalField(exifTag, exifTagName, ((Rational) val));
fileAtt.put(exifTagName, String.valueOf(val));
} else if (val instanceof RationalNumber) {
md = createRationalField(exifTag, exifTagName, ((RationalNumber) val));
fileAtt.put(exifTagName, String.valueOf(val));
} else {
throw new IllegalArgumentException("unsupported value class: " + val.getClass().getName());
}
addList.add(md);
}
/**
*
* @param tagInfo {@link TiffTagConstants} like {@link TiffTagConstants#TIFF_TAG_XMP}
* @param val String, byte[],
*/
public void add(TagInfo tagInfo, Object val) {
if (tagInfo instanceof TagInfoBytes) {
if (!(val instanceof byte[])) {
throw new IllegalArgumentException("expecting byte[] value");
}
} else if (tagInfo instanceof TagInfoAscii) {
if (!(val instanceof String)) {
throw new IllegalArgumentException("expecting String value");
}
} else if (tagInfo instanceof TagInfoShort) {
if (val instanceof Short || val instanceof Integer) {
// ok
} else {
throw new IllegalArgumentException("expecting Short/Integer value");
}
} else if (tagInfo instanceof TagInfoLong) {
if (!(val instanceof Long)) {
throw new IllegalArgumentException("expecting Long value");
}
} else if (tagInfo instanceof TagInfoDouble) {
if (!(val instanceof Double)) {
throw new IllegalArgumentException("expecting double value");
}
} else if (tagInfo instanceof TagInfoFloat) {
if (!(val instanceof Float)) {
throw new IllegalArgumentException("expecting float value");
}
} else if (tagInfo instanceof TagInfoRational) {
if (val instanceof RationalNumber || val instanceof Rational) {
// ok
} else {
throw new IllegalArgumentException("expecting rational value");
}
}
add(tagInfo.tag, tagInfo.name, val);
}
private static IIOMetadataNode createBytesField(int number, String name, byte[] bytes) {
IIOMetadataNode field = new IIOMetadataNode("TIFFField");
field.setAttribute("number", Integer.toString(number));
field.setAttribute("name", name);
IIOMetadataNode arrayNode = new IIOMetadataNode("TIFFBytes");
field.appendChild(arrayNode);
for (byte b : bytes) {
IIOMetadataNode valueNode = new IIOMetadataNode("TIFFByte");
valueNode.setAttribute("value", Integer.toString(b));
arrayNode.appendChild(valueNode);
}
return field;
}
private static IIOMetadataNode createShortField(int number, String name, int val) {
IIOMetadataNode field, arrayNode, valueNode;
field = new IIOMetadataNode("TIFFField");
field.setAttribute("number", Integer.toString(number));
field.setAttribute("name", name);
arrayNode = new IIOMetadataNode("TIFFShorts");
field.appendChild(arrayNode);
valueNode = new IIOMetadataNode("TIFFShort");
arrayNode.appendChild(valueNode);
valueNode.setAttribute("value", Integer.toString(val));
return field;
}
private static IIOMetadataNode createAsciiField(int number, String name, String val) {
IIOMetadataNode field, arrayNode, valueNode;
field = new IIOMetadataNode("TIFFField");
field.setAttribute("number", Integer.toString(number));
field.setAttribute("name", name);
arrayNode = new IIOMetadataNode("TIFFAsciis");
field.appendChild(arrayNode);
valueNode = new IIOMetadataNode("TIFFAscii");
arrayNode.appendChild(valueNode);
valueNode.setAttribute("value", val);
return field;
}
private static IIOMetadataNode createLongField(int number, String name, long val) {
IIOMetadataNode field, arrayNode, valueNode;
field = new IIOMetadataNode("TIFFField");
field.setAttribute("number", Integer.toString(number));
field.setAttribute("name", name);
arrayNode = new IIOMetadataNode("TIFFLongs");
field.appendChild(arrayNode);
valueNode = new IIOMetadataNode("TIFFLong");
arrayNode.appendChild(valueNode);
valueNode.setAttribute("value", Long.toString(val));
return field;
}
private static IIOMetadataNode createFloatField(int number, String name, float val) {
IIOMetadataNode field, arrayNode, valueNode;
field = new IIOMetadataNode("TIFFField");
field.setAttribute("number", Integer.toString(number));
field.setAttribute("name", name);
arrayNode = new IIOMetadataNode("TIFFFloats");
field.appendChild(arrayNode);
valueNode = new IIOMetadataNode("TIFFFloat");
arrayNode.appendChild(valueNode);
valueNode.setAttribute("value", Float.toString(val));
return field;
}
private static IIOMetadataNode createDoubleField(int number, String name, double val) {
IIOMetadataNode field, arrayNode, valueNode;
field = new IIOMetadataNode("TIFFField");
field.setAttribute("number", Integer.toString(number));
field.setAttribute("name", name);
arrayNode = new IIOMetadataNode("TIFFDoubles");
field.appendChild(arrayNode);
valueNode = new IIOMetadataNode("TIFFDouble");
arrayNode.appendChild(valueNode);
valueNode.setAttribute("value", Double.toString(val));
return field;
}
private static IIOMetadataNode createRationalField(int number, String name, Rational rational) {
return createRationalField(number, name, rational.numerator(), rational.denominator());
}
private static IIOMetadataNode createRationalField(int number, String name,
RationalNumber rational) {
return createRationalField(number, name, rational.numerator, rational.divisor);
}
private static IIOMetadataNode createRationalField(int number, String name, long numerator,
long denominator) {
IIOMetadataNode field, arrayNode, valueNode;
field = new IIOMetadataNode("TIFFField");
field.setAttribute("number", Integer.toString(number));
field.setAttribute("name", name);
arrayNode = new IIOMetadataNode("TIFFRationals");
field.appendChild(arrayNode);
valueNode = new IIOMetadataNode("TIFFRational");
arrayNode.appendChild(valueNode);
valueNode.setAttribute("value", numerator + "/" + denominator);
return field;
}
}
}
Usage
byte[] bytes = create();
TiffMetadata.Metadata metaData = new TiffMetadata.Metadata();
metaData.add(TiffTagConstants.TIFF_TAG_SOFTWARE, "FUBAR");
// metaData.add(TiffMetadata.TIFF_TAG_XMP, TiffMetadata.TIFF_TAG_XMP_NAME, bytes );
metaData.add(TiffTagConstants.TIFF_TAG_XMP, bytes);
TiffMetadata.setMetaData(tiffIn, tiffOut, metaData);