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)

Reply via email to