- /*
- * @(#)JPEGImageReader.java 1.49 04/03/29
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN 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.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 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;
-
- private static final int MAX_WARNING = WARNING_NO_JFIF_IN_THUMB;
-
- /**
- * 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;
-
- /**
- * Maintain an array of the default image types corresponding to the
- * various supported IJG colorspace codes.
- */
- private static final ImageTypeSpecifier [] defaultTypes =
- new ImageTypeSpecifier [JPEG.NUM_JCS_CODES];
-
- static {
- defaultTypes[JPEG.JCS_GRAYSCALE] =
- ImageTypeSpecifier.createFromBufferedImageType
- (BufferedImage.TYPE_BYTE_GRAY);
- defaultTypes[JPEG.JCS_RGB] =
- ImageTypeSpecifier.createInterleaved
- (JPEG.sRGB,
- JPEG.bOffsRGB,
- DataBuffer.TYPE_BYTE,
- false,
- false);
- defaultTypes[JPEG.JCS_RGBA] =
- ImageTypeSpecifier.createPacked
- (JPEG.sRGB,
- 0xff000000,
- 0x00ff0000,
- 0x0000ff00,
- 0x000000ff,
- DataBuffer.TYPE_INT,
- false);
- if (JPEG.YCC != null) {
- defaultTypes[JPEG.JCS_YCC] =
- ImageTypeSpecifier.createInterleaved
- (JPEG.YCC,
- JPEG.bandOffsets[2],
- DataBuffer.TYPE_BYTE,
- false,
- false);
- defaultTypes[JPEG.JCS_YCCA] =
- ImageTypeSpecifier.createInterleaved
- (JPEG.YCC,
- JPEG.bandOffsets[3],
- DataBuffer.TYPE_BYTE,
- true,
- false);
- }
- }
-
- /** 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) {
- if ((code < 0) || (code > MAX_WARNING)){
- throw new InternalError("Invalid warning index");
- }
- processWarningOccurred
- ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
- Integer.toString(code));
- }
-
- /**
- * 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) {
- processWarningOccurred(msg);
- }
-
- public void setInput(Object input,
- boolean seekForwardOnly,
- boolean ignoreMetadata)
- {
- super.setInput(input, seekForwardOnly, ignoreMetadata);
- this.ignoreMetadata = ignoreMetadata;
- resetInternalState();
- iis = (ImageInputStream) input; // Always works
- setSource(structPointer, iis);
- }
-
- private native void setSource(long structPointer,
- ImageInputStream source);
-
- 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 {
- 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.
- * <pre>
- * @exception IllegalStateException if the input source has not been
- * set.
- * @exception IndexOutOfBoundsException if the supplied index is
- * out of bounds.
- * </pre>
- */
- 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 <code>true</code> 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");
- }
- iis.seek(iis.getStreamPosition()-num);
- // The buffer is clear after this, so no need to set haveSeeked.
- }
-
- /**
- * 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 <code>true</code> 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 <code>true</code>, 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;
- iccCS = null;
- if (iccData != null) {
- iccCS = new ICC_ColorSpace(ICC_Profile.getInstance(iccData));
- }
- }
-
- public int getWidth(int imageIndex) throws IOException {
- if (currentImage != imageIndex) {
- readHeader(imageIndex, true);
- }
- return width;
- }
-
- public int getHeight(int imageIndex) throws IOException {
- if (currentImage != imageIndex) {
- readHeader(imageIndex, true);
- }
- return height;
- }
-
- /////////// Color Conversion and Image Types
-
- /**
- * Return an ImageTypeSpecifier corresponding to the given
- * color space code, or null if the color space is unsupported.
- */
- private ImageTypeSpecifier getImageType(int code) {
- ImageTypeSpecifier ret = null;
-
- if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) {
- ret = defaultTypes[code];
- }
- return ret;
- }
-
- public ImageTypeSpecifier getRawImageType(int imageIndex)
- throws IOException {
- if (currentImage != imageIndex) {
- readHeader(imageIndex, true);
- }
- // Returns null if it can't be represented
- return getImageType(colorSpaceCode);
- }
-
- public Iterator getImageTypes(int imageIndex)
- throws IOException {
- if (currentImage != imageIndex) {
- 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.
- ImageTypeSpecifier raw = getImageType(colorSpaceCode);
-
- // Given the encoded colorspace, build a list of ITS's
- // representing outputs you could handle starting
- // with the default.
-
- ArrayList list = new ArrayList(1);
-
- switch (colorSpaceCode) {
- case JPEG.JCS_GRAYSCALE:
- list.add(raw);
- list.add(getImageType(JPEG.JCS_RGB));
- break;
- case JPEG.JCS_RGB:
- list.add(raw);
- list.add(getImageType(JPEG.JCS_GRAYSCALE));
- if (JPEG.YCC != null) {
- list.add(getImageType(JPEG.JCS_YCC));
- }
- break;
- case JPEG.JCS_RGBA:
- list.add(raw);
- break;
- case JPEG.JCS_YCC:
- if (raw != null) { // Might be null if PYCC.pf not installed
- list.add(raw);
- list.add(getImageType(JPEG.JCS_RGB));
- }
- break;
- case JPEG.JCS_YCCA:
- if (raw != null) { // Might be null if PYCC.pf not installed
- list.add(raw);
- }
- break;
- case JPEG.JCS_YCbCr:
- // As there is no YCbCr ColorSpace, we can't support
- // the raw type.
- // If there is an ICC Profile, use that as the default
- if (iccCS != null) {
- list.add(ImageTypeSpecifier.createInterleaved
- (iccCS,
- JPEG.bOffsRGB, // Assume it's for RGB
- DataBuffer.TYPE_BYTE,
- false,
- false));
-
- }
- list.add(getImageType(JPEG.JCS_RGB));
- list.add(getImageType(JPEG.JCS_GRAYSCALE));
- if (JPEG.YCC != null) { // Might be null if PYCC.pf not installed
- list.add(getImageType(JPEG.JCS_YCC));
- }
- break;
- case JPEG.JCS_YCbCrA: // Default is to convert to RGBA
- // As there is no YCbCr ColorSpace, we can't support
- // the raw type.
- list.add(getImageType(JPEG.JCS_RGBA));
- break;
- }
-
- return list.iterator();
- }
-
- /**
- * Checks the implied color conversion between the stream and
- * the target image, altering the IJG output color space if necessary.
- * If a java color conversion is required, then this sets up
- * <code>convert</code>.
- * 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);
- } 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);
- }
- } 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 ((!cs.isCS_sRGB()) &&
- (cm.getNumComponents() == numComponents)) {
- // Target isn't sRGB, so convert from sRGB to the target
- convert = new ColorConvertOp(JPEG.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:
- if (JPEG.YCC == null) { // We can't do YCC at all
- throw new IIOException("Incompatible color conversion");
- }
- if ((cs != JPEG.YCC) &&
- (cm.getNumComponents() == numComponents)) {
- convert = new ColorConvertOp(JPEG.YCC, cs, null);
- }
- break;
- case JPEG.JCS_YCCA:
- // No conversions available; image must be YCCA
- if ((JPEG.YCC == null) || // We can't do YCC at all
- (cs != JPEG.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 {
- if (!tablesOnlyChecked) {
- checkTablesOnly();
- }
- return streamMetadata;
- }
-
- public IIOMetadata getImageMetadata(int imageIndex)
- throws IOException {
-
- // 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;
- }
-
- gotoImage(imageIndex);
-
- imageMetadata = new JPEGMetadata(false, false, iis, this);
-
- imageMetadataIndex = imageIndex;
-
- return imageMetadata;
-
- }
-
- public BufferedImage read(int imageIndex, ImageReadParam param)
- throws IOException {
- 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;
- }
-
- 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();
-
- /*
- * 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; i<srcBands.length;i++)
- System.out.print(" " + srcBands[i]);
- System.out.println();
- System.out.println("destination bands is " + destinationBands);
- if (destinationBands != null) {
- for (int i = 0; i < destinationBands.length; i++) {
- System.out.print(" " + destinationBands[i]);
- }
- System.out.println();
- }
- System.out.println("sourceROI is " + srcROI);
- System.out.println("destROI is " + destROI);
- System.out.println("periodX is " + periodX);
- System.out.println("periodY is " + periodY);
- System.out.println("minProgressivePass is " + minProgressivePass);
- System.out.println("maxProgressivePass is " + maxProgressivePass);
- System.out.println("callbackUpdates is " + callbackUpdates);
- }
-
- // Finally, we are ready to read
-
- processImageStarted(currentImage);
-
- boolean aborted = false;
-
- aborted = readImage(structPointer,
- buffer.getData(),
- numRasterBands,
- srcBands,
- bandSizes,
- srcROI.x, srcROI.y,
- srcROI.width, srcROI.height,
- periodX, periodY,
- abbrevQTables,
- abbrevDCHuffmanTables,
- abbrevACHuffmanTables,
- minProgressivePass, maxProgressivePass,
- callbackUpdates);
-
- if (aborted) {
- processReadAborted();
- } else {
- processImageComplete();
- }
-
- return target;
-
- }
-
- /**
- * This method is called back from C when the intermediate Raster
- * is full. The parameter indicates the scanline in the target
- * Raster to which the intermediate Raster should be copied.
- * After the copy, we notify update listeners.
- */
- private void acceptPixels(int y, boolean progressive) {
- if (convert != null) {
- convert.filter(raster, raster);
- }
- target.setRect(destROI.x, destROI.y + y, raster);
-
- processImageUpdate(image,
- destROI.x, destROI.y+y,
- raster.getWidth(), 1,
- 1, 1,
- destinationBands);
- if ((y > 0) && (y%progInterval == 0)) {
- int height = target.getHeight()-1;
- float percentOfPass = ((float)y)/height;
- if (progressive) {
- if (knownPassCount != UNKNOWN) {
- processImageProgress((pass + percentOfPass)*100.0F
- / knownPassCount);
- } else if (maxProgressivePass != Integer.MAX_VALUE) {
- // Use the range of allowed progressive passes
- processImageProgress((pass + percentOfPass)*100.0F
- / (maxProgressivePass - minProgressivePass + 1));
- } else {
- // Assume there are a minimum of MIN_ESTIMATED_PASSES
- // and that there is always one more pass
- // Compute the percentage as the percentage at the end
- // of the previous pass, plus the percentage of this
- // pass scaled to be the percentage of the total remaining,
- // assuming a minimum of MIN_ESTIMATED_PASSES passes and
- // that there is always one more pass. This is monotonic
- // and asymptotic to 1.0, which is what we need.
- int remainingPasses = // including this one
- Math.max(2, MIN_ESTIMATED_PASSES-pass);
- int totalPasses = pass + remainingPasses-1;
- progInterval = Math.max(height20*totalPasses,
- totalPasses);
- if (y%progInterval == 0) {
- percentToDate = previousPassPercentage +
- (1.0F - previousPassPercentage)
- * (percentOfPass)/remainingPasses;
- if (debug) {
- System.out.print("pass= " + pass);
- System.out.print(", y= " + y);
- System.out.print(", progInt= " + progInterval);
- System.out.print(", % of pass: " + percentOfPass);
- System.out.print(", rem. passes: "
- + remainingPasses);
- System.out.print(", prev%: "
- + previousPassPercentage);
- System.out.print(", %ToDate: " + percentToDate);
- System.out.print(" ");
- }
- processImageProgress(percentToDate*100.0F);
- }
- }
- } else {
- processImageProgress(percentOfPass * 100.0F);
- }
- }
- }
-
- private void initProgressData() {
- knownPassCount = UNKNOWN;
- pass = 0;
- percentToDate = 0.0F;
- previousPassPercentage = 0.0F;
- progInterval = 0;
- }
-
- private void passStarted (int pass) {
- this.pass = pass;
- previousPassPercentage = percentToDate;
- processPassStarted(image,
- pass,
- minProgressivePass,
- maxProgressivePass,
- 0, 0,
- 1,1,
- destinationBands);
- }
-
- private void passComplete () {
- processPassComplete(image);
- }
-
- void thumbnailStarted(int thumbnailIndex) {
- processThumbnailStarted(currentImage, thumbnailIndex);
- }
-
- // Provide access to protected superclass method
- void thumbnailProgress(float percentageDone) {
- processThumbnailProgress(percentageDone);
- }
-
- // Provide access to protected superclass method
- void thumbnailComplete() {
- processThumbnailComplete();
- }
-
- /**
- * Returns <code>true</code> 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() {
- super.abort();
- abortRead(structPointer);
- }
-
- /** 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 {
- Raster retval = null;
- try {
- /*
- * 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;
- }
- return retval;
- }
-
- public boolean readerSupportsThumbnails() {
- return true;
- }
-
- public int getNumThumbnails(int imageIndex) throws IOException {
- 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;
- }
-
- public int getThumbnailWidth(int imageIndex, int thumbnailIndex)
- throws IOException {
- 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);
- }
-
- public int getThumbnailHeight(int imageIndex, int thumbnailIndex)
- throws IOException {
- 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);
- }
-
- public BufferedImage readThumbnail(int imageIndex,
- int thumbnailIndex)
- throws IOException {
- 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);
- }
-
- 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();
- }
-
- /**
- * Note that there is no need to override reset() here, as the default
- * implementation will call setInput(null, false, false), which will
- * invoke resetInternalState().
- */
-
- private native void resetReader(long structPointer);
-
- public void dispose() {
- if (structPointer != 0) {
- disposerRecord.dispose();
- structPointer = 0;
- }
- }
-
- private static native void disposeReader(long structPointer);
-
- private static class JPEGReaderDisposerRecord extends DisposerRecord {
- private long pData;
-
- public JPEGReaderDisposerRecord(long pData) {
- this.pData = pData;
- }
-
- public synchronized void dispose() {
- if (pData != 0) {
- disposeReader(pData);
- pData = 0;
- }
- }
- }
- }