pdfbox-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From til...@apache.org
Subject svn commit: r1826261 [1/2] - in /pdfbox/branches/2.0/pdfbox/src: main/java/org/apache/pdfbox/cos/ main/java/org/apache/pdfbox/filter/ main/java/org/apache/pdfbox/pdmodel/common/ main/java/org/apache/pdfbox/pdmodel/graphics/image/ main/java/org/apache/p...
Date Thu, 08 Mar 2018 18:21:35 GMT
Author: tilman
Date: Thu Mar  8 18:21:35 2018
New Revision: 1826261

URL: http://svn.apache.org/viewvc?rev=1826261&view=rev
Log:
PDFBOX-4137: support rendering with subsampling and subimages, by Itai Shaked

Added:
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/DecodeOptions.java   (with props)
Modified:
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSInputStream.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/DCTFilter.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/Filter.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/JBIG2Filter.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/JPXFilter.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDStream.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImage.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDInlineImage.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/SampledImageReader.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawerParameters.java
    pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/common/PDStreamTest.java

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSInputStream.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSInputStream.java?rev=1826261&r1=1826260&r2=1826261&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSInputStream.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSInputStream.java Thu Mar  8 18:21:35 2018
@@ -24,6 +24,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
+
+import org.apache.pdfbox.filter.DecodeOptions;
 import org.apache.pdfbox.filter.DecodeResult;
 import org.apache.pdfbox.filter.Filter;
 import org.apache.pdfbox.io.RandomAccess;
@@ -51,6 +53,12 @@ public final class COSInputStream extend
     static COSInputStream create(List<Filter> filters, COSDictionary parameters, InputStream in,
                                  ScratchFile scratchFile) throws IOException
     {
+        return create(filters, parameters, in, scratchFile, DecodeOptions.DEFAULT);
+    }
+
+    static COSInputStream create(List<Filter> filters, COSDictionary parameters, InputStream in,
+                                 ScratchFile scratchFile, DecodeOptions options) throws IOException
+    {
         List<DecodeResult> results = new ArrayList<DecodeResult>();
         InputStream input = in;
         if (filters.isEmpty())
@@ -66,7 +74,7 @@ public final class COSInputStream extend
                 {
                     // scratch file
                     final RandomAccess buffer = scratchFile.createBuffer();
-                    DecodeResult result = filters.get(i).decode(input, new RandomAccessOutputStream(buffer), parameters, i);
+                    DecodeResult result = filters.get(i).decode(input, new RandomAccessOutputStream(buffer), parameters, i, options);
                     results.add(result);
                     input = new RandomAccessInputStream(buffer)
                     {
@@ -81,7 +89,7 @@ public final class COSInputStream extend
                 {
                     // in-memory
                     ByteArrayOutputStream output = new ByteArrayOutputStream();
-                    DecodeResult result = filters.get(i).decode(input, output, parameters, i);
+                    DecodeResult result = filters.get(i).decode(input, output, parameters, i, options);
                     results.add(result);
                     input = new ByteArrayInputStream(output.toByteArray());
                 }

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java?rev=1826261&r1=1826260&r2=1826261&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java Thu Mar  8 18:21:35 2018
@@ -26,6 +26,7 @@ import java.util.ArrayList;
 import java.util.List;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.filter.DecodeOptions;
 import org.apache.pdfbox.filter.Filter;
 import org.apache.pdfbox.filter.FilterFactory;
 import org.apache.pdfbox.io.IOUtils;
@@ -159,6 +160,11 @@ public class COSStream extends COSDictio
      */
     public COSInputStream createInputStream() throws IOException
     {
+        return createInputStream(DecodeOptions.DEFAULT);
+    }
+
+    public COSInputStream createInputStream(DecodeOptions options) throws IOException
+    {
         checkClosed();
         if (isWriting)
         {
@@ -166,7 +172,7 @@ public class COSStream extends COSDictio
         }
         ensureRandomAccessExists(true);
         InputStream input = new RandomAccessInputStream(randomAccess);
-        return COSInputStream.create(getFilterList(), this, input, scratchFile);
+        return COSInputStream.create(getFilterList(), this, input, scratchFile, options);
     }
 
     /**

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/DCTFilter.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/DCTFilter.java?rev=1826261&r1=1826260&r2=1826261&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/DCTFilter.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/DCTFilter.java Thu Mar  8 18:21:35 2018
@@ -26,6 +26,7 @@ import java.io.OutputStream;
 
 import javax.imageio.IIOException;
 import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
 import javax.imageio.ImageReader;
 import javax.imageio.metadata.IIOMetadata;
 import javax.imageio.metadata.IIOMetadataNode;
@@ -51,8 +52,8 @@ final class DCTFilter extends Filter
     private static final String ADOBE = "Adobe";
 
     @Override
-    public DecodeResult decode(InputStream encoded, OutputStream decoded,
-                                         COSDictionary parameters, int index) throws IOException
+    public DecodeResult decode(InputStream encoded, OutputStream decoded, COSDictionary
+            parameters, int index, DecodeOptions options) throws IOException
     {
         ImageReader reader = findImageReader("JPEG", "a suitable JAI I/O image filter is not installed");
         ImageInputStream iis = null;
@@ -67,7 +68,12 @@ final class DCTFilter extends Filter
             }
             
             reader.setInput(iis);
-            
+            ImageReadParam irp = reader.getDefaultReadParam();
+            irp.setSourceSubsampling(options.getSubsamplingX(), options.getSubsamplingY(),
+                    options.getSubsamplingOffsetX(), options.getSubsamplingOffsetY());
+            irp.setSourceRegion(options.getSourceRegion());
+            options.setFilterSubsampled(true);
+
             String numChannels = getNumChannels(reader);
 
             // get the raster using horrible JAI workarounds
@@ -82,21 +88,21 @@ final class DCTFilter extends Filter
                 try
                 {
                     // I'd like to use ImageReader#readRaster but it is buggy and can't read RGB correctly
-                    BufferedImage image = reader.read(0);
+                    BufferedImage image = reader.read(0, irp);
                     raster = image.getRaster();
                 }
                 catch (IIOException e)
                 {
                     // JAI can't read CMYK JPEGs using ImageReader#read or ImageIO.read but
                     // fortunately ImageReader#readRaster isn't buggy when reading 4-channel files
-                    raster = reader.readRaster(0, null);
+                    raster = reader.readRaster(0, irp);
                 }
             }
             else
             {
                 // JAI can't read CMYK JPEGs using ImageReader#read or ImageIO.read but
                 // fortunately ImageReader#readRaster isn't buggy when reading 4-channel files
-                raster = reader.readRaster(0, null);
+                raster = reader.readRaster(0, irp);
             }
 
             // special handling for 4-component images
@@ -156,6 +162,13 @@ final class DCTFilter extends Filter
         return new DecodeResult(parameters);
     }
 
+    @Override
+    public DecodeResult decode(InputStream encoded, OutputStream decoded,
+                               COSDictionary parameters, int index) throws IOException
+    {
+        return decode(encoded, decoded, parameters, index, DecodeOptions.DEFAULT);
+    }
+
     // reads the APP14 Adobe transform tag and returns its value, or 0 if unknown
     private Integer getAdobeTransform(IIOMetadata metadata)
     {
@@ -169,7 +182,7 @@ final class DCTFilter extends Filter
         }
         return 0;
     }
-        
+
     // See in https://github.com/haraldk/TwelveMonkeys
     // com.twelvemonkeys.imageio.plugins.jpeg.AdobeDCT class for structure of APP14 segment
     private int getAdobeTransformByBruteForce(ImageInputStream iis) throws IOException

Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/DecodeOptions.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/DecodeOptions.java?rev=1826261&view=auto
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/DecodeOptions.java (added)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/DecodeOptions.java Thu Mar  8 18:21:35 2018
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.filter;
+
+import java.awt.Rectangle;
+
+/**
+ * Options that may be passed to a Filter to request special handling when decoding the stream.
+ * Filters may not honor some or all of the specified options, and so callers should check the
+ * honored flag if further processing relies on the options being used.
+ */
+public class DecodeOptions
+{
+    /**
+     * Default decode options. The honored flag for this instance is always true, as it represents
+     * the default behavior.
+     */
+    public static final DecodeOptions DEFAULT = new FinalDecodeOptions(true);
+
+    private Rectangle sourceRegion = null;
+    private int subsamplingX = 1, subsamplingY = 1, subsamplingOffsetX = 0, subsamplingOffsetY = 0;
+    private boolean filterSubsampled = false;
+
+    /**
+     * Constructs an empty DecodeOptions instance
+     */
+    public DecodeOptions()
+    {
+    }
+
+    /**
+     * Constructs an instance specifying the region of the image that should be decoded. The actual
+     * region will be clipped to the dimensions of the image.
+     *
+     * @param sourceRegion Region of the source image that should be decoded
+     */
+    public DecodeOptions(Rectangle sourceRegion)
+    {
+        this.sourceRegion = sourceRegion;
+    }
+
+    /**
+     * Constructs an instance specifying the region of the image that should be decoded. The actual
+     * region will be clipped to the dimensions of the image.
+     *
+     * @param x x-coordinate of the top-left corner of the region to be decoded
+     * @param y y-coordinate of the top-left corner of the region to be decoded
+     * @param width Width of the region to be decoded
+     * @param height Height of the region to be decoded
+     */
+    public DecodeOptions(int x, int y, int width, int height)
+    {
+        this(new Rectangle(x, y, width, height));
+    }
+
+    /**
+     * Constructs an instance specifying the image should be decoded using subsampling. The
+     * subsampling will be the same for the X and Y axes.
+     *
+     * @param subsampling The number of rows and columns to advance in the source for each pixel in
+     * the decoded image.
+     */
+    public DecodeOptions(int subsampling)
+    {
+        subsamplingX = subsampling;
+        subsamplingY = subsampling;
+    }
+
+    /**
+     * When decoding an image, the part of the image that should be decoded, or null if the entire
+     * image is needed.
+     *
+     * @return The source region to decode, or null if the entire image should be decoded
+     */
+    public Rectangle getSourceRegion()
+    {
+        return sourceRegion;
+    }
+
+    /**
+     * Sets the region of the source image that should be decoded. The region will be clipped to the
+     * dimensions of the source image. Setting this value to null will result in the entire image
+     * being decoded.
+     *
+     * @param sourceRegion The source region to decode, or null if the entire image should be
+     * decoded.
+     */
+    public void setSourceRegion(Rectangle sourceRegion)
+    {
+        this.sourceRegion = sourceRegion;
+    }
+
+    /**
+     * When decoding an image, the number of columns to advance in the source for every pixel
+     * decoded.
+     *
+     * @return The x-axis subsampling value
+     */
+    public int getSubsamplingX()
+    {
+        return subsamplingX;
+    }
+
+    /**
+     * Sets the number of columns to advance in the source for every pixel decoded
+     *
+     * @param ssX The x-axis subsampling value
+     */
+    public void setSubsamplingX(int ssX)
+    {
+        this.subsamplingX = ssX;
+    }
+
+    /**
+     * When decoding an image, the number of rows to advance in the source for every pixel decoded.
+     *
+     * @return The y-axis subsampling value
+     */
+    public int getSubsamplingY()
+    {
+        return subsamplingY;
+    }
+
+    /**
+     * Sets the number of rows to advance in the source for every pixel decoded
+     *
+     * @param ssY The y-axis subsampling value
+     */
+    public void setSubsamplingY(int ssY)
+    {
+        this.subsamplingY = ssY;
+    }
+
+    /**
+     * When decoding an image, the horizontal offset for subsampling
+     *
+     * @return The x-axis subsampling offset
+     */
+    public int getSubsamplingOffsetX()
+    {
+        return subsamplingOffsetX;
+    }
+
+    /**
+     * Sets the horizontal subsampling offset for decoding images
+     *
+     * @param ssOffsetX The x-axis subsampling offset
+     */
+    public void setSubsamplingOffsetX(int ssOffsetX)
+    {
+        this.subsamplingOffsetX = ssOffsetX;
+    }
+
+    /**
+     * When decoding an image, the vertical offset for subsampling
+     *
+     * @return The y-axis subsampling offset
+     */
+    public int getSubsamplingOffsetY()
+    {
+        return subsamplingOffsetY;
+    }
+
+    /**
+     * Sets the vertical subsampling offset for decoding images
+     *
+     * @param ssOffsetY The y-axis subsampling offset
+     */
+    public void setSubsamplingOffsetY(int ssOffsetY)
+    {
+        this.subsamplingOffsetY = ssOffsetY;
+    }
+
+    /**
+     * Flag used by the filter to specify if it performed subsampling.
+     *
+     * Some filters may be unable or unwilling to apply subsampling, and so the caller must check
+     * this flag after decoding.
+     *
+     * @return True if the filter applied the options specified by this instance, false otherwise.
+     */
+    public boolean isFilterSubsampled()
+    {
+        return filterSubsampled;
+    }
+
+    /**
+     * Used internally by filters to signal they have applied subsampling as requested by this
+     * options instance.
+     *
+     * @param filterSubsampled Value specifying if the filter could meet the requested options.
+     * Usually a filter will only call this with the value <code>true</code>, as the default value
+     * for the flag is <code>false</code>.
+     */
+    void setFilterSubsampled(boolean filterSubsampled)
+    {
+        this.filterSubsampled = filterSubsampled;
+    }
+
+    /**
+     * Helper class for reusable instances which may not be modified.
+     */
+    private static class FinalDecodeOptions extends DecodeOptions
+    {
+        FinalDecodeOptions(boolean filterSubsampled)
+        {
+            super.setFilterSubsampled(filterSubsampled);
+        }
+
+        @Override
+        public void setSourceRegion(Rectangle sourceRegion)
+        {
+            throw new UnsupportedOperationException("This instance may not be modified.");
+        }
+
+        @Override
+        public void setSubsamplingX(int ssX)
+        {
+            throw new UnsupportedOperationException("This instance may not be modified.");
+        }
+
+        @Override
+        public void setSubsamplingY(int ssY)
+        {
+            throw new UnsupportedOperationException("This instance may not be modified.");
+        }
+
+        @Override
+        public void setSubsamplingOffsetX(int ssOffsetX)
+        {
+            throw new UnsupportedOperationException("This instance may not be modified.");
+        }
+
+        @Override
+        public void setSubsamplingOffsetY(int ssOffsetY)
+        {
+            throw new UnsupportedOperationException("This instance may not be modified.");
+        }
+
+        @Override
+        void setFilterSubsampled(boolean filterSubsampled)
+        {
+            // Silently ignore the request.
+        }
+    }
+}

Propchange: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/DecodeOptions.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/Filter.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/Filter.java?rev=1826261&r1=1826260&r2=1826261&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/Filter.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/Filter.java Thu Mar  8 18:21:35 2018
@@ -67,7 +67,25 @@ public abstract class Filter
      * @throws IOException if the stream cannot be decoded
      */
     public abstract DecodeResult decode(InputStream encoded, OutputStream decoded, COSDictionary parameters,
-                            int index) throws IOException;
+                    int index) throws IOException;
+
+    /**
+     * Decodes data, with optional DecodeOptions. Not all filters support all options, and so
+     * callers should check the options' <code>honored</code> flag to test if they were applied.
+     *
+     * @param encoded the encoded byte stream
+     * @param decoded the stream where decoded data will be written
+     * @param parameters the parameters used for decoding
+     * @param index the index to the filter being decoded
+     * @param options additional options for decoding
+     * @return repaired parameters dictionary, or the original parameters dictionary
+     * @throws IOException if the stream cannot be decoded
+     */
+    public DecodeResult decode(InputStream encoded, OutputStream decoded, COSDictionary parameters,
+            int index, DecodeOptions options) throws IOException
+    {
+        return decode(encoded, decoded, parameters, index);
+    }
 
     /**
      * Encodes data.

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/JBIG2Filter.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/JBIG2Filter.java?rev=1826261&r1=1826260&r2=1826261&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/JBIG2Filter.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/JBIG2Filter.java Thu Mar  8 18:21:35 2018
@@ -25,6 +25,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.SequenceInputStream;
 import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
 import javax.imageio.ImageReader;
 import javax.imageio.stream.ImageInputStream;
 import org.apache.commons.logging.Log;
@@ -60,8 +61,8 @@ final class JBIG2Filter extends Filter
     }
 
     @Override
-    public DecodeResult decode(InputStream encoded, OutputStream decoded,
-                                         COSDictionary parameters, int index) throws IOException
+    public DecodeResult decode(InputStream encoded, OutputStream decoded, COSDictionary
+            parameters, int index, DecodeOptions options) throws IOException
     {
         ImageReader reader = findImageReader("JBIG2", "jbig2-imageio is not installed");
         if (reader.getClass().getName().contains("levigo"))
@@ -72,6 +73,12 @@ final class JBIG2Filter extends Filter
         int bits = parameters.getInt(COSName.BITS_PER_COMPONENT, 1);
         COSDictionary params = getDecodeParams(parameters, index);
 
+        ImageReadParam irp = reader.getDefaultReadParam();
+        irp.setSourceSubsampling(options.getSubsamplingX(), options.getSubsamplingY(),
+                options.getSubsamplingOffsetX(), options.getSubsamplingOffsetY());
+        irp.setSourceRegion(options.getSourceRegion());
+        options.setFilterSubsampled(true);
+
         COSStream globals = null;
         if (params != null)
         {
@@ -96,7 +103,7 @@ final class JBIG2Filter extends Filter
             BufferedImage image;
             try
             {
-                image = reader.read(0, reader.getDefaultReadParam());
+                image = reader.read(0, irp);
             }
             catch (Exception e)
             {
@@ -142,6 +149,13 @@ final class JBIG2Filter extends Filter
     }
 
     @Override
+    public DecodeResult decode(InputStream encoded, OutputStream decoded,
+                               COSDictionary parameters, int index) throws IOException
+    {
+        return decode(encoded, decoded, parameters, index, DecodeOptions.DEFAULT);
+    }
+
+    @Override
     protected void encode(InputStream input, OutputStream encoded, COSDictionary parameters)
             throws IOException
     {

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/JPXFilter.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/JPXFilter.java?rev=1826261&r1=1826260&r2=1826261&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/JPXFilter.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/filter/JPXFilter.java Thu Mar  8 18:21:35 2018
@@ -24,6 +24,7 @@ import java.awt.image.WritableRaster;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import javax.imageio.ImageReadParam;
 import javax.imageio.ImageReader;
 import javax.imageio.stream.ImageInputStream;
 import javax.imageio.stream.MemoryCacheImageInputStream;
@@ -49,12 +50,12 @@ import org.apache.pdfbox.pdmodel.graphic
 public final class JPXFilter extends Filter
 {
     @Override
-    public DecodeResult decode(InputStream encoded, OutputStream decoded,
-                                         COSDictionary parameters, int index) throws IOException
+    public DecodeResult decode(InputStream encoded, OutputStream decoded, COSDictionary
+            parameters, int index, DecodeOptions options) throws IOException
     {
         DecodeResult result = new DecodeResult(new COSDictionary());
         result.getParameters().addAll(parameters);
-        BufferedImage image = readJPX(encoded, result);
+        BufferedImage image = readJPX(encoded, options, result);
 
         WritableRaster raster = image.getRaster();
         switch (raster.getDataBuffer().getDataType())
@@ -75,11 +76,18 @@ public final class JPXFilter extends Fil
 
             default:
                 throw new IOException("Data type " + raster.getDataBuffer().getDataType() + " not implemented");
-        }        
+        }
+    }
+
+    @Override
+    public DecodeResult decode(InputStream encoded, OutputStream decoded,
+                               COSDictionary parameters, int index) throws IOException
+    {
+        return decode(encoded, decoded, parameters, index, DecodeOptions.DEFAULT);
     }
 
     // try to read using JAI Image I/O
-    private BufferedImage readJPX(InputStream input, DecodeResult result) throws IOException
+    private BufferedImage readJPX(InputStream input, DecodeOptions options, DecodeResult result) throws IOException
     {
         ImageReader reader = findImageReader("JPEG2000", "Java Advanced Imaging (JAI) Image I/O Tools are not installed");
         ImageInputStream iis = null;
@@ -87,12 +95,18 @@ public final class JPXFilter extends Fil
         {
             // PDFBOX-4121: ImageIO.createImageInputStream() is much slower
             iis = new MemoryCacheImageInputStream(input);
+
             reader.setInput(iis, true, true);
+            ImageReadParam irp = reader.getDefaultReadParam();
+            irp.setSourceRegion(options.getSourceRegion());
+            irp.setSourceSubsampling(options.getSubsamplingX(), options.getSubsamplingY(),
+                    options.getSubsamplingOffsetX(), options.getSubsamplingOffsetY());
+            options.setFilterSubsampled(true);
 
             BufferedImage image;
             try
             {
-                image = reader.read(0);
+                image = reader.read(0, irp);
             }
             catch (Exception e)
             {
@@ -116,8 +130,8 @@ public final class JPXFilter extends Fil
             }
 
             // override dimensions, see PDFBOX-1735
-            parameters.setInt(COSName.WIDTH, image.getWidth());
-            parameters.setInt(COSName.HEIGHT, image.getHeight());
+            parameters.setInt(COSName.WIDTH, reader.getWidth(0));
+            parameters.setInt(COSName.HEIGHT, reader.getHeight(0));
 
             // extract embedded color space
             if (!parameters.containsKey(COSName.COLORSPACE))

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDStream.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDStream.java?rev=1826261&r1=1826260&r2=1826261&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDStream.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDStream.java Thu Mar  8 18:21:35 2018
@@ -32,6 +32,7 @@ import org.apache.pdfbox.cos.COSInputStr
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.cos.COSNull;
 import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.filter.DecodeOptions;
 import org.apache.pdfbox.filter.Filter;
 import org.apache.pdfbox.filter.FilterFactory;
 import org.apache.pdfbox.io.IOUtils;
@@ -235,6 +236,11 @@ public class PDStream implements COSObje
         return stream.createInputStream();
     }
 
+    public COSInputStream createInputStream(DecodeOptions options) throws IOException
+    {
+        return stream.createInputStream(options);
+    }
+
     /**
      * This will get a stream with some filters applied but not others. This is
      * useful when doing images, ie filters = [flate,dct], we want to remove

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImage.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImage.java?rev=1826261&r1=1826260&r2=1826261&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImage.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImage.java Thu Mar  8 18:21:35 2018
@@ -1,157 +1,185 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.pdfbox.pdmodel.graphics.image;
-
-import java.awt.Paint;
-import java.awt.image.BufferedImage;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-import org.apache.pdfbox.cos.COSArray;
-import org.apache.pdfbox.pdmodel.common.COSObjectable;
-import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
-
-/**
- * An image in a PDF document.
- *
- * @author John Hewson
- */
-public interface PDImage extends COSObjectable
-{
-    /**
-     * Returns the content of this image as an AWT buffered image with an (A)RGB color space.
-     * The size of the returned image is the larger of the size of the image itself or its mask. 
-     * @return content of this image as a buffered image.
-     * @throws IOException
-     */
-    BufferedImage getImage() throws IOException;
-
-    /**
-     * Returns an ARGB image filled with the given paint and using this image as a mask.
-     * @param paint the paint to fill the visible portions of the image with
-     * @return a masked image filled with the given paint
-     * @throws IOException if the image cannot be read
-     * @throws IllegalStateException if the image is not a stencil.
-     */
-    BufferedImage getStencilImage(Paint paint) throws IOException;
-    
-    /**
-     * Returns an InputStream containing the image data, irrespective of whether this is an
-     * inline image or an image XObject.
-     * @return Decoded stream
-     * @throws IOException if the data could not be read.
-     */
-    InputStream createInputStream() throws IOException;
-
-    /**
-     * Returns an InputStream containing the image data, irrespective of whether this is an
-     * inline image or an image XObject. The given filters will not be decoded.
-     * @param stopFilters A list of filters to stop decoding at.
-     * @return Decoded stream
-     * @throws IOException if the data could not be read.
-     */
-    InputStream createInputStream(List<String> stopFilters) throws IOException;
-
-    /**
-     * Returns true if the image has no data.
-     */
-    boolean isEmpty();
-
-    /**
-     * Returns true if the image is a stencil mask.
-     */
-    boolean isStencil();
-
-    /**
-     * Sets whether or not the image is a stencil.
-     * This corresponds to the {@code ImageMask} entry in the image stream's dictionary.
-     * @param isStencil True to make the image a stencil.
-     */
-    void setStencil(boolean isStencil);
-
-    /**
-     * Returns bits per component of this image, or -1 if one has not been set.
-     */
-    int getBitsPerComponent();
-
-    /**
-     * Set the number of bits per component.
-     * @param bitsPerComponent The number of bits per component.
-     */
-    void setBitsPerComponent(int bitsPerComponent);
-
-    /**
-     * Returns the image's color space.
-     * @throws IOException If there is an error getting the color space.
-     */
-    PDColorSpace getColorSpace() throws IOException;
-
-    /**
-     * Sets the color space for this image.
-     * @param colorSpace The color space for this image.
-     */
-    void setColorSpace(PDColorSpace colorSpace);
-
-    /**
-     * Returns height of this image, or -1 if one has not been set.
-     */
-    int getHeight();
-
-    /**
-     * Sets the height of the image.
-     * @param height The height of the image.
-     */
-    void setHeight(int height);
-
-    /**
-     * Returns the width of this image, or -1 if one has not been set.
-     */
-    int getWidth();
-
-    /**
-     * Sets the width of the image.
-     * @param width The width of the image.
-     */
-    void setWidth(int width);
-
-    /**
-     * Sets the decode array.
-     * @param decode  the new decode array.
-     */
-    void setDecode(COSArray decode);
-
-    /**
-     * Returns the decode array.
-     */
-    COSArray getDecode();
-
-    /**
-     * Returns true if the image should be interpolated when rendered.
-     */
-    boolean getInterpolate();
-
-
-    /**
-     * Sets the Interpolate flag, true for high-quality image scaling.
-     */
-    void setInterpolate(boolean value);
-
-    /**
-     * Returns the suffix for this image type, e.g. "jpg"
-     */
-    String getSuffix();
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.graphics.image;
+
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.filter.DecodeOptions;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+
+/**
+ * An image in a PDF document.
+ *
+ * @author John Hewson
+ */
+public interface PDImage extends COSObjectable
+{
+    /**
+     * Returns the content of this image as an AWT buffered image with an (A)RGB color space.
+     * The size of the returned image is the larger of the size of the image itself or its mask. 
+     * @return content of this image as a buffered image.
+     * @throws IOException
+     */
+    BufferedImage getImage() throws IOException;
+
+    /**
+     * Returns the content of this image as an AWT buffered image with an (A)RGB colored space. Only
+     * the subregion specified is rendered, and is subsampled by advancing the specified amount of
+     * rows and columns in the source image for every resulting pixel.
+     *
+     * Note that unlike {@link PDImage#getImage() the unparameterized version}, this method does not
+     * cache the resulting image.
+     *
+     * @param region The region of the source image to get, or null if the entire image is needed.
+     * The actual region will be clipped to the dimensions of the source image.
+     * @param subsampling The amount of rows and columns to advance for every output pixel, a value
+     * of 1 meaning every pixel will be read
+     * @return subsampled content of the requested subregion as a buffered image.
+     * @throws IOException
+     */
+    BufferedImage getImage(Rectangle region, int subsampling) throws IOException;
+
+    /**
+     * Returns an ARGB image filled with the given paint and using this image as a mask.
+     * @param paint the paint to fill the visible portions of the image with
+     * @return a masked image filled with the given paint
+     * @throws IOException if the image cannot be read
+     * @throws IllegalStateException if the image is not a stencil.
+     */
+    BufferedImage getStencilImage(Paint paint) throws IOException;
+    
+    /**
+     * Returns an InputStream containing the image data, irrespective of whether this is an
+     * inline image or an image XObject.
+     * @return Decoded stream
+     * @throws IOException if the data could not be read.
+     */
+    InputStream createInputStream() throws IOException;
+
+    /**
+     * Returns an InputStream containing the image data, irrespective of whether this is an
+     * inline image or an image XObject. The given filters will not be decoded.
+     * @param stopFilters A list of filters to stop decoding at.
+     * @return Decoded stream
+     * @throws IOException if the data could not be read.
+     */
+    InputStream createInputStream(List<String> stopFilters) throws IOException;
+
+    /**
+     * Returns an InputStream, passing additional options to each filter
+     *
+     * @param options Additional decoding options passed to the filters used
+     * @return Decoded stream
+     * @throws IOException if the data could not be read
+     */
+    InputStream createInputStream(DecodeOptions options) throws IOException;
+
+    /**
+     * Returns true if the image has no data.
+     */
+    boolean isEmpty();
+
+    /**
+     * Returns true if the image is a stencil mask.
+     */
+    boolean isStencil();
+
+    /**
+     * Sets whether or not the image is a stencil.
+     * This corresponds to the {@code ImageMask} entry in the image stream's dictionary.
+     * @param isStencil True to make the image a stencil.
+     */
+    void setStencil(boolean isStencil);
+
+    /**
+     * Returns bits per component of this image, or -1 if one has not been set.
+     */
+    int getBitsPerComponent();
+
+    /**
+     * Set the number of bits per component.
+     * @param bitsPerComponent The number of bits per component.
+     */
+    void setBitsPerComponent(int bitsPerComponent);
+
+    /**
+     * Returns the image's color space.
+     * @throws IOException If there is an error getting the color space.
+     */
+    PDColorSpace getColorSpace() throws IOException;
+
+    /**
+     * Sets the color space for this image.
+     * @param colorSpace The color space for this image.
+     */
+    void setColorSpace(PDColorSpace colorSpace);
+
+    /**
+     * Returns height of this image, or -1 if one has not been set.
+     */
+    int getHeight();
+
+    /**
+     * Sets the height of the image.
+     * @param height The height of the image.
+     */
+    void setHeight(int height);
+
+    /**
+     * Returns the width of this image, or -1 if one has not been set.
+     */
+    int getWidth();
+
+    /**
+     * Sets the width of the image.
+     * @param width The width of the image.
+     */
+    void setWidth(int width);
+
+    /**
+     * Sets the decode array.
+     * @param decode  the new decode array.
+     */
+    void setDecode(COSArray decode);
+
+    /**
+     * Returns the decode array.
+     */
+    COSArray getDecode();
+
+    /**
+     * Returns true if the image should be interpolated when rendered.
+     */
+    boolean getInterpolate();
+
+
+    /**
+     * Sets the Interpolate flag, true for high-quality image scaling.
+     */
+    void setInterpolate(boolean value);
+
+    /**
+     * Returns the suffix for this image type, e.g. "jpg"
+     */
+    String getSuffix();
+}

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java?rev=1826261&r1=1826260&r2=1826261&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java Thu Mar  8 18:21:35 2018
@@ -18,6 +18,7 @@ package org.apache.pdfbox.pdmodel.graphi
 
 import java.awt.Graphics2D;
 import java.awt.Paint;
+import java.awt.Rectangle;
 import java.awt.RenderingHints;
 import java.awt.image.BufferedImage;
 import java.awt.image.WritableRaster;
@@ -39,6 +40,7 @@ import org.apache.pdfbox.cos.COSInputStr
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.cos.COSObject;
 import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.filter.DecodeOptions;
 import org.apache.pdfbox.filter.DecodeResult;
 import org.apache.pdfbox.io.IOUtils;
 import org.apache.pdfbox.pdmodel.PDDocument;
@@ -67,6 +69,9 @@ public final class PDImageXObject extend
     private SoftReference<BufferedImage> cachedImage;
     private PDColorSpace colorSpace;
 
+    // initialize to MAX_VALUE as we prefer lower subsampling when keeping/replacing cache.
+    private int cachedImageSubsampling = Integer.MAX_VALUE;
+
     /**
      * current resource dictionary (has color spaces)
      */
@@ -394,7 +399,16 @@ public final class PDImageXObject extend
     @Override
     public BufferedImage getImage() throws IOException
     {
-        if (cachedImage != null)
+        return getImage(null, 1);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public BufferedImage getImage(Rectangle region, int subsampling) throws IOException
+    {
+        if (region == null && subsampling == cachedImageSubsampling && cachedImage != null)
         {
             BufferedImage cached = cachedImage.get();
             if (cached != null)
@@ -404,7 +418,7 @@ public final class PDImageXObject extend
         }
 
         // get image as RGB
-        BufferedImage image = SampledImageReader.getRGBImage(this, getColorKeyMask());
+        BufferedImage image = SampledImageReader.getRGBImage(this, region, subsampling, getColorKeyMask());
 
         // soft mask (overrides explicit mask)
         PDImageXObject softMask = getSoftMask();
@@ -422,7 +436,14 @@ public final class PDImageXObject extend
             }
         }
 
-        cachedImage = new SoftReference<BufferedImage>(image);
+        if (region == null && subsampling <= cachedImageSubsampling)
+        {
+            // only cache full-image renders, and prefer lower subsampling frequency, as lower
+            // subsampling means higher quality and longer render times.
+            cachedImageSubsampling = subsampling;
+            cachedImage = new SoftReference<BufferedImage>(image);
+        }
+
         return image;
     }
 
@@ -648,6 +669,12 @@ public final class PDImageXObject extend
     {
         return getStream().createInputStream();
     }
+    
+    @Override
+    public InputStream createInputStream(DecodeOptions options) throws IOException
+    {
+        return getStream().createInputStream(options);
+    }
 
     @Override
     public InputStream createInputStream(List<String> stopFilters) throws IOException

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDInlineImage.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDInlineImage.java?rev=1826261&r1=1826260&r2=1826261&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDInlineImage.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDInlineImage.java Thu Mar  8 18:21:35 2018
@@ -1,380 +1,395 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.pdfbox.pdmodel.graphics.image;
-
-import java.awt.Paint;
-import java.awt.image.BufferedImage;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-import org.apache.pdfbox.cos.COSArray;
-import org.apache.pdfbox.cos.COSBase;
-import org.apache.pdfbox.cos.COSDictionary;
-import org.apache.pdfbox.cos.COSName;
-import org.apache.pdfbox.filter.DecodeResult;
-import org.apache.pdfbox.filter.Filter;
-import org.apache.pdfbox.filter.FilterFactory;
-import org.apache.pdfbox.pdmodel.PDResources;
-import org.apache.pdfbox.pdmodel.common.COSArrayList;
-import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
-import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
-
-/**
- * An inline image object which uses a special syntax to express the data for a
- * small image directly within the content stream.
- *
- * @author Ben Litchfield
- * @author John Hewson
- */
-public final class PDInlineImage implements PDImage
-{
-    // image parameters
-    private final COSDictionary parameters;
-
-    // the current resources, contains named color spaces
-    private final PDResources resources;
-
-    // image data
-    private final byte[] rawData;
-    private final byte[] decodedData;
-
-    /**
-     * Creates an inline image from the given parameters and data.
-     *
-     * @param parameters the image parameters
-     * @param data the image data
-     * @param resources the current resources
-     * @throws IOException if the stream cannot be decoded
-     */
-    public PDInlineImage(COSDictionary parameters, byte[] data, PDResources resources)
-            throws IOException
-    {
-        this.parameters = parameters;
-        this.resources = resources;
-        this.rawData = data;
-
-        DecodeResult decodeResult = null;
-        List<String> filters = getFilters();
-        if (filters == null || filters.isEmpty())
-        {
-            this.decodedData = data;
-        }
-        else
-        {
-            ByteArrayInputStream in = new ByteArrayInputStream(data);
-            ByteArrayOutputStream out = new ByteArrayOutputStream(data.length);
-            for (int i = 0; i < filters.size(); i++)
-            {
-                // TODO handling of abbreviated names belongs here, rather than in other classes
-                out.reset();
-                Filter filter = FilterFactory.INSTANCE.getFilter(filters.get(i));
-                decodeResult = filter.decode(in, out, parameters, i);
-                in = new ByteArrayInputStream(out.toByteArray());
-            }
-            this.decodedData = out.toByteArray();
-        }
-
-        // repair parameters
-        if (decodeResult != null)
-        {
-            parameters.addAll(decodeResult.getParameters());
-        }
-    }
-
-    @Override
-    public COSBase getCOSObject()
-    {
-        return parameters;
-    }
-
-    @Override
-    public int getBitsPerComponent()
-    {
-        if (isStencil())
-        {
-            return 1;
-        }
-        else
-        {
-            return parameters.getInt(COSName.BPC, COSName.BITS_PER_COMPONENT, -1);
-        }
-    }
-
-    @Override
-    public void setBitsPerComponent(int bitsPerComponent)
-    {
-        parameters.setInt(COSName.BPC, bitsPerComponent);
-    }
-
-    @Override
-    public PDColorSpace getColorSpace() throws IOException
-    {
-        COSBase cs = parameters.getDictionaryObject(COSName.CS, COSName.COLORSPACE);
-        if (cs != null)
-        {
-            return createColorSpace(cs);
-        }
-        else if (isStencil())
-        {
-            // stencil mask color space must be gray, it is often missing
-            return PDDeviceGray.INSTANCE;
-        }
-        else
-        {
-            // an image without a color space is always broken
-            throw new IOException("could not determine inline image color space");
-        }
-    }
-    
-    // deliver the long name of a device colorspace, or the parameter
-    private COSBase toLongName(COSBase cs)
-    {
-        if (COSName.RGB.equals(cs))
-        {
-            return COSName.DEVICERGB;
-        }
-        if (COSName.CMYK.equals(cs))
-        {
-            return COSName.DEVICECMYK;
-        }
-        if (COSName.G.equals(cs))
-        {
-            return COSName.DEVICEGRAY;
-        }
-        return cs;
-    }
-    
-    private PDColorSpace createColorSpace(COSBase cs) throws IOException
-    {
-        if (cs instanceof COSName)
-        {
-            return PDColorSpace.create(toLongName(cs), resources);
-        }
-
-        if (cs instanceof COSArray && ((COSArray) cs).size() > 1)
-        {
-            COSArray srcArray = (COSArray) cs;
-            COSBase csType = srcArray.get(0);
-            if (COSName.I.equals(csType) || COSName.INDEXED.equals(csType))
-            {
-                COSArray dstArray = new COSArray();
-                dstArray.addAll(srcArray);
-                dstArray.set(0, COSName.INDEXED);
-                dstArray.set(1, toLongName(srcArray.get(1)));
-                return PDColorSpace.create(dstArray, resources);
-            }
-
-            throw new IOException("Illegal type of inline image color space: " + csType);
-        }
-
-        throw new IOException("Illegal type of object for inline image color space: " + cs);
-    }
-
-    @Override
-    public void setColorSpace(PDColorSpace colorSpace)
-    {
-        COSBase base = null;
-        if (colorSpace != null)
-        {
-            base = colorSpace.getCOSObject();
-        }
-        parameters.setItem(COSName.CS, base);
-    }
-
-    @Override
-    public int getHeight()
-    {
-        return parameters.getInt(COSName.H, COSName.HEIGHT, -1);
-    }
-
-    @Override
-    public void setHeight(int height)
-    {
-        parameters.setInt(COSName.H, height);
-    }
-
-    @Override
-    public int getWidth()
-    {
-        return parameters.getInt(COSName.W, COSName.WIDTH, -1);
-    }
-
-    @Override
-    public void setWidth(int width)
-    {
-        parameters.setInt(COSName.W, width);
-    }
-
-    @Override
-    public boolean getInterpolate()
-    {
-        return parameters.getBoolean(COSName.I, COSName.INTERPOLATE, false);
-    }
-
-    @Override
-    public void setInterpolate(boolean value)
-    {
-        parameters.setBoolean(COSName.I, value);
-    }
-
-    /**
-     * Returns a list of filters applied to this stream, or null if there are none.
-     *
-     * @return a list of filters applied to this stream
-     */
-    // TODO return an empty list if there are none?
-    public List<String> getFilters()
-    {
-        List<String> names = null;
-        COSBase filters = parameters.getDictionaryObject(COSName.F, COSName.FILTER);
-        if (filters instanceof COSName)
-        {
-            COSName name = (COSName) filters;
-            names = new COSArrayList<String>(name.getName(), name, parameters, COSName.FILTER);
-        }
-        else if (filters instanceof COSArray)
-        {
-            names = COSArrayList.convertCOSNameCOSArrayToList((COSArray) filters);
-        }
-        return names;
-    }
-
-    /**
-     * Sets which filters are applied to this stream.
-     *
-     * @param filters the filters to apply to this stream.
-     */
-    public void setFilters(List<String> filters)
-    {
-        COSBase obj = COSArrayList.convertStringListToCOSNameCOSArray(filters);
-        parameters.setItem(COSName.F, obj);
-    }
-
-    @Override
-    public void setDecode(COSArray decode)
-    {
-        parameters.setItem(COSName.D, decode);
-    }
-
-    @Override
-    public COSArray getDecode()
-    {
-        return (COSArray) parameters.getDictionaryObject(COSName.D, COSName.DECODE);
-    }
-
-    @Override
-    public boolean isStencil()
-    {
-        return parameters.getBoolean(COSName.IM, COSName.IMAGE_MASK, false);
-    }
-
-    @Override
-    public void setStencil(boolean isStencil)
-    {
-        parameters.setBoolean(COSName.IM, isStencil);
-    }
-
-    @Override
-    public InputStream createInputStream() throws IOException
-    {
-        return new ByteArrayInputStream(decodedData);
-    }
-
-    @Override
-    public InputStream createInputStream(List<String> stopFilters) throws IOException
-    {
-        List<String> filters = getFilters();
-        ByteArrayInputStream in = new ByteArrayInputStream(rawData);
-        ByteArrayOutputStream out = new ByteArrayOutputStream(rawData.length);
-        for (int i = 0; filters != null && i < filters.size(); i++)
-        {
-            // TODO handling of abbreviated names belongs here, rather than in other classes
-            out.reset();
-            if (stopFilters.contains(filters.get(i)))
-            {
-                break;
-            }
-            else
-            {
-                Filter filter = FilterFactory.INSTANCE.getFilter(filters.get(i));
-                filter.decode(in, out, parameters, i);
-                in = new ByteArrayInputStream(out.toByteArray());
-            }
-        }
-        return new ByteArrayInputStream(out.toByteArray());
-    }
-
-    @Override
-    public boolean isEmpty()
-    {
-        return decodedData.length == 0;
-    }
-
-    /**
-     * Returns the inline image data.
-     */
-    public byte[] getData()
-    {
-        return decodedData;
-    }
-    
-    @Override
-    public BufferedImage getImage() throws IOException
-    {
-        return SampledImageReader.getRGBImage(this, getColorKeyMask());
-    }
-
-    @Override
-    public BufferedImage getStencilImage(Paint paint) throws IOException
-    {
-        if (!isStencil())
-        {
-            throw new IllegalStateException("Image is not a stencil");
-        }
-        return SampledImageReader.getStencilImage(this, paint);
-    }
-
-    /**
-     * Returns the color key mask array associated with this image, or null if
-     * there is none.
-     *
-     * @return Mask Image XObject
-     */
-    public COSArray getColorKeyMask()
-    {
-        COSBase mask = parameters.getDictionaryObject(COSName.IM, COSName.MASK);
-        if (mask instanceof COSArray)
-        {
-            return (COSArray) mask;
-        }
-        return null;
-    }
-
-    /**
-     * Returns the suffix for this image type, e.g. jpg/png.
-     *
-     * @return The image suffix.
-     */
-    @Override
-    public String getSuffix()
-    {
-        // TODO implement me
-        return null;
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.graphics.image;
+
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.filter.DecodeOptions;
+import org.apache.pdfbox.filter.DecodeResult;
+import org.apache.pdfbox.filter.Filter;
+import org.apache.pdfbox.filter.FilterFactory;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+
+/**
+ * An inline image object which uses a special syntax to express the data for a
+ * small image directly within the content stream.
+ *
+ * @author Ben Litchfield
+ * @author John Hewson
+ */
+public final class PDInlineImage implements PDImage
+{
+    // image parameters
+    private final COSDictionary parameters;
+
+    // the current resources, contains named color spaces
+    private final PDResources resources;
+
+    // image data
+    private final byte[] rawData;
+    private final byte[] decodedData;
+
+    /**
+     * Creates an inline image from the given parameters and data.
+     *
+     * @param parameters the image parameters
+     * @param data the image data
+     * @param resources the current resources
+     * @throws IOException if the stream cannot be decoded
+     */
+    public PDInlineImage(COSDictionary parameters, byte[] data, PDResources resources)
+            throws IOException
+    {
+        this.parameters = parameters;
+        this.resources = resources;
+        this.rawData = data;
+
+        DecodeResult decodeResult = null;
+        List<String> filters = getFilters();
+        if (filters == null || filters.isEmpty())
+        {
+            this.decodedData = data;
+        }
+        else
+        {
+            ByteArrayInputStream in = new ByteArrayInputStream(data);
+            ByteArrayOutputStream out = new ByteArrayOutputStream(data.length);
+            for (int i = 0; i < filters.size(); i++)
+            {
+                // TODO handling of abbreviated names belongs here, rather than in other classes
+                out.reset();
+                Filter filter = FilterFactory.INSTANCE.getFilter(filters.get(i));
+                decodeResult = filter.decode(in, out, parameters, i);
+                in = new ByteArrayInputStream(out.toByteArray());
+            }
+            this.decodedData = out.toByteArray();
+        }
+
+        // repair parameters
+        if (decodeResult != null)
+        {
+            parameters.addAll(decodeResult.getParameters());
+        }
+    }
+
+    @Override
+    public COSBase getCOSObject()
+    {
+        return parameters;
+    }
+
+    @Override
+    public int getBitsPerComponent()
+    {
+        if (isStencil())
+        {
+            return 1;
+        }
+        else
+        {
+            return parameters.getInt(COSName.BPC, COSName.BITS_PER_COMPONENT, -1);
+        }
+    }
+
+    @Override
+    public void setBitsPerComponent(int bitsPerComponent)
+    {
+        parameters.setInt(COSName.BPC, bitsPerComponent);
+    }
+
+    @Override
+    public PDColorSpace getColorSpace() throws IOException
+    {
+        COSBase cs = parameters.getDictionaryObject(COSName.CS, COSName.COLORSPACE);
+        if (cs != null)
+        {
+            return createColorSpace(cs);
+        }
+        else if (isStencil())
+        {
+            // stencil mask color space must be gray, it is often missing
+            return PDDeviceGray.INSTANCE;
+        }
+        else
+        {
+            // an image without a color space is always broken
+            throw new IOException("could not determine inline image color space");
+        }
+    }
+    
+    // deliver the long name of a device colorspace, or the parameter
+    private COSBase toLongName(COSBase cs)
+    {
+        if (COSName.RGB.equals(cs))
+        {
+            return COSName.DEVICERGB;
+        }
+        if (COSName.CMYK.equals(cs))
+        {
+            return COSName.DEVICECMYK;
+        }
+        if (COSName.G.equals(cs))
+        {
+            return COSName.DEVICEGRAY;
+        }
+        return cs;
+    }
+    
+    private PDColorSpace createColorSpace(COSBase cs) throws IOException
+    {
+        if (cs instanceof COSName)
+        {
+            return PDColorSpace.create(toLongName(cs), resources);
+        }
+
+        if (cs instanceof COSArray && ((COSArray) cs).size() > 1)
+        {
+            COSArray srcArray = (COSArray) cs;
+            COSBase csType = srcArray.get(0);
+            if (COSName.I.equals(csType) || COSName.INDEXED.equals(csType))
+            {
+                COSArray dstArray = new COSArray();
+                dstArray.addAll(srcArray);
+                dstArray.set(0, COSName.INDEXED);
+                dstArray.set(1, toLongName(srcArray.get(1)));
+                return PDColorSpace.create(dstArray, resources);
+            }
+
+            throw new IOException("Illegal type of inline image color space: " + csType);
+        }
+
+        throw new IOException("Illegal type of object for inline image color space: " + cs);
+    }
+
+    @Override
+    public void setColorSpace(PDColorSpace colorSpace)
+    {
+        COSBase base = null;
+        if (colorSpace != null)
+        {
+            base = colorSpace.getCOSObject();
+        }
+        parameters.setItem(COSName.CS, base);
+    }
+
+    @Override
+    public int getHeight()
+    {
+        return parameters.getInt(COSName.H, COSName.HEIGHT, -1);
+    }
+
+    @Override
+    public void setHeight(int height)
+    {
+        parameters.setInt(COSName.H, height);
+    }
+
+    @Override
+    public int getWidth()
+    {
+        return parameters.getInt(COSName.W, COSName.WIDTH, -1);
+    }
+
+    @Override
+    public void setWidth(int width)
+    {
+        parameters.setInt(COSName.W, width);
+    }
+
+    @Override
+    public boolean getInterpolate()
+    {
+        return parameters.getBoolean(COSName.I, COSName.INTERPOLATE, false);
+    }
+
+    @Override
+    public void setInterpolate(boolean value)
+    {
+        parameters.setBoolean(COSName.I, value);
+    }
+
+    /**
+     * Returns a list of filters applied to this stream, or null if there are none.
+     *
+     * @return a list of filters applied to this stream
+     */
+    // TODO return an empty list if there are none?
+    public List<String> getFilters()
+    {
+        List<String> names = null;
+        COSBase filters = parameters.getDictionaryObject(COSName.F, COSName.FILTER);
+        if (filters instanceof COSName)
+        {
+            COSName name = (COSName) filters;
+            names = new COSArrayList<String>(name.getName(), name, parameters, COSName.FILTER);
+        }
+        else if (filters instanceof COSArray)
+        {
+            names = COSArrayList.convertCOSNameCOSArrayToList((COSArray) filters);
+        }
+        return names;
+    }
+
+    /**
+     * Sets which filters are applied to this stream.
+     *
+     * @param filters the filters to apply to this stream.
+     */
+    public void setFilters(List<String> filters)
+    {
+        COSBase obj = COSArrayList.convertStringListToCOSNameCOSArray(filters);
+        parameters.setItem(COSName.F, obj);
+    }
+
+    @Override
+    public void setDecode(COSArray decode)
+    {
+        parameters.setItem(COSName.D, decode);
+    }
+
+    @Override
+    public COSArray getDecode()
+    {
+        return (COSArray) parameters.getDictionaryObject(COSName.D, COSName.DECODE);
+    }
+
+    @Override
+    public boolean isStencil()
+    {
+        return parameters.getBoolean(COSName.IM, COSName.IMAGE_MASK, false);
+    }
+
+    @Override
+    public void setStencil(boolean isStencil)
+    {
+        parameters.setBoolean(COSName.IM, isStencil);
+    }
+
+    @Override
+    public InputStream createInputStream() throws IOException
+    {
+        return new ByteArrayInputStream(decodedData);
+    }
+
+    @Override
+    public InputStream createInputStream(DecodeOptions options) throws IOException
+    {
+        // Decode options are irrelevant for inline image, as the data is always buffered.
+        return createInputStream();
+    }
+
+    @Override
+    public InputStream createInputStream(List<String> stopFilters) throws IOException
+    {
+        List<String> filters = getFilters();
+        ByteArrayInputStream in = new ByteArrayInputStream(rawData);
+        ByteArrayOutputStream out = new ByteArrayOutputStream(rawData.length);
+        for (int i = 0; filters != null && i < filters.size(); i++)
+        {
+            // TODO handling of abbreviated names belongs here, rather than in other classes
+            out.reset();
+            if (stopFilters.contains(filters.get(i)))
+            {
+                break;
+            }
+            else
+            {
+                Filter filter = FilterFactory.INSTANCE.getFilter(filters.get(i));
+                filter.decode(in, out, parameters, i);
+                in = new ByteArrayInputStream(out.toByteArray());
+            }
+        }
+        return new ByteArrayInputStream(out.toByteArray());
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        return decodedData.length == 0;
+    }
+
+    /**
+     * Returns the inline image data.
+     */
+    public byte[] getData()
+    {
+        return decodedData;
+    }
+    
+    @Override
+    public BufferedImage getImage() throws IOException
+    {
+        return SampledImageReader.getRGBImage(this, getColorKeyMask());
+    }
+
+    @Override
+    public BufferedImage getImage(Rectangle region, int subsampling) throws IOException
+    {
+        return SampledImageReader.getRGBImage(this, region, subsampling, getColorKeyMask());
+    }
+
+    @Override
+    public BufferedImage getStencilImage(Paint paint) throws IOException
+    {
+        if (!isStencil())
+        {
+            throw new IllegalStateException("Image is not a stencil");
+        }
+        return SampledImageReader.getStencilImage(this, paint);
+    }
+
+    /**
+     * Returns the color key mask array associated with this image, or null if
+     * there is none.
+     *
+     * @return Mask Image XObject
+     */
+    public COSArray getColorKeyMask()
+    {
+        COSBase mask = parameters.getDictionaryObject(COSName.IM, COSName.MASK);
+        if (mask instanceof COSArray)
+        {
+            return (COSArray) mask;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the suffix for this image type, e.g. jpg/png.
+     *
+     * @return The image suffix.
+     */
+    @Override
+    public String getSuffix()
+    {
+        // TODO implement me
+        return null;
+    }
+}

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/SampledImageReader.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/SampledImageReader.java?rev=1826261&r1=1826260&r2=1826261&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/SampledImageReader.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/SampledImageReader.java Thu Mar  8 18:21:35 2018
@@ -19,6 +19,7 @@ package org.apache.pdfbox.pdmodel.graphi
 import java.awt.Graphics2D;
 import java.awt.Paint;
 import java.awt.Point;
+import java.awt.Rectangle;
 import java.awt.image.BufferedImage;
 import java.awt.image.DataBuffer;
 import java.awt.image.DataBufferByte;
@@ -33,6 +34,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.pdfbox.cos.COSArray;
 import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.filter.DecodeOptions;
 import org.apache.pdfbox.io.IOUtils;
 import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
 import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
@@ -149,27 +151,50 @@ final class SampledImageReader
      */
     public static BufferedImage getRGBImage(PDImage pdImage, COSArray colorKey) throws IOException
     {
+        return getRGBImage(pdImage, null, 1, colorKey);
+    }
+
+    private static Rectangle clipRegion(PDImage pdImage, Rectangle region)
+    {
+        if (region == null)
+        {
+            return new Rectangle(0, 0, pdImage.getWidth(), pdImage.getHeight());
+        }
+        else
+        {
+            int x = Math.max(0, region.x);
+            int y = Math.max(0, region.y);
+            int width = Math.min(region.width, pdImage.getWidth() - x);
+            int height = Math.min(region.height, pdImage.getHeight() - y);
+            return new Rectangle(x, y, width, height);
+        }
+    }
+
+    public static BufferedImage getRGBImage(PDImage pdImage, Rectangle region, int subsampling,
+                                            COSArray colorKey) throws IOException
+    {
         if (pdImage.isEmpty())
         {
             throw new IOException("Image stream is empty");
         }
+        Rectangle clipped = clipRegion(pdImage, region);
 
         // get parameters, they must be valid or have been repaired
         final PDColorSpace colorSpace = pdImage.getColorSpace();
         final int numComponents = colorSpace.getNumberOfComponents();
-        final int width = pdImage.getWidth();
-        final int height = pdImage.getHeight();
+        final int width = (int) Math.ceil(clipped.getWidth() / subsampling);
+        final int height = (int) Math.ceil(clipped.getHeight() / subsampling);
         final int bitsPerComponent = pdImage.getBitsPerComponent();
         final float[] decode = getDecodeArray(pdImage);
 
-        if (width <= 0 || height <= 0)
+        if (width <= 0 || height <= 0 || pdImage.getWidth() <= 0 || pdImage.getHeight() <= 0)
         {
             throw new IOException("image width and height must be positive");
         }
 
         if (bitsPerComponent == 1 && colorKey == null && numComponents == 1)
         {
-            return from1Bit(pdImage);
+            return from1Bit(pdImage, clipped, subsampling, width, height);
         }
 
         //
@@ -184,44 +209,68 @@ final class SampledImageReader
         if (bitsPerComponent == 8 && Arrays.equals(decode, defaultDecode) && colorKey == null)
         {
             // convert image, faster path for non-decoded, non-colormasked 8-bit images
-            return from8bit(pdImage, raster);
+            return from8bit(pdImage, raster, clipped, subsampling, width, height);
         }
-        return fromAny(pdImage, raster, colorKey);
+        return fromAny(pdImage, raster, colorKey, clipped, subsampling, width, height);
     }
 
-    private static BufferedImage from1Bit(PDImage pdImage) throws IOException
+    private static BufferedImage from1Bit(PDImage pdImage, Rectangle clipped, int subsampling,
+                                          final int width, final int height) throws IOException
     {
         final PDColorSpace colorSpace = pdImage.getColorSpace();
-        final int width = pdImage.getWidth();
-        final int height = pdImage.getHeight();
         final float[] decode = getDecodeArray(pdImage);
         BufferedImage bim = null;
         WritableRaster raster;
         byte[] output;
-        if (colorSpace instanceof PDDeviceGray)
-        {
-            // TYPE_BYTE_GRAY and not TYPE_BYTE_BINARY because this one is handled
-            // without conversion to RGB by Graphics.drawImage
-            // this reduces the memory footprint, only one byte per pixel instead of three.
-            bim = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
-            raster = bim.getRaster();
-        }
-        else
-        {
-            raster = Raster.createBandedRaster(DataBuffer.TYPE_BYTE, width, height, 1, new Point(0, 0));
-        }
-        output = ((DataBufferByte) raster.getDataBuffer()).getData();
 
+        DecodeOptions options = new DecodeOptions(subsampling);
+        options.setSourceRegion(clipped);
         // read bit stream
         InputStream iis = null;
         try
         {
-            // create stream
-            iis = pdImage.createInputStream();
+            final int inputWidth, inputHeight, startx, starty, scanWidth, scanHeight;
+            if (options.isFilterSubsampled())
+            {
+                // Decode options were honored, and so there is no need for additional clipping or subsampling
+                inputWidth = width;
+                inputHeight = height;
+                startx = 0;
+                starty = 0;
+                scanWidth = width;
+                scanHeight = height;
+                subsampling = 1;
+            }
+            else
+            {
+                // Decode options not honored, so we need to clip and subsample ourselves.
+                inputWidth = pdImage.getWidth();
+                inputHeight = pdImage.getHeight();
+                startx = clipped.x;
+                starty = clipped.y;
+                scanWidth = clipped.width;
+                scanHeight = clipped.height;
+            }
+            if (colorSpace instanceof PDDeviceGray)
+            {
+                // TYPE_BYTE_GRAY and not TYPE_BYTE_BINARY because this one is handled
+                // without conversion to RGB by Graphics.drawImage
+                // this reduces the memory footprint, only one byte per pixel instead of three.
+                bim = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
+                raster = bim.getRaster();
+            }
+            else
+            {
+                raster = Raster.createBandedRaster(DataBuffer.TYPE_BYTE, width, height, 1, new Point(0, 0));
+            }
+            output = ((DataBufferByte) raster.getDataBuffer()).getData();
             final boolean isIndexed = colorSpace instanceof PDIndexed;
 
-            int rowLen = width / 8;
-            if (width % 8 > 0)
+            // create stream
+            iis = pdImage.createInputStream(options);
+
+            int rowLen = inputWidth / 8;
+            if (inputWidth % 8 > 0)
             {
                 rowLen++;
             }
@@ -241,24 +290,31 @@ final class SampledImageReader
             }
             byte[] buff = new byte[rowLen];
             int idx = 0;
-            for (int y = 0; y < height; y++)
+            for (int y = 0; y < starty + scanHeight; y++)
             {
                 int x = 0;
                 int readLen = iis.read(buff);
+                if (y < starty || y % subsampling > 0)
+                {
+                    continue;
+                }
                 for (int r = 0; r < rowLen && r < readLen; r++)
                 {
                     int value = buff[r];
                     int mask = 128;
                     for (int i = 0; i < 8; i++)
                     {
+                        if (x >= startx + scanWidth)
+                        {
+                            break;
+                        }
                         int bit = value & mask;
                         mask >>= 1;
-                        output[idx++] = bit == 0 ? value0 : value1;
-                        x++;
-                        if (x == width)
+                        if (x >= startx && x % subsampling == 0)
                         {
-                            break;
+                            output[idx++] = bit == 0 ? value0 : value1;
                         }
+                        x++;
                     }
                 }
                 if (readLen != rowLen)
@@ -286,26 +342,53 @@ final class SampledImageReader
     }
 
     // faster, 8-bit non-decoded, non-colormasked image conversion
-    private static BufferedImage from8bit(PDImage pdImage, WritableRaster raster)
-            throws IOException
+    private static BufferedImage from8bit(PDImage pdImage, WritableRaster raster, Rectangle clipped, int subsampling,
+                                          final int width, final int height) throws IOException
     {
-        InputStream input = pdImage.createInputStream();
+        DecodeOptions options = new DecodeOptions(subsampling);
+        options.setSourceRegion(clipped);
+        InputStream input = pdImage.createInputStream(options);
         try
         {
+            final int inputWidth, inputHeight, startx, starty, scanWidth, scanHeight;
+            if (options.isFilterSubsampled())
+            {
+                // Decode options were honored, and so there is no need for additional clipping or subsampling
+                inputWidth = width;
+                inputHeight = height;
+                startx = 0;
+                starty = 0;
+                scanWidth = width;
+                scanHeight = height;
+                subsampling = 1;
+            }
+            else
+            {
+                // Decode options not honored, so we need to clip and subsample ourselves.
+                inputWidth = pdImage.getWidth();
+                inputHeight = pdImage.getHeight();
+                startx = clipped.x;
+                starty = clipped.y;
+                scanWidth = clipped.width;
+                scanHeight = clipped.height;
+            }
+            final int numComponents = pdImage.getColorSpace().getNumberOfComponents();
             // get the raster's underlying byte buffer
             byte[][] banks = ((DataBufferByte) raster.getDataBuffer()).getBankData();
-            final int width = pdImage.getWidth();
-            final int height = pdImage.getHeight();
-            final int numComponents = pdImage.getColorSpace().getNumberOfComponents();
-            byte[] tempBytes = new byte[numComponents * width];
+            byte[] tempBytes = new byte[numComponents * inputWidth];
             // compromise between memory and time usage:
             // reading the whole image consumes too much memory
             // reading one pixel at a time makes it slow in our buffering infrastructure 
             int i = 0;
-            for (int y = 0; y < height; ++y)
+            for (int y = 0; y < starty + scanHeight; ++y)
             {
                 input.read(tempBytes);
-                for (int x = 0; x < width; ++x)
+                if (y < starty || y % subsampling > 0)
+                {
+                    continue;
+                }
+
+                for (int x = startx; x < startx + scanWidth; x += subsampling)
                 {
                     for (int c = 0; c < numComponents; c++)
                     {
@@ -324,23 +407,46 @@ final class SampledImageReader
     }
 
     // slower, general-purpose image conversion from any image format
-    private static BufferedImage fromAny(PDImage pdImage, WritableRaster raster, COSArray colorKey)
+    private static BufferedImage fromAny(PDImage pdImage, WritableRaster raster, COSArray colorKey, Rectangle clipped,
+                                         int subsampling, final int width, final int height)
             throws IOException
     {
         final PDColorSpace colorSpace = pdImage.getColorSpace();
         final int numComponents = colorSpace.getNumberOfComponents();
-        final int width = pdImage.getWidth();
-        final int height = pdImage.getHeight();
         final int bitsPerComponent = pdImage.getBitsPerComponent();
         final float[] decode = getDecodeArray(pdImage);
 
+        DecodeOptions options = new DecodeOptions(subsampling);
+        options.setSourceRegion(clipped);
         // read bit stream
         ImageInputStream iis = null;
         try
         {
+            final int inputWidth, inputHeight, startx, starty, scanWidth, scanHeight;
+            if (options.isFilterSubsampled())
+            {
+                // Decode options were honored, and so there is no need for additional clipping or subsampling
+                inputWidth = width;
+                inputHeight = height;
+                startx = 0;
+                starty = 0;
+                scanWidth = width;
+                scanHeight = height;
+                subsampling = 1;
+            }
+            else
+            {
+                // Decode options not honored, so we need to clip and subsample ourselves.
+                inputWidth = pdImage.getWidth();
+                inputHeight = pdImage.getHeight();
+                startx = clipped.x;
+                starty = clipped.y;
+                scanWidth = clipped.width;
+                scanHeight = clipped.height;
+            }
             // create stream
-            iis = new MemoryCacheImageInputStream(pdImage.createInputStream());
-            final float sampleMax = (float)Math.pow(2, bitsPerComponent) - 1f;
+            final float sampleMax = (float) Math.pow(2, bitsPerComponent) - 1f;
+            iis = new MemoryCacheImageInputStream(pdImage.createInputStream(options));
             final boolean isIndexed = colorSpace instanceof PDIndexed;
 
             // init color key mask
@@ -354,17 +460,17 @@ final class SampledImageReader
 
             // calculate row padding
             int padding = 0;
-            if (width * numComponents * bitsPerComponent % 8 > 0)
+            if (inputWidth * numComponents * bitsPerComponent % 8 > 0)
             {
-                padding = 8 - (width * numComponents * bitsPerComponent % 8);
+                padding = 8 - (inputWidth * numComponents * bitsPerComponent % 8);
             }
 
             // read stream
             byte[] srcColorValues = new byte[numComponents];
             byte[] alpha = new byte[1];
-            for (int y = 0; y < height; y++)
+            for (int y = 0; y < starty + scanHeight; y++)
             {
-                for (int x = 0; x < width; x++)
+                for (int x = 0; x < startx + scanWidth; x++)
                 {
                     boolean isMasked = true;
                     for (int c = 0; c < numComponents; c++)
@@ -401,13 +507,17 @@ final class SampledImageReader
                             srcColorValues[c] = (byte)outputByte;
                         }
                     }
-                    raster.setDataElements(x, y, srcColorValues);
-
-                    // set alpha channel in color key mask, if any
-                    if (colorKeyMask != null)
+                    // only write to output if within requested region and subsample.
+                    if (x >= startx && y >= starty && x % subsampling == 0 && y % subsampling == 0)
                     {
-                        alpha[0] = (byte)(isMasked ? 255 : 0);
-                        colorKeyMask.getRaster().setDataElements(x, y, alpha);
+                        raster.setDataElements((x - startx) / subsampling, (y - starty) / subsampling, srcColorValues);
+
+                        // set alpha channel in color key mask, if any
+                        if (colorKeyMask != null)
+                        {
+                            alpha[0] = (byte)(isMasked ? 255 : 0);
+                            colorKeyMask.getRaster().setDataElements((x - startx) / subsampling, (y - starty) / subsampling, alpha);
+                        }
                     }
                 }
 

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java?rev=1826261&r1=1826260&r2=1826261&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java Thu Mar  8 18:21:35 2018
@@ -53,6 +53,8 @@ public class PDFRenderer
          }
      };
 
+    private boolean subsamplingAllowed = false;
+
     /**
      * Creates a new PDFRenderer.
      * @param document the document to render
@@ -85,6 +87,34 @@ public class PDFRenderer
     }
 
     /**
+     * Value indicating if the renderer is allowed to subsample images before drawing, according to
+     * image dimensions and requested scale.
+     *
+     * Subsampling may be faster and less memory-intensive in some cases, but it may also lead to
+     * loss of quality, especially in images with high spatial frequency.
+     *
+     * @return true if subsampling of images is allowed, false otherwise.
+     */
+    public boolean isSubsamplingAllowed()
+    {
+        return subsamplingAllowed;
+    }
+
+    /**
+     * Sets a value instructing the renderer whether it is allowed to subsample images before
+     * drawing. The subsampling frequency is determined according to image size and requested scale.
+     *
+     * Subsampling may be faster and less memory-intensive in some cases, but it may also lead to
+     * loss of quality, especially in images with high spatial frequency.
+     *
+     * @param subsamplingAllowed The new value indicating if subsampling is allowed.
+     */
+    public void setSubsamplingAllowed(boolean subsamplingAllowed)
+    {
+        this.subsamplingAllowed = subsamplingAllowed;
+    }
+
+    /**
      * Returns the given page as an RGB image at 72 DPI
      * @param pageIndex the zero-based index of the page to be converted.
      * @return the rendered page image
@@ -190,7 +220,7 @@ public class PDFRenderer
         transform(g, page, scale);
 
         // the end-user may provide a custom PageDrawer
-        PageDrawerParameters parameters = new PageDrawerParameters(this, page);
+        PageDrawerParameters parameters = new PageDrawerParameters(this, page, subsamplingAllowed);
         PageDrawer drawer = createPageDrawer(parameters);
         drawer.drawPage(g, page.getCropBox());       
         
@@ -242,7 +272,7 @@ public class PDFRenderer
         graphics.clearRect(0, 0, (int) cropBox.getWidth(), (int) cropBox.getHeight());
 
         // the end-user may provide a custom PageDrawer
-        PageDrawerParameters parameters = new PageDrawerParameters(this, page);
+        PageDrawerParameters parameters = new PageDrawerParameters(this, page, subsamplingAllowed);
         PageDrawer drawer = createPageDrawer(parameters);
         drawer.drawPage(graphics, cropBox);
     }



Mime
View raw message