/* * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package com.sun.imageio.plugins.jpeg; import javax.imageio.IIOException; import javax.imageio.ImageReader; import javax.imageio.ImageReadParam; import javax.imageio.ImageTypeSpecifier; import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import javax.imageio.plugins.jpeg.JPEGImageReadParam; import javax.imageio.plugins.jpeg.JPEGQTable; import javax.imageio.plugins.jpeg.JPEGHuffmanTable; import java.awt.Point; import java.awt.Rectangle; import java.awt.color.ColorSpace; import java.awt.color.ICC_Profile; import java.awt.color.ICC_ColorSpace; import java.awt.color.CMMException; import java.awt.image.BufferedImage; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.ColorModel; import java.awt.image.IndexColorModel; import java.awt.image.ColorConvertOp; import java.io.IOException; import java.util.List; import java.util.Iterator; import java.util.ArrayList; import java.util.NoSuchElementException; import sun.java2d.Disposer; import sun.java2d.DisposerRecord; public class JPEGImageReader extends ImageReader { private boolean debug = false; /** * The following variable contains a pointer to the IJG library * structure for this reader. It is assigned in the constructor * and then is passed in to every native call. It is set to 0 * by dispose to avoid disposing twice. */ private long structPointer = 0; /** The input stream we read from */ private ImageInputStream iis = null; /** * List of stream positions for images, reinitialized every time * a new input source is set. */ private List imagePositions = null; /** * The number of images in the stream, or 0. */ private int numImages = 0; static { java.security.AccessController.doPrivileged( new sun.security.action.LoadLibraryAction("jpeg")); initReaderIDs(ImageInputStream.class, JPEGQTable.class, JPEGHuffmanTable.class); } // The following warnings are converted to strings when used // as keys to get localized resources from JPEGImageReaderResources // and its children. /** * Warning code to be passed to warningOccurred to indicate * that the EOI marker is missing from the end of the stream. * This usually signals that the stream is corrupted, but * everything up to the last MCU should be usable. */ protected static final int WARNING_NO_EOI = 0; /** * Warning code to be passed to warningOccurred to indicate * that a JFIF segment was encountered inside a JFXX JPEG * thumbnail and is being ignored. */ protected static final int WARNING_NO_JFIF_IN_THUMB = 1; /** * Warning code to be passed to warningOccurred to indicate * that embedded ICC profile is invalid and will be ignored. */ protected static final int WARNING_IGNORE_INVALID_ICC = 2; private static final int MAX_WARNING = WARNING_IGNORE_INVALID_ICC; /** * Image index of image for which header information * is available. */ private int currentImage = -1; // The following is copied out from C after reading the header. // Unlike metadata, which may never be retrieved, we need this // if we are to read an image at all. /** Set by setImageData native code callback */ private int width; /** Set by setImageData native code callback */ private int height; /** * Set by setImageData native code callback. A modified * IJG+NIFTY colorspace code. */ private int colorSpaceCode; /** * Set by setImageData native code callback. A modified * IJG+NIFTY colorspace code. */ private int outColorSpaceCode; /** Set by setImageData native code callback */ private int numComponents; /** Set by setImageData native code callback */ private ColorSpace iccCS = null; /** If we need to post-convert in Java, convert with this op */ private ColorConvertOp convert = null; /** The image we are going to fill */ private BufferedImage image = null; /** An intermediate Raster to hold decoded data */ private WritableRaster raster = null; /** A view of our target Raster that we can setRect to */ private WritableRaster target = null; /** The databuffer for the above Raster */ private DataBufferByte buffer = null; /** The region in the destination where we will write pixels */ private Rectangle destROI = null; /** The list of destination bands, if any */ private int [] destinationBands = null; /** Stream metadata, cached, even when the stream is changed. */ private JPEGMetadata streamMetadata = null; /** Image metadata, valid for the imageMetadataIndex only. */ private JPEGMetadata imageMetadata = null; private int imageMetadataIndex = -1; /** * Set to true every time we seek in the stream; used to * invalidate the native buffer contents in C. */ private boolean haveSeeked = false; /** * Tables that have been read from a tables-only image at the * beginning of a stream. */ private JPEGQTable [] abbrevQTables = null; private JPEGHuffmanTable[] abbrevDCHuffmanTables = null; private JPEGHuffmanTable[] abbrevACHuffmanTables = null; private int minProgressivePass = 0; private int maxProgressivePass = Integer.MAX_VALUE; /** * Variables used by progress monitoring. */ private static final int UNKNOWN = -1; // Number of passes private static final int MIN_ESTIMATED_PASSES = 10; // IJG default private int knownPassCount = UNKNOWN; private int pass = 0; private float percentToDate = 0.0F; private float previousPassPercentage = 0.0F; private int progInterval = 0; /** * Set to true once stream has been checked for stream metadata */ private boolean tablesOnlyChecked = false; /** The referent to be registered with the Disposer. */ private Object disposerReferent = new Object(); /** The DisposerRecord that handles the actual disposal of this reader. */ private DisposerRecord disposerRecord; /** Sets up static C structures. */ private static native void initReaderIDs(Class iisClass, Class qTableClass, Class huffClass); public JPEGImageReader(ImageReaderSpi originator) { super(originator); structPointer = initJPEGImageReader(); disposerRecord = new JPEGReaderDisposerRecord(structPointer); Disposer.addRecord(disposerReferent, disposerRecord); } /** Sets up per-reader C structure and returns a pointer to it. */ private native long initJPEGImageReader(); /** * Called by the native code or other classes to signal a warning. * The code is used to lookup a localized message to be used when * sending warnings to listeners. */ protected void warningOccurred(int code) { cbLock.lock(); try { if ((code < 0) || (code > MAX_WARNING)){ throw new InternalError("Invalid warning index"); } processWarningOccurred ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources", Integer.toString(code)); } finally { cbLock.unlock(); } } /** * The library has it's own error facility that emits warning messages. * This routine is called by the native code when it has already * formatted a string for output. * XXX For truly complete localization of all warning messages, * the sun_jpeg_output_message routine in the native code should * send only the codes and parameters to a method here in Java, * which will then format and send the warnings, using localized * strings. This method will have to deal with all the parameters * and formats (%u with possibly large numbers, %02d, %02x, etc.) * that actually occur in the JPEG library. For now, this prevents * library warnings from being printed to stderr. */ protected void warningWithMessage(String msg) { cbLock.lock(); try { processWarningOccurred(msg); } finally { cbLock.unlock(); } } public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) { setThreadLock(); try { cbLock.check(); super.setInput(input, seekForwardOnly, ignoreMetadata); this.ignoreMetadata = ignoreMetadata; resetInternalState(); iis = (ImageInputStream) input; // Always works setSource(structPointer); } finally { clearThreadLock(); } } /** * This method is called from native code in order to fill * native input buffer. * * We block any attempt to change the reading state during this * method, in order to prevent a corruption of the native decoder * state. * * @return number of bytes read from the stream. */ private int readInputData(byte[] buf, int off, int len) throws IOException { cbLock.lock(); try { return iis.read(buf, off, len); } finally { cbLock.unlock(); } } /** * This method is called from the native code in order to * skip requested number of bytes in the input stream. * * @param n * @return * @throws IOException */ private long skipInputBytes(long n) throws IOException { cbLock.lock(); try { return iis.skipBytes(n); } finally { cbLock.unlock(); } } private native void setSource(long structPointer); private void checkTablesOnly() throws IOException { if (debug) { System.out.println("Checking for tables-only image"); } long savePos = iis.getStreamPosition(); if (debug) { System.out.println("saved pos is " + savePos); System.out.println("length is " + iis.length()); } // Read the first header boolean tablesOnly = readNativeHeader(true); if (tablesOnly) { if (debug) { System.out.println("tables-only image found"); long pos = iis.getStreamPosition(); System.out.println("pos after return from native is " + pos); } // This reads the tables-only image twice, once from C // and once from Java, but only if ignoreMetadata is false if (ignoreMetadata == false) { iis.seek(savePos); haveSeeked = true; streamMetadata = new JPEGMetadata(true, false, iis, this); long pos = iis.getStreamPosition(); if (debug) { System.out.println ("pos after constructing stream metadata is " + pos); } } // Now we are at the first image if there are any, so add it // to the list if (hasNextImage()) { imagePositions.add(new Long(iis.getStreamPosition())); } } else { // Not tables only, so add original pos to the list imagePositions.add(new Long(savePos)); // And set current image since we've read it now currentImage = 0; } if (seekForwardOnly) { Long pos = (Long) imagePositions.get(imagePositions.size()-1); iis.flushBefore(pos.longValue()); } tablesOnlyChecked = true; } public int getNumImages(boolean allowSearch) throws IOException { setThreadLock(); try { // locked thread cbLock.check(); return getNumImagesOnThread(allowSearch); } finally { clearThreadLock(); } } private int getNumImagesOnThread(boolean allowSearch) throws IOException { if (numImages != 0) { return numImages; } if (iis == null) { throw new IllegalStateException("Input not set"); } if (allowSearch == true) { if (seekForwardOnly) { throw new IllegalStateException( "seekForwardOnly and allowSearch can't both be true!"); } // Otherwise we have to read the entire stream if (!tablesOnlyChecked) { checkTablesOnly(); } iis.mark(); gotoImage(0); JPEGBuffer buffer = new JPEGBuffer(iis); buffer.loadBuf(0); boolean done = false; while (!done) { done = buffer.scanForFF(this); switch (buffer.buf[buffer.bufPtr] & 0xff) { case JPEG.SOI: numImages++; // FALL THROUGH to decrement buffer vars // This first set doesn't have a length case 0: // not a marker, just a data 0xff case JPEG.RST0: case JPEG.RST1: case JPEG.RST2: case JPEG.RST3: case JPEG.RST4: case JPEG.RST5: case JPEG.RST6: case JPEG.RST7: case JPEG.EOI: buffer.bufAvail--; buffer.bufPtr++; break; // All the others have a length default: buffer.bufAvail--; buffer.bufPtr++; buffer.loadBuf(2); int length = ((buffer.buf[buffer.bufPtr++] & 0xff) << 8) | (buffer.buf[buffer.bufPtr++] & 0xff); buffer.bufAvail -= 2; length -= 2; // length includes itself buffer.skipData(length); } } iis.reset(); return numImages; } return -1; // Search is necessary for JPEG } /** * Sets the input stream to the start of the requested image. *
* @exception IllegalStateException if the input source has not been * set. * @exception IndexOutOfBoundsException if the supplied index is * out of bounds. **/ private void gotoImage(int imageIndex) throws IOException { if (iis == null) { throw new IllegalStateException("Input not set"); } if (imageIndex < minIndex) { throw new IndexOutOfBoundsException(); } if (!tablesOnlyChecked) { checkTablesOnly(); } if (imageIndex < imagePositions.size()) { iis.seek(((Long)(imagePositions.get(imageIndex))).longValue()); } else { // read to start of image, saving positions // First seek to the last position we already have, and skip the // entire image Long pos = (Long) imagePositions.get(imagePositions.size()-1); iis.seek(pos.longValue()); skipImage(); // Now add all intervening positions, skipping images for (int index = imagePositions.size(); index <= imageIndex; index++) { // Is there an image? if (!hasNextImage()) { throw new IndexOutOfBoundsException(); } pos = new Long(iis.getStreamPosition()); imagePositions.add(pos); if (seekForwardOnly) { iis.flushBefore(pos.longValue()); } if (index < imageIndex) { skipImage(); } // Otherwise we are where we want to be } } if (seekForwardOnly) { minIndex = imageIndex; } haveSeeked = true; // No way is native buffer still valid } /** * Skip over a complete image in the stream, leaving the stream * positioned such that the next byte to be read is the first * byte of the next image. For JPEG, this means that we read * until we encounter an EOI marker or until the end of the stream. * If the stream ends before an EOI marker is encountered, an * IndexOutOfBoundsException is thrown. */ private void skipImage() throws IOException { if (debug) { System.out.println("skipImage called"); } boolean foundFF = false; for (int byteval = iis.read(); byteval != -1; byteval = iis.read()) { if (foundFF == true) { if (byteval == JPEG.EOI) { return; } } foundFF = (byteval == 0xff) ? true : false; } throw new IndexOutOfBoundsException(); } /** * Returns
true
if there is an image beyond
* the current stream position. Does not disturb the
* stream position.
*/
private boolean hasNextImage() throws IOException {
if (debug) {
System.out.print("hasNextImage called; returning ");
}
iis.mark();
boolean foundFF = false;
for (int byteval = iis.read();
byteval != -1;
byteval = iis.read()) {
if (foundFF == true) {
if (byteval == JPEG.SOI) {
iis.reset();
if (debug) {
System.out.println("true");
}
return true;
}
}
foundFF = (byteval == 0xff) ? true : false;
}
// We hit the end of the stream before we hit an SOI, so no image
iis.reset();
if (debug) {
System.out.println("false");
}
return false;
}
/**
* Push back the given number of bytes to the input stream.
* Called by the native code at the end of each image so
* that the next one can be identified from Java.
*/
private void pushBack(int num) throws IOException {
if (debug) {
System.out.println("pushing back " + num + " bytes");
}
cbLock.lock();
try {
iis.seek(iis.getStreamPosition()-num);
// The buffer is clear after this, so no need to set haveSeeked.
} finally {
cbLock.unlock();
}
}
/**
* Reads header information for the given image, if possible.
*/
private void readHeader(int imageIndex, boolean reset)
throws IOException {
gotoImage(imageIndex);
readNativeHeader(reset); // Ignore return
currentImage = imageIndex;
}
private boolean readNativeHeader(boolean reset) throws IOException {
boolean retval = false;
retval = readImageHeader(structPointer, haveSeeked, reset);
haveSeeked = false;
return retval;
}
/**
* Read in the header information starting from the current
* stream position, returning true
if the
* header was a tables-only image. After this call, the
* native IJG decompression struct will contain the image
* information required by most query calls below
* (e.g. getWidth, getHeight, etc.), if the header was not
* a tables-only image.
* If reset is true
, the state of the IJG
* object is reset so that it can read a header again.
* This happens automatically if the header was a tables-only
* image.
*/
private native boolean readImageHeader(long structPointer,
boolean clearBuffer,
boolean reset)
throws IOException;
/*
* Called by the native code whenever an image header has been
* read. Whether we read metadata or not, we always need this
* information, so it is passed back independently of
* metadata, which may never be read.
*/
private void setImageData(int width,
int height,
int colorSpaceCode,
int outColorSpaceCode,
int numComponents,
byte [] iccData) {
this.width = width;
this.height = height;
this.colorSpaceCode = colorSpaceCode;
this.outColorSpaceCode = outColorSpaceCode;
this.numComponents = numComponents;
if (iccData == null) {
iccCS = null;
return;
}
ICC_Profile newProfile = null;
try {
newProfile = ICC_Profile.getInstance(iccData);
} catch (IllegalArgumentException e) {
/*
* Color profile data seems to be invalid.
* Ignore this profile.
*/
iccCS = null;
warningOccurred(WARNING_IGNORE_INVALID_ICC);
return;
}
byte[] newData = newProfile.getData();
ICC_Profile oldProfile = null;
if (iccCS instanceof ICC_ColorSpace) {
oldProfile = ((ICC_ColorSpace)iccCS).getProfile();
}
byte[] oldData = null;
if (oldProfile != null) {
oldData = oldProfile.getData();
}
/*
* At the moment we can't rely on the ColorSpace.equals()
* and ICC_Profile.equals() because they do not detect
* the case when two profiles are created from same data.
*
* So, we have to do data comparison in order to avoid
* creation of different ColorSpace instances for the same
* embedded data.
*/
if (oldData == null ||
!java.util.Arrays.equals(oldData, newData))
{
iccCS = new ICC_ColorSpace(newProfile);
// verify new color space
try {
float[] colors = iccCS.fromRGB(new float[] {1f, 0f, 0f});
} catch (CMMException e) {
/*
* Embedded profile seems to be corrupted.
* Ignore this profile.
*/
iccCS = null;
cbLock.lock();
try {
warningOccurred(WARNING_IGNORE_INVALID_ICC);
} finally {
cbLock.unlock();
}
}
}
}
public int getWidth(int imageIndex) throws IOException {
setThreadLock();
try {
if (currentImage != imageIndex) {
cbLock.check();
readHeader(imageIndex, true);
}
return width;
} finally {
clearThreadLock();
}
}
public int getHeight(int imageIndex) throws IOException {
setThreadLock();
try {
if (currentImage != imageIndex) {
cbLock.check();
readHeader(imageIndex, true);
}
return height;
} finally {
clearThreadLock();
}
}
/////////// Color Conversion and Image Types
/**
* Return an ImageTypeSpecifier corresponding to the given
* color space code, or null if the color space is unsupported.
*/
private ImageTypeProducer getImageType(int code) {
ImageTypeProducer ret = null;
if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) {
ret = ImageTypeProducer.getTypeProducer(code);
}
return ret;
}
public ImageTypeSpecifier getRawImageType(int imageIndex)
throws IOException {
setThreadLock();
try {
if (currentImage != imageIndex) {
cbLock.check();
readHeader(imageIndex, true);
}
// Returns null if it can't be represented
return getImageType(colorSpaceCode).getType();
} finally {
clearThreadLock();
}
}
public Iterator getImageTypes(int imageIndex)
throws IOException {
setThreadLock();
try {
return getImageTypesOnThread(imageIndex);
} finally {
clearThreadLock();
}
}
private Iterator getImageTypesOnThread(int imageIndex)
throws IOException {
if (currentImage != imageIndex) {
cbLock.check();
readHeader(imageIndex, true);
}
// We return an iterator containing the default, any
// conversions that the library provides, and
// all the other default types with the same number
// of components, as we can do these as a post-process.
// As we convert Rasters rather than images, images
// with alpha cannot be converted in a post-process.
// If this image can't be interpreted, this method
// returns an empty Iterator.
// Get the raw ITS, if there is one. Note that this
// won't always be the same as the default.
ImageTypeProducer raw = getImageType(colorSpaceCode);
// Given the encoded colorspace, build a list of ITS's
// representing outputs you could handle starting
// with the default.
ArrayListconvert
.
* If bands are being rearranged at all (either source or destination
* bands are specified in the param), then the default color
* conversions are assumed to be correct.
* Throws an IIOException if there is no conversion available.
*/
private void checkColorConversion(BufferedImage image,
ImageReadParam param)
throws IIOException {
// If we are rearranging channels at all, the default
// conversions remain in place. If the user wants
// raw channels then he should do this while reading
// a Raster.
if (param != null) {
if ((param.getSourceBands() != null) ||
(param.getDestinationBands() != null)) {
// Accept default conversions out of decoder, silently
return;
}
}
// XXX - We do not currently support any indexed color models,
// though we could, as IJG will quantize for us.
// This is a performance and memory-use issue, as
// users can read RGB and then convert to indexed in Java.
ColorModel cm = image.getColorModel();
if (cm instanceof IndexColorModel) {
throw new IIOException("IndexColorModel not supported");
}
// Now check the ColorSpace type against outColorSpaceCode
// We may want to tweak the default
ColorSpace cs = cm.getColorSpace();
int csType = cs.getType();
convert = null;
switch (outColorSpaceCode) {
case JPEG.JCS_GRAYSCALE: // Its gray in the file
if (csType == ColorSpace.TYPE_RGB) { // We want RGB
// IJG can do this for us more efficiently
setOutColorSpace(structPointer, JPEG.JCS_RGB);
// Update java state according to changes
// in the native part of decoder.
outColorSpaceCode = JPEG.JCS_RGB;
numComponents = 3;
} else if (csType != ColorSpace.TYPE_GRAY) {
throw new IIOException("Incompatible color conversion");
}
break;
case JPEG.JCS_RGB: // IJG wants to go to RGB
if (csType == ColorSpace.TYPE_GRAY) { // We want gray
if (colorSpaceCode == JPEG.JCS_YCbCr) {
// If the jpeg space is YCbCr, IJG can do it
setOutColorSpace(structPointer, JPEG.JCS_GRAYSCALE);
// Update java state according to changes
// in the native part of decoder.
outColorSpaceCode = JPEG.JCS_GRAYSCALE;
numComponents = 1;
}
} else if ((iccCS != null) &&
(cm.getNumComponents() == numComponents) &&
(cs != iccCS)) {
// We have an ICC profile but it isn't used in the dest
// image. So convert from the profile cs to the target cs
convert = new ColorConvertOp(iccCS, cs, null);
// Leave IJG conversion in place; we still need it
} else if ((iccCS == null) &&
(!cs.isCS_sRGB()) &&
(cm.getNumComponents() == numComponents)) {
// Target isn't sRGB, so convert from sRGB to the target
convert = new ColorConvertOp(JPEG.JCS.sRGB, cs, null);
} else if (csType != ColorSpace.TYPE_RGB) {
throw new IIOException("Incompatible color conversion");
}
break;
case JPEG.JCS_RGBA:
// No conversions available; image must be RGBA
if ((csType != ColorSpace.TYPE_RGB) ||
(cm.getNumComponents() != numComponents)) {
throw new IIOException("Incompatible color conversion");
}
break;
case JPEG.JCS_YCC:
{
ColorSpace YCC = JPEG.JCS.getYCC();
if (YCC == null) { // We can't do YCC at all
throw new IIOException("Incompatible color conversion");
}
if ((cs != YCC) &&
(cm.getNumComponents() == numComponents)) {
convert = new ColorConvertOp(YCC, cs, null);
}
}
break;
case JPEG.JCS_YCCA:
{
ColorSpace YCC = JPEG.JCS.getYCC();
// No conversions available; image must be YCCA
if ((YCC == null) || // We can't do YCC at all
(cs != YCC) ||
(cm.getNumComponents() != numComponents)) {
throw new IIOException("Incompatible color conversion");
}
}
break;
default:
// Anything else we can't handle at all
throw new IIOException("Incompatible color conversion");
}
}
/**
* Set the IJG output space to the given value. The library will
* perform the appropriate colorspace conversions.
*/
private native void setOutColorSpace(long structPointer, int id);
/////// End of Color Conversion & Image Types
public ImageReadParam getDefaultReadParam() {
return new JPEGImageReadParam();
}
public IIOMetadata getStreamMetadata() throws IOException {
setThreadLock();
try {
if (!tablesOnlyChecked) {
cbLock.check();
checkTablesOnly();
}
return streamMetadata;
} finally {
clearThreadLock();
}
}
public IIOMetadata getImageMetadata(int imageIndex)
throws IOException {
setThreadLock();
try {
// imageMetadataIndex will always be either a valid index or
// -1, in which case imageMetadata will not be null.
// So we can leave checking imageIndex for gotoImage.
if ((imageMetadataIndex == imageIndex)
&& (imageMetadata != null)) {
return imageMetadata;
}
cbLock.check();
gotoImage(imageIndex);
imageMetadata = new JPEGMetadata(false, false, iis, this);
imageMetadataIndex = imageIndex;
return imageMetadata;
} finally {
clearThreadLock();
}
}
public BufferedImage read(int imageIndex, ImageReadParam param)
throws IOException {
setThreadLock();
try {
cbLock.check();
try {
readInternal(imageIndex, param, false);
} catch (RuntimeException e) {
resetLibraryState(structPointer);
throw e;
} catch (IOException e) {
resetLibraryState(structPointer);
throw e;
}
BufferedImage ret = image;
image = null; // don't keep a reference here
return ret;
} finally {
clearThreadLock();
}
}
private Raster readInternal(int imageIndex,
ImageReadParam param,
boolean wantRaster) throws IOException {
readHeader(imageIndex, false);
WritableRaster imRas = null;
int numImageBands = 0;
if (!wantRaster){
// Can we read this image?
Iterator imageTypes = getImageTypes(imageIndex);
if (imageTypes.hasNext() == false) {
throw new IIOException("Unsupported Image Type");
}
image = getDestination(param, imageTypes, width, height);
imRas = image.getRaster();
// The destination may still be incompatible.
numImageBands = image.getSampleModel().getNumBands();
// Check whether we can handle any implied color conversion
// Throws IIOException if the stream and the image are
// incompatible, and sets convert if a java conversion
// is necessary
checkColorConversion(image, param);
// Check the source and destination bands in the param
checkReadParamBandSettings(param, numComponents, numImageBands);
} else {
// Set the output color space equal to the input colorspace
// This disables all conversions
setOutColorSpace(structPointer, colorSpaceCode);
image = null;
}
// Create an intermediate 1-line Raster that will hold the decoded,
// subsampled, clipped, band-selected image data in a single
// byte-interleaved buffer. The above transformations
// will occur in C for performance. Every time this Raster
// is filled we will call back to acceptPixels below to copy
// this to whatever kind of buffer our image has.
int [] srcBands = JPEG.bandOffsets[numComponents-1];
int numRasterBands = (wantRaster ? numComponents : numImageBands);
destinationBands = null;
Rectangle srcROI = new Rectangle(0, 0, 0, 0);
destROI = new Rectangle(0, 0, 0, 0);
computeRegions(param, width, height, image, srcROI, destROI);
int periodX = 1;
int periodY = 1;
minProgressivePass = 0;
maxProgressivePass = Integer.MAX_VALUE;
if (param != null) {
periodX = param.getSourceXSubsampling();
periodY = param.getSourceYSubsampling();
int[] sBands = param.getSourceBands();
if (sBands != null) {
srcBands = sBands;
numRasterBands = srcBands.length;
}
if (!wantRaster) { // ignore dest bands for Raster
destinationBands = param.getDestinationBands();
}
minProgressivePass = param.getSourceMinProgressivePass();
maxProgressivePass = param.getSourceMaxProgressivePass();
if (param instanceof JPEGImageReadParam) {
JPEGImageReadParam jparam = (JPEGImageReadParam) param;
if (jparam.areTablesSet()) {
abbrevQTables = jparam.getQTables();
abbrevDCHuffmanTables = jparam.getDCHuffmanTables();
abbrevACHuffmanTables = jparam.getACHuffmanTables();
}
}
}
int lineSize = destROI.width*numRasterBands;
buffer = new DataBufferByte(lineSize);
int [] bandOffs = JPEG.bandOffsets[numRasterBands-1];
raster = Raster.createInterleavedRaster(buffer,
destROI.width, 1,
lineSize,
numRasterBands,
bandOffs,
null);
// Now that we have the Raster we'll decode to, get a view of the
// target Raster that will permit a simple setRect for each scanline
if (wantRaster) {
target = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
destROI.width,
destROI.height,
lineSize,
numRasterBands,
bandOffs,
null);
} else {
target = imRas;
}
int [] bandSizes = target.getSampleModel().getSampleSize();
for (int i = 0; i < bandSizes.length; i++) {
if (bandSizes[i] <= 0 || bandSizes[i] > 8) {
throw new IIOException("Illegal band size: should be 0 < size <= 8");
}
}
/*
* If the process is sequential, and we have restart markers,
* we could skip to the correct restart marker, if the library
* lets us. That's an optimization to investigate later.
*/
// Check for update listeners (don't call back if none)
boolean callbackUpdates = ((updateListeners != null)
|| (progressListeners != null));
// Set up progression data
initProgressData();
// if we have a metadata object, we can count the scans
// and set knownPassCount
if (imageIndex == imageMetadataIndex) { // We have metadata
knownPassCount = 0;
for (Iterator iter = imageMetadata.markerSequence.iterator();
iter.hasNext();) {
if (iter.next() instanceof SOSMarkerSegment) {
knownPassCount++;
}
}
}
progInterval = Math.max((target.getHeight()-1) / 20, 1);
if (knownPassCount > 0) {
progInterval *= knownPassCount;
} else if (maxProgressivePass != Integer.MAX_VALUE) {
progInterval *= (maxProgressivePass - minProgressivePass + 1);
}
if (debug) {
System.out.println("**** Read Data *****");
System.out.println("numRasterBands is " + numRasterBands);
System.out.print("srcBands:");
for (int i = 0; itrue
if the read was aborted.
*/
private native boolean readImage(long structPointer,
byte [] buffer,
int numRasterBands,
int [] srcBands,
int [] bandSizes,
int sourceXOffset, int sourceYOffset,
int sourceWidth, int sourceHeight,
int periodX, int periodY,
JPEGQTable [] abbrevQTables,
JPEGHuffmanTable [] abbrevDCHuffmanTables,
JPEGHuffmanTable [] abbrevACHuffmanTables,
int minProgressivePass,
int maxProgressivePass,
boolean wantUpdates);
public void abort() {
setThreadLock();
try {
/**
* NB: we do not check the call back lock here,
* we allow to abort the reader any time.
*/
super.abort();
abortRead(structPointer);
} finally {
clearThreadLock();
}
}
/** Set the C level abort flag. Keep it atomic for thread safety. */
private native void abortRead(long structPointer);
/** Resets library state when an exception occurred during a read. */
private native void resetLibraryState(long structPointer);
public boolean canReadRaster() {
return true;
}
public Raster readRaster(int imageIndex, ImageReadParam param)
throws IOException {
setThreadLock();
Raster retval = null;
try {
cbLock.check();
/*
* This could be further optimized by not resetting the dest.
* offset and creating a translated raster in readInternal()
* (see bug 4994702 for more info).
*/
// For Rasters, destination offset is logical, not physical, so
// set it to 0 before calling computeRegions, so that the destination
// region is not clipped.
Point saveDestOffset = null;
if (param != null) {
saveDestOffset = param.getDestinationOffset();
param.setDestinationOffset(new Point(0, 0));
}
retval = readInternal(imageIndex, param, true);
// Apply the destination offset, if any, as a logical offset
if (saveDestOffset != null) {
target = target.createWritableTranslatedChild(saveDestOffset.x,
saveDestOffset.y);
}
} catch (RuntimeException e) {
resetLibraryState(structPointer);
throw e;
} catch (IOException e) {
resetLibraryState(structPointer);
throw e;
} finally {
clearThreadLock();
}
return retval;
}
public boolean readerSupportsThumbnails() {
return true;
}
public int getNumThumbnails(int imageIndex) throws IOException {
setThreadLock();
try {
cbLock.check();
getImageMetadata(imageIndex); // checks iis state for us
// Now check the jfif segments
JFIFMarkerSegment jfif =
(JFIFMarkerSegment) imageMetadata.findMarkerSegment
(JFIFMarkerSegment.class, true);
int retval = 0;
if (jfif != null) {
retval = (jfif.thumb == null) ? 0 : 1;
retval += jfif.extSegments.size();
}
return retval;
} finally {
clearThreadLock();
}
}
public int getThumbnailWidth(int imageIndex, int thumbnailIndex)
throws IOException {
setThreadLock();
try {
cbLock.check();
if ((thumbnailIndex < 0)
|| (thumbnailIndex >= getNumThumbnails(imageIndex))) {
throw new IndexOutOfBoundsException("No such thumbnail");
}
// Now we know that there is a jfif segment
JFIFMarkerSegment jfif =
(JFIFMarkerSegment) imageMetadata.findMarkerSegment
(JFIFMarkerSegment.class, true);
return jfif.getThumbnailWidth(thumbnailIndex);
} finally {
clearThreadLock();
}
}
public int getThumbnailHeight(int imageIndex, int thumbnailIndex)
throws IOException {
setThreadLock();
try {
cbLock.check();
if ((thumbnailIndex < 0)
|| (thumbnailIndex >= getNumThumbnails(imageIndex))) {
throw new IndexOutOfBoundsException("No such thumbnail");
}
// Now we know that there is a jfif segment
JFIFMarkerSegment jfif =
(JFIFMarkerSegment) imageMetadata.findMarkerSegment
(JFIFMarkerSegment.class, true);
return jfif.getThumbnailHeight(thumbnailIndex);
} finally {
clearThreadLock();
}
}
public BufferedImage readThumbnail(int imageIndex,
int thumbnailIndex)
throws IOException {
setThreadLock();
try {
cbLock.check();
if ((thumbnailIndex < 0)
|| (thumbnailIndex >= getNumThumbnails(imageIndex))) {
throw new IndexOutOfBoundsException("No such thumbnail");
}
// Now we know that there is a jfif segment and that iis is good
JFIFMarkerSegment jfif =
(JFIFMarkerSegment) imageMetadata.findMarkerSegment
(JFIFMarkerSegment.class, true);
return jfif.getThumbnail(iis, thumbnailIndex, this);
} finally {
clearThreadLock();
}
}
private void resetInternalState() {
// reset C structures
resetReader(structPointer);
// reset local Java structures
numImages = 0;
imagePositions = new ArrayList();
currentImage = -1;
image = null;
raster = null;
target = null;
buffer = null;
destROI = null;
destinationBands = null;
streamMetadata = null;
imageMetadata = null;
imageMetadataIndex = -1;
haveSeeked = false;
tablesOnlyChecked = false;
iccCS = null;
initProgressData();
}
public void reset() {
setThreadLock();
try {
cbLock.check();
super.reset();
} finally {
clearThreadLock();
}
}
private native void resetReader(long structPointer);
public void dispose() {
setThreadLock();
try {
cbLock.check();
if (structPointer != 0) {
disposerRecord.dispose();
structPointer = 0;
}
} finally {
clearThreadLock();
}
}
private static native void disposeReader(long structPointer);
private static class JPEGReaderDisposerRecord implements DisposerRecord {
private long pData;
public JPEGReaderDisposerRecord(long pData) {
this.pData = pData;
}
public synchronized void dispose() {
if (pData != 0) {
disposeReader(pData);
pData = 0;
}
}
}
private Thread theThread = null;
private int theLockCount = 0;
private synchronized void setThreadLock() {
Thread currThread = Thread.currentThread();
if (theThread != null) {
if (theThread != currThread) {
// it looks like that this reader instance is used
// by multiple threads.
throw new IllegalStateException("Attempt to use instance of " +
this + " locked on thread " +
theThread + " from thread " +
currThread);
} else {
theLockCount ++;
}
} else {
theThread = currThread;
theLockCount = 1;
}
}
private synchronized void clearThreadLock() {
Thread currThread = Thread.currentThread();
if (theThread == null || theThread != currThread) {
throw new IllegalStateException("Attempt to clear thread lock " +
" form wrong thread." +
" Locked thread: " + theThread +
"; current thread: " + currThread);
}
theLockCount --;
if (theLockCount == 0) {
theThread = null;
}
}
private CallBackLock cbLock = new CallBackLock();
private static class CallBackLock {
private State lockState;
CallBackLock() {
lockState = State.Unlocked;
}
void check() {
if (lockState != State.Unlocked) {
throw new IllegalStateException("Access to the reader is not allowed");
}
}
private void lock() {
lockState = State.Locked;
}
private void unlock() {
lockState = State.Unlocked;
}
private static enum State {
Unlocked,
Locked
}
}
}
/**
* An internal helper class that wraps producer's iterator
* and extracts specifier instances on demand.
*/
class ImageTypeIterator implements Iterator