You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2014/02/22 19:35:15 UTC

svn commit: r1570877 - in /pdfbox/branches/1.8/pdfbox/src: main/java/org/apache/pdfbox/util/ImageIOUtil.java test/java/org/apache/pdfbox/util/TestImageIOUtils.java

Author: tilman
Date: Sat Feb 22 18:35:15 2014
New Revision: 1570877

URL: http://svn.apache.org/r1570877
Log:
PDFBOX-1734: ImageIOUtil.WriteImage now supports (optimized) TIFF images, based on ImageIO until we get a better alternative

Modified:
    pdfbox/branches/1.8/pdfbox/src/main/java/org/apache/pdfbox/util/ImageIOUtil.java
    pdfbox/branches/1.8/pdfbox/src/test/java/org/apache/pdfbox/util/TestImageIOUtils.java

Modified: pdfbox/branches/1.8/pdfbox/src/main/java/org/apache/pdfbox/util/ImageIOUtil.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/1.8/pdfbox/src/main/java/org/apache/pdfbox/util/ImageIOUtil.java?rev=1570877&r1=1570876&r2=1570877&view=diff
==============================================================================
--- pdfbox/branches/1.8/pdfbox/src/main/java/org/apache/pdfbox/util/ImageIOUtil.java (original)
+++ pdfbox/branches/1.8/pdfbox/src/main/java/org/apache/pdfbox/util/ImageIOUtil.java Sat Feb 22 18:35:15 2014
@@ -17,10 +17,10 @@
 package org.apache.pdfbox.util;
 
 import java.awt.image.BufferedImage;
-import java.awt.image.RenderedImage;
 import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.StringWriter;
 import java.util.Iterator;
 
 import javax.imageio.IIOException;
@@ -33,11 +33,20 @@ import javax.imageio.metadata.IIOInvalid
 import javax.imageio.metadata.IIOMetadata;
 import javax.imageio.metadata.IIOMetadataNode;
 import javax.imageio.stream.ImageOutputStream;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
-
 /**
  * This class handles some ImageIO operations.
  *
@@ -47,6 +56,11 @@ import org.w3c.dom.NodeList;
 public class ImageIOUtil
 {   
     /**
+     * Log instance.
+     */
+    private static final Log LOG = LogFactory.getLog(ImageIOUtil.class);
+
+    /**
      * Default screen resolution: 72dpi.
      */
     public static final int DEFAULT_SCREEN_RESOLUTION = 72;
@@ -77,7 +91,7 @@ public class ImageIOUtil
     throws IOException
     {
         String fileName = filename + "." + imageFormat;
-        File file = new File( fileName );
+        File file = new File(fileName);
         return writeImage(image, imageFormat, file, resolution);
     }
 
@@ -121,7 +135,8 @@ public class ImageIOUtil
      * @param imageFormat the target format (ex. "png")
      * @param outputStream the output stream to be used for writing
      * @param resolution resolution to be used when writing the image
-     * @param quality quality to be used when compressing the image (0 < quality < 1.0f)
+     * @param quality quality to be used when compressing the image (0 <
+     * quality < 1.0f)
      * 
      * @return true if the images were produced, false if there was an error
      * @throws IOException if an I/O error occurs
@@ -135,54 +150,73 @@ public class ImageIOUtil
         ImageWriter imageWriter = null;
         try
         {
-            output = ImageIO.createImageOutputStream( outputStream );
+            output = ImageIO.createImageOutputStream(outputStream);
     
             boolean foundWriter = false;
-            Iterator<ImageWriter> writerIter = ImageIO.getImageWritersByFormatName( imageFormat );
-            while( writerIter.hasNext() && !foundWriter )
+            Iterator<ImageWriter> writerIter = ImageIO.getImageWritersByFormatName(imageFormat);
+            while (writerIter.hasNext() && !foundWriter)
             {
                 try
                 {
-                    imageWriter = (ImageWriter)writerIter.next();
+                    imageWriter = (ImageWriter) writerIter.next();
                     ImageWriteParam writerParams = imageWriter.getDefaultWriteParam();
-                    if( writerParams.canWriteCompressed() )
+                    if (writerParams.canWriteCompressed())
                     {
                         writerParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                         // reset the compression type if overwritten by setCompressionMode
+
                         if (writerParams.getCompressionType() == null)
                         {
-                            writerParams.setCompressionType(writerParams.getCompressionTypes()[0]);
+                            if (imageFormat.toLowerCase().startsWith("tif"))
+                            {
+                                // avoid error: first compression type is RLE, not optimal and incorrect for color images
+                                //TODO? another writeImage() call with extra compression param so user can decide
+                                if (image.getType() == BufferedImage.TYPE_BYTE_BINARY && image.getColorModel().getPixelSize() == 1)
+                                {
+                                    writerParams.setCompressionType("CCITT T.6");
+                                }
+                                else
+                                {
+                                    writerParams.setCompressionType("LZW");
+                                }
+                            }
+                            else
+                            {
+                                writerParams.setCompressionType(writerParams.getCompressionTypes()[0]);
+                            }
                         }
-                        writerParams.setCompressionQuality(quality);
+                        writerParams.setCompressionQuality(0);
                     }
-                    IIOMetadata meta = createMetadata( image, imageWriter, writerParams, resolution);
+                    IIOMetadata meta = createMetadata(image, imageWriter, writerParams, resolution);
                     if (meta != null)
                     {
-                    	imageWriter.setOutput( output );
-                    	imageWriter.write( null, new IIOImage( image, null, meta ), writerParams );
+                        imageWriter.setOutput(output);
+                        imageWriter.write(null, new IIOImage(image, null, meta), writerParams);
                     	foundWriter = true;
                     }
                 }
-                catch( IIOException io )
+                catch (IIOException io)
                 {
-                    throw new IOException( io.getMessage() );
+                    LOG.error("IIOException in writeImage()", io);
+                    throw new IOException(io.getMessage());
                 }
                 finally
                 {
-                    if( imageWriter != null )
+                    if (imageWriter != null)
                     {
                         imageWriter.dispose();
                     }
                 }
             }
-            if( !foundWriter )
+            if (!foundWriter)
             {
+                LOG.error("No writer found for format '" + imageFormat + "'");
                 bSuccess = false;
             }
         }
         finally
         {
-            if( output != null )
+            if (output != null)
             {
                 output.flush();
                 output.close();
@@ -191,7 +225,7 @@ public class ImageIOUtil
         return bSuccess;
     }
 
-    private static IIOMetadata createMetadata(RenderedImage image, ImageWriter imageWriter,
+    private static IIOMetadata createMetadata(BufferedImage image, ImageWriter imageWriter,
             ImageWriteParam writerParams, int resolution)
     {
         ImageTypeSpecifier type;
@@ -203,11 +237,72 @@ public class ImageIOUtil
         {
             type = ImageTypeSpecifier.createFromRenderedImage( image );
         }
-        IIOMetadata meta = imageWriter.getDefaultImageMetadata( type, writerParams );
-        return (addResolution(meta, resolution) ? meta : null);
+        IIOMetadata meta = imageWriter.getDefaultImageMetadata(type, writerParams);
+        logMeta(meta, STANDARD_METADATA_FORMAT);
+        if (imageWriter.getClass().getName().toUpperCase().contains("TIFF"))
+        {
+            updateMetadata(image, meta, resolution);
+        }
+        else
+        {
+            if (!addResolution(meta, resolution))
+            {
+                meta = null;
+            }
+        }
+        logMeta(meta, STANDARD_METADATA_FORMAT);
+        return meta;
+    }
+
+    /**
+     * log the meta data as an XML tree if debug is enabled.
+     * 
+     * @param meta meta data.
+     * @param format the XML format to be used.
+     */
+    private static void logMeta(IIOMetadata meta, String format)
+    {
+        if (!LOG.isDebugEnabled())
+        {
+            return;
+        }
+        if (meta == null)
+        {
+            LOG.debug("meta is null");
+            return;
+        }
+        IIOMetadataNode root = (IIOMetadataNode) meta.getAsTree(format);
+        // http://download.java.net/jdk8/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
+        // http://www.java-forum.org/java-basics-anfaenger-themen/96982-aufloesung-dpi-tiff-png-bildern-auslesen.html#post617178
+
+        try
+        {
+            StringWriter xmlStringWriter = new StringWriter();
+            StreamResult streamResult = new StreamResult(xmlStringWriter);
+            Transformer transformer = TransformerFactory.newInstance().newTransformer();
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes"); // http://stackoverflow.com/a/1264872/535646
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+            DOMSource domSource = new DOMSource(root);
+            transformer.transform(domSource, streamResult);
+            LOG.debug("\n" + xmlStringWriter);
+        }
+        catch (TransformerFactoryConfigurationError ex)
+        {
+            LOG.error(ex, ex);
+        }
+        catch (IllegalArgumentException ex)
+        {
+            LOG.error(ex, ex);
+        }
+        catch (TransformerException ex)
+        {
+            LOG.error(ex, ex);
+        }
     }
 
     private static final String STANDARD_METADATA_FORMAT = "javax_imageio_1.0";
+    private static final String SUN_TIFF_NATIVE_FORMAT
+            = "com_sun_media_imageio_plugins_tiff_image_1.0";
 
     private static boolean addResolution(IIOMetadata meta, int resolution)
     {
@@ -265,4 +360,112 @@ public class ImageIOUtil
         return null;
     }
     
+    /**
+     * Sets the resolution in a TIFF image, the resolution unit (Inches), rows
+     * per strip to the height to get smaller files, and the name of the
+     * software to "PDFBOX".
+     *
+     * @param tiffImage the TIFF Image
+     * @param meta the meta data that is to be set
+     * @param resolution in dots per inch
+     */
+    protected static void updateMetadata(BufferedImage tiffImage, IIOMetadata meta, int resolution)
+    {
+        // Code inspired by Apache XML graphics
+        // https://svn.apache.org/repos/asf/xmlgraphics/commons/tags/commons-1_3_1/src/java/org/apache/xmlgraphics/image/writer/imageio/ImageIOTIFFImageWriter.java
+        // DTD:
+        // http://download.java.net/media/jai-imageio/javadoc/1.0_01/com/sun/media/imageio/plugins/tiff/package-summary.html
+        // TIFF6 Spec:
+        // http://partners.adobe.com/public/developer/tiff/index.html
+        // We set the resolution manually using the native format since it appears that
+        // it doesn't work properly through the standard metadata. Haven't figured out why
+        // that happens.
+        if (SUN_TIFF_NATIVE_FORMAT.equals(meta.getNativeMetadataFormatName()))
+        {
+            IIOMetadataNode root = new IIOMetadataNode(SUN_TIFF_NATIVE_FORMAT);
+            IIOMetadataNode ifd = getChildNode(root, "TIFFIFD");
+            if (ifd == null)
+            {
+                ifd = new IIOMetadataNode("TIFFIFD");
+                ifd.setAttribute("tagSets",
+                        "com.sun.media.imageio.plugins.tiff.BaselineTIFFTagSet");
+                root.appendChild(ifd);
+            }
+
+            ifd.appendChild(createRationalField(282, "XResolution", resolution, 1));
+            ifd.appendChild(createRationalField(283, "YResolution", resolution, 1));
+            ifd.appendChild(createShortField(296, "ResolutionUnit", 2)); // Inch
+
+            ifd.appendChild(createLongField(278, "RowsPerStrip", tiffImage.getHeight()));
+
+            ifd.appendChild(createAsciiField(305, "Software", "PDFBOX"));
+
+            try
+            {
+                meta.mergeTree(SUN_TIFF_NATIVE_FORMAT, root);
+            }
+            catch (IIOInvalidTreeException e)
+            {
+                throw new RuntimeException("Cannot update image metadata: "
+                        + e.getMessage(), e);
+            }
+        }
+    }
+
+    private static IIOMetadataNode createShortField(int tiffTagNumber, String name, int val)
+    {
+        IIOMetadataNode field, arrayNode, valueNode;
+        field = new IIOMetadataNode("TIFFField");
+        field.setAttribute("number", Integer.toString(tiffTagNumber));
+        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 createRationalField(int number, String name, int numerator, int 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;
+    }
+
 }

Modified: pdfbox/branches/1.8/pdfbox/src/test/java/org/apache/pdfbox/util/TestImageIOUtils.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/1.8/pdfbox/src/test/java/org/apache/pdfbox/util/TestImageIOUtils.java?rev=1570877&r1=1570876&r2=1570877&view=diff
==============================================================================
--- pdfbox/branches/1.8/pdfbox/src/test/java/org/apache/pdfbox/util/TestImageIOUtils.java (original)
+++ pdfbox/branches/1.8/pdfbox/src/test/java/org/apache/pdfbox/util/TestImageIOUtils.java Sat Feb 22 18:35:15 2014
@@ -77,6 +77,10 @@ public class TestImageIOUtils extends Te
             imageType = "wbmp";
             writeImage(document, imageType, outDir + file.getName() + "-", 
                     BufferedImage.TYPE_BYTE_BINARY, resolution);
+            // testing TIFF
+            imageType = "tif";
+            writeImage(document, imageType, outDir + file.getName() + "-bw-", BufferedImage.TYPE_BYTE_BINARY, resolution);
+            writeImage(document, imageType, outDir + file.getName() + "-co-", BufferedImage.TYPE_INT_RGB, resolution);
         }
         catch(Exception e)
         {