/* * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ 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) { 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) { setThreadLock(); try { super.setInput(input, seekForwardOnly, ignoreMetadata); this.ignoreMetadata = ignoreMetadata; resetInternalState(); iis = (ImageInputStream) input; // Always works setSource(structPointer, iis); } finally { clearThreadLock(); } } 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 { setThreadLock(); try { // locked thread 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. * <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();Lines 1–500 of 1675. Load more
/* * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ 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) { 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) { setThreadLock(); try { super.setInput(input, seekForwardOnly, ignoreMetadata); this.ignoreMetadata = ignoreMetadata; resetInternalState(); iis = (ImageInputStream) input; // Always works setSource(structPointer, iis); } finally { clearThreadLock(); } } 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 { setThreadLock(); try { // locked thread 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. * <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();