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 2019/11/17 10:55:16 UTC

svn commit: r1869928 - /pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/JPEGFactory.java

Author: tilman
Date: Sun Nov 17 10:55:15 2019
New Revision: 1869928

URL: http://svn.apache.org/viewvc?rev=1869928&view=rev
Log:
PDFBOX-4691: avoid decoding JPEG raster, try using metadata instead to lessen time and memory footprint

Modified:
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/JPEGFactory.java

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/JPEGFactory.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/JPEGFactory.java?rev=1869928&r1=1869927&r2=1869928&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/JPEGFactory.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/JPEGFactory.java Sun Nov 17 10:55:15 2019
@@ -39,7 +39,12 @@ import javax.imageio.metadata.IIOMetadat
 import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
 import javax.imageio.stream.ImageInputStream;
 import javax.imageio.stream.ImageOutputStream;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.apache.pdfbox.cos.COSArray;
 import org.apache.pdfbox.cos.COSInteger;
 import org.apache.pdfbox.cos.COSName;
@@ -58,6 +63,8 @@ import org.w3c.dom.Element;
  */
 public final class JPEGFactory
 {
+    private static final Log LOG = LogFactory.getLog(JPEGFactory.class);
+
     private JPEGFactory()
     {
     }
@@ -93,12 +100,10 @@ public final class JPEGFactory
         // copy stream
         ByteArrayInputStream byteStream = new ByteArrayInputStream(byteArray);
 
-        // read image
-        Raster raster = readJPEGRaster(byteStream);
-        byteStream.reset();
+        Dimensions meta = retrieveDimensions(byteStream);
 
         PDColorSpace colorSpace;
-        switch (raster.getNumDataElements())
+        switch (meta.numComponents)
         {
             case 1:
                 colorSpace = PDDeviceGray.INSTANCE;
@@ -111,12 +116,12 @@ public final class JPEGFactory
                 break;
             default:
                 throw new UnsupportedOperationException("number of data elements not supported: " +
-                        raster.getNumDataElements());
+                        meta.numComponents);
         }
 
         // create PDImageXObject from stream
         PDImageXObject pdImage = new PDImageXObject(document, byteStream, 
-                COSName.DCT_DECODE, raster.getWidth(), raster.getHeight(), 8, colorSpace);
+                COSName.DCT_DECODE, meta.width, meta.height, 8, colorSpace);
 
         if (colorSpace instanceof PDDeviceCMYK)
         {
@@ -135,7 +140,12 @@ public final class JPEGFactory
         return pdImage;
     }
 
-    private static Raster readJPEGRaster(InputStream stream) throws IOException
+    private static class Dimensions
+    {
+        private int width, height, numComponents;
+    }
+
+    private static Dimensions retrieveDimensions(ByteArrayInputStream stream) throws IOException
     {
         // find suitable image reader
         Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG");
@@ -160,19 +170,68 @@ public final class JPEGFactory
         {
             iis = ImageIO.createImageInputStream(stream);
             reader.setInput(iis);
+
+            Dimensions meta = new Dimensions();
+            meta.width = reader.getWidth(0);
+            meta.height = reader.getHeight(0);
+            // PDFBOX-4691: get from image metadata (faster because no decoding)
+            try
+            {
+                meta.numComponents = getNumComponentsFromImageMetadata(reader);
+                if (meta.numComponents != 0)
+                {
+                    return meta;
+                }
+                LOG.warn("No image metadata, will decode image and use raster size");
+            }
+            catch (IOException ex)
+            {
+                LOG.warn("Error reading image metadata, will decode image and use raster size", ex);
+            }            
+
+            // Old method: get from raster (slower)
             ImageIO.setUseCache(false);
-            return reader.readRaster(0, null);
+            Raster raster = reader.readRaster(0, null);
+            meta.numComponents = raster.getNumDataElements();
+            return meta;
         }
         finally
         {
-            if (iis != null)
-            {
-                iis.close();
-            }
+            IOUtils.closeQuietly(iis);
+            stream.reset();
             reader.dispose();
         }
     }
 
+    private static int getNumComponentsFromImageMetadata(ImageReader reader) throws IOException
+    {
+        IIOMetadata imageMetadata = reader.getImageMetadata(0);
+        if (imageMetadata == null)
+        {
+            return 0;
+        }
+        Element root = (Element) imageMetadata.getAsTree("javax_imageio_jpeg_image_1.0");
+        if (root == null)
+        {
+            return 0;
+        }
+
+        try
+        {
+            XPath xpath = XPathFactory.newInstance().newXPath();
+            String numScanComponents = xpath.evaluate("markerSequence/sos/@numScanComponents", root);
+            return Integer.parseInt(numScanComponents);
+        }
+        catch (NumberFormatException ex)
+        {
+            return 0;
+        }
+        catch (XPathExpressionException ex)
+        {
+            return 0;
+        }
+    }
+
     /**
      * Creates a new JPEG PDImageXObject from a BufferedImage.
      * <p>