JPEGImageReader.java revision 15662:25c0c37cd66a
1/*
2 * Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.imageio.plugins.jpeg;
27
28import javax.imageio.IIOException;
29import javax.imageio.ImageReader;
30import javax.imageio.ImageReadParam;
31import javax.imageio.ImageTypeSpecifier;
32import javax.imageio.metadata.IIOMetadata;
33import javax.imageio.spi.ImageReaderSpi;
34import javax.imageio.stream.ImageInputStream;
35import javax.imageio.plugins.jpeg.JPEGImageReadParam;
36import javax.imageio.plugins.jpeg.JPEGQTable;
37import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
38
39import java.awt.Point;
40import java.awt.Rectangle;
41import java.awt.color.ColorSpace;
42import java.awt.color.ICC_Profile;
43import java.awt.color.ICC_ColorSpace;
44import java.awt.color.CMMException;
45import java.awt.image.BufferedImage;
46import java.awt.image.Raster;
47import java.awt.image.WritableRaster;
48import java.awt.image.DataBuffer;
49import java.awt.image.DataBufferByte;
50import java.awt.image.ColorModel;
51import java.awt.image.IndexColorModel;
52import java.awt.image.ColorConvertOp;
53import java.io.IOException;
54import java.util.List;
55import java.util.Iterator;
56import java.util.ArrayList;
57import java.util.NoSuchElementException;
58
59import sun.java2d.Disposer;
60import sun.java2d.DisposerRecord;
61
62public class JPEGImageReader extends ImageReader {
63
64    private boolean debug = false;
65
66    /**
67     * The following variable contains a pointer to the IJG library
68     * structure for this reader.  It is assigned in the constructor
69     * and then is passed in to every native call.  It is set to 0
70     * by dispose to avoid disposing twice.
71     */
72    private long structPointer = 0;
73
74    /** The input stream we read from */
75    private ImageInputStream iis = null;
76
77    /**
78     * List of stream positions for images, reinitialized every time
79     * a new input source is set.
80     */
81    private List<Long> imagePositions = null;
82
83    /**
84     * The number of images in the stream, or 0.
85     */
86    private int numImages = 0;
87
88    static {
89        java.security.AccessController.doPrivileged(
90            new java.security.PrivilegedAction<Void>() {
91                public Void run() {
92                    System.loadLibrary("javajpeg");
93                    return null;
94                }
95            });
96        initReaderIDs(ImageInputStream.class,
97                      JPEGQTable.class,
98                      JPEGHuffmanTable.class);
99    }
100
101    // The following warnings are converted to strings when used
102    // as keys to get localized resources from JPEGImageReaderResources
103    // and its children.
104
105    /**
106     * Warning code to be passed to warningOccurred to indicate
107     * that the EOI marker is missing from the end of the stream.
108     * This usually signals that the stream is corrupted, but
109     * everything up to the last MCU should be usable.
110     */
111    protected static final int WARNING_NO_EOI = 0;
112
113    /**
114     * Warning code to be passed to warningOccurred to indicate
115     * that a JFIF segment was encountered inside a JFXX JPEG
116     * thumbnail and is being ignored.
117     */
118    protected static final int WARNING_NO_JFIF_IN_THUMB = 1;
119
120    /**
121     * Warning code to be passed to warningOccurred to indicate
122     * that embedded ICC profile is invalid and will be ignored.
123     */
124    protected static final int WARNING_IGNORE_INVALID_ICC = 2;
125
126    private static final int MAX_WARNING = WARNING_IGNORE_INVALID_ICC;
127
128    /**
129     * Image index of image for which header information
130     * is available.
131     */
132    private int currentImage = -1;
133
134    // The following is copied out from C after reading the header.
135    // Unlike metadata, which may never be retrieved, we need this
136    // if we are to read an image at all.
137
138    /** Set by setImageData native code callback */
139    private int width;
140    /** Set by setImageData native code callback */
141    private int height;
142    /**
143     * Set by setImageData native code callback.  A modified
144     * IJG+NIFTY colorspace code.
145     */
146    private int colorSpaceCode;
147    /**
148     * Set by setImageData native code callback.  A modified
149     * IJG+NIFTY colorspace code.
150     */
151    private int outColorSpaceCode;
152    /** Set by setImageData native code callback */
153    private int numComponents;
154    /** Set by setImageData native code callback */
155    private ColorSpace iccCS = null;
156
157
158    /** If we need to post-convert in Java, convert with this op */
159    private ColorConvertOp convert = null;
160
161    /** The image we are going to fill */
162    private BufferedImage image = null;
163
164    /** An intermediate Raster to hold decoded data */
165    private WritableRaster raster = null;
166
167    /** A view of our target Raster that we can setRect to */
168    private WritableRaster target = null;
169
170    /** The databuffer for the above Raster */
171    private DataBufferByte buffer = null;
172
173    /** The region in the destination where we will write pixels */
174    private Rectangle destROI = null;
175
176    /** The list of destination bands, if any */
177    private int [] destinationBands = null;
178
179    /** Stream metadata, cached, even when the stream is changed. */
180    private JPEGMetadata streamMetadata = null;
181
182    /** Image metadata, valid for the imageMetadataIndex only. */
183    private JPEGMetadata imageMetadata = null;
184    private int imageMetadataIndex = -1;
185
186    /**
187     * Set to true every time we seek in the stream; used to
188     * invalidate the native buffer contents in C.
189     */
190    private boolean haveSeeked = false;
191
192    /**
193     * Tables that have been read from a tables-only image at the
194     * beginning of a stream.
195     */
196    private JPEGQTable [] abbrevQTables = null;
197    private JPEGHuffmanTable[] abbrevDCHuffmanTables = null;
198    private JPEGHuffmanTable[] abbrevACHuffmanTables = null;
199
200    private int minProgressivePass = 0;
201    private int maxProgressivePass = Integer.MAX_VALUE;
202
203    /**
204     * Variables used by progress monitoring.
205     */
206    private static final int UNKNOWN = -1;  // Number of passes
207    private static final int MIN_ESTIMATED_PASSES = 10; // IJG default
208    private int knownPassCount = UNKNOWN;
209    private int pass = 0;
210    private float percentToDate = 0.0F;
211    private float previousPassPercentage = 0.0F;
212    private int progInterval = 0;
213
214    /**
215     * Set to true once stream has been checked for stream metadata
216     */
217    private boolean tablesOnlyChecked = false;
218
219    /** The referent to be registered with the Disposer. */
220    private Object disposerReferent = new Object();
221
222    /** The DisposerRecord that handles the actual disposal of this reader. */
223    private DisposerRecord disposerRecord;
224
225    /** Sets up static C structures. */
226    private static native void initReaderIDs(Class<?> iisClass,
227                                             Class<?> qTableClass,
228                                             Class<?> huffClass);
229
230    public JPEGImageReader(ImageReaderSpi originator) {
231        super(originator);
232        structPointer = initJPEGImageReader();
233        disposerRecord = new JPEGReaderDisposerRecord(structPointer);
234        Disposer.addRecord(disposerReferent, disposerRecord);
235    }
236
237    /** Sets up per-reader C structure and returns a pointer to it. */
238    private native long initJPEGImageReader();
239
240    /**
241     * Called by the native code or other classes to signal a warning.
242     * The code is used to lookup a localized message to be used when
243     * sending warnings to listeners.
244     */
245    protected void warningOccurred(int code) {
246        cbLock.lock();
247        try {
248            if ((code < 0) || (code > MAX_WARNING)){
249                throw new InternalError("Invalid warning index");
250            }
251            processWarningOccurred
252                ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
253                 Integer.toString(code));
254        } finally {
255            cbLock.unlock();
256        }
257    }
258
259    /**
260     * The library has it's own error facility that emits warning messages.
261     * This routine is called by the native code when it has already
262     * formatted a string for output.
263     * XXX  For truly complete localization of all warning messages,
264     * the sun_jpeg_output_message routine in the native code should
265     * send only the codes and parameters to a method here in Java,
266     * which will then format and send the warnings, using localized
267     * strings.  This method will have to deal with all the parameters
268     * and formats (%u with possibly large numbers, %02d, %02x, etc.)
269     * that actually occur in the JPEG library.  For now, this prevents
270     * library warnings from being printed to stderr.
271     */
272    protected void warningWithMessage(String msg) {
273        cbLock.lock();
274        try {
275            processWarningOccurred(msg);
276        } finally {
277            cbLock.unlock();
278        }
279    }
280
281    public void setInput(Object input,
282                         boolean seekForwardOnly,
283                         boolean ignoreMetadata)
284    {
285        setThreadLock();
286        try {
287            cbLock.check();
288
289            super.setInput(input, seekForwardOnly, ignoreMetadata);
290            this.ignoreMetadata = ignoreMetadata;
291            resetInternalState();
292            iis = (ImageInputStream) input; // Always works
293            setSource(structPointer);
294        } finally {
295            clearThreadLock();
296        }
297    }
298
299    /**
300     * This method is called from native code in order to fill
301     * native input buffer.
302     *
303     * We block any attempt to change the reading state during this
304     * method, in order to prevent a corruption of the native decoder
305     * state.
306     *
307     * @return number of bytes read from the stream.
308     */
309    private int readInputData(byte[] buf, int off, int len) throws IOException {
310        cbLock.lock();
311        try {
312            return iis.read(buf, off, len);
313        } finally {
314            cbLock.unlock();
315        }
316    }
317
318    /**
319     * This method is called from the native code in order to
320     * skip requested number of bytes in the input stream.
321     *
322     * @param n
323     * @return
324     * @throws IOException
325     */
326    private long skipInputBytes(long n) throws IOException {
327        cbLock.lock();
328        try {
329            return iis.skipBytes(n);
330        } finally {
331            cbLock.unlock();
332        }
333    }
334
335    private native void setSource(long structPointer);
336
337    private void checkTablesOnly() throws IOException {
338        if (debug) {
339            System.out.println("Checking for tables-only image");
340        }
341        long savePos = iis.getStreamPosition();
342        if (debug) {
343            System.out.println("saved pos is " + savePos);
344            System.out.println("length is " + iis.length());
345        }
346        // Read the first header
347        boolean tablesOnly = readNativeHeader(true);
348        if (tablesOnly) {
349            if (debug) {
350                System.out.println("tables-only image found");
351                long pos = iis.getStreamPosition();
352                System.out.println("pos after return from native is " + pos);
353            }
354            // This reads the tables-only image twice, once from C
355            // and once from Java, but only if ignoreMetadata is false
356            if (ignoreMetadata == false) {
357                iis.seek(savePos);
358                haveSeeked = true;
359                streamMetadata = new JPEGMetadata(true, false,
360                                                  iis, this);
361                long pos = iis.getStreamPosition();
362                if (debug) {
363                    System.out.println
364                        ("pos after constructing stream metadata is " + pos);
365                }
366            }
367            // Now we are at the first image if there are any, so add it
368            // to the list
369            if (hasNextImage()) {
370                imagePositions.add(iis.getStreamPosition());
371            }
372        } else { // Not tables only, so add original pos to the list
373            imagePositions.add(savePos);
374            // And set current image since we've read it now
375            currentImage = 0;
376        }
377        if (seekForwardOnly) {
378            Long pos = imagePositions.get(imagePositions.size()-1);
379            iis.flushBefore(pos.longValue());
380        }
381        tablesOnlyChecked = true;
382    }
383
384    public int getNumImages(boolean allowSearch) throws IOException {
385        setThreadLock();
386        try { // locked thread
387            cbLock.check();
388
389            return getNumImagesOnThread(allowSearch);
390        } finally {
391            clearThreadLock();
392        }
393    }
394
395    @SuppressWarnings("fallthrough")
396    private int getNumImagesOnThread(boolean allowSearch)
397      throws IOException {
398        if (numImages != 0) {
399            return numImages;
400        }
401        if (iis == null) {
402            throw new IllegalStateException("Input not set");
403        }
404        if (allowSearch == true) {
405            if (seekForwardOnly) {
406                throw new IllegalStateException(
407                    "seekForwardOnly and allowSearch can't both be true!");
408            }
409            // Otherwise we have to read the entire stream
410
411            if (!tablesOnlyChecked) {
412                checkTablesOnly();
413            }
414
415            iis.mark();
416
417            gotoImage(0);
418
419            JPEGBuffer buffer = new JPEGBuffer(iis);
420            buffer.loadBuf(0);
421
422            boolean done = false;
423            while (!done) {
424                done = buffer.scanForFF(this);
425                switch (buffer.buf[buffer.bufPtr] & 0xff) {
426                case JPEG.SOI:
427                    numImages++;
428                    // FALL THROUGH to decrement buffer vars
429                    // This first set doesn't have a length
430                case 0: // not a marker, just a data 0xff
431                case JPEG.RST0:
432                case JPEG.RST1:
433                case JPEG.RST2:
434                case JPEG.RST3:
435                case JPEG.RST4:
436                case JPEG.RST5:
437                case JPEG.RST6:
438                case JPEG.RST7:
439                case JPEG.EOI:
440                    buffer.bufAvail--;
441                    buffer.bufPtr++;
442                    break;
443                    // All the others have a length
444                default:
445                    buffer.bufAvail--;
446                    buffer.bufPtr++;
447                    buffer.loadBuf(2);
448                    int length = ((buffer.buf[buffer.bufPtr++] & 0xff) << 8) |
449                        (buffer.buf[buffer.bufPtr++] & 0xff);
450                    buffer.bufAvail -= 2;
451                    length -= 2; // length includes itself
452                    buffer.skipData(length);
453                }
454            }
455
456
457            iis.reset();
458
459            return numImages;
460        }
461
462        return -1;  // Search is necessary for JPEG
463    }
464
465    /**
466     * Sets the input stream to the start of the requested image.
467     * <pre>
468     * @exception IllegalStateException if the input source has not been
469     * set.
470     * @exception IndexOutOfBoundsException if the supplied index is
471     * out of bounds.
472     * </pre>
473     */
474    private void gotoImage(int imageIndex) throws IOException {
475        if (iis == null) {
476            throw new IllegalStateException("Input not set");
477        }
478        if (imageIndex < minIndex) {
479            throw new IndexOutOfBoundsException();
480        }
481        if (!tablesOnlyChecked) {
482            checkTablesOnly();
483        }
484        if (imageIndex < imagePositions.size()) {
485            iis.seek(imagePositions.get(imageIndex).longValue());
486        } else {
487            // read to start of image, saving positions
488            // First seek to the last position we already have, and skip the
489            // entire image
490            Long pos = imagePositions.get(imagePositions.size()-1);
491            iis.seek(pos.longValue());
492            skipImage();
493            // Now add all intervening positions, skipping images
494            for (int index = imagePositions.size();
495                 index <= imageIndex;
496                 index++) {
497                // Is there an image?
498                if (!hasNextImage()) {
499                    throw new IndexOutOfBoundsException();
500                }
501                pos = iis.getStreamPosition();
502                imagePositions.add(pos);
503                if (seekForwardOnly) {
504                    iis.flushBefore(pos.longValue());
505                }
506                if (index < imageIndex) {
507                    skipImage();
508                }  // Otherwise we are where we want to be
509            }
510        }
511
512        if (seekForwardOnly) {
513            minIndex = imageIndex;
514        }
515
516        haveSeeked = true;  // No way is native buffer still valid
517    }
518
519    /**
520     * Skip over a complete image in the stream, leaving the stream
521     * positioned such that the next byte to be read is the first
522     * byte of the next image. For JPEG, this means that we read
523     * until we encounter an EOI marker or until the end of the stream.
524     * We can find data same as EOI marker in some headers
525     * or comments, so we have to skip bytes related to these headers.
526     * If the stream ends before an EOI marker is encountered,
527     * an IndexOutOfBoundsException is thrown.
528     */
529    private void skipImage() throws IOException {
530        if (debug) {
531            System.out.println("skipImage called");
532        }
533        // verify if image starts with an SOI marker
534        int initialFF = iis.read();
535        if (initialFF == 0xff) {
536            int soiMarker = iis.read();
537            if (soiMarker != JPEG.SOI) {
538                throw new IOException("skipImage : Invalid image doesn't "
539                        + "start with SOI marker");
540            }
541        } else {
542            throw new IOException("skipImage : Invalid image doesn't start "
543                    + "with 0xff");
544        }
545        boolean foundFF = false;
546        String IOOBE = "skipImage : Reached EOF before we got EOI marker";
547        int markerLength = 2;
548        for (int byteval = iis.read();
549             byteval != -1;
550             byteval = iis.read()) {
551
552            if (foundFF == true) {
553                switch (byteval) {
554                    case JPEG.EOI:
555                        if (debug) {
556                            System.out.println("skipImage : Found EOI at " +
557                                    (iis.getStreamPosition() - markerLength));
558                        }
559                        return;
560                    case JPEG.SOI:
561                        throw new IOException("skipImage : Found extra SOI"
562                                + " marker before getting to EOI");
563                    case 0:
564                    case 255:
565                    // markers which doesn't contain length data
566                    case JPEG.RST0:
567                    case JPEG.RST1:
568                    case JPEG.RST2:
569                    case JPEG.RST3:
570                    case JPEG.RST4:
571                    case JPEG.RST5:
572                    case JPEG.RST6:
573                    case JPEG.RST7:
574                    case JPEG.TEM:
575                        break;
576                    // markers which contains length data
577                    case JPEG.SOF0:
578                    case JPEG.SOF1:
579                    case JPEG.SOF2:
580                    case JPEG.SOF3:
581                    case JPEG.DHT:
582                    case JPEG.SOF5:
583                    case JPEG.SOF6:
584                    case JPEG.SOF7:
585                    case JPEG.JPG:
586                    case JPEG.SOF9:
587                    case JPEG.SOF10:
588                    case JPEG.SOF11:
589                    case JPEG.DAC:
590                    case JPEG.SOF13:
591                    case JPEG.SOF14:
592                    case JPEG.SOF15:
593                    case JPEG.SOS:
594                    case JPEG.DQT:
595                    case JPEG.DNL:
596                    case JPEG.DRI:
597                    case JPEG.DHP:
598                    case JPEG.EXP:
599                    case JPEG.APP0:
600                    case JPEG.APP1:
601                    case JPEG.APP2:
602                    case JPEG.APP3:
603                    case JPEG.APP4:
604                    case JPEG.APP5:
605                    case JPEG.APP6:
606                    case JPEG.APP7:
607                    case JPEG.APP8:
608                    case JPEG.APP9:
609                    case JPEG.APP10:
610                    case JPEG.APP11:
611                    case JPEG.APP12:
612                    case JPEG.APP13:
613                    case JPEG.APP14:
614                    case JPEG.APP15:
615                    case JPEG.COM:
616                        // read length of header from next 2 bytes
617                        int lengthHigherBits, lengthLowerBits, length;
618                        lengthHigherBits = iis.read();
619                        if (lengthHigherBits != (-1)) {
620                            lengthLowerBits = iis.read();
621                            if (lengthLowerBits != (-1)) {
622                                length = (lengthHigherBits << 8) |
623                                        lengthLowerBits;
624                                // length contains already read 2 bytes
625                                length -= 2;
626                            } else {
627                                throw new IndexOutOfBoundsException(IOOBE);
628                            }
629                        } else {
630                            throw new IndexOutOfBoundsException(IOOBE);
631                        }
632                        // skip the length specified in marker
633                        iis.skipBytes(length);
634                        break;
635                    case (-1):
636                        throw new IndexOutOfBoundsException(IOOBE);
637                    default:
638                        throw new IOException("skipImage : Invalid marker "
639                                + "starting with ff "
640                                + Integer.toHexString(byteval));
641                }
642            }
643            foundFF = (byteval == 0xff);
644        }
645        throw new IndexOutOfBoundsException(IOOBE);
646    }
647
648    /**
649     * Returns {@code true} if there is an image beyond
650     * the current stream position.  Does not disturb the
651     * stream position.
652     */
653    private boolean hasNextImage() throws IOException {
654        if (debug) {
655            System.out.print("hasNextImage called; returning ");
656        }
657        iis.mark();
658        boolean foundFF = false;
659        for (int byteval = iis.read();
660             byteval != -1;
661             byteval = iis.read()) {
662
663            if (foundFF == true) {
664                if (byteval == JPEG.SOI) {
665                    iis.reset();
666                    if (debug) {
667                        System.out.println("true");
668                    }
669                    return true;
670                }
671            }
672            foundFF = (byteval == 0xff) ? true : false;
673        }
674        // We hit the end of the stream before we hit an SOI, so no image
675        iis.reset();
676        if (debug) {
677            System.out.println("false");
678        }
679        return false;
680    }
681
682    /**
683     * Push back the given number of bytes to the input stream.
684     * Called by the native code at the end of each image so
685     * that the next one can be identified from Java.
686     */
687    private void pushBack(int num) throws IOException {
688        if (debug) {
689            System.out.println("pushing back " + num + " bytes");
690        }
691        cbLock.lock();
692        try {
693            iis.seek(iis.getStreamPosition()-num);
694            // The buffer is clear after this, so no need to set haveSeeked.
695        } finally {
696            cbLock.unlock();
697        }
698    }
699
700    /**
701     * Reads header information for the given image, if possible.
702     */
703    private void readHeader(int imageIndex, boolean reset)
704        throws IOException {
705        gotoImage(imageIndex);
706        readNativeHeader(reset); // Ignore return
707        currentImage = imageIndex;
708    }
709
710    private boolean readNativeHeader(boolean reset) throws IOException {
711        boolean retval = false;
712        retval = readImageHeader(structPointer, haveSeeked, reset);
713        haveSeeked = false;
714        return retval;
715    }
716
717    /**
718     * Read in the header information starting from the current
719     * stream position, returning {@code true} if the
720     * header was a tables-only image.  After this call, the
721     * native IJG decompression struct will contain the image
722     * information required by most query calls below
723     * (e.g. getWidth, getHeight, etc.), if the header was not
724     * a tables-only image.
725     * If reset is {@code true}, the state of the IJG
726     * object is reset so that it can read a header again.
727     * This happens automatically if the header was a tables-only
728     * image.
729     */
730    private native boolean readImageHeader(long structPointer,
731                                           boolean clearBuffer,
732                                           boolean reset)
733        throws IOException;
734
735    /*
736     * Called by the native code whenever an image header has been
737     * read.  Whether we read metadata or not, we always need this
738     * information, so it is passed back independently of
739     * metadata, which may never be read.
740     */
741    private void setImageData(int width,
742                              int height,
743                              int colorSpaceCode,
744                              int outColorSpaceCode,
745                              int numComponents,
746                              byte [] iccData) {
747        this.width = width;
748        this.height = height;
749        this.colorSpaceCode = colorSpaceCode;
750        this.outColorSpaceCode = outColorSpaceCode;
751        this.numComponents = numComponents;
752
753        if (iccData == null) {
754            iccCS = null;
755            return;
756        }
757
758        ICC_Profile newProfile = null;
759        try {
760            newProfile = ICC_Profile.getInstance(iccData);
761        } catch (IllegalArgumentException e) {
762            /*
763             * Color profile data seems to be invalid.
764             * Ignore this profile.
765             */
766            iccCS = null;
767            warningOccurred(WARNING_IGNORE_INVALID_ICC);
768
769            return;
770        }
771        byte[] newData = newProfile.getData();
772
773        ICC_Profile oldProfile = null;
774        if (iccCS instanceof ICC_ColorSpace) {
775            oldProfile = ((ICC_ColorSpace)iccCS).getProfile();
776        }
777        byte[] oldData = null;
778        if (oldProfile != null) {
779            oldData = oldProfile.getData();
780        }
781
782        /*
783         * At the moment we can't rely on the ColorSpace.equals()
784         * and ICC_Profile.equals() because they do not detect
785         * the case when two profiles are created from same data.
786         *
787         * So, we have to do data comparison in order to avoid
788         * creation of different ColorSpace instances for the same
789         * embedded data.
790         */
791        if (oldData == null ||
792            !java.util.Arrays.equals(oldData, newData))
793        {
794            iccCS = new ICC_ColorSpace(newProfile);
795            // verify new color space
796            try {
797                float[] colors = iccCS.fromRGB(new float[] {1f, 0f, 0f});
798            } catch (CMMException e) {
799                /*
800                 * Embedded profile seems to be corrupted.
801                 * Ignore this profile.
802                 */
803                iccCS = null;
804                cbLock.lock();
805                try {
806                    warningOccurred(WARNING_IGNORE_INVALID_ICC);
807                } finally {
808                    cbLock.unlock();
809                }
810            }
811        }
812    }
813
814    public int getWidth(int imageIndex) throws IOException {
815        setThreadLock();
816        try {
817            if (currentImage != imageIndex) {
818                cbLock.check();
819                readHeader(imageIndex, true);
820            }
821            return width;
822        } finally {
823            clearThreadLock();
824        }
825    }
826
827    public int getHeight(int imageIndex) throws IOException {
828        setThreadLock();
829        try {
830            if (currentImage != imageIndex) {
831                cbLock.check();
832                readHeader(imageIndex, true);
833            }
834            return height;
835        } finally {
836            clearThreadLock();
837        }
838    }
839
840    /////////// Color Conversion and Image Types
841
842    /**
843     * Return an ImageTypeSpecifier corresponding to the given
844     * color space code, or null if the color space is unsupported.
845     */
846    private ImageTypeProducer getImageType(int code) {
847        ImageTypeProducer ret = null;
848
849        if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) {
850            ret = ImageTypeProducer.getTypeProducer(code);
851        }
852        return ret;
853    }
854
855    public ImageTypeSpecifier getRawImageType(int imageIndex)
856        throws IOException {
857        setThreadLock();
858        try {
859            if (currentImage != imageIndex) {
860                cbLock.check();
861
862                readHeader(imageIndex, true);
863            }
864
865            // Returns null if it can't be represented
866            return getImageType(colorSpaceCode).getType();
867        } finally {
868            clearThreadLock();
869        }
870    }
871
872    public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex)
873        throws IOException {
874        setThreadLock();
875        try {
876            return getImageTypesOnThread(imageIndex);
877        } finally {
878            clearThreadLock();
879        }
880    }
881
882    private Iterator<ImageTypeSpecifier> getImageTypesOnThread(int imageIndex)
883        throws IOException {
884        if (currentImage != imageIndex) {
885            cbLock.check();
886            readHeader(imageIndex, true);
887        }
888
889        // We return an iterator containing the default, any
890        // conversions that the library provides, and
891        // all the other default types with the same number
892        // of components, as we can do these as a post-process.
893        // As we convert Rasters rather than images, images
894        // with alpha cannot be converted in a post-process.
895
896        // If this image can't be interpreted, this method
897        // returns an empty Iterator.
898
899        // Get the raw ITS, if there is one.  Note that this
900        // won't always be the same as the default.
901        ImageTypeProducer raw = getImageType(colorSpaceCode);
902
903        // Given the encoded colorspace, build a list of ITS's
904        // representing outputs you could handle starting
905        // with the default.
906
907        ArrayList<ImageTypeProducer> list = new ArrayList<ImageTypeProducer>(1);
908
909        switch (colorSpaceCode) {
910        case JPEG.JCS_GRAYSCALE:
911            list.add(raw);
912            list.add(getImageType(JPEG.JCS_RGB));
913            break;
914        case JPEG.JCS_RGB:
915            list.add(raw);
916            list.add(getImageType(JPEG.JCS_GRAYSCALE));
917            list.add(getImageType(JPEG.JCS_YCC));
918            break;
919        case JPEG.JCS_RGBA:
920            list.add(raw);
921            break;
922        case JPEG.JCS_YCC:
923            if (raw != null) {  // Might be null if PYCC.pf not installed
924                list.add(raw);
925                list.add(getImageType(JPEG.JCS_RGB));
926            }
927            break;
928        case JPEG.JCS_YCCA:
929            if (raw != null) {  // Might be null if PYCC.pf not installed
930                list.add(raw);
931            }
932            break;
933        case JPEG.JCS_YCbCr:
934            // As there is no YCbCr ColorSpace, we can't support
935            // the raw type.
936
937            // due to 4705399, use RGB as default in order to avoid
938            // slowing down of drawing operations with result image.
939            list.add(getImageType(JPEG.JCS_RGB));
940
941            if (iccCS != null) {
942                list.add(new ImageTypeProducer() {
943                    protected ImageTypeSpecifier produce() {
944                        return ImageTypeSpecifier.createInterleaved
945                         (iccCS,
946                          JPEG.bOffsRGB,  // Assume it's for RGB
947                          DataBuffer.TYPE_BYTE,
948                          false,
949                          false);
950                    }
951                });
952
953            }
954
955            list.add(getImageType(JPEG.JCS_GRAYSCALE));
956            list.add(getImageType(JPEG.JCS_YCC));
957            break;
958        case JPEG.JCS_YCbCrA:  // Default is to convert to RGBA
959            // As there is no YCbCr ColorSpace, we can't support
960            // the raw type.
961            list.add(getImageType(JPEG.JCS_RGBA));
962            break;
963        }
964
965        return new ImageTypeIterator(list.iterator());
966    }
967
968    /**
969     * Checks the implied color conversion between the stream and
970     * the target image, altering the IJG output color space if necessary.
971     * If a java color conversion is required, then this sets up
972     * {@code convert}.
973     * If bands are being rearranged at all (either source or destination
974     * bands are specified in the param), then the default color
975     * conversions are assumed to be correct.
976     * Throws an IIOException if there is no conversion available.
977     */
978    private void checkColorConversion(BufferedImage image,
979                                      ImageReadParam param)
980        throws IIOException {
981
982        // If we are rearranging channels at all, the default
983        // conversions remain in place.  If the user wants
984        // raw channels then he should do this while reading
985        // a Raster.
986        if (param != null) {
987            if ((param.getSourceBands() != null) ||
988                (param.getDestinationBands() != null)) {
989                // Accept default conversions out of decoder, silently
990                return;
991            }
992        }
993
994        // XXX - We do not currently support any indexed color models,
995        // though we could, as IJG will quantize for us.
996        // This is a performance and memory-use issue, as
997        // users can read RGB and then convert to indexed in Java.
998
999        ColorModel cm = image.getColorModel();
1000
1001        if (cm instanceof IndexColorModel) {
1002            throw new IIOException("IndexColorModel not supported");
1003        }
1004
1005        // Now check the ColorSpace type against outColorSpaceCode
1006        // We may want to tweak the default
1007        ColorSpace cs = cm.getColorSpace();
1008        int csType = cs.getType();
1009        convert = null;
1010        switch (outColorSpaceCode) {
1011        case JPEG.JCS_GRAYSCALE:  // Its gray in the file
1012            if  (csType == ColorSpace.TYPE_RGB) { // We want RGB
1013                // IJG can do this for us more efficiently
1014                setOutColorSpace(structPointer, JPEG.JCS_RGB);
1015                // Update java state according to changes
1016                // in the native part of decoder.
1017                outColorSpaceCode = JPEG.JCS_RGB;
1018                numComponents = 3;
1019            } else if (csType != ColorSpace.TYPE_GRAY) {
1020                throw new IIOException("Incompatible color conversion");
1021            }
1022            break;
1023        case JPEG.JCS_RGB:  // IJG wants to go to RGB
1024            if (csType ==  ColorSpace.TYPE_GRAY) {  // We want gray
1025                if (colorSpaceCode == JPEG.JCS_YCbCr) {
1026                    // If the jpeg space is YCbCr, IJG can do it
1027                    setOutColorSpace(structPointer, JPEG.JCS_GRAYSCALE);
1028                    // Update java state according to changes
1029                    // in the native part of decoder.
1030                    outColorSpaceCode = JPEG.JCS_GRAYSCALE;
1031                    numComponents = 1;
1032                }
1033            } else if ((iccCS != null) &&
1034                       (cm.getNumComponents() == numComponents) &&
1035                       (cs != iccCS)) {
1036                // We have an ICC profile but it isn't used in the dest
1037                // image.  So convert from the profile cs to the target cs
1038                convert = new ColorConvertOp(iccCS, cs, null);
1039                // Leave IJG conversion in place; we still need it
1040            } else if ((iccCS == null) &&
1041                       (!cs.isCS_sRGB()) &&
1042                       (cm.getNumComponents() == numComponents)) {
1043                // Target isn't sRGB, so convert from sRGB to the target
1044                convert = new ColorConvertOp(JPEG.JCS.sRGB, cs, null);
1045            } else if (csType != ColorSpace.TYPE_RGB) {
1046                throw new IIOException("Incompatible color conversion");
1047            }
1048            break;
1049        case JPEG.JCS_RGBA:
1050            // No conversions available; image must be RGBA
1051            if ((csType != ColorSpace.TYPE_RGB) ||
1052                (cm.getNumComponents() != numComponents)) {
1053                throw new IIOException("Incompatible color conversion");
1054            }
1055            break;
1056        case JPEG.JCS_YCC:
1057            {
1058                ColorSpace YCC = JPEG.JCS.getYCC();
1059                if (YCC == null) { // We can't do YCC at all
1060                    throw new IIOException("Incompatible color conversion");
1061                }
1062                if ((cs != YCC) &&
1063                    (cm.getNumComponents() == numComponents)) {
1064                    convert = new ColorConvertOp(YCC, cs, null);
1065                }
1066            }
1067            break;
1068        case JPEG.JCS_YCCA:
1069            {
1070                ColorSpace YCC = JPEG.JCS.getYCC();
1071                // No conversions available; image must be YCCA
1072                if ((YCC == null) || // We can't do YCC at all
1073                    (cs != YCC) ||
1074                    (cm.getNumComponents() != numComponents)) {
1075                    throw new IIOException("Incompatible color conversion");
1076                }
1077            }
1078            break;
1079        default:
1080            // Anything else we can't handle at all
1081            throw new IIOException("Incompatible color conversion");
1082        }
1083    }
1084
1085    /**
1086     * Set the IJG output space to the given value.  The library will
1087     * perform the appropriate colorspace conversions.
1088     */
1089    private native void setOutColorSpace(long structPointer, int id);
1090
1091    /////// End of Color Conversion & Image Types
1092
1093    public ImageReadParam getDefaultReadParam() {
1094        return new JPEGImageReadParam();
1095    }
1096
1097    public IIOMetadata getStreamMetadata() throws IOException {
1098        setThreadLock();
1099        try {
1100            if (!tablesOnlyChecked) {
1101                cbLock.check();
1102                checkTablesOnly();
1103            }
1104            return streamMetadata;
1105        } finally {
1106            clearThreadLock();
1107        }
1108    }
1109
1110    public IIOMetadata getImageMetadata(int imageIndex)
1111        throws IOException {
1112        setThreadLock();
1113        try {
1114            // imageMetadataIndex will always be either a valid index or
1115            // -1, in which case imageMetadata will not be null.
1116            // So we can leave checking imageIndex for gotoImage.
1117            if ((imageMetadataIndex == imageIndex)
1118                && (imageMetadata != null)) {
1119                return imageMetadata;
1120            }
1121
1122            cbLock.check();
1123
1124            gotoImage(imageIndex);
1125
1126            imageMetadata = new JPEGMetadata(false, false, iis, this);
1127
1128            imageMetadataIndex = imageIndex;
1129
1130            return imageMetadata;
1131        } finally {
1132            clearThreadLock();
1133        }
1134    }
1135
1136    public BufferedImage read(int imageIndex, ImageReadParam param)
1137        throws IOException {
1138        setThreadLock();
1139        try {
1140            cbLock.check();
1141            try {
1142                readInternal(imageIndex, param, false);
1143            } catch (RuntimeException e) {
1144                resetLibraryState(structPointer);
1145                throw e;
1146            } catch (IOException e) {
1147                resetLibraryState(structPointer);
1148                throw e;
1149            }
1150
1151            BufferedImage ret = image;
1152            image = null;  // don't keep a reference here
1153            return ret;
1154        } finally {
1155            clearThreadLock();
1156        }
1157    }
1158
1159    private Raster readInternal(int imageIndex,
1160                                ImageReadParam param,
1161                                boolean wantRaster) throws IOException {
1162        readHeader(imageIndex, false);
1163
1164        WritableRaster imRas = null;
1165        int numImageBands = 0;
1166
1167        if (!wantRaster){
1168            // Can we read this image?
1169            Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
1170            if (imageTypes.hasNext() == false) {
1171                throw new IIOException("Unsupported Image Type");
1172            }
1173
1174            image = getDestination(param, imageTypes, width, height);
1175            imRas = image.getRaster();
1176
1177            // The destination may still be incompatible.
1178
1179            numImageBands = image.getSampleModel().getNumBands();
1180
1181            // Check whether we can handle any implied color conversion
1182
1183            // Throws IIOException if the stream and the image are
1184            // incompatible, and sets convert if a java conversion
1185            // is necessary
1186            checkColorConversion(image, param);
1187
1188            // Check the source and destination bands in the param
1189            checkReadParamBandSettings(param, numComponents, numImageBands);
1190        } else {
1191            // Set the output color space equal to the input colorspace
1192            // This disables all conversions
1193            setOutColorSpace(structPointer, colorSpaceCode);
1194            image = null;
1195        }
1196
1197        // Create an intermediate 1-line Raster that will hold the decoded,
1198        // subsampled, clipped, band-selected image data in a single
1199        // byte-interleaved buffer.  The above transformations
1200        // will occur in C for performance.  Every time this Raster
1201        // is filled we will call back to acceptPixels below to copy
1202        // this to whatever kind of buffer our image has.
1203
1204        int [] srcBands = JPEG.bandOffsets[numComponents-1];
1205        int numRasterBands = (wantRaster ? numComponents : numImageBands);
1206        destinationBands = null;
1207
1208        Rectangle srcROI = new Rectangle(0, 0, 0, 0);
1209        destROI = new Rectangle(0, 0, 0, 0);
1210        computeRegions(param, width, height, image, srcROI, destROI);
1211
1212        int periodX = 1;
1213        int periodY = 1;
1214
1215        minProgressivePass = 0;
1216        maxProgressivePass = Integer.MAX_VALUE;
1217
1218        if (param != null) {
1219            periodX = param.getSourceXSubsampling();
1220            periodY = param.getSourceYSubsampling();
1221
1222            int[] sBands = param.getSourceBands();
1223            if (sBands != null) {
1224                srcBands = sBands;
1225                numRasterBands = srcBands.length;
1226            }
1227            if (!wantRaster) {  // ignore dest bands for Raster
1228                destinationBands = param.getDestinationBands();
1229            }
1230
1231            minProgressivePass = param.getSourceMinProgressivePass();
1232            maxProgressivePass = param.getSourceMaxProgressivePass();
1233
1234            if (param instanceof JPEGImageReadParam) {
1235                JPEGImageReadParam jparam = (JPEGImageReadParam) param;
1236                if (jparam.areTablesSet()) {
1237                    abbrevQTables = jparam.getQTables();
1238                    abbrevDCHuffmanTables = jparam.getDCHuffmanTables();
1239                    abbrevACHuffmanTables = jparam.getACHuffmanTables();
1240                }
1241            }
1242        }
1243
1244        int lineSize = destROI.width*numRasterBands;
1245
1246        buffer = new DataBufferByte(lineSize);
1247
1248        int [] bandOffs = JPEG.bandOffsets[numRasterBands-1];
1249
1250        raster = Raster.createInterleavedRaster(buffer,
1251                                                destROI.width, 1,
1252                                                lineSize,
1253                                                numRasterBands,
1254                                                bandOffs,
1255                                                null);
1256
1257        // Now that we have the Raster we'll decode to, get a view of the
1258        // target Raster that will permit a simple setRect for each scanline
1259        if (wantRaster) {
1260            target =  Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
1261                                                     destROI.width,
1262                                                     destROI.height,
1263                                                     lineSize,
1264                                                     numRasterBands,
1265                                                     bandOffs,
1266                                                     null);
1267        } else {
1268            target = imRas;
1269        }
1270        int [] bandSizes = target.getSampleModel().getSampleSize();
1271        for (int i = 0; i < bandSizes.length; i++) {
1272            if (bandSizes[i] <= 0 || bandSizes[i] > 8) {
1273                throw new IIOException("Illegal band size: should be 0 < size <= 8");
1274            }
1275        }
1276
1277        /*
1278         * If the process is sequential, and we have restart markers,
1279         * we could skip to the correct restart marker, if the library
1280         * lets us.  That's an optimization to investigate later.
1281         */
1282
1283        // Check for update listeners (don't call back if none)
1284        boolean callbackUpdates = ((updateListeners != null)
1285                                   || (progressListeners != null));
1286
1287        // Set up progression data
1288        initProgressData();
1289        // if we have a metadata object, we can count the scans
1290        // and set knownPassCount
1291        if (imageIndex == imageMetadataIndex) { // We have metadata
1292            knownPassCount = 0;
1293            for (Iterator<MarkerSegment> iter =
1294                    imageMetadata.markerSequence.iterator(); iter.hasNext();) {
1295                if (iter.next() instanceof SOSMarkerSegment) {
1296                    knownPassCount++;
1297                }
1298            }
1299        }
1300        progInterval = Math.max((target.getHeight()-1) / 20, 1);
1301        if (knownPassCount > 0) {
1302            progInterval *= knownPassCount;
1303        } else if (maxProgressivePass != Integer.MAX_VALUE) {
1304            progInterval *= (maxProgressivePass - minProgressivePass + 1);
1305        }
1306
1307        if (debug) {
1308            System.out.println("**** Read Data *****");
1309            System.out.println("numRasterBands is " + numRasterBands);
1310            System.out.print("srcBands:");
1311            for (int i = 0; i<srcBands.length;i++)
1312                System.out.print(" " + srcBands[i]);
1313            System.out.println();
1314            System.out.println("destination bands is " + destinationBands);
1315            if (destinationBands != null) {
1316                for (int i = 0; i < destinationBands.length; i++) {
1317                    System.out.print(" " + destinationBands[i]);
1318                }
1319                System.out.println();
1320            }
1321            System.out.println("sourceROI is " + srcROI);
1322            System.out.println("destROI is " + destROI);
1323            System.out.println("periodX is " + periodX);
1324            System.out.println("periodY is " + periodY);
1325            System.out.println("minProgressivePass is " + minProgressivePass);
1326            System.out.println("maxProgressivePass is " + maxProgressivePass);
1327            System.out.println("callbackUpdates is " + callbackUpdates);
1328        }
1329
1330        /*
1331         * All the Jpeg processing happens in native, we should clear
1332         * abortFlag of imageIODataStruct in imageioJPEG.c. And we need to
1333         * clear abortFlag because if in previous read() if we had called
1334         * reader.abort() that will continue to be valid for present call also.
1335         */
1336        clearNativeReadAbortFlag(structPointer);
1337        processImageStarted(currentImage);
1338        /*
1339         * Note that getData disables acceleration on buffer, but it is
1340         * just a 1-line intermediate data transfer buffer that will not
1341         * affect the acceleration of the resulting image.
1342         */
1343        boolean aborted = readImage(structPointer,
1344                                    buffer.getData(),
1345                                    numRasterBands,
1346                                    srcBands,
1347                                    bandSizes,
1348                                    srcROI.x, srcROI.y,
1349                                    srcROI.width, srcROI.height,
1350                                    periodX, periodY,
1351                                    abbrevQTables,
1352                                    abbrevDCHuffmanTables,
1353                                    abbrevACHuffmanTables,
1354                                    minProgressivePass, maxProgressivePass,
1355                                    callbackUpdates);
1356
1357        if (aborted) {
1358            processReadAborted();
1359        } else {
1360            processImageComplete();
1361        }
1362
1363        return target;
1364
1365    }
1366
1367    /**
1368     * This method is called back from C when the intermediate Raster
1369     * is full.  The parameter indicates the scanline in the target
1370     * Raster to which the intermediate Raster should be copied.
1371     * After the copy, we notify update listeners.
1372     */
1373    private void acceptPixels(int y, boolean progressive) {
1374        if (convert != null) {
1375            convert.filter(raster, raster);
1376        }
1377        target.setRect(destROI.x, destROI.y + y, raster);
1378
1379        cbLock.lock();
1380        try {
1381            processImageUpdate(image,
1382                               destROI.x, destROI.y+y,
1383                               raster.getWidth(), 1,
1384                               1, 1,
1385                               destinationBands);
1386            if ((y > 0) && (y%progInterval == 0)) {
1387                int height = target.getHeight()-1;
1388                float percentOfPass = ((float)y)/height;
1389                if (progressive) {
1390                    if (knownPassCount != UNKNOWN) {
1391                        processImageProgress((pass + percentOfPass)*100.0F
1392                                             / knownPassCount);
1393                    } else if (maxProgressivePass != Integer.MAX_VALUE) {
1394                        // Use the range of allowed progressive passes
1395                        processImageProgress((pass + percentOfPass)*100.0F
1396                                             / (maxProgressivePass - minProgressivePass + 1));
1397                    } else {
1398                        // Assume there are a minimum of MIN_ESTIMATED_PASSES
1399                        // and that there is always one more pass
1400                        // Compute the percentage as the percentage at the end
1401                        // of the previous pass, plus the percentage of this
1402                        // pass scaled to be the percentage of the total remaining,
1403                        // assuming a minimum of MIN_ESTIMATED_PASSES passes and
1404                        // that there is always one more pass.  This is monotonic
1405                        // and asymptotic to 1.0, which is what we need.
1406                        int remainingPasses = // including this one
1407                            Math.max(2, MIN_ESTIMATED_PASSES-pass);
1408                        int totalPasses = pass + remainingPasses-1;
1409                        progInterval = Math.max(height/20*totalPasses,
1410                                                totalPasses);
1411                        if (y%progInterval == 0) {
1412                            percentToDate = previousPassPercentage +
1413                                (1.0F - previousPassPercentage)
1414                                * (percentOfPass)/remainingPasses;
1415                            if (debug) {
1416                                System.out.print("pass= " + pass);
1417                                System.out.print(", y= " + y);
1418                                System.out.print(", progInt= " + progInterval);
1419                                System.out.print(", % of pass: " + percentOfPass);
1420                                System.out.print(", rem. passes: "
1421                                                 + remainingPasses);
1422                                System.out.print(", prev%: "
1423                                                 + previousPassPercentage);
1424                                System.out.print(", %ToDate: " + percentToDate);
1425                                System.out.print(" ");
1426                            }
1427                            processImageProgress(percentToDate*100.0F);
1428                        }
1429                    }
1430                } else {
1431                    processImageProgress(percentOfPass * 100.0F);
1432                }
1433            }
1434        } finally {
1435            cbLock.unlock();
1436        }
1437    }
1438
1439    private void initProgressData() {
1440        knownPassCount = UNKNOWN;
1441        pass = 0;
1442        percentToDate = 0.0F;
1443        previousPassPercentage = 0.0F;
1444        progInterval = 0;
1445    }
1446
1447    private void passStarted (int pass) {
1448        cbLock.lock();
1449        try {
1450            this.pass = pass;
1451            previousPassPercentage = percentToDate;
1452            processPassStarted(image,
1453                               pass,
1454                               minProgressivePass,
1455                               maxProgressivePass,
1456                               0, 0,
1457                               1,1,
1458                               destinationBands);
1459        } finally {
1460            cbLock.unlock();
1461        }
1462    }
1463
1464    private void passComplete () {
1465        cbLock.lock();
1466        try {
1467            processPassComplete(image);
1468        } finally {
1469            cbLock.unlock();
1470        }
1471    }
1472
1473    void thumbnailStarted(int thumbnailIndex) {
1474        cbLock.lock();
1475        try {
1476            processThumbnailStarted(currentImage, thumbnailIndex);
1477        } finally {
1478            cbLock.unlock();
1479        }
1480    }
1481
1482    // Provide access to protected superclass method
1483    void thumbnailProgress(float percentageDone) {
1484        cbLock.lock();
1485        try {
1486            processThumbnailProgress(percentageDone);
1487        } finally {
1488            cbLock.unlock();
1489        }
1490    }
1491
1492    // Provide access to protected superclass method
1493    void thumbnailComplete() {
1494        cbLock.lock();
1495        try {
1496            processThumbnailComplete();
1497        } finally {
1498            cbLock.unlock();
1499        }
1500    }
1501
1502    /**
1503     * Returns {@code true} if the read was aborted.
1504     */
1505    private native boolean readImage(long structPointer,
1506                                     byte [] buffer,
1507                                     int numRasterBands,
1508                                     int [] srcBands,
1509                                     int [] bandSizes,
1510                                     int sourceXOffset, int sourceYOffset,
1511                                     int sourceWidth, int sourceHeight,
1512                                     int periodX, int periodY,
1513                                     JPEGQTable [] abbrevQTables,
1514                                     JPEGHuffmanTable [] abbrevDCHuffmanTables,
1515                                     JPEGHuffmanTable [] abbrevACHuffmanTables,
1516                                     int minProgressivePass,
1517                                     int maxProgressivePass,
1518                                     boolean wantUpdates);
1519
1520    /*
1521     * We should call clearNativeReadAbortFlag() before we start reading
1522     * jpeg image as image processing happens at native side.
1523     */
1524    private native void clearNativeReadAbortFlag(long structPointer);
1525
1526    public void abort() {
1527        setThreadLock();
1528        try {
1529            /**
1530             * NB: we do not check the call back lock here,
1531             * we allow to abort the reader any time.
1532             */
1533
1534            super.abort();
1535            abortRead(structPointer);
1536        } finally {
1537            clearThreadLock();
1538        }
1539    }
1540
1541    /** Set the C level abort flag. Keep it atomic for thread safety. */
1542    private native void abortRead(long structPointer);
1543
1544    /** Resets library state when an exception occurred during a read. */
1545    private native void resetLibraryState(long structPointer);
1546
1547    public boolean canReadRaster() {
1548        return true;
1549    }
1550
1551    public Raster readRaster(int imageIndex, ImageReadParam param)
1552        throws IOException {
1553        setThreadLock();
1554        Raster retval = null;
1555        try {
1556            cbLock.check();
1557            /*
1558             * This could be further optimized by not resetting the dest.
1559             * offset and creating a translated raster in readInternal()
1560             * (see bug 4994702 for more info).
1561             */
1562
1563            // For Rasters, destination offset is logical, not physical, so
1564            // set it to 0 before calling computeRegions, so that the destination
1565            // region is not clipped.
1566            Point saveDestOffset = null;
1567            if (param != null) {
1568                saveDestOffset = param.getDestinationOffset();
1569                param.setDestinationOffset(new Point(0, 0));
1570            }
1571            retval = readInternal(imageIndex, param, true);
1572            // Apply the destination offset, if any, as a logical offset
1573            if (saveDestOffset != null) {
1574                target = target.createWritableTranslatedChild(saveDestOffset.x,
1575                                                              saveDestOffset.y);
1576            }
1577        } catch (RuntimeException e) {
1578            resetLibraryState(structPointer);
1579            throw e;
1580        } catch (IOException e) {
1581            resetLibraryState(structPointer);
1582            throw e;
1583        } finally {
1584            clearThreadLock();
1585        }
1586        return retval;
1587    }
1588
1589    public boolean readerSupportsThumbnails() {
1590        return true;
1591    }
1592
1593    public int getNumThumbnails(int imageIndex) throws IOException {
1594        setThreadLock();
1595        try {
1596            cbLock.check();
1597
1598            getImageMetadata(imageIndex);  // checks iis state for us
1599            // Now check the jfif segments
1600            JFIFMarkerSegment jfif =
1601                (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1602                (JFIFMarkerSegment.class, true);
1603            int retval = 0;
1604            if (jfif != null) {
1605                retval = (jfif.thumb == null) ? 0 : 1;
1606                retval += jfif.extSegments.size();
1607            }
1608            return retval;
1609        } finally {
1610            clearThreadLock();
1611        }
1612    }
1613
1614    public int getThumbnailWidth(int imageIndex, int thumbnailIndex)
1615        throws IOException {
1616        setThreadLock();
1617        try {
1618            cbLock.check();
1619
1620            if ((thumbnailIndex < 0)
1621                || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1622                throw new IndexOutOfBoundsException("No such thumbnail");
1623            }
1624            // Now we know that there is a jfif segment
1625            JFIFMarkerSegment jfif =
1626                (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1627                (JFIFMarkerSegment.class, true);
1628            return  jfif.getThumbnailWidth(thumbnailIndex);
1629        } finally {
1630            clearThreadLock();
1631        }
1632    }
1633
1634    public int getThumbnailHeight(int imageIndex, int thumbnailIndex)
1635        throws IOException {
1636        setThreadLock();
1637        try {
1638            cbLock.check();
1639
1640            if ((thumbnailIndex < 0)
1641                || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1642                throw new IndexOutOfBoundsException("No such thumbnail");
1643            }
1644            // Now we know that there is a jfif segment
1645            JFIFMarkerSegment jfif =
1646                (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1647                (JFIFMarkerSegment.class, true);
1648            return  jfif.getThumbnailHeight(thumbnailIndex);
1649        } finally {
1650            clearThreadLock();
1651        }
1652    }
1653
1654    public BufferedImage readThumbnail(int imageIndex,
1655                                       int thumbnailIndex)
1656        throws IOException {
1657        setThreadLock();
1658        try {
1659            cbLock.check();
1660
1661            if ((thumbnailIndex < 0)
1662                || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1663                throw new IndexOutOfBoundsException("No such thumbnail");
1664            }
1665            // Now we know that there is a jfif segment and that iis is good
1666            JFIFMarkerSegment jfif =
1667                (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1668                (JFIFMarkerSegment.class, true);
1669            return  jfif.getThumbnail(iis, thumbnailIndex, this);
1670        } finally {
1671            clearThreadLock();
1672        }
1673    }
1674
1675    private void resetInternalState() {
1676        // reset C structures
1677        resetReader(structPointer);
1678
1679        // reset local Java structures
1680        numImages = 0;
1681        imagePositions = new ArrayList<>();
1682        currentImage = -1;
1683        image = null;
1684        raster = null;
1685        target = null;
1686        buffer = null;
1687        destROI = null;
1688        destinationBands = null;
1689        streamMetadata = null;
1690        imageMetadata = null;
1691        imageMetadataIndex = -1;
1692        haveSeeked = false;
1693        tablesOnlyChecked = false;
1694        iccCS = null;
1695        initProgressData();
1696    }
1697
1698    public void reset() {
1699        setThreadLock();
1700        try {
1701            cbLock.check();
1702            super.reset();
1703        } finally {
1704            clearThreadLock();
1705        }
1706    }
1707
1708    private native void resetReader(long structPointer);
1709
1710    public void dispose() {
1711        setThreadLock();
1712        try {
1713            cbLock.check();
1714
1715            if (structPointer != 0) {
1716                disposerRecord.dispose();
1717                structPointer = 0;
1718            }
1719        } finally {
1720            clearThreadLock();
1721        }
1722    }
1723
1724    private static native void disposeReader(long structPointer);
1725
1726    private static class JPEGReaderDisposerRecord implements DisposerRecord {
1727        private long pData;
1728
1729        public JPEGReaderDisposerRecord(long pData) {
1730            this.pData = pData;
1731        }
1732
1733        public synchronized void dispose() {
1734            if (pData != 0) {
1735                disposeReader(pData);
1736                pData = 0;
1737            }
1738        }
1739    }
1740
1741    private Thread theThread = null;
1742    private int theLockCount = 0;
1743
1744    private synchronized void setThreadLock() {
1745        Thread currThread = Thread.currentThread();
1746        if (theThread != null) {
1747            if (theThread != currThread) {
1748                // it looks like that this reader instance is used
1749                // by multiple threads.
1750                throw new IllegalStateException("Attempt to use instance of " +
1751                                                this + " locked on thread " +
1752                                                theThread + " from thread " +
1753                                                currThread);
1754            } else {
1755                theLockCount ++;
1756            }
1757        } else {
1758            theThread = currThread;
1759            theLockCount = 1;
1760        }
1761    }
1762
1763    private synchronized void clearThreadLock() {
1764        Thread currThread = Thread.currentThread();
1765        if (theThread == null || theThread != currThread) {
1766            throw new IllegalStateException("Attempt to clear thread lock " +
1767                                            " form wrong thread." +
1768                                            " Locked thread: " + theThread +
1769                                            "; current thread: " + currThread);
1770        }
1771        theLockCount --;
1772        if (theLockCount == 0) {
1773            theThread = null;
1774        }
1775    }
1776
1777    private CallBackLock cbLock = new CallBackLock();
1778
1779    private static class CallBackLock {
1780
1781        private State lockState;
1782
1783        CallBackLock() {
1784            lockState = State.Unlocked;
1785        }
1786
1787        void check() {
1788            if (lockState != State.Unlocked) {
1789                throw new IllegalStateException("Access to the reader is not allowed");
1790            }
1791        }
1792
1793        private void lock() {
1794            lockState = State.Locked;
1795        }
1796
1797        private void unlock() {
1798            lockState = State.Unlocked;
1799        }
1800
1801        private static enum State {
1802            Unlocked,
1803            Locked
1804        }
1805    }
1806}
1807
1808/**
1809 * An internal helper class that wraps producer's iterator
1810 * and extracts specifier instances on demand.
1811 */
1812class ImageTypeIterator implements Iterator<ImageTypeSpecifier> {
1813     private Iterator<ImageTypeProducer> producers;
1814     private ImageTypeSpecifier theNext = null;
1815
1816     public ImageTypeIterator(Iterator<ImageTypeProducer> producers) {
1817         this.producers = producers;
1818     }
1819
1820     public boolean hasNext() {
1821         if (theNext != null) {
1822             return true;
1823         }
1824         if (!producers.hasNext()) {
1825             return false;
1826         }
1827         do {
1828             theNext = producers.next().getType();
1829         } while (theNext == null && producers.hasNext());
1830
1831         return (theNext != null);
1832     }
1833
1834     public ImageTypeSpecifier next() {
1835         if (theNext != null || hasNext()) {
1836             ImageTypeSpecifier t = theNext;
1837             theNext = null;
1838             return t;
1839         } else {
1840             throw new NoSuchElementException();
1841         }
1842     }
1843
1844     public void remove() {
1845         producers.remove();
1846     }
1847}
1848
1849/**
1850 * An internal helper class that provides means for deferred creation
1851 * of ImageTypeSpecifier instance required to describe available
1852 * destination types.
1853 *
1854 * This implementation only supports standard
1855 * jpeg color spaces (defined by corresponding JCS color space code).
1856 *
1857 * To support other color spaces one can override produce() method to
1858 * return custom instance of ImageTypeSpecifier.
1859 */
1860class ImageTypeProducer {
1861
1862    private ImageTypeSpecifier type = null;
1863    boolean failed = false;
1864    private int csCode;
1865
1866    public ImageTypeProducer(int csCode) {
1867        this.csCode = csCode;
1868    }
1869
1870    public ImageTypeProducer() {
1871        csCode = -1; // undefined
1872    }
1873
1874    public synchronized ImageTypeSpecifier getType() {
1875        if (!failed && type == null) {
1876            try {
1877                type = produce();
1878            } catch (Throwable e) {
1879                failed = true;
1880            }
1881        }
1882        return type;
1883    }
1884
1885    private static final ImageTypeProducer [] defaultTypes =
1886            new ImageTypeProducer [JPEG.NUM_JCS_CODES];
1887
1888    public static synchronized ImageTypeProducer getTypeProducer(int csCode) {
1889        if (csCode < 0 || csCode >= JPEG.NUM_JCS_CODES) {
1890            return null;
1891        }
1892        if (defaultTypes[csCode] == null) {
1893            defaultTypes[csCode] = new ImageTypeProducer(csCode);
1894        }
1895        return defaultTypes[csCode];
1896    }
1897
1898    protected ImageTypeSpecifier produce() {
1899        switch (csCode) {
1900            case JPEG.JCS_GRAYSCALE:
1901                return ImageTypeSpecifier.createFromBufferedImageType
1902                        (BufferedImage.TYPE_BYTE_GRAY);
1903            case JPEG.JCS_YCbCr:
1904            //there is no YCbCr raw type so by default we assume it as RGB
1905            case JPEG.JCS_RGB:
1906                return ImageTypeSpecifier.createInterleaved(JPEG.JCS.sRGB,
1907                        JPEG.bOffsRGB,
1908                        DataBuffer.TYPE_BYTE,
1909                        false,
1910                        false);
1911            case JPEG.JCS_RGBA:
1912                return ImageTypeSpecifier.createPacked(JPEG.JCS.sRGB,
1913                        0xff000000,
1914                        0x00ff0000,
1915                        0x0000ff00,
1916                        0x000000ff,
1917                        DataBuffer.TYPE_INT,
1918                        false);
1919            case JPEG.JCS_YCC:
1920                if (JPEG.JCS.getYCC() != null) {
1921                    return ImageTypeSpecifier.createInterleaved(
1922                            JPEG.JCS.getYCC(),
1923                        JPEG.bandOffsets[2],
1924                        DataBuffer.TYPE_BYTE,
1925                        false,
1926                        false);
1927                } else {
1928                    return null;
1929                }
1930            case JPEG.JCS_YCCA:
1931                if (JPEG.JCS.getYCC() != null) {
1932                    return ImageTypeSpecifier.createInterleaved(
1933                            JPEG.JCS.getYCC(),
1934                        JPEG.bandOffsets[3],
1935                        DataBuffer.TYPE_BYTE,
1936                        true,
1937                        false);
1938                } else {
1939                    return null;
1940                }
1941            default:
1942                return null;
1943        }
1944    }
1945}
1946