I recently had the requirement for a process that would read TIFF Group IV
images, 'stamp' them with a string, and save the images. Below is some code
I wrote as part of playing with options. The code barely changes the size
of the image. In fact, most of the size difference was from adding a
software tag. I've copied my code below. This will run headless if you need
it to run on a server without access to a display. Maybe it will be of use.
(NOTE: XPath does not always work on a metadata Node tree. I've been told
that IIOMetadata's getAsTree() does not return a true W3C Node, so XPath
will not work in all cases. However I've found that it does work on the
Group IV's that I deal with.)
public class ImageStamper {
public static void main(String[] args) {
new ImageStamper(args[0]);
}
private final static float FONT_SIZE_IN_POINTS = 12.0f;
private final static String FONT_FAMILY = "Times New Roman";
private final static float OFFSET_FROM_RIGHT = 3.0f;
private final static float OFFSET_FROM_TOP = 0.3f;
public ImageStamper() {}
public ImageStamper(String inFilename) {
Path p = FileSystems.getDefault().getPath(".", inFilename);
int width = 0;
int height = 0;
int xDpi = 0;
int yDpi = 0;
ImageReader reader = getTIFFImageReader();
ImageWriter writer = getTIFFImageWriter();
try {
// Make new image.
Path outFile =
Files.createTempFile(FileSystems.getDefault().getPath("."), "tmp", ".tif");
ImageOutputStream ios =
ImageIO.createImageOutputStream(outFile.toFile());
writer.setOutput(ios);
writer.prepareWriteSequence(null);
byte [] buffer = Files.readAllBytes(p);
// We'll need TIFFDirectory for dpi
ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
ImageInputStream iis = ImageIO.createImageInputStream(bais);
reader.setInput(iis);
int pages = reader.getNumImages(true);
for (int page = 0; page < pages; page++) {
BufferedImage image = reader.read(page);
IIOMetadata inMetadata = reader.getImageMetadata(page);
TIFFDirectory tiffDir =
TIFFDirectory.createFromMetadata(inMetadata);
// Get size
TIFFField w =
tiffDir.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
width = (w != null) ? w.getAsInt(0) : image.getWidth();
TIFFField h =
tiffDir.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
height = (h!= null) ? h.getAsInt(0) : image.getHeight();
// Get dpi
TIFFField xrez =
tiffDir.getTIFFField(BaselineTIFFTagSet.TAG_X_RESOLUTION);
xDpi = (xrez != null) ? xrez.getAsInt(0) : 72;
TIFFField yrez =
tiffDir.getTIFFField(BaselineTIFFTagSet.TAG_Y_RESOLUTION);
yDpi = (yrez != null) ? yrez.getAsInt(0) : 72;
TIFFField rezu =
tiffDir.getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT);
int rezUnit = (rezu != null) ? rezu.getAsInt(0) :
BaselineTIFFTagSet.RESOLUTION_UNIT_INCH;
if (rezUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER) {
xDpi = (int)((float)xDpi/2.54);
yDpi = (int)((float)yDpi/2.54);
}
Graphics2D g2 = image.createGraphics();
// Scale the font.
float fontRatio = xDpi/72.0f;
int fontSize = Math.round(FONT_SIZE_IN_POINTS * fontRatio);
// Set up the font.
Map<TextAttribute, Object> fontAttributes = new
HashMap<TextAttribute, Object>();
fontAttributes.put(TextAttribute.FAMILY, FONT_FAMILY);
fontAttributes.put(TextAttribute.SIZE, fontSize);
fontAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
Font font = new Font("Serif", fontSize, Font.BOLD);
font = font.deriveFont(fontAttributes);
g2.setFont(font);
FontMetrics fm = g2.getFontMetrics();
// Text
String stamp = "PLACE STAMP HERE";
g2.setColor(java.awt.Color.black);
g2.setPaintMode();
// Position & print top right
int y = Math.round(OFFSET_FROM_TOP * yDpi + fm.getMaxDescent());
int x = width - Math.round(OFFSET_FROM_RIGHT * xDpi) -
fm.stringWidth(stamp);
g2.drawString(stamp, x, y);
// Position & print in other locations.
// Set compression while writing.
ImageWriteParam writeParam = writer.getDefaultWriteParam();
writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
writeParam.setCompressionType("CCITT T.6");
writeParam.setCompressionQuality(1.0f);
// Get the initial metadata
ImageTypeSpecifier spec =
ImageTypeSpecifier.createFromRenderedImage(image);
IIOMetadata metadata = writer.getDefaultImageMetadata(spec,
writeParam);
String formatName = metadata.getNativeMetadataFormatName();
// Reset the metadata with new values.
try {
Node metadataNode = metadata.getAsTree(formatName);
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
NodeList items = (NodeList) xpath.evaluate("//TIFFField",
metadataNode, XPathConstants.NODESET);
for (int i = 0; i < items.getLength(); i++) {
Node item = items.item(i);
int tagNumber = Integer.parseInt(((Element)
item).getAttributeNode("number").getValue());
Element e;
switch (tagNumber) {
case BaselineTIFFTagSet.TAG_IMAGE_WIDTH:
e = (Element) xpath.evaluate("TIFFShorts/TIFFShort", item,
XPathConstants.NODE);
e.getAttributeNode("value").setValue("" + width);
break;
case BaselineTIFFTagSet.TAG_IMAGE_LENGTH:
e = (Element) xpath.evaluate("TIFFShorts/TIFFShort", item,
XPathConstants.NODE);
e.getAttributeNode("value").setValue("" + height);
break;
case BaselineTIFFTagSet.TAG_ROWS_PER_STRIP:
e = (Element) xpath.evaluate("TIFFShorts/TIFFShort", item,
XPathConstants.NODE);
e.getAttributeNode("value").setValue("" + height);
break;
case BaselineTIFFTagSet.TAG_X_RESOLUTION:
e = (Element) xpath.evaluate("TIFFRationals/TIFFRational",
item, XPathConstants.NODE);
e.getAttributeNode("value").setValue("" + xDpi + "/1");
break;
case BaselineTIFFTagSet.TAG_Y_RESOLUTION:
e = (Element) xpath.evaluate("TIFFRationals/TIFFRational",
item, XPathConstants.NODE);
e.getAttributeNode("value").setValue("" + yDpi + "/1");
break;
case BaselineTIFFTagSet.TAG_RESOLUTION_UNIT:
e = (Element) xpath.evaluate("TIFFShorts/TIFFShort", item,
XPathConstants.NODE);
e.getAttributeNode("value").setValue("2");
e.getAttributeNode("description").setValue("Inch");
break;
}
}
// Add software tag.
IIOMetadataNode tiffFieldNode = new IIOMetadataNode("TIFFField");
tiffFieldNode.setAttribute("name", "Software");
tiffFieldNode.setAttribute("number", "" +
BaselineTIFFTagSet.TAG_SOFTWARE);
IIOMetadataNode asciisNode = new IIOMetadataNode("TIFFAsciis");
IIOMetadataNode asciiNode = new IIOMetadataNode("TIFFAscii");
asciiNode.setAttribute("keyword", "Software");
asciiNode.setAttribute("value", "Test of stamper positioning.");
asciisNode.appendChild(asciiNode);
tiffFieldNode.appendChild(asciisNode);
Element tiffIfd = (Element) xpath.evaluate("//TIFFIFD",
metadataNode, XPathConstants.NODE);
tiffIfd.appendChild(tiffFieldNode);
// Set new metadata.
metadata.setFromTree(formatName, metadataNode);
}
catch (XPathExpressionException e) {
e.printStackTrace();
}
// Convert to an IIOImage and write.
IIOImage iioImage = new IIOImage(image, null, metadata);
writer.writeToSequence(iioImage, writeParam);
}
writer.endWriteSequence();
iis.close();
ios.close();
System.out.println("TIFF image written: "+outFile.toString());
}
catch (IOException e) {
e.printStackTrace();
}
}
/**
* Return an image reader for TIFF files.
* @return an image reader.
*/
private ImageReader getTIFFImageReader() {
Iterator<ImageReader> readers =
ImageIO.getImageReadersByFormatName("tiff");
while (readers.hasNext()) {
ImageReader reader = (ImageReader) readers.next();
// Bypass the reader known to be buggy on the Mac.
if
(!reader.toString().startsWith("com.sun.imageio.plugins.tiff.TIFFImageReader"))
return reader;
}
return null;
}
/**
* Return an image writer for TIFF files.
* @return an image writer.
*/
private static ImageWriter getTIFFImageWriter() {
Iterator<ImageWriter> writers =
ImageIO.getImageWritersByFormatName("tiff");
while (writers.hasNext()) {
ImageWriter writer = writers.next();
// Is there a buggy Mac type?
return writer;
}
return null;
}
}
On Mon, Apr 27, 2015 at 4:44 AM, Remi Malessa <[email protected]> wrote:
> Hi,
>
> I work with scanned Tiff images. Because of the type of scanner used,
> there is a tag I need to remove.
> I tried to accomplish this with Commons Imaging libraries.
>
> The problem is, the images gain significantly in size, after the operation:
>
> 15M tags.original.tif
> 28M tags.new.tif
>
> Here's how I remove the Tag:
>
> File tInputFile = new File("/home/rem/TEMP/tags.original.tif");
> File toutputFile = new File("/home/rem/TEMP/tags.new.tif");
>
> final BufferedImage image = Imaging.getBufferedImage(tInputFile);
>
> TiffOutputSet outputSet = null;
> OutputStream os = new FileOutputStream(toutputFile);
>
> ImageMetadata tMetadata = Imaging.getMetadata(tInputFile);
>
> TiffImageMetadata imageMetadata = (TiffImageMetadata) tMetadata;
> outputSet = imageMetadata.getOutputSet();
> TiffOutputDirectory exifDirectory = outputSet.getExifDirectory();
> exifDirectory.removeField(41488);
>
> ImageFormats format = ImageFormats.TIFF;
> Map<String, Object> params = new HashMap<String, Object>();
> byte[] bytes = Imaging.writeImageToBytes(image, format, params);
>
> TiffImageWriterLossless writerLossLess = new
> TiffImageWriterLossless(bytes);
> writerLossLess.write(os, outputSet);
>
>
> Would much appreciate if somebody could tell me if there is a method to
> trim or prevent creation of the extra data when saving the image + set, to
> a file ?
>
>
> Cheers
> Remi
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [email protected]
> For additional commands, e-mail: [email protected]
>
>
--
"Hell hath no limits, nor is circumscrib'd In one self-place; but where we
are is hell, And where hell is, there must we ever be" --Christopher
Marlowe, *Doctor Faustus* (v. 121-24)