FileDocCategorySizeDatePackage
JPEGImageReader.javaAPI DocJava SE 6 API51487Tue Jun 10 00:21:52 BST 2008com.sun.imageio.plugins.jpeg

JPEGImageReader

public class JPEGImageReader extends ImageReader

Fields Summary
private boolean
debug
private long
structPointer
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 ImageInputStream
iis
The input stream we read from
private List
imagePositions
List of stream positions for images, reinitialized every time a new input source is set.
private int
numImages
The number of images in the stream, or 0.
protected static final int
WARNING_NO_EOI
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_JFIF_IN_THUMB
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_IGNORE_INVALID_ICC
Warning code to be passed to warningOccurred to indicate that embedded ICC profile is invalid and will be ignored.
private static final int
MAX_WARNING
private int
currentImage
Image index of image for which header information is available.
private int
width
Set by setImageData native code callback
private int
height
Set by setImageData native code callback
private int
colorSpaceCode
Set by setImageData native code callback. A modified IJG+NIFTY colorspace code.
private int
outColorSpaceCode
Set by setImageData native code callback. A modified IJG+NIFTY colorspace code.
private int
numComponents
Set by setImageData native code callback
private ColorSpace
iccCS
Set by setImageData native code callback
private ColorConvertOp
convert
If we need to post-convert in Java, convert with this op
private BufferedImage
image
The image we are going to fill
private WritableRaster
raster
An intermediate Raster to hold decoded data
private WritableRaster
target
A view of our target Raster that we can setRect to
private DataBufferByte
buffer
The databuffer for the above Raster
private Rectangle
destROI
The region in the destination where we will write pixels
private int[]
destinationBands
The list of destination bands, if any
private JPEGMetadata
streamMetadata
Stream metadata, cached, even when the stream is changed.
private JPEGMetadata
imageMetadata
Image metadata, valid for the imageMetadataIndex only.
private int
imageMetadataIndex
private boolean
haveSeeked
Set to true every time we seek in the stream; used to invalidate the native buffer contents in C.
private JPEGQTable[]
abbrevQTables
Tables that have been read from a tables-only image at the beginning of a stream.
private JPEGHuffmanTable[]
abbrevDCHuffmanTables
private JPEGHuffmanTable[]
abbrevACHuffmanTables
private int
minProgressivePass
private int
maxProgressivePass
private static final int
UNKNOWN
Variables used by progress monitoring.
private static final int
MIN_ESTIMATED_PASSES
private int
knownPassCount
private int
pass
private float
percentToDate
private float
previousPassPercentage
private int
progInterval
private boolean
tablesOnlyChecked
Set to true once stream has been checked for stream metadata
private Object
disposerReferent
The referent to be registered with the Disposer.
private DisposerRecord
disposerRecord
The DisposerRecord that handles the actual disposal of this reader.
private static final ImageTypeSpecifier[]
defaultTypes
Maintain an array of the default image types corresponding to the various supported IJG colorspace codes.
Constructors Summary
public JPEGImageReader(ImageReaderSpi originator)


     
        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);
        }
    
        super(originator);
        structPointer = initJPEGImageReader();
        disposerRecord = new JPEGReaderDisposerRecord(structPointer);
        Disposer.addRecord(disposerReferent, disposerRecord);
    
Methods Summary
public voidabort()

        super.abort();
        abortRead(structPointer);
    
private native voidabortRead(long structPointer)
Set the C level abort flag. Keep it atomic for thread safety.

private voidacceptPixels(int y, boolean progressive)
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.

        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(height/20*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);
            }
        }
    
public booleancanReadRaster()

        return true;
    
private voidcheckColorConversion(java.awt.image.BufferedImage image, javax.imageio.ImageReadParam param)
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 convert. 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.


        // 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 ((iccCS == null) &&
                       (!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");
        }
    
private voidcheckTablesOnly()

        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 voiddispose()

        if (structPointer != 0) {
            disposerRecord.dispose();
            structPointer = 0;
        }
    
private static native voiddisposeReader(long structPointer)

public javax.imageio.ImageReadParamgetDefaultReadParam()

        return new JPEGImageReadParam();
    
public intgetHeight(int imageIndex)

        if (currentImage != imageIndex) {
            readHeader(imageIndex, true);
        }
        return height;
    
public javax.imageio.metadata.IIOMetadatagetImageMetadata(int imageIndex)


        // 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;
        
    
private javax.imageio.ImageTypeSpecifiergetImageType(int code)
Return an ImageTypeSpecifier corresponding to the given color space code, or null if the color space is unsupported.

        ImageTypeSpecifier ret = null;

        if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) {
            ret = defaultTypes[code];
        }
        return ret;
    
public java.util.IteratorgetImageTypes(int imageIndex)

        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.

            // due to 4705399, use RGB as default in order to avoid
            // slowing down of drawing operations with result image.
            list.add(getImageType(JPEG.JCS_RGB));

            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_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();
    
public intgetNumImages(boolean allowSearch)

        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
    
public intgetNumThumbnails(int imageIndex)

        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 javax.imageio.ImageTypeSpecifiergetRawImageType(int imageIndex)

        if (currentImage != imageIndex) {
            readHeader(imageIndex, true);
        }
        // Returns null if it can't be represented
        return getImageType(colorSpaceCode);
    
public javax.imageio.metadata.IIOMetadatagetStreamMetadata()

        if (!tablesOnlyChecked) {
            checkTablesOnly();
        }
        return streamMetadata;
    
public intgetThumbnailHeight(int imageIndex, int thumbnailIndex)

        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 intgetThumbnailWidth(int imageIndex, int thumbnailIndex)

        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 intgetWidth(int imageIndex)

        if (currentImage != imageIndex) {
            readHeader(imageIndex, true);
        }
        return width;
    
private voidgotoImage(int imageIndex)
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.

 
        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
    
private booleanhasNextImage()
Returns true if there is an image beyond the current stream position. Does not disturb the stream position.

        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;
    
private native longinitJPEGImageReader()
Sets up per-reader C structure and returns a pointer to it.

private voidinitProgressData()

        knownPassCount = UNKNOWN;
        pass = 0;
        percentToDate = 0.0F;
        previousPassPercentage = 0.0F;
        progInterval = 0;
    
private static native voidinitReaderIDs(java.lang.Class iisClass, java.lang.Class qTableClass, java.lang.Class huffClass)
Sets up static C structures.

private voidpassComplete()

        processPassComplete(image);
    
private voidpassStarted(int pass)

        this.pass = pass;
        previousPassPercentage = percentToDate;
        processPassStarted(image, 
                           pass,
                           minProgressivePass,
                           maxProgressivePass,
                           0, 0,
                           1,1,
                           destinationBands);
    
private voidpushBack(int num)
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.

        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.
    
public java.awt.image.BufferedImageread(int imageIndex, javax.imageio.ImageReadParam param)

        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 voidreadHeader(int imageIndex, boolean reset)
Reads header information for the given image, if possible.

 
        gotoImage(imageIndex);
        readNativeHeader(reset); // Ignore return
        currentImage = imageIndex;
    
private native booleanreadImage(long structPointer, byte[] buffer, int numRasterBands, int[] srcBands, int[] bandSizes, int sourceXOffset, int sourceYOffset, int sourceWidth, int sourceHeight, int periodX, int periodY, javax.imageio.plugins.jpeg.JPEGQTable[] abbrevQTables, javax.imageio.plugins.jpeg.JPEGHuffmanTable[] abbrevDCHuffmanTables, javax.imageio.plugins.jpeg.JPEGHuffmanTable[] abbrevACHuffmanTables, int minProgressivePass, int maxProgressivePass, boolean wantUpdates)
Returns true if the read was aborted.

private native booleanreadImageHeader(long structPointer, boolean clearBuffer, boolean reset)
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 java.awt.image.RasterreadInternal(int imageIndex, javax.imageio.ImageReadParam param, boolean wantRaster)

        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;

    
private booleanreadNativeHeader(boolean reset)

        boolean retval = false;
        retval = readImageHeader(structPointer, haveSeeked, reset);
        haveSeeked = false;
        return retval;
    
public java.awt.image.RasterreadRaster(int imageIndex, javax.imageio.ImageReadParam param)

        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 java.awt.image.BufferedImagereadThumbnail(int imageIndex, int thumbnailIndex)

        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);
    
public booleanreaderSupportsThumbnails()

        return true;
    
private voidresetInternalState()

        // 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();
    
private native voidresetLibraryState(long structPointer)
Resets library state when an exception occurred during a read.

private native voidresetReader(long structPointer)
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 voidsetImageData(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);               
        }       
    
public voidsetInput(java.lang.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 voidsetOutColorSpace(long structPointer, int id)
Set the IJG output space to the given value. The library will perform the appropriate colorspace conversions.

private native voidsetSource(long structPointer, javax.imageio.stream.ImageInputStream source)

private voidskipImage()
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.

        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();
    
voidthumbnailComplete()

        processThumbnailComplete();
    
voidthumbnailProgress(float percentageDone)

        processThumbnailProgress(percentageDone);
    
voidthumbnailStarted(int thumbnailIndex)

        processThumbnailStarted(currentImage, thumbnailIndex);
    
protected voidwarningOccurred(int code)
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.

        if ((code < 0) || (code > MAX_WARNING)){
            throw new InternalError("Invalid warning index");
        }
        processWarningOccurred
            ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
             Integer.toString(code));
    
protected voidwarningWithMessage(java.lang.String msg)
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.

        processWarningOccurred(msg);