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;
+ }
+}