JPXFilter.java
/*
* 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.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferUShort;
import java.awt.image.IndexColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.Raster;
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;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.graphics.color.PDJPXColorSpace;
/**
* Decompress data encoded using the wavelet-based JPEG 2000 standard,
* reproducing the original data.
*
* Requires the Java Advanced Imaging (JAI) Image I/O Tools to be installed from java.net, see
* <a href="http://download.java.net/media/jai-imageio/builds/release/1.1/">jai-imageio</a>.
* Alternatively you can build from the source available in the
* <a href="https://java.net/projects/jai-imageio-core/">jai-imageio-core svn repo</a>.
*
* Mac OS X users should download the tar.gz file for linux and unpack it to obtain the
* required jar files. The .so file can be safely ignored.
*
* @author John Hewson
* @author Timo Boehme
*/
public final class JPXFilter extends Filter
{
/**
* {@inheritDoc}
*/
@Override
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, options, result);
Raster raster = image.getRaster();
switch (raster.getDataBuffer().getDataType())
{
case DataBuffer.TYPE_BYTE:
DataBufferByte byteBuffer = (DataBufferByte) raster.getDataBuffer();
decoded.write(byteBuffer.getData());
return result;
case DataBuffer.TYPE_USHORT:
DataBufferUShort wordBuffer = (DataBufferUShort) raster.getDataBuffer();
for (short w : wordBuffer.getData())
{
decoded.write(w >> 8);
decoded.write(w);
}
return result;
case DataBuffer.TYPE_INT:
// not yet used (as of October 2018) but works as fallback
// if we decide to convert to BufferedImage.TYPE_INT_RGB
int[] ar = new int[raster.getNumBands()];
for (int y = 0; y < image.getHeight(); ++y)
{
for (int x = 0; x < image.getWidth(); ++x)
{
raster.getPixel(x, y, ar);
for (int i = 0; i < ar.length; ++i)
{
decoded.write(ar[i]);
}
}
}
return result;
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, 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, irp);
}
catch (Exception e)
{
// wrap and rethrow any exceptions
throw new IOException("Could not read JPEG 2000 (JPX) image", e);
}
COSDictionary parameters = result.getParameters();
// "If the image stream uses the JPXDecode filter, this entry is optional
// and shall be ignored if present"
//
// note that indexed color spaces make the BPC logic tricky, see PDFBOX-2204
int bpc = image.getColorModel().getPixelSize() / image.getRaster().getNumBands();
parameters.setInt(COSName.BITS_PER_COMPONENT, bpc);
// "Decode shall be ignored, except in the case where the image is treated as a mask"
if (!parameters.getBoolean(COSName.IMAGE_MASK, false))
{
parameters.setItem(COSName.DECODE, null);
}
// override dimensions, see PDFBOX-1735
parameters.setInt(COSName.WIDTH, reader.getWidth(0));
parameters.setInt(COSName.HEIGHT, reader.getHeight(0));
// extract embedded color space
if (!parameters.containsKey(COSName.COLORSPACE))
{
if (image.getSampleModel() instanceof MultiPixelPackedSampleModel &&
image.getColorModel().getPixelSize() == 1 &&
image.getRaster().getNumBands() == 1 &&
image.getColorModel() instanceof IndexColorModel)
{
// PDFBOX-4326:
// force CS_GRAY colorspace because colorspace in IndexColorModel
// has 3 colors despite that there is only 1 color per pixel
// in raster
result.setColorSpace(new PDJPXColorSpace(ColorSpace.getInstance(ColorSpace.CS_GRAY)));
}
else
{
result.setColorSpace(new PDJPXColorSpace(image.getColorModel().getColorSpace()));
}
}
return image;
}
finally
{
reader.dispose();
}
}
/**
* {@inheritDoc}
*/
@Override
protected void encode(InputStream input, OutputStream encoded, COSDictionary parameters)
throws IOException
{
throw new UnsupportedOperationException("JPX encoding not implemented");
}
}