src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java
changeset 47216 71c04702a3d5
parent 45973 3daf29464c02
child 48643 2ea3667af41d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,1958 @@
+/*
+ * Copyright (c) 2000, 2016, 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<Long> imagePositions = null;
+
+    /**
+     * The number of images in the stream, or 0.
+     */
+    private int numImages = 0;
+
+    static {
+        java.security.AccessController.doPrivileged(
+            new java.security.PrivilegedAction<Void>() {
+                public Void run() {
+                    System.loadLibrary("javajpeg");
+                    return null;
+                }
+            });
+        initReaderIDs(ImageInputStream.class,
+                      JPEGQTable.class,
+                      JPEGHuffmanTable.class);
+    }
+
+    // The following warnings are converted to strings when used
+    // as keys to get localized resources from JPEGImageReaderResources
+    // and its children.
+
+    /**
+     * Warning code to be passed to warningOccurred to indicate
+     * that the EOI marker is missing from the end of the stream.
+     * This usually signals that the stream is corrupted, but
+     * everything up to the last MCU should be usable.
+     */
+    protected static final int WARNING_NO_EOI = 0;
+
+    /**
+     * Warning code to be passed to warningOccurred to indicate
+     * that a JFIF segment was encountered inside a JFXX JPEG
+     * thumbnail and is being ignored.
+     */
+    protected static final int WARNING_NO_JFIF_IN_THUMB = 1;
+
+    /**
+     * Warning code to be passed to warningOccurred to indicate
+     * that embedded ICC profile is invalid and will be ignored.
+     */
+    protected static final int WARNING_IGNORE_INVALID_ICC = 2;
+
+    private static final int MAX_WARNING = WARNING_IGNORE_INVALID_ICC;
+
+    /**
+     * Image index of image for which header information
+     * is available.
+     */
+    private int currentImage = -1;
+
+    // The following is copied out from C after reading the header.
+    // Unlike metadata, which may never be retrieved, we need this
+    // if we are to read an image at all.
+
+    /** Set by setImageData native code callback */
+    private int width;
+    /** Set by setImageData native code callback */
+    private int height;
+    /**
+     * Set by setImageData native code callback.  A modified
+     * IJG+NIFTY colorspace code.
+     */
+    private int colorSpaceCode;
+    /**
+     * Set by setImageData native code callback.  A modified
+     * IJG+NIFTY colorspace code.
+     */
+    private int outColorSpaceCode;
+    /** Set by setImageData native code callback */
+    private int numComponents;
+    /** Set by setImageData native code callback */
+    private ColorSpace iccCS = null;
+
+
+    /** If we need to post-convert in Java, convert with this op */
+    private ColorConvertOp convert = null;
+
+    /** The image we are going to fill */
+    private BufferedImage image = null;
+
+    /** An intermediate Raster to hold decoded data */
+    private WritableRaster raster = null;
+
+    /** A view of our target Raster that we can setRect to */
+    private WritableRaster target = null;
+
+    /** The databuffer for the above Raster */
+    private DataBufferByte buffer = null;
+
+    /** The region in the destination where we will write pixels */
+    private Rectangle destROI = null;
+
+    /** The list of destination bands, if any */
+    private int [] destinationBands = null;
+
+    /** Stream metadata, cached, even when the stream is changed. */
+    private JPEGMetadata streamMetadata = null;
+
+    /** Image metadata, valid for the imageMetadataIndex only. */
+    private JPEGMetadata imageMetadata = null;
+    private int imageMetadataIndex = -1;
+
+    /**
+     * Set to true every time we seek in the stream; used to
+     * invalidate the native buffer contents in C.
+     */
+    private boolean haveSeeked = false;
+
+    /**
+     * Tables that have been read from a tables-only image at the
+     * beginning of a stream.
+     */
+    private JPEGQTable [] abbrevQTables = null;
+    private JPEGHuffmanTable[] abbrevDCHuffmanTables = null;
+    private JPEGHuffmanTable[] abbrevACHuffmanTables = null;
+
+    private int minProgressivePass = 0;
+    private int maxProgressivePass = Integer.MAX_VALUE;
+
+    /**
+     * Variables used by progress monitoring.
+     */
+    private static final int UNKNOWN = -1;  // Number of passes
+    private static final int MIN_ESTIMATED_PASSES = 10; // IJG default
+    private int knownPassCount = UNKNOWN;
+    private int pass = 0;
+    private float percentToDate = 0.0F;
+    private float previousPassPercentage = 0.0F;
+    private int progInterval = 0;
+
+    /**
+     * Set to true once stream has been checked for stream metadata
+     */
+    private boolean tablesOnlyChecked = false;
+
+    /** The referent to be registered with the Disposer. */
+    private Object disposerReferent = new Object();
+
+    /** The DisposerRecord that handles the actual disposal of this reader. */
+    private DisposerRecord disposerRecord;
+
+    /** Sets up static C structures. */
+    private static native void initReaderIDs(Class<?> iisClass,
+                                             Class<?> qTableClass,
+                                             Class<?> huffClass);
+
+    public JPEGImageReader(ImageReaderSpi originator) {
+        super(originator);
+        structPointer = initJPEGImageReader();
+        disposerRecord = new JPEGReaderDisposerRecord(structPointer);
+        Disposer.addRecord(disposerReferent, disposerRecord);
+    }
+
+    /** Sets up per-reader C structure and returns a pointer to it. */
+    private native long initJPEGImageReader();
+
+    /**
+     * Called by the native code or other classes to signal a warning.
+     * The code is used to lookup a localized message to be used when
+     * sending warnings to listeners.
+     */
+    protected void warningOccurred(int code) {
+        cbLock.lock();
+        try {
+            if ((code < 0) || (code > MAX_WARNING)){
+                throw new InternalError("Invalid warning index");
+            }
+            processWarningOccurred
+                ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
+                 Integer.toString(code));
+        } finally {
+            cbLock.unlock();
+        }
+    }
+
+    /**
+     * The library has it's own error facility that emits warning messages.
+     * This routine is called by the native code when it has already
+     * formatted a string for output.
+     * XXX  For truly complete localization of all warning messages,
+     * the sun_jpeg_output_message routine in the native code should
+     * send only the codes and parameters to a method here in Java,
+     * which will then format and send the warnings, using localized
+     * strings.  This method will have to deal with all the parameters
+     * and formats (%u with possibly large numbers, %02d, %02x, etc.)
+     * that actually occur in the JPEG library.  For now, this prevents
+     * library warnings from being printed to stderr.
+     */
+    protected void warningWithMessage(String msg) {
+        cbLock.lock();
+        try {
+            processWarningOccurred(msg);
+        } finally {
+            cbLock.unlock();
+        }
+    }
+
+    public void setInput(Object input,
+                         boolean seekForwardOnly,
+                         boolean ignoreMetadata)
+    {
+        setThreadLock();
+        try {
+            cbLock.check();
+
+            super.setInput(input, seekForwardOnly, ignoreMetadata);
+            this.ignoreMetadata = ignoreMetadata;
+            resetInternalState();
+            iis = (ImageInputStream) input; // Always works
+            setSource(structPointer);
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    /**
+     * This method is called from native code in order to fill
+     * native input buffer.
+     *
+     * We block any attempt to change the reading state during this
+     * method, in order to prevent a corruption of the native decoder
+     * state.
+     *
+     * @return number of bytes read from the stream.
+     */
+    private int readInputData(byte[] buf, int off, int len) throws IOException {
+        cbLock.lock();
+        try {
+            return iis.read(buf, off, len);
+        } finally {
+            cbLock.unlock();
+        }
+    }
+
+    /**
+     * This method is called from the native code in order to
+     * skip requested number of bytes in the input stream.
+     *
+     * @param n
+     * @return
+     * @throws IOException
+     */
+    private long skipInputBytes(long n) throws IOException {
+        cbLock.lock();
+        try {
+            return iis.skipBytes(n);
+        } finally {
+            cbLock.unlock();
+        }
+    }
+
+    private native void setSource(long structPointer);
+
+    private void checkTablesOnly() throws IOException {
+        if (debug) {
+            System.out.println("Checking for tables-only image");
+        }
+        long savePos = iis.getStreamPosition();
+        if (debug) {
+            System.out.println("saved pos is " + savePos);
+            System.out.println("length is " + iis.length());
+        }
+        // Read the first header
+        boolean tablesOnly = readNativeHeader(true);
+        if (tablesOnly) {
+            if (debug) {
+                System.out.println("tables-only image found");
+                long pos = iis.getStreamPosition();
+                System.out.println("pos after return from native is " + pos);
+            }
+            // This reads the tables-only image twice, once from C
+            // and once from Java, but only if ignoreMetadata is false
+            if (ignoreMetadata == false) {
+                iis.seek(savePos);
+                haveSeeked = true;
+                streamMetadata = new JPEGMetadata(true, false,
+                                                  iis, this);
+                long pos = iis.getStreamPosition();
+                if (debug) {
+                    System.out.println
+                        ("pos after constructing stream metadata is " + pos);
+                }
+            }
+            // Now we are at the first image if there are any, so add it
+            // to the list
+            if (hasNextImage()) {
+                imagePositions.add(iis.getStreamPosition());
+            }
+        } else { // Not tables only, so add original pos to the list
+            imagePositions.add(savePos);
+            // And set current image since we've read it now
+            currentImage = 0;
+        }
+        if (seekForwardOnly) {
+            Long pos = imagePositions.get(imagePositions.size()-1);
+            iis.flushBefore(pos.longValue());
+        }
+        tablesOnlyChecked = true;
+    }
+
+    public int getNumImages(boolean allowSearch) throws IOException {
+        setThreadLock();
+        try { // locked thread
+            cbLock.check();
+
+            return getNumImagesOnThread(allowSearch);
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    private void skipPastImage(int imageIndex) {
+        cbLock.lock();
+        try {
+            gotoImage(imageIndex);
+            skipImage();
+        } catch (IOException | IndexOutOfBoundsException e) {
+        } finally {
+            cbLock.unlock();
+        }
+    }
+
+    @SuppressWarnings("fallthrough")
+    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(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 = 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 = 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.
+     * We can find data same as EOI marker in some headers
+     * or comments, so we have to skip bytes related to these headers.
+     * 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");
+        }
+        // verify if image starts with an SOI marker
+        int initialFF = iis.read();
+        if (initialFF == 0xff) {
+            int soiMarker = iis.read();
+            if (soiMarker != JPEG.SOI) {
+                throw new IOException("skipImage : Invalid image doesn't "
+                        + "start with SOI marker");
+            }
+        } else {
+            throw new IOException("skipImage : Invalid image doesn't start "
+                    + "with 0xff");
+        }
+        boolean foundFF = false;
+        String IOOBE = "skipImage : Reached EOF before we got EOI marker";
+        int markerLength = 2;
+        for (int byteval = iis.read();
+             byteval != -1;
+             byteval = iis.read()) {
+
+            if (foundFF == true) {
+                switch (byteval) {
+                    case JPEG.EOI:
+                        if (debug) {
+                            System.out.println("skipImage : Found EOI at " +
+                                    (iis.getStreamPosition() - markerLength));
+                        }
+                        return;
+                    case JPEG.SOI:
+                        throw new IOException("skipImage : Found extra SOI"
+                                + " marker before getting to EOI");
+                    case 0:
+                    case 255:
+                    // markers which doesn't contain length data
+                    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.TEM:
+                        break;
+                    // markers which contains length data
+                    case JPEG.SOF0:
+                    case JPEG.SOF1:
+                    case JPEG.SOF2:
+                    case JPEG.SOF3:
+                    case JPEG.DHT:
+                    case JPEG.SOF5:
+                    case JPEG.SOF6:
+                    case JPEG.SOF7:
+                    case JPEG.JPG:
+                    case JPEG.SOF9:
+                    case JPEG.SOF10:
+                    case JPEG.SOF11:
+                    case JPEG.DAC:
+                    case JPEG.SOF13:
+                    case JPEG.SOF14:
+                    case JPEG.SOF15:
+                    case JPEG.SOS:
+                    case JPEG.DQT:
+                    case JPEG.DNL:
+                    case JPEG.DRI:
+                    case JPEG.DHP:
+                    case JPEG.EXP:
+                    case JPEG.APP0:
+                    case JPEG.APP1:
+                    case JPEG.APP2:
+                    case JPEG.APP3:
+                    case JPEG.APP4:
+                    case JPEG.APP5:
+                    case JPEG.APP6:
+                    case JPEG.APP7:
+                    case JPEG.APP8:
+                    case JPEG.APP9:
+                    case JPEG.APP10:
+                    case JPEG.APP11:
+                    case JPEG.APP12:
+                    case JPEG.APP13:
+                    case JPEG.APP14:
+                    case JPEG.APP15:
+                    case JPEG.COM:
+                        // read length of header from next 2 bytes
+                        int lengthHigherBits, lengthLowerBits, length;
+                        lengthHigherBits = iis.read();
+                        if (lengthHigherBits != (-1)) {
+                            lengthLowerBits = iis.read();
+                            if (lengthLowerBits != (-1)) {
+                                length = (lengthHigherBits << 8) |
+                                        lengthLowerBits;
+                                // length contains already read 2 bytes
+                                length -= 2;
+                            } else {
+                                throw new IndexOutOfBoundsException(IOOBE);
+                            }
+                        } else {
+                            throw new IndexOutOfBoundsException(IOOBE);
+                        }
+                        // skip the length specified in marker
+                        iis.skipBytes(length);
+                        break;
+                    case (-1):
+                        throw new IndexOutOfBoundsException(IOOBE);
+                    default:
+                        throw new IOException("skipImage : Invalid marker "
+                                + "starting with ff "
+                                + Integer.toHexString(byteval));
+                }
+            }
+            foundFF = (byteval == 0xff);
+        }
+        throw new IndexOutOfBoundsException(IOOBE);
+    }
+
+    /**
+     * Returns {@code true} if there is an image beyond
+     * the current stream position.  Does not disturb the
+     * stream position.
+     */
+    private boolean hasNextImage() throws IOException {
+        if (debug) {
+            System.out.print("hasNextImage called; returning ");
+        }
+        iis.mark();
+        boolean foundFF = false;
+        for (int byteval = iis.read();
+             byteval != -1;
+             byteval = iis.read()) {
+
+            if (foundFF == true) {
+                if (byteval == JPEG.SOI) {
+                    iis.reset();
+                    if (debug) {
+                        System.out.println("true");
+                    }
+                    return true;
+                }
+            }
+            foundFF = (byteval == 0xff) ? true : false;
+        }
+        // We hit the end of the stream before we hit an SOI, so no image
+        iis.reset();
+        if (debug) {
+            System.out.println("false");
+        }
+        return false;
+    }
+
+    /**
+     * Push back the given number of bytes to the input stream.
+     * Called by the native code at the end of each image so
+     * that the next one can be identified from Java.
+     */
+    private void pushBack(int num) throws IOException {
+        if (debug) {
+            System.out.println("pushing back " + num + " bytes");
+        }
+        cbLock.lock();
+        try {
+            iis.seek(iis.getStreamPosition()-num);
+            // The buffer is clear after this, so no need to set haveSeeked.
+        } finally {
+            cbLock.unlock();
+        }
+    }
+
+    /**
+     * Reads header information for the given image, if possible.
+     */
+    private void readHeader(int imageIndex, boolean reset)
+        throws IOException {
+        gotoImage(imageIndex);
+        readNativeHeader(reset); // Ignore return
+        currentImage = imageIndex;
+    }
+
+    private boolean readNativeHeader(boolean reset) throws IOException {
+        boolean retval = false;
+        retval = readImageHeader(structPointer, haveSeeked, reset);
+        haveSeeked = false;
+        return retval;
+    }
+
+    /**
+     * Read in the header information starting from the current
+     * stream position, returning {@code 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 {@code true}, the state of the IJG
+     * object is reset so that it can read a header again.
+     * This happens automatically if the header was a tables-only
+     * image.
+     */
+    private native boolean readImageHeader(long structPointer,
+                                           boolean clearBuffer,
+                                           boolean reset)
+        throws IOException;
+
+    /*
+     * Called by the native code whenever an image header has been
+     * read.  Whether we read metadata or not, we always need this
+     * information, so it is passed back independently of
+     * metadata, which may never be read.
+     */
+    private void setImageData(int width,
+                              int height,
+                              int colorSpaceCode,
+                              int outColorSpaceCode,
+                              int numComponents,
+                              byte [] iccData) {
+        this.width = width;
+        this.height = height;
+        this.colorSpaceCode = colorSpaceCode;
+        this.outColorSpaceCode = outColorSpaceCode;
+        this.numComponents = numComponents;
+
+        if (iccData == null) {
+            iccCS = null;
+            return;
+        }
+
+        ICC_Profile newProfile = null;
+        try {
+            newProfile = ICC_Profile.getInstance(iccData);
+        } catch (IllegalArgumentException e) {
+            /*
+             * Color profile data seems to be invalid.
+             * Ignore this profile.
+             */
+            iccCS = null;
+            warningOccurred(WARNING_IGNORE_INVALID_ICC);
+
+            return;
+        }
+        byte[] newData = newProfile.getData();
+
+        ICC_Profile oldProfile = null;
+        if (iccCS instanceof ICC_ColorSpace) {
+            oldProfile = ((ICC_ColorSpace)iccCS).getProfile();
+        }
+        byte[] oldData = null;
+        if (oldProfile != null) {
+            oldData = oldProfile.getData();
+        }
+
+        /*
+         * At the moment we can't rely on the ColorSpace.equals()
+         * and ICC_Profile.equals() because they do not detect
+         * the case when two profiles are created from same data.
+         *
+         * So, we have to do data comparison in order to avoid
+         * creation of different ColorSpace instances for the same
+         * embedded data.
+         */
+        if (oldData == null ||
+            !java.util.Arrays.equals(oldData, newData))
+        {
+            iccCS = new ICC_ColorSpace(newProfile);
+            // verify new color space
+            try {
+                float[] colors = iccCS.fromRGB(new float[] {1f, 0f, 0f});
+            } catch (CMMException e) {
+                /*
+                 * Embedded profile seems to be corrupted.
+                 * Ignore this profile.
+                 */
+                iccCS = null;
+                cbLock.lock();
+                try {
+                    warningOccurred(WARNING_IGNORE_INVALID_ICC);
+                } finally {
+                    cbLock.unlock();
+                }
+            }
+        }
+    }
+
+    public int getWidth(int imageIndex) throws IOException {
+        setThreadLock();
+        try {
+            if (currentImage != imageIndex) {
+                cbLock.check();
+                readHeader(imageIndex, true);
+            }
+            return width;
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    public int getHeight(int imageIndex) throws IOException {
+        setThreadLock();
+        try {
+            if (currentImage != imageIndex) {
+                cbLock.check();
+                readHeader(imageIndex, true);
+            }
+            return height;
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    /////////// Color Conversion and Image Types
+
+    /**
+     * Return an ImageTypeSpecifier corresponding to the given
+     * color space code, or null if the color space is unsupported.
+     */
+    private ImageTypeProducer getImageType(int code) {
+        ImageTypeProducer ret = null;
+
+        if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) {
+            ret = ImageTypeProducer.getTypeProducer(code);
+        }
+        return ret;
+    }
+
+    public ImageTypeSpecifier getRawImageType(int imageIndex)
+        throws IOException {
+        setThreadLock();
+        try {
+            if (currentImage != imageIndex) {
+                cbLock.check();
+
+                readHeader(imageIndex, true);
+            }
+
+            // Returns null if it can't be represented
+            return getImageType(colorSpaceCode).getType();
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex)
+        throws IOException {
+        setThreadLock();
+        try {
+            return getImageTypesOnThread(imageIndex);
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    private Iterator<ImageTypeSpecifier> getImageTypesOnThread(int imageIndex)
+        throws IOException {
+        if (currentImage != imageIndex) {
+            cbLock.check();
+            readHeader(imageIndex, true);
+        }
+
+        // We return an iterator containing the default, any
+        // conversions that the library provides, and
+        // all the other default types with the same number
+        // of components, as we can do these as a post-process.
+        // As we convert Rasters rather than images, images
+        // with alpha cannot be converted in a post-process.
+
+        // If this image can't be interpreted, this method
+        // returns an empty Iterator.
+
+        // Get the raw ITS, if there is one.  Note that this
+        // won't always be the same as the default.
+        ImageTypeProducer raw = getImageType(colorSpaceCode);
+
+        // Given the encoded colorspace, build a list of ITS's
+        // representing outputs you could handle starting
+        // with the default.
+
+        ArrayList<ImageTypeProducer> list = new ArrayList<ImageTypeProducer>(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));
+            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(new ImageTypeProducer() {
+                    protected ImageTypeSpecifier produce() {
+                        return ImageTypeSpecifier.createInterleaved
+                         (iccCS,
+                          JPEG.bOffsRGB,  // Assume it's for RGB
+                          DataBuffer.TYPE_BYTE,
+                          false,
+                          false);
+                    }
+                });
+
+            }
+
+            list.add(getImageType(JPEG.JCS_GRAYSCALE));
+            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 new ImageTypeIterator(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}.
+     * If bands are being rearranged at all (either source or destination
+     * bands are specified in the param), then the default color
+     * conversions are assumed to be correct.
+     * Throws an IIOException if there is no conversion available.
+     */
+    private void checkColorConversion(BufferedImage image,
+                                      ImageReadParam param)
+        throws IIOException {
+
+        // If we are rearranging channels at all, the default
+        // conversions remain in place.  If the user wants
+        // raw channels then he should do this while reading
+        // a Raster.
+        if (param != null) {
+            if ((param.getSourceBands() != null) ||
+                (param.getDestinationBands() != null)) {
+                // Accept default conversions out of decoder, silently
+                return;
+            }
+        }
+
+        // XXX - We do not currently support any indexed color models,
+        // though we could, as IJG will quantize for us.
+        // This is a performance and memory-use issue, as
+        // users can read RGB and then convert to indexed in Java.
+
+        ColorModel cm = image.getColorModel();
+
+        if (cm instanceof IndexColorModel) {
+            throw new IIOException("IndexColorModel not supported");
+        }
+
+        // Now check the ColorSpace type against outColorSpaceCode
+        // We may want to tweak the default
+        ColorSpace cs = cm.getColorSpace();
+        int csType = cs.getType();
+        convert = null;
+        switch (outColorSpaceCode) {
+        case JPEG.JCS_GRAYSCALE:  // Its gray in the file
+            if  (csType == ColorSpace.TYPE_RGB) { // We want RGB
+                // IJG can do this for us more efficiently
+                setOutColorSpace(structPointer, JPEG.JCS_RGB);
+                // Update java state according to changes
+                // in the native part of decoder.
+                outColorSpaceCode = JPEG.JCS_RGB;
+                numComponents = 3;
+            } else if (csType != ColorSpace.TYPE_GRAY) {
+                throw new IIOException("Incompatible color conversion");
+            }
+            break;
+        case JPEG.JCS_RGB:  // IJG wants to go to RGB
+            if (csType ==  ColorSpace.TYPE_GRAY) {  // We want gray
+                if (colorSpaceCode == JPEG.JCS_YCbCr) {
+                    // If the jpeg space is YCbCr, IJG can do it
+                    setOutColorSpace(structPointer, JPEG.JCS_GRAYSCALE);
+                    // Update java state according to changes
+                    // in the native part of decoder.
+                    outColorSpaceCode = JPEG.JCS_GRAYSCALE;
+                    numComponents = 1;
+                }
+            } else if ((iccCS != null) &&
+                       (cm.getNumComponents() == numComponents) &&
+                       (cs != iccCS)) {
+                // We have an ICC profile but it isn't used in the dest
+                // image.  So convert from the profile cs to the target cs
+                convert = new ColorConvertOp(iccCS, cs, null);
+                // Leave IJG conversion in place; we still need it
+            } else if ((iccCS == null) &&
+                       (!cs.isCS_sRGB()) &&
+                       (cm.getNumComponents() == numComponents)) {
+                // Target isn't sRGB, so convert from sRGB to the target
+                convert = new ColorConvertOp(JPEG.JCS.sRGB, cs, null);
+            } else if (csType != ColorSpace.TYPE_RGB) {
+                throw new IIOException("Incompatible color conversion");
+            }
+            break;
+        case JPEG.JCS_RGBA:
+            // No conversions available; image must be RGBA
+            if ((csType != ColorSpace.TYPE_RGB) ||
+                (cm.getNumComponents() != numComponents)) {
+                throw new IIOException("Incompatible color conversion");
+            }
+            break;
+        case JPEG.JCS_YCC:
+            {
+                ColorSpace YCC = JPEG.JCS.getYCC();
+                if (YCC == null) { // We can't do YCC at all
+                    throw new IIOException("Incompatible color conversion");
+                }
+                if ((cs != YCC) &&
+                    (cm.getNumComponents() == numComponents)) {
+                    convert = new ColorConvertOp(YCC, cs, null);
+                }
+            }
+            break;
+        case JPEG.JCS_YCCA:
+            {
+                ColorSpace YCC = JPEG.JCS.getYCC();
+                // No conversions available; image must be YCCA
+                if ((YCC == null) || // We can't do YCC at all
+                    (cs != YCC) ||
+                    (cm.getNumComponents() != numComponents)) {
+                    throw new IIOException("Incompatible color conversion");
+                }
+            }
+            break;
+        default:
+            // Anything else we can't handle at all
+            throw new IIOException("Incompatible color conversion");
+        }
+    }
+
+    /**
+     * Set the IJG output space to the given value.  The library will
+     * perform the appropriate colorspace conversions.
+     */
+    private native void setOutColorSpace(long structPointer, int id);
+
+    /////// End of Color Conversion & Image Types
+
+    public ImageReadParam getDefaultReadParam() {
+        return new JPEGImageReadParam();
+    }
+
+    public IIOMetadata getStreamMetadata() throws IOException {
+        setThreadLock();
+        try {
+            if (!tablesOnlyChecked) {
+                cbLock.check();
+                checkTablesOnly();
+            }
+            return streamMetadata;
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    public IIOMetadata getImageMetadata(int imageIndex)
+        throws IOException {
+        setThreadLock();
+        try {
+            // imageMetadataIndex will always be either a valid index or
+            // -1, in which case imageMetadata will not be null.
+            // So we can leave checking imageIndex for gotoImage.
+            if ((imageMetadataIndex == imageIndex)
+                && (imageMetadata != null)) {
+                return imageMetadata;
+            }
+
+            cbLock.check();
+
+            gotoImage(imageIndex);
+
+            imageMetadata = new JPEGMetadata(false, false, iis, this);
+
+            imageMetadataIndex = imageIndex;
+
+            return imageMetadata;
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    public BufferedImage read(int imageIndex, ImageReadParam param)
+        throws IOException {
+        setThreadLock();
+        try {
+            cbLock.check();
+            try {
+                readInternal(imageIndex, param, false);
+            } catch (RuntimeException e) {
+                resetLibraryState(structPointer);
+                throw e;
+            } catch (IOException e) {
+                resetLibraryState(structPointer);
+                throw e;
+            }
+
+            BufferedImage ret = image;
+            image = null;  // don't keep a reference here
+            return ret;
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    private Raster readInternal(int imageIndex,
+                                ImageReadParam param,
+                                boolean wantRaster) throws IOException {
+        readHeader(imageIndex, false);
+
+        WritableRaster imRas = null;
+        int numImageBands = 0;
+
+        if (!wantRaster){
+            // Can we read this image?
+            Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
+            if (imageTypes.hasNext() == false) {
+                throw new IIOException("Unsupported Image Type");
+            }
+
+            image = getDestination(param, imageTypes, width, height);
+            imRas = image.getRaster();
+
+            // The destination may still be incompatible.
+
+            numImageBands = image.getSampleModel().getNumBands();
+
+            // Check whether we can handle any implied color conversion
+
+            // Throws IIOException if the stream and the image are
+            // incompatible, and sets convert if a java conversion
+            // is necessary
+            checkColorConversion(image, param);
+
+            // Check the source and destination bands in the param
+            checkReadParamBandSettings(param, numComponents, numImageBands);
+        } else {
+            // Set the output color space equal to the input colorspace
+            // This disables all conversions
+            setOutColorSpace(structPointer, colorSpaceCode);
+            image = null;
+        }
+
+        // Create an intermediate 1-line Raster that will hold the decoded,
+        // subsampled, clipped, band-selected image data in a single
+        // byte-interleaved buffer.  The above transformations
+        // will occur in C for performance.  Every time this Raster
+        // is filled we will call back to acceptPixels below to copy
+        // this to whatever kind of buffer our image has.
+
+        int [] srcBands = JPEG.bandOffsets[numComponents-1];
+        int numRasterBands = (wantRaster ? numComponents : numImageBands);
+        destinationBands = null;
+
+        Rectangle srcROI = new Rectangle(0, 0, 0, 0);
+        destROI = new Rectangle(0, 0, 0, 0);
+        computeRegions(param, width, height, image, srcROI, destROI);
+
+        int periodX = 1;
+        int periodY = 1;
+
+        minProgressivePass = 0;
+        maxProgressivePass = Integer.MAX_VALUE;
+
+        if (param != null) {
+            periodX = param.getSourceXSubsampling();
+            periodY = param.getSourceYSubsampling();
+
+            int[] sBands = param.getSourceBands();
+            if (sBands != null) {
+                srcBands = sBands;
+                numRasterBands = srcBands.length;
+            }
+            if (!wantRaster) {  // ignore dest bands for Raster
+                destinationBands = param.getDestinationBands();
+            }
+
+            minProgressivePass = param.getSourceMinProgressivePass();
+            maxProgressivePass = param.getSourceMaxProgressivePass();
+
+            if (param instanceof JPEGImageReadParam) {
+                JPEGImageReadParam jparam = (JPEGImageReadParam) param;
+                if (jparam.areTablesSet()) {
+                    abbrevQTables = jparam.getQTables();
+                    abbrevDCHuffmanTables = jparam.getDCHuffmanTables();
+                    abbrevACHuffmanTables = jparam.getACHuffmanTables();
+                }
+            }
+        }
+
+        int lineSize = destROI.width*numRasterBands;
+
+        buffer = new DataBufferByte(lineSize);
+
+        int [] bandOffs = JPEG.bandOffsets[numRasterBands-1];
+
+        raster = Raster.createInterleavedRaster(buffer,
+                                                destROI.width, 1,
+                                                lineSize,
+                                                numRasterBands,
+                                                bandOffs,
+                                                null);
+
+        // Now that we have the Raster we'll decode to, get a view of the
+        // target Raster that will permit a simple setRect for each scanline
+        if (wantRaster) {
+            target =  Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
+                                                     destROI.width,
+                                                     destROI.height,
+                                                     lineSize,
+                                                     numRasterBands,
+                                                     bandOffs,
+                                                     null);
+        } else {
+            target = imRas;
+        }
+        int [] bandSizes = target.getSampleModel().getSampleSize();
+        for (int i = 0; i < bandSizes.length; i++) {
+            if (bandSizes[i] <= 0 || bandSizes[i] > 8) {
+                throw new IIOException("Illegal band size: should be 0 < size <= 8");
+            }
+        }
+
+        /*
+         * If the process is sequential, and we have restart markers,
+         * we could skip to the correct restart marker, if the library
+         * lets us.  That's an optimization to investigate later.
+         */
+
+        // Check for update listeners (don't call back if none)
+        boolean callbackUpdates = ((updateListeners != null)
+                                   || (progressListeners != null));
+
+        // Set up progression data
+        initProgressData();
+        // if we have a metadata object, we can count the scans
+        // and set knownPassCount
+        if (imageIndex == imageMetadataIndex) { // We have metadata
+            knownPassCount = 0;
+            for (Iterator<MarkerSegment> 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);
+        }
+
+        /*
+         * All the Jpeg processing happens in native, we should clear
+         * abortFlag of imageIODataStruct in imageioJPEG.c. And we need to
+         * clear abortFlag because if in previous read() if we had called
+         * reader.abort() that will continue to be valid for present call also.
+         */
+        clearNativeReadAbortFlag(structPointer);
+        processImageStarted(currentImage);
+        /*
+         * Note that getData disables acceleration on buffer, but it is
+         * just a 1-line intermediate data transfer buffer that will not
+         * affect the acceleration of the resulting image.
+         */
+        boolean aborted = readImage(imageIndex,
+                                    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);
+
+        cbLock.lock();
+        try {
+            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);
+                }
+            }
+        } finally {
+            cbLock.unlock();
+        }
+    }
+
+    private void initProgressData() {
+        knownPassCount = UNKNOWN;
+        pass = 0;
+        percentToDate = 0.0F;
+        previousPassPercentage = 0.0F;
+        progInterval = 0;
+    }
+
+    private void passStarted (int pass) {
+        cbLock.lock();
+        try {
+            this.pass = pass;
+            previousPassPercentage = percentToDate;
+            processPassStarted(image,
+                               pass,
+                               minProgressivePass,
+                               maxProgressivePass,
+                               0, 0,
+                               1,1,
+                               destinationBands);
+        } finally {
+            cbLock.unlock();
+        }
+    }
+
+    private void passComplete () {
+        cbLock.lock();
+        try {
+            processPassComplete(image);
+        } finally {
+            cbLock.unlock();
+        }
+    }
+
+    void thumbnailStarted(int thumbnailIndex) {
+        cbLock.lock();
+        try {
+            processThumbnailStarted(currentImage, thumbnailIndex);
+        } finally {
+            cbLock.unlock();
+        }
+    }
+
+    // Provide access to protected superclass method
+    void thumbnailProgress(float percentageDone) {
+        cbLock.lock();
+        try {
+            processThumbnailProgress(percentageDone);
+        } finally {
+            cbLock.unlock();
+        }
+    }
+
+    // Provide access to protected superclass method
+    void thumbnailComplete() {
+        cbLock.lock();
+        try {
+            processThumbnailComplete();
+        } finally {
+            cbLock.unlock();
+        }
+    }
+
+    /**
+     * Returns {@code true} if the read was aborted.
+     */
+    private native boolean readImage(int imageIndex,
+                                     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);
+
+    /*
+     * We should call clearNativeReadAbortFlag() before we start reading
+     * jpeg image as image processing happens at native side.
+     */
+    private native void clearNativeReadAbortFlag(long structPointer);
+
+    public void abort() {
+        setThreadLock();
+        try {
+            /**
+             * NB: we do not check the call back lock here,
+             * we allow to abort the reader any time.
+             */
+
+            super.abort();
+            abortRead(structPointer);
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    /** Set the C level abort flag. Keep it atomic for thread safety. */
+    private native void abortRead(long structPointer);
+
+    /** Resets library state when an exception occurred during a read. */
+    private native void resetLibraryState(long structPointer);
+
+    public boolean canReadRaster() {
+        return true;
+    }
+
+    public Raster readRaster(int imageIndex, ImageReadParam param)
+        throws IOException {
+        setThreadLock();
+        Raster retval = null;
+        try {
+            cbLock.check();
+            /*
+             * This could be further optimized by not resetting the dest.
+             * offset and creating a translated raster in readInternal()
+             * (see bug 4994702 for more info).
+             */
+
+            // For Rasters, destination offset is logical, not physical, so
+            // set it to 0 before calling computeRegions, so that the destination
+            // region is not clipped.
+            Point saveDestOffset = null;
+            if (param != null) {
+                saveDestOffset = param.getDestinationOffset();
+                param.setDestinationOffset(new Point(0, 0));
+            }
+            retval = readInternal(imageIndex, param, true);
+            // Apply the destination offset, if any, as a logical offset
+            if (saveDestOffset != null) {
+                target = target.createWritableTranslatedChild(saveDestOffset.x,
+                                                              saveDestOffset.y);
+            }
+        } catch (RuntimeException e) {
+            resetLibraryState(structPointer);
+            throw e;
+        } catch (IOException e) {
+            resetLibraryState(structPointer);
+            throw e;
+        } finally {
+            clearThreadLock();
+        }
+        return retval;
+    }
+
+    public boolean readerSupportsThumbnails() {
+        return true;
+    }
+
+    public int getNumThumbnails(int imageIndex) throws IOException {
+        setThreadLock();
+        try {
+            cbLock.check();
+
+            getImageMetadata(imageIndex);  // checks iis state for us
+            // Now check the jfif segments
+            JFIFMarkerSegment jfif =
+                (JFIFMarkerSegment) imageMetadata.findMarkerSegment
+                (JFIFMarkerSegment.class, true);
+            int retval = 0;
+            if (jfif != null) {
+                retval = (jfif.thumb == null) ? 0 : 1;
+                retval += jfif.extSegments.size();
+            }
+            return retval;
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    public int getThumbnailWidth(int imageIndex, int thumbnailIndex)
+        throws IOException {
+        setThreadLock();
+        try {
+            cbLock.check();
+
+            if ((thumbnailIndex < 0)
+                || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
+                throw new IndexOutOfBoundsException("No such thumbnail");
+            }
+            // Now we know that there is a jfif segment
+            JFIFMarkerSegment jfif =
+                (JFIFMarkerSegment) imageMetadata.findMarkerSegment
+                (JFIFMarkerSegment.class, true);
+            return  jfif.getThumbnailWidth(thumbnailIndex);
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    public int getThumbnailHeight(int imageIndex, int thumbnailIndex)
+        throws IOException {
+        setThreadLock();
+        try {
+            cbLock.check();
+
+            if ((thumbnailIndex < 0)
+                || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
+                throw new IndexOutOfBoundsException("No such thumbnail");
+            }
+            // Now we know that there is a jfif segment
+            JFIFMarkerSegment jfif =
+                (JFIFMarkerSegment) imageMetadata.findMarkerSegment
+                (JFIFMarkerSegment.class, true);
+            return  jfif.getThumbnailHeight(thumbnailIndex);
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    public BufferedImage readThumbnail(int imageIndex,
+                                       int thumbnailIndex)
+        throws IOException {
+        setThreadLock();
+        try {
+            cbLock.check();
+
+            if ((thumbnailIndex < 0)
+                || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
+                throw new IndexOutOfBoundsException("No such thumbnail");
+            }
+            // Now we know that there is a jfif segment and that iis is good
+            JFIFMarkerSegment jfif =
+                (JFIFMarkerSegment) imageMetadata.findMarkerSegment
+                (JFIFMarkerSegment.class, true);
+            return  jfif.getThumbnail(iis, thumbnailIndex, this);
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    private void resetInternalState() {
+        // reset C structures
+        resetReader(structPointer);
+
+        // reset local Java structures
+        numImages = 0;
+        imagePositions = new ArrayList<>();
+        currentImage = -1;
+        image = null;
+        raster = null;
+        target = null;
+        buffer = null;
+        destROI = null;
+        destinationBands = null;
+        streamMetadata = null;
+        imageMetadata = null;
+        imageMetadataIndex = -1;
+        haveSeeked = false;
+        tablesOnlyChecked = false;
+        iccCS = null;
+        initProgressData();
+    }
+
+    public void reset() {
+        setThreadLock();
+        try {
+            cbLock.check();
+            super.reset();
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    private native void resetReader(long structPointer);
+
+    public void dispose() {
+        setThreadLock();
+        try {
+            cbLock.check();
+
+            if (structPointer != 0) {
+                disposerRecord.dispose();
+                structPointer = 0;
+            }
+        } finally {
+            clearThreadLock();
+        }
+    }
+
+    private static native void disposeReader(long structPointer);
+
+    private static class JPEGReaderDisposerRecord implements DisposerRecord {
+        private long pData;
+
+        public JPEGReaderDisposerRecord(long pData) {
+            this.pData = pData;
+        }
+
+        public synchronized void dispose() {
+            if (pData != 0) {
+                disposeReader(pData);
+                pData = 0;
+            }
+        }
+    }
+
+    private Thread theThread = null;
+    private int theLockCount = 0;
+
+    private synchronized void setThreadLock() {
+        Thread currThread = Thread.currentThread();
+        if (theThread != null) {
+            if (theThread != currThread) {
+                // it looks like that this reader instance is used
+                // by multiple threads.
+                throw new IllegalStateException("Attempt to use instance of " +
+                                                this + " locked on thread " +
+                                                theThread + " from thread " +
+                                                currThread);
+            } else {
+                theLockCount ++;
+            }
+        } else {
+            theThread = currThread;
+            theLockCount = 1;
+        }
+    }
+
+    private synchronized void clearThreadLock() {
+        Thread currThread = Thread.currentThread();
+        if (theThread == null || theThread != currThread) {
+            throw new IllegalStateException("Attempt to clear thread lock " +
+                                            " form wrong thread." +
+                                            " Locked thread: " + theThread +
+                                            "; current thread: " + currThread);
+        }
+        theLockCount --;
+        if (theLockCount == 0) {
+            theThread = null;
+        }
+    }
+
+    private CallBackLock cbLock = new CallBackLock();
+
+    private static class CallBackLock {
+
+        private State lockState;
+
+        CallBackLock() {
+            lockState = State.Unlocked;
+        }
+
+        void check() {
+            if (lockState != State.Unlocked) {
+                throw new IllegalStateException("Access to the reader is not allowed");
+            }
+        }
+
+        private void lock() {
+            lockState = State.Locked;
+        }
+
+        private void unlock() {
+            lockState = State.Unlocked;
+        }
+
+        private static enum State {
+            Unlocked,
+            Locked
+        }
+    }
+}
+
+/**
+ * An internal helper class that wraps producer's iterator
+ * and extracts specifier instances on demand.
+ */
+class ImageTypeIterator implements Iterator<ImageTypeSpecifier> {
+     private Iterator<ImageTypeProducer> producers;
+     private ImageTypeSpecifier theNext = null;
+
+     public ImageTypeIterator(Iterator<ImageTypeProducer> producers) {
+         this.producers = producers;
+     }
+
+     public boolean hasNext() {
+         if (theNext != null) {
+             return true;
+         }
+         if (!producers.hasNext()) {
+             return false;
+         }
+         do {
+             theNext = producers.next().getType();
+         } while (theNext == null && producers.hasNext());
+
+         return (theNext != null);
+     }
+
+     public ImageTypeSpecifier next() {
+         if (theNext != null || hasNext()) {
+             ImageTypeSpecifier t = theNext;
+             theNext = null;
+             return t;
+         } else {
+             throw new NoSuchElementException();
+         }
+     }
+
+     public void remove() {
+         producers.remove();
+     }
+}
+
+/**
+ * An internal helper class that provides means for deferred creation
+ * of ImageTypeSpecifier instance required to describe available
+ * destination types.
+ *
+ * This implementation only supports standard
+ * jpeg color spaces (defined by corresponding JCS color space code).
+ *
+ * To support other color spaces one can override produce() method to
+ * return custom instance of ImageTypeSpecifier.
+ */
+class ImageTypeProducer {
+
+    private ImageTypeSpecifier type = null;
+    boolean failed = false;
+    private int csCode;
+
+    public ImageTypeProducer(int csCode) {
+        this.csCode = csCode;
+    }
+
+    public ImageTypeProducer() {
+        csCode = -1; // undefined
+    }
+
+    public synchronized ImageTypeSpecifier getType() {
+        if (!failed && type == null) {
+            try {
+                type = produce();
+            } catch (Throwable e) {
+                failed = true;
+            }
+        }
+        return type;
+    }
+
+    private static final ImageTypeProducer [] defaultTypes =
+            new ImageTypeProducer [JPEG.NUM_JCS_CODES];
+
+    public static synchronized ImageTypeProducer getTypeProducer(int csCode) {
+        if (csCode < 0 || csCode >= JPEG.NUM_JCS_CODES) {
+            return null;
+        }
+        if (defaultTypes[csCode] == null) {
+            defaultTypes[csCode] = new ImageTypeProducer(csCode);
+        }
+        return defaultTypes[csCode];
+    }
+
+    protected ImageTypeSpecifier produce() {
+        switch (csCode) {
+            case JPEG.JCS_GRAYSCALE:
+                return ImageTypeSpecifier.createFromBufferedImageType
+                        (BufferedImage.TYPE_BYTE_GRAY);
+            case JPEG.JCS_YCbCr:
+            //there is no YCbCr raw type so by default we assume it as RGB
+            case JPEG.JCS_RGB:
+                return ImageTypeSpecifier.createInterleaved(JPEG.JCS.sRGB,
+                        JPEG.bOffsRGB,
+                        DataBuffer.TYPE_BYTE,
+                        false,
+                        false);
+            case JPEG.JCS_RGBA:
+                return ImageTypeSpecifier.createPacked(JPEG.JCS.sRGB,
+                        0xff000000,
+                        0x00ff0000,
+                        0x0000ff00,
+                        0x000000ff,
+                        DataBuffer.TYPE_INT,
+                        false);
+            case JPEG.JCS_YCC:
+                if (JPEG.JCS.getYCC() != null) {
+                    return ImageTypeSpecifier.createInterleaved(
+                            JPEG.JCS.getYCC(),
+                        JPEG.bandOffsets[2],
+                        DataBuffer.TYPE_BYTE,
+                        false,
+                        false);
+                } else {
+                    return null;
+                }
+            case JPEG.JCS_YCCA:
+                if (JPEG.JCS.getYCC() != null) {
+                    return ImageTypeSpecifier.createInterleaved(
+                            JPEG.JCS.getYCC(),
+                        JPEG.bandOffsets[3],
+                        DataBuffer.TYPE_BYTE,
+                        true,
+                        false);
+                } else {
+                    return null;
+                }
+            default:
+                return null;
+        }
+    }
+}