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 2018/03/07 21:40:42 UTC

svn commit: r1826161 [1/2] - in /pdfbox/trunk/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/pdfbox/r...

Author: tilman
Date: Wed Mar  7 21:40:42 2018
New Revision: 1826161

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

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

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSInputStream.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSInputStream.java?rev=1826161&r1=1826160&r2=1826161&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSInputStream.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSInputStream.java Wed Mar  7 21:40:42 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<>();
         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/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java?rev=1826161&r1=1826160&r2=1826161&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java Wed Mar  7 21:40:42 2018
@@ -26,6 +26,8 @@ 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.DecodeResult;
 import org.apache.pdfbox.filter.Filter;
 import org.apache.pdfbox.filter.FilterFactory;
 import org.apache.pdfbox.io.IOUtils;
@@ -159,6 +161,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 +173,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/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/DCTFilter.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/DCTFilter.java?rev=1826161&r1=1826160&r2=1826161&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/DCTFilter.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/DCTFilter.java Wed Mar  7 21:40:42 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");
         try (ImageInputStream iis = ImageIO.createImageInputStream(encoded))
@@ -63,9 +64,14 @@ final class DCTFilter extends Filter
             {
                 iis.seek(0);
             }
-            
+
             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
@@ -80,7 +86,7 @@ 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)
@@ -88,14 +94,14 @@ final class DCTFilter extends Filter
                     // JAI can't read CMYK JPEGs using ImageReader#read or ImageIO.read but
                     // fortunately ImageReader#readRaster isn't buggy when reading 4-channel files
                     LOG.debug("Couldn't read use read() for RGB image - using readRaster() as fallback", e);
-                    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
@@ -147,6 +153,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)
     {

Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/DecodeOptions.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/DecodeOptions.java?rev=1826161&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/DecodeOptions.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/DecodeOptions.java Wed Mar  7 21:40:42 2018
@@ -0,0 +1,263 @@
+/*
+ * 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;
+    private int subsamplingY = 1;
+    private int subsamplingOffsetX = 0;
+    private int 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/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/DecodeOptions.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/Filter.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/Filter.java?rev=1826161&r1=1826160&r2=1826161&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/Filter.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/Filter.java Wed Mar  7 21:40:42 2018
@@ -70,6 +70,24 @@ public abstract class Filter
                             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.
      * @param input the byte stream to encode
      * @param encoded the stream where encoded data will be written

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/JBIG2Filter.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/JBIG2Filter.java?rev=1826161&r1=1826160&r2=1826161&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/JBIG2Filter.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/JBIG2Filter.java Wed Mar  7 21:40:42 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;
@@ -61,8 +62,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"))
@@ -73,6 +74,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);
+
         InputStream source = encoded;
         if (params != null)
         {
@@ -90,7 +97,7 @@ final class JBIG2Filter extends Filter
             BufferedImage image;
             try
             {
-                image = reader.read(0, reader.getDefaultReadParam());
+                image = reader.read(0, irp);
             }
             catch (Exception e)
             {
@@ -128,10 +135,18 @@ final class JBIG2Filter extends Filter
         {
             reader.dispose();
         }
+
         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);
+    }
+
+    @Override
     protected void encode(InputStream input, OutputStream encoded, COSDictionary parameters)
             throws IOException
     {

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/JPXFilter.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/JPXFilter.java?rev=1826161&r1=1826160&r2=1826161&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/JPXFilter.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/JPXFilter.java Wed Mar  7 21:40:42 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,22 +76,34 @@ 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");
         // PDFBOX-4121: ImageIO.createImageInputStream() is much slower
         try (ImageInputStream 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)
             {
@@ -114,8 +127,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/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDStream.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDStream.java?rev=1826161&r1=1826160&r2=1826161&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDStream.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDStream.java Wed Mar  7 21:40:42 2018
@@ -32,6 +32,8 @@ 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.DecodeResult;
 import org.apache.pdfbox.filter.Filter;
 import org.apache.pdfbox.filter.FilterFactory;
 import org.apache.pdfbox.io.IOUtils;
@@ -229,6 +231,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/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImage.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImage.java?rev=1826161&r1=1826160&r2=1826161&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImage.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImage.java Wed Mar  7 21:40:42 2018
@@ -1,157 +1,183 @@
-/*
- * 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/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java?rev=1826161&r1=1826160&r2=1826161&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java Wed Mar  7 21:40:42 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)
      */
@@ -370,7 +375,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)
@@ -378,9 +392,8 @@ public final class PDImageXObject extend
                 return cached;
             }
         }
-
         // 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();
@@ -398,7 +411,14 @@ public final class PDImageXObject extend
             }
         }
 
-        cachedImage = new SoftReference<>(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<>(image);
+        }
+
         return image;
     }
 
@@ -624,6 +644,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/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDInlineImage.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDInlineImage.java?rev=1826161&r1=1826160&r2=1826161&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDInlineImage.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDInlineImage.java Wed Mar  7 21:40:42 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<>(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<>(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;
+    }
+}