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    private void skipPastImage(int imageIndex) {
396        cbLock.lock();
397        try {
398            gotoImage(imageIndex);
399            skipImage();
400        } catch (IOException | IndexOutOfBoundsException e) {
401        } finally {
402            cbLock.unlock();
403        }
404    }
405
406    @SuppressWarnings("fallthrough")
407    private int getNumImagesOnThread(boolean allowSearch)
408      throws IOException {
409        if (numImages != 0) {
410            return numImages;
411        }
412        if (iis == null) {
413            throw new IllegalStateException("Input not set");
414        }
415        if (allowSearch == true) {
416            if (seekForwardOnly) {
417                throw new IllegalStateException(
418                    "seekForwardOnly and allowSearch can't both be true!");
419            }
420            // Otherwise we have to read the entire stream
421
422            if (!tablesOnlyChecked) {
423                checkTablesOnly();
424            }
425
426            iis.mark();
427
428            gotoImage(0);
429
430            JPEGBuffer buffer = new JPEGBuffer(iis);
431            buffer.loadBuf(0);
432
433            boolean done = false;
434            while (!done) {
435                done = buffer.scanForFF(this);
436                switch (buffer.buf[buffer.bufPtr] & 0xff) {
437                case JPEG.SOI:
438                    numImages++;
439                    // FALL THROUGH to decrement buffer vars
440                    // This first set doesn't have a length
441                case 0: // not a marker, just a data 0xff
442                case JPEG.RST0:
443                case JPEG.RST1:
444                case JPEG.RST2:
445                case JPEG.RST3:
446                case JPEG.RST4:
447                case JPEG.RST5:
448                case JPEG.RST6:
449                case JPEG.RST7:
450                case JPEG.EOI:
451                    buffer.bufAvail--;
452                    buffer.bufPtr++;
453                    break;
454                    // All the others have a length
455                default:
456                    buffer.bufAvail--;
457                    buffer.bufPtr++;
458                    buffer.loadBuf(2);
459                    int length = ((buffer.buf[buffer.bufPtr++] & 0xff) << 8) |
460                        (buffer.buf[buffer.bufPtr++] & 0xff);
461                    buffer.bufAvail -= 2;
462                    length -= 2; // length includes itself
463                    buffer.skipData(length);
464                }
465            }
466
467
468            iis.reset();
469
470            return numImages;
471        }
472
473        return -1;  // Search is necessary for JPEG
474    }
475
476    /**
477     * Sets the input stream to the start of the requested image.
478     * <pre>
479     * @exception IllegalStateException if the input source has not been
480     * set.
481     * @exception IndexOutOfBoundsException if the supplied index is
482     * out of bounds.
483     * </pre>
484     */
485    private void gotoImage(int imageIndex) throws IOException {
486        if (iis == null) {
487            throw new IllegalStateException("Input not set");
488        }
489        if (imageIndex < minIndex) {
490            throw new IndexOutOfBoundsException();
491        }
492        if (!tablesOnlyChecked) {
493            checkTablesOnly();
494        }
495        if (imageIndex < imagePositions.size()) {
496            iis.seek(imagePositions.get(imageIndex).longValue());
497        } else {
498            // read to start of image, saving positions
499            // First seek to the last position we already have, and skip the
500            // entire image
501            Long pos = imagePositions.get(imagePositions.size()-1);
502            iis.seek(pos.longValue());
503            skipImage();
504            // Now add all intervening positions, skipping images
505            for (int index = imagePositions.size();
506                 index <= imageIndex;
507                 index++) {
508                // Is there an image?
509                if (!hasNextImage()) {
510                    throw new IndexOutOfBoundsException();
511                }
512                pos = iis.getStreamPosition();
513                imagePositions.add(pos);
514                if (seekForwardOnly) {
515                    iis.flushBefore(pos.longValue());
516                }
517                if (index < imageIndex) {
518                    skipImage();
519                }  // Otherwise we are where we want to be
520            }
521        }
522
523        if (seekForwardOnly) {
524            minIndex = imageIndex;
525        }
526
527        haveSeeked = true;  // No way is native buffer still valid
528    }
529
530    /**
531     * Skip over a complete image in the stream, leaving the stream
532     * positioned such that the next byte to be read is the first
533     * byte of the next image. For JPEG, this means that we read
534     * until we encounter an EOI marker or until the end of the stream.
535     * We can find data same as EOI marker in some headers
536     * or comments, so we have to skip bytes related to these headers.
537     * If the stream ends before an EOI marker is encountered,
538     * an IndexOutOfBoundsException is thrown.
539     */
540    private void skipImage() throws IOException {
541        if (debug) {
542            System.out.println("skipImage called");
543        }
544        // verify if image starts with an SOI marker
545        int initialFF = iis.read();
546        if (initialFF == 0xff) {
547            int soiMarker = iis.read();
548            if (soiMarker != JPEG.SOI) {
549                throw new IOException("skipImage : Invalid image doesn't "
550                        + "start with SOI marker");
551            }
552        } else {
553            throw new IOException("skipImage : Invalid image doesn't start "
554                    + "with 0xff");
555        }
556        boolean foundFF = false;
557        String IOOBE = "skipImage : Reached EOF before we got EOI marker";
558        int markerLength = 2;
559        for (int byteval = iis.read();
560             byteval != -1;
561             byteval = iis.read()) {
562
563            if (foundFF == true) {
564                switch (byteval) {
565                    case JPEG.EOI:
566                        if (debug) {
567                            System.out.println("skipImage : Found EOI at " +
568                                    (iis.getStreamPosition() - markerLength));
569                        }
570                        return;
571                    case JPEG.SOI:
572                        throw new IOException("skipImage : Found extra SOI"
573                                + " marker before getting to EOI");
574                    case 0:
575                    case 255:
576                    // markers which doesn't contain length data
577                    case JPEG.RST0:
578                    case JPEG.RST1:
579                    case JPEG.RST2:
580                    case JPEG.RST3:
581                    case JPEG.RST4:
582                    case JPEG.RST5:
583                    case JPEG.RST6:
584                    case JPEG.RST7:
585                    case JPEG.TEM:
586                        break;
587                    // markers which contains length data
588                    case JPEG.SOF0:
589                    case JPEG.SOF1:
590                    case JPEG.SOF2:
591                    case JPEG.SOF3:
592                    case JPEG.DHT:
593                    case JPEG.SOF5:
594                    case JPEG.SOF6:
595                    case JPEG.SOF7:
596                    case JPEG.JPG:
597                    case JPEG.SOF9:
598                    case JPEG.SOF10:
599                    case JPEG.SOF11:
600                    case JPEG.DAC:
601                    case JPEG.SOF13:
602                    case JPEG.SOF14:
603                    case JPEG.SOF15:
604                    case JPEG.SOS:
605                    case JPEG.DQT:
606                    case JPEG.DNL:
607                    case JPEG.DRI:
608                    case JPEG.DHP:
609                    case JPEG.EXP:
610                    case JPEG.APP0:
611                    case JPEG.APP1:
612                    case JPEG.APP2:
613                    case JPEG.APP3:
614                    case JPEG.APP4:
615                    case JPEG.APP5:
616                    case JPEG.APP6:
617                    case JPEG.APP7:
618                    case JPEG.APP8:
619                    case JPEG.APP9:
620                    case JPEG.APP10:
621                    case JPEG.APP11:
622                    case JPEG.APP12:
623                    case JPEG.APP13:
624                    case JPEG.APP14:
625                    case JPEG.APP15:
626                    case JPEG.COM:
627                        // read length of header from next 2 bytes
628                        int lengthHigherBits, lengthLowerBits, length;
629                        lengthHigherBits = iis.read();
630                        if (lengthHigherBits != (-1)) {
631                            lengthLowerBits = iis.read();
632                            if (lengthLowerBits != (-1)) {
633                                length = (lengthHigherBits << 8) |
634                                        lengthLowerBits;
635                                // length contains already read 2 bytes
636                                length -= 2;
637                            } else {
638                                throw new IndexOutOfBoundsException(IOOBE);
639                            }
640                        } else {
641                            throw new IndexOutOfBoundsException(IOOBE);
642                        }
643                        // skip the length specified in marker
644                        iis.skipBytes(length);
645                        break;
646                    case (-1):
647                        throw new IndexOutOfBoundsException(IOOBE);
648                    default:
649                        throw new IOException("skipImage : Invalid marker "
650                                + "starting with ff "
651                                + Integer.toHexString(byteval));
652                }
653            }
654            foundFF = (byteval == 0xff);
655        }
656        throw new IndexOutOfBoundsException(IOOBE);
657    }
658
659    /**
660     * Returns {@code true} if there is an image beyond
661     * the current stream position.  Does not disturb the
662     * stream position.
663     */
664    private boolean hasNextImage() throws IOException {
665        if (debug) {
666            System.out.print("hasNextImage called; returning ");
667        }
668        iis.mark();
669        boolean foundFF = false;
670        for (int byteval = iis.read();
671             byteval != -1;
672             byteval = iis.read()) {
673
674            if (foundFF == true) {
675                if (byteval == JPEG.SOI) {
676                    iis.reset();
677                    if (debug) {
678                        System.out.println("true");
679                    }
680                    return true;
681                }
682            }
683            foundFF = (byteval == 0xff) ? true : false;
684        }
685        // We hit the end of the stream before we hit an SOI, so no image
686        iis.reset();
687        if (debug) {
688            System.out.println("false");
689        }
690        return false;
691    }
692
693    /**
694     * Push back the given number of bytes to the input stream.
695     * Called by the native code at the end of each image so
696     * that the next one can be identified from Java.
697     */
698    private void pushBack(int num) throws IOException {
699        if (debug) {
700            System.out.println("pushing back " + num + " bytes");
701        }
702        cbLock.lock();
703        try {
704            iis.seek(iis.getStreamPosition()-num);
705            // The buffer is clear after this, so no need to set haveSeeked.
706        } finally {
707            cbLock.unlock();
708        }
709    }
710
711    /**
712     * Reads header information for the given image, if possible.
713     */
714    private void readHeader(int imageIndex, boolean reset)
715        throws IOException {
716        gotoImage(imageIndex);
717        readNativeHeader(reset); // Ignore return
718        currentImage = imageIndex;
719    }
720
721    private boolean readNativeHeader(boolean reset) throws IOException {
722        boolean retval = false;
723        retval = readImageHeader(structPointer, haveSeeked, reset);
724        haveSeeked = false;
725        return retval;
726    }
727
728    /**
729     * Read in the header information starting from the current
730     * stream position, returning {@code true} if the
731     * header was a tables-only image.  After this call, the
732     * native IJG decompression struct will contain the image
733     * information required by most query calls below
734     * (e.g. getWidth, getHeight, etc.), if the header was not
735     * a tables-only image.
736     * If reset is {@code true}, the state of the IJG
737     * object is reset so that it can read a header again.
738     * This happens automatically if the header was a tables-only
739     * image.
740     */
741    private native boolean readImageHeader(long structPointer,
742                                           boolean clearBuffer,
743                                           boolean reset)
744        throws IOException;
745
746    /*
747     * Called by the native code whenever an image header has been
748     * read.  Whether we read metadata or not, we always need this
749     * information, so it is passed back independently of
750     * metadata, which may never be read.
751     */
752    private void setImageData(int width,
753                              int height,
754                              int colorSpaceCode,
755                              int outColorSpaceCode,
756                              int numComponents,
757                              byte [] iccData) {
758        this.width = width;
759        this.height = height;
760        this.colorSpaceCode = colorSpaceCode;
761        this.outColorSpaceCode = outColorSpaceCode;
762        this.numComponents = numComponents;
763
764        if (iccData == null) {
765            iccCS = null;
766            return;
767        }
768
769        ICC_Profile newProfile = null;
770        try {
771            newProfile = ICC_Profile.getInstance(iccData);
772        } catch (IllegalArgumentException e) {
773            /*
774             * Color profile data seems to be invalid.
775             * Ignore this profile.
776             */
777            iccCS = null;
778            warningOccurred(WARNING_IGNORE_INVALID_ICC);
779
780            return;
781        }
782        byte[] newData = newProfile.getData();
783
784        ICC_Profile oldProfile = null;
785        if (iccCS instanceof ICC_ColorSpace) {
786            oldProfile = ((ICC_ColorSpace)iccCS).getProfile();
787        }
788        byte[] oldData = null;
789        if (oldProfile != null) {
790            oldData = oldProfile.getData();
791        }
792
793        /*
794         * At the moment we can't rely on the ColorSpace.equals()
795         * and ICC_Profile.equals() because they do not detect
796         * the case when two profiles are created from same data.
797         *
798         * So, we have to do data comparison in order to avoid
799         * creation of different ColorSpace instances for the same
800         * embedded data.
801         */
802        if (oldData == null ||
803            !java.util.Arrays.equals(oldData, newData))
804        {
805            iccCS = new ICC_ColorSpace(newProfile);
806            // verify new color space
807            try {
808                float[] colors = iccCS.fromRGB(new float[] {1f, 0f, 0f});
809            } catch (CMMException e) {
810                /*
811                 * Embedded profile seems to be corrupted.
812                 * Ignore this profile.
813                 */
814                iccCS = null;
815                cbLock.lock();
816                try {
817                    warningOccurred(WARNING_IGNORE_INVALID_ICC);
818                } finally {
819                    cbLock.unlock();
820                }
821            }
822        }
823    }
824
825    public int getWidth(int imageIndex) throws IOException {
826        setThreadLock();
827        try {
828            if (currentImage != imageIndex) {
829                cbLock.check();
830                readHeader(imageIndex, true);
831            }
832            return width;
833        } finally {
834            clearThreadLock();
835        }
836    }
837
838    public int getHeight(int imageIndex) throws IOException {
839        setThreadLock();
840        try {
841            if (currentImage != imageIndex) {
842                cbLock.check();
843                readHeader(imageIndex, true);
844            }
845            return height;
846        } finally {
847            clearThreadLock();
848        }
849    }
850
851    /////////// Color Conversion and Image Types
852
853    /**
854     * Return an ImageTypeSpecifier corresponding to the given
855     * color space code, or null if the color space is unsupported.
856     */
857    private ImageTypeProducer getImageType(int code) {
858        ImageTypeProducer ret = null;
859
860        if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) {
861            ret = ImageTypeProducer.getTypeProducer(code);
862        }
863        return ret;
864    }
865
866    public ImageTypeSpecifier getRawImageType(int imageIndex)
867        throws IOException {
868        setThreadLock();
869        try {
870            if (currentImage != imageIndex) {
871                cbLock.check();
872
873                readHeader(imageIndex, true);
874            }
875
876            // Returns null if it can't be represented
877            return getImageType(colorSpaceCode).getType();
878        } finally {
879            clearThreadLock();
880        }
881    }
882
883    public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex)
884        throws IOException {
885        setThreadLock();
886        try {
887            return getImageTypesOnThread(imageIndex);
888        } finally {
889            clearThreadLock();
890        }
891    }
892
893    private Iterator<ImageTypeSpecifier> getImageTypesOnThread(int imageIndex)
894        throws IOException {
895        if (currentImage != imageIndex) {
896            cbLock.check();
897            readHeader(imageIndex, true);
898        }
899
900        // We return an iterator containing the default, any
901        // conversions that the library provides, and
902        // all the other default types with the same number
903        // of components, as we can do these as a post-process.
904        // As we convert Rasters rather than images, images
905        // with alpha cannot be converted in a post-process.
906
907        // If this image can't be interpreted, this method
908        // returns an empty Iterator.
909
910        // Get the raw ITS, if there is one.  Note that this
911        // won't always be the same as the default.
912        ImageTypeProducer raw = getImageType(colorSpaceCode);
913
914        // Given the encoded colorspace, build a list of ITS's
915        // representing outputs you could handle starting
916        // with the default.
917
918        ArrayList<ImageTypeProducer> list = new ArrayList<ImageTypeProducer>(1);
919
920        switch (colorSpaceCode) {
921        case JPEG.JCS_GRAYSCALE:
922            list.add(raw);
923            list.add(getImageType(JPEG.JCS_RGB));
924            break;
925        case JPEG.JCS_RGB:
926            list.add(raw);
927            list.add(getImageType(JPEG.JCS_GRAYSCALE));
928            list.add(getImageType(JPEG.JCS_YCC));
929            break;
930        case JPEG.JCS_RGBA:
931            list.add(raw);
932            break;
933        case JPEG.JCS_YCC:
934            if (raw != null) {  // Might be null if PYCC.pf not installed
935                list.add(raw);
936                list.add(getImageType(JPEG.JCS_RGB));
937            }
938            break;
939        case JPEG.JCS_YCCA:
940            if (raw != null) {  // Might be null if PYCC.pf not installed
941                list.add(raw);
942            }
943            break;
944        case JPEG.JCS_YCbCr:
945            // As there is no YCbCr ColorSpace, we can't support
946            // the raw type.
947
948            // due to 4705399, use RGB as default in order to avoid
949            // slowing down of drawing operations with result image.
950            list.add(getImageType(JPEG.JCS_RGB));
951
952            if (iccCS != null) {
953                list.add(new ImageTypeProducer() {
954                    protected ImageTypeSpecifier produce() {
955                        return ImageTypeSpecifier.createInterleaved
956                         (iccCS,
957                          JPEG.bOffsRGB,  // Assume it's for RGB
958                          DataBuffer.TYPE_BYTE,
959                          false,
960                          false);
961                    }
962                });
963
964            }
965
966            list.add(getImageType(JPEG.JCS_GRAYSCALE));
967            list.add(getImageType(JPEG.JCS_YCC));
968            break;
969        case JPEG.JCS_YCbCrA:  // Default is to convert to RGBA
970            // As there is no YCbCr ColorSpace, we can't support
971            // the raw type.
972            list.add(getImageType(JPEG.JCS_RGBA));
973            break;
974        }
975
976        return new ImageTypeIterator(list.iterator());
977    }
978
979    /**
980     * Checks the implied color conversion between the stream and
981     * the target image, altering the IJG output color space if necessary.
982     * If a java color conversion is required, then this sets up
983     * {@code convert}.
984     * If bands are being rearranged at all (either source or destination
985     * bands are specified in the param), then the default color
986     * conversions are assumed to be correct.
987     * Throws an IIOException if there is no conversion available.
988     */
989    private void checkColorConversion(BufferedImage image,
990                                      ImageReadParam param)
991        throws IIOException {
992
993        // If we are rearranging channels at all, the default
994        // conversions remain in place.  If the user wants
995        // raw channels then he should do this while reading
996        // a Raster.
997        if (param != null) {
998            if ((param.getSourceBands() != null) ||
999                (param.getDestinationBands() != null)) {
1000                // Accept default conversions out of decoder, silently
1001                return;
1002            }
1003        }
1004
1005        // XXX - We do not currently support any indexed color models,
1006        // though we could, as IJG will quantize for us.
1007        // This is a performance and memory-use issue, as
1008        // users can read RGB and then convert to indexed in Java.
1009
1010        ColorModel cm = image.getColorModel();
1011
1012        if (cm instanceof IndexColorModel) {
1013            throw new IIOException("IndexColorModel not supported");
1014        }
1015
1016        // Now check the ColorSpace type against outColorSpaceCode
1017        // We may want to tweak the default
1018        ColorSpace cs = cm.getColorSpace();
1019        int csType = cs.getType();
1020        convert = null;
1021        switch (outColorSpaceCode) {
1022        case JPEG.JCS_GRAYSCALE:  // Its gray in the file
1023            if  (csType == ColorSpace.TYPE_RGB) { // We want RGB
1024                // IJG can do this for us more efficiently
1025                setOutColorSpace(structPointer, JPEG.JCS_RGB);
1026                // Update java state according to changes
1027                // in the native part of decoder.
1028                outColorSpaceCode = JPEG.JCS_RGB;
1029                numComponents = 3;
1030            } else if (csType != ColorSpace.TYPE_GRAY) {
1031                throw new IIOException("Incompatible color conversion");
1032            }
1033            break;
1034        case JPEG.JCS_RGB:  // IJG wants to go to RGB
1035            if (csType ==  ColorSpace.TYPE_GRAY) {  // We want gray
1036                if (colorSpaceCode == JPEG.JCS_YCbCr) {
1037                    // If the jpeg space is YCbCr, IJG can do it
1038                    setOutColorSpace(structPointer, JPEG.JCS_GRAYSCALE);
1039                    // Update java state according to changes
1040                    // in the native part of decoder.
1041                    outColorSpaceCode = JPEG.JCS_GRAYSCALE;
1042                    numComponents = 1;
1043                }
1044            } else if ((iccCS != null) &&
1045                       (cm.getNumComponents() == numComponents) &&
1046                       (cs != iccCS)) {
1047                // We have an ICC profile but it isn't used in the dest
1048                // image.  So convert from the profile cs to the target cs
1049                convert = new ColorConvertOp(iccCS, cs, null);
1050                // Leave IJG conversion in place; we still need it
1051            } else if ((iccCS == null) &&
1052                       (!cs.isCS_sRGB()) &&
1053                       (cm.getNumComponents() == numComponents)) {
1054                // Target isn't sRGB, so convert from sRGB to the target
1055                convert = new ColorConvertOp(JPEG.JCS.sRGB, cs, null);
1056            } else if (csType != ColorSpace.TYPE_RGB) {
1057                throw new IIOException("Incompatible color conversion");
1058            }
1059            break;
1060        case JPEG.JCS_RGBA:
1061            // No conversions available; image must be RGBA
1062            if ((csType != ColorSpace.TYPE_RGB) ||
1063                (cm.getNumComponents() != numComponents)) {
1064                throw new IIOException("Incompatible color conversion");
1065            }
1066            break;
1067        case JPEG.JCS_YCC:
1068            {
1069                ColorSpace YCC = JPEG.JCS.getYCC();
1070                if (YCC == null) { // We can't do YCC at all
1071                    throw new IIOException("Incompatible color conversion");
1072                }
1073                if ((cs != YCC) &&
1074                    (cm.getNumComponents() == numComponents)) {
1075                    convert = new ColorConvertOp(YCC, cs, null);
1076                }
1077            }
1078            break;
1079        case JPEG.JCS_YCCA:
1080            {
1081                ColorSpace YCC = JPEG.JCS.getYCC();
1082                // No conversions available; image must be YCCA
1083                if ((YCC == null) || // We can't do YCC at all
1084                    (cs != YCC) ||
1085                    (cm.getNumComponents() != numComponents)) {
1086                    throw new IIOException("Incompatible color conversion");
1087                }
1088            }
1089            break;
1090        default:
1091            // Anything else we can't handle at all
1092            throw new IIOException("Incompatible color conversion");
1093        }
1094    }
1095
1096    /**
1097     * Set the IJG output space to the given value.  The library will
1098     * perform the appropriate colorspace conversions.
1099     */
1100    private native void setOutColorSpace(long structPointer, int id);
1101
1102    /////// End of Color Conversion & Image Types
1103
1104    public ImageReadParam getDefaultReadParam() {
1105        return new JPEGImageReadParam();
1106    }
1107
1108    public IIOMetadata getStreamMetadata() throws IOException {
1109        setThreadLock();
1110        try {
1111            if (!tablesOnlyChecked) {
1112                cbLock.check();
1113                checkTablesOnly();
1114            }
1115            return streamMetadata;
1116        } finally {
1117            clearThreadLock();
1118        }
1119    }
1120
1121    public IIOMetadata getImageMetadata(int imageIndex)
1122        throws IOException {
1123        setThreadLock();
1124        try {
1125            // imageMetadataIndex will always be either a valid index or
1126            // -1, in which case imageMetadata will not be null.
1127            // So we can leave checking imageIndex for gotoImage.
1128            if ((imageMetadataIndex == imageIndex)
1129                && (imageMetadata != null)) {
1130                return imageMetadata;
1131            }
1132
1133            cbLock.check();
1134
1135            gotoImage(imageIndex);
1136
1137            imageMetadata = new JPEGMetadata(false, false, iis, this);
1138
1139            imageMetadataIndex = imageIndex;
1140
1141            return imageMetadata;
1142        } finally {
1143            clearThreadLock();
1144        }
1145    }
1146
1147    public BufferedImage read(int imageIndex, ImageReadParam param)
1148        throws IOException {
1149        setThreadLock();
1150        try {
1151            cbLock.check();
1152            try {
1153                readInternal(imageIndex, param, false);
1154            } catch (RuntimeException e) {
1155                resetLibraryState(structPointer);
1156                throw e;
1157            } catch (IOException e) {
1158                resetLibraryState(structPointer);
1159                throw e;
1160            }
1161
1162            BufferedImage ret = image;
1163            image = null;  // don't keep a reference here
1164            return ret;
1165        } finally {
1166            clearThreadLock();
1167        }
1168    }
1169
1170    private Raster readInternal(int imageIndex,
1171                                ImageReadParam param,
1172                                boolean wantRaster) throws IOException {
1173        readHeader(imageIndex, false);
1174
1175        WritableRaster imRas = null;
1176        int numImageBands = 0;
1177
1178        if (!wantRaster){
1179            // Can we read this image?
1180            Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
1181            if (imageTypes.hasNext() == false) {
1182                throw new IIOException("Unsupported Image Type");
1183            }
1184
1185            image = getDestination(param, imageTypes, width, height);
1186            imRas = image.getRaster();
1187
1188            // The destination may still be incompatible.
1189
1190            numImageBands = image.getSampleModel().getNumBands();
1191
1192            // Check whether we can handle any implied color conversion
1193
1194            // Throws IIOException if the stream and the image are
1195            // incompatible, and sets convert if a java conversion
1196            // is necessary
1197            checkColorConversion(image, param);
1198
1199            // Check the source and destination bands in the param
1200            checkReadParamBandSettings(param, numComponents, numImageBands);
1201        } else {
1202            // Set the output color space equal to the input colorspace
1203            // This disables all conversions
1204            setOutColorSpace(structPointer, colorSpaceCode);
1205            image = null;
1206        }
1207
1208        // Create an intermediate 1-line Raster that will hold the decoded,
1209        // subsampled, clipped, band-selected image data in a single
1210        // byte-interleaved buffer.  The above transformations
1211        // will occur in C for performance.  Every time this Raster
1212        // is filled we will call back to acceptPixels below to copy
1213        // this to whatever kind of buffer our image has.
1214
1215        int [] srcBands = JPEG.bandOffsets[numComponents-1];
1216        int numRasterBands = (wantRaster ? numComponents : numImageBands);
1217        destinationBands = null;
1218
1219        Rectangle srcROI = new Rectangle(0, 0, 0, 0);
1220        destROI = new Rectangle(0, 0, 0, 0);
1221        computeRegions(param, width, height, image, srcROI, destROI);
1222
1223        int periodX = 1;
1224        int periodY = 1;
1225
1226        minProgressivePass = 0;
1227        maxProgressivePass = Integer.MAX_VALUE;
1228
1229        if (param != null) {
1230            periodX = param.getSourceXSubsampling();
1231            periodY = param.getSourceYSubsampling();
1232
1233            int[] sBands = param.getSourceBands();
1234            if (sBands != null) {
1235                srcBands = sBands;
1236                numRasterBands = srcBands.length;
1237            }
1238            if (!wantRaster) {  // ignore dest bands for Raster
1239                destinationBands = param.getDestinationBands();
1240            }
1241
1242            minProgressivePass = param.getSourceMinProgressivePass();
1243            maxProgressivePass = param.getSourceMaxProgressivePass();
1244
1245            if (param instanceof JPEGImageReadParam) {
1246                JPEGImageReadParam jparam = (JPEGImageReadParam) param;
1247                if (jparam.areTablesSet()) {
1248                    abbrevQTables = jparam.getQTables();
1249                    abbrevDCHuffmanTables = jparam.getDCHuffmanTables();
1250                    abbrevACHuffmanTables = jparam.getACHuffmanTables();
1251                }
1252            }
1253        }
1254
1255        int lineSize = destROI.width*numRasterBands;
1256
1257        buffer = new DataBufferByte(lineSize);
1258
1259        int [] bandOffs = JPEG.bandOffsets[numRasterBands-1];
1260
1261        raster = Raster.createInterleavedRaster(buffer,
1262                                                destROI.width, 1,
1263                                                lineSize,
1264                                                numRasterBands,
1265                                                bandOffs,
1266                                                null);
1267
1268        // Now that we have the Raster we'll decode to, get a view of the
1269        // target Raster that will permit a simple setRect for each scanline
1270        if (wantRaster) {
1271            target =  Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
1272                                                     destROI.width,
1273                                                     destROI.height,
1274                                                     lineSize,
1275                                                     numRasterBands,
1276                                                     bandOffs,
1277                                                     null);
1278        } else {
1279            target = imRas;
1280        }
1281        int [] bandSizes = target.getSampleModel().getSampleSize();
1282        for (int i = 0; i < bandSizes.length; i++) {
1283            if (bandSizes[i] <= 0 || bandSizes[i] > 8) {
1284                throw new IIOException("Illegal band size: should be 0 < size <= 8");
1285            }
1286        }
1287
1288        /*
1289         * If the process is sequential, and we have restart markers,
1290         * we could skip to the correct restart marker, if the library
1291         * lets us.  That's an optimization to investigate later.
1292         */
1293
1294        // Check for update listeners (don't call back if none)
1295        boolean callbackUpdates = ((updateListeners != null)
1296                                   || (progressListeners != null));
1297
1298        // Set up progression data
1299        initProgressData();
1300        // if we have a metadata object, we can count the scans
1301        // and set knownPassCount
1302        if (imageIndex == imageMetadataIndex) { // We have metadata
1303            knownPassCount = 0;
1304            for (Iterator<MarkerSegment> iter =
1305                    imageMetadata.markerSequence.iterator(); iter.hasNext();) {
1306                if (iter.next() instanceof SOSMarkerSegment) {
1307                    knownPassCount++;
1308                }
1309            }
1310        }
1311        progInterval = Math.max((target.getHeight()-1) / 20, 1);
1312        if (knownPassCount > 0) {
1313            progInterval *= knownPassCount;
1314        } else if (maxProgressivePass != Integer.MAX_VALUE) {
1315            progInterval *= (maxProgressivePass - minProgressivePass + 1);
1316        }
1317
1318        if (debug) {
1319            System.out.println("**** Read Data *****");
1320            System.out.println("numRasterBands is " + numRasterBands);
1321            System.out.print("srcBands:");
1322            for (int i = 0; i<srcBands.length;i++)
1323                System.out.print(" " + srcBands[i]);
1324            System.out.println();
1325            System.out.println("destination bands is " + destinationBands);
1326            if (destinationBands != null) {
1327                for (int i = 0; i < destinationBands.length; i++) {
1328                    System.out.print(" " + destinationBands[i]);
1329                }
1330                System.out.println();
1331            }
1332            System.out.println("sourceROI is " + srcROI);
1333            System.out.println("destROI is " + destROI);
1334            System.out.println("periodX is " + periodX);
1335            System.out.println("periodY is " + periodY);
1336            System.out.println("minProgressivePass is " + minProgressivePass);
1337            System.out.println("maxProgressivePass is " + maxProgressivePass);
1338            System.out.println("callbackUpdates is " + callbackUpdates);
1339        }
1340
1341        /*
1342         * All the Jpeg processing happens in native, we should clear
1343         * abortFlag of imageIODataStruct in imageioJPEG.c. And we need to
1344         * clear abortFlag because if in previous read() if we had called
1345         * reader.abort() that will continue to be valid for present call also.
1346         */
1347        clearNativeReadAbortFlag(structPointer);
1348        processImageStarted(currentImage);
1349        /*
1350         * Note that getData disables acceleration on buffer, but it is
1351         * just a 1-line intermediate data transfer buffer that will not
1352         * affect the acceleration of the resulting image.
1353         */
1354        boolean aborted = readImage(imageIndex,
1355                                    structPointer,
1356                                    buffer.getData(),
1357                                    numRasterBands,
1358                                    srcBands,
1359                                    bandSizes,
1360                                    srcROI.x, srcROI.y,
1361                                    srcROI.width, srcROI.height,
1362                                    periodX, periodY,
1363                                    abbrevQTables,
1364                                    abbrevDCHuffmanTables,
1365                                    abbrevACHuffmanTables,
1366                                    minProgressivePass, maxProgressivePass,
1367                                    callbackUpdates);
1368
1369        if (aborted) {
1370            processReadAborted();
1371        } else {
1372            processImageComplete();
1373        }
1374
1375        return target;
1376
1377    }
1378
1379    /**
1380     * This method is called back from C when the intermediate Raster
1381     * is full.  The parameter indicates the scanline in the target
1382     * Raster to which the intermediate Raster should be copied.
1383     * After the copy, we notify update listeners.
1384     */
1385    private void acceptPixels(int y, boolean progressive) {
1386        if (convert != null) {
1387            convert.filter(raster, raster);
1388        }
1389        target.setRect(destROI.x, destROI.y + y, raster);
1390
1391        cbLock.lock();
1392        try {
1393            processImageUpdate(image,
1394                               destROI.x, destROI.y+y,
1395                               raster.getWidth(), 1,
1396                               1, 1,
1397                               destinationBands);
1398            if ((y > 0) && (y%progInterval == 0)) {
1399                int height = target.getHeight()-1;
1400                float percentOfPass = ((float)y)/height;
1401                if (progressive) {
1402                    if (knownPassCount != UNKNOWN) {
1403                        processImageProgress((pass + percentOfPass)*100.0F
1404                                             / knownPassCount);
1405                    } else if (maxProgressivePass != Integer.MAX_VALUE) {
1406                        // Use the range of allowed progressive passes
1407                        processImageProgress((pass + percentOfPass)*100.0F
1408                                             / (maxProgressivePass - minProgressivePass + 1));
1409                    } else {
1410                        // Assume there are a minimum of MIN_ESTIMATED_PASSES
1411                        // and that there is always one more pass
1412                        // Compute the percentage as the percentage at the end
1413                        // of the previous pass, plus the percentage of this
1414                        // pass scaled to be the percentage of the total remaining,
1415                        // assuming a minimum of MIN_ESTIMATED_PASSES passes and
1416                        // that there is always one more pass.  This is monotonic
1417                        // and asymptotic to 1.0, which is what we need.
1418                        int remainingPasses = // including this one
1419                            Math.max(2, MIN_ESTIMATED_PASSES-pass);
1420                        int totalPasses = pass + remainingPasses-1;
1421                        progInterval = Math.max(height/20*totalPasses,
1422                                                totalPasses);
1423                        if (y%progInterval == 0) {
1424                            percentToDate = previousPassPercentage +
1425                                (1.0F - previousPassPercentage)
1426                                * (percentOfPass)/remainingPasses;
1427                            if (debug) {
1428                                System.out.print("pass= " + pass);
1429                                System.out.print(", y= " + y);
1430                                System.out.print(", progInt= " + progInterval);
1431                                System.out.print(", % of pass: " + percentOfPass);
1432                                System.out.print(", rem. passes: "
1433                                                 + remainingPasses);
1434                                System.out.print(", prev%: "
1435                                                 + previousPassPercentage);
1436                                System.out.print(", %ToDate: " + percentToDate);
1437                                System.out.print(" ");
1438                            }
1439                            processImageProgress(percentToDate*100.0F);
1440                        }
1441                    }
1442                } else {
1443                    processImageProgress(percentOfPass * 100.0F);
1444                }
1445            }
1446        } finally {
1447            cbLock.unlock();
1448        }
1449    }
1450
1451    private void initProgressData() {
1452        knownPassCount = UNKNOWN;
1453        pass = 0;
1454        percentToDate = 0.0F;
1455        previousPassPercentage = 0.0F;
1456        progInterval = 0;
1457    }
1458
1459    private void passStarted (int pass) {
1460        cbLock.lock();
1461        try {
1462            this.pass = pass;
1463            previousPassPercentage = percentToDate;
1464            processPassStarted(image,
1465                               pass,
1466                               minProgressivePass,
1467                               maxProgressivePass,
1468                               0, 0,
1469                               1,1,
1470                               destinationBands);
1471        } finally {
1472            cbLock.unlock();
1473        }
1474    }
1475
1476    private void passComplete () {
1477        cbLock.lock();
1478        try {
1479            processPassComplete(image);
1480        } finally {
1481            cbLock.unlock();
1482        }
1483    }
1484
1485    void thumbnailStarted(int thumbnailIndex) {
1486        cbLock.lock();
1487        try {
1488            processThumbnailStarted(currentImage, thumbnailIndex);
1489        } finally {
1490            cbLock.unlock();
1491        }
1492    }
1493
1494    // Provide access to protected superclass method
1495    void thumbnailProgress(float percentageDone) {
1496        cbLock.lock();
1497        try {
1498            processThumbnailProgress(percentageDone);
1499        } finally {
1500            cbLock.unlock();
1501        }
1502    }
1503
1504    // Provide access to protected superclass method
1505    void thumbnailComplete() {
1506        cbLock.lock();
1507        try {
1508            processThumbnailComplete();
1509        } finally {
1510            cbLock.unlock();
1511        }
1512    }
1513
1514    /**
1515     * Returns {@code true} if the read was aborted.
1516     */
1517    private native boolean readImage(int imageIndex,
1518                                     long structPointer,
1519                                     byte [] buffer,
1520                                     int numRasterBands,
1521                                     int [] srcBands,
1522                                     int [] bandSizes,
1523                                     int sourceXOffset, int sourceYOffset,
1524                                     int sourceWidth, int sourceHeight,
1525                                     int periodX, int periodY,
1526                                     JPEGQTable [] abbrevQTables,
1527                                     JPEGHuffmanTable [] abbrevDCHuffmanTables,
1528                                     JPEGHuffmanTable [] abbrevACHuffmanTables,
1529                                     int minProgressivePass,
1530                                     int maxProgressivePass,
1531                                     boolean wantUpdates);
1532
1533    /*
1534     * We should call clearNativeReadAbortFlag() before we start reading
1535     * jpeg image as image processing happens at native side.
1536     */
1537    private native void clearNativeReadAbortFlag(long structPointer);
1538
1539    public void abort() {
1540        setThreadLock();
1541        try {
1542            /**
1543             * NB: we do not check the call back lock here,
1544             * we allow to abort the reader any time.
1545             */
1546
1547            super.abort();
1548            abortRead(structPointer);
1549        } finally {
1550            clearThreadLock();
1551        }
1552    }
1553
1554    /** Set the C level abort flag. Keep it atomic for thread safety. */
1555    private native void abortRead(long structPointer);
1556
1557    /** Resets library state when an exception occurred during a read. */
1558    private native void resetLibraryState(long structPointer);
1559
1560    public boolean canReadRaster() {
1561        return true;
1562    }
1563
1564    public Raster readRaster(int imageIndex, ImageReadParam param)
1565        throws IOException {
1566        setThreadLock();
1567        Raster retval = null;
1568        try {
1569            cbLock.check();
1570            /*
1571             * This could be further optimized by not resetting the dest.
1572             * offset and creating a translated raster in readInternal()
1573             * (see bug 4994702 for more info).
1574             */
1575
1576            // For Rasters, destination offset is logical, not physical, so
1577            // set it to 0 before calling computeRegions, so that the destination
1578            // region is not clipped.
1579            Point saveDestOffset = null;
1580            if (param != null) {
1581                saveDestOffset = param.getDestinationOffset();
1582                param.setDestinationOffset(new Point(0, 0));
1583            }
1584            retval = readInternal(imageIndex, param, true);
1585            // Apply the destination offset, if any, as a logical offset
1586            if (saveDestOffset != null) {
1587                target = target.createWritableTranslatedChild(saveDestOffset.x,
1588                                                              saveDestOffset.y);
1589            }
1590        } catch (RuntimeException e) {
1591            resetLibraryState(structPointer);
1592            throw e;
1593        } catch (IOException e) {
1594            resetLibraryState(structPointer);
1595            throw e;
1596        } finally {
1597            clearThreadLock();
1598        }
1599        return retval;
1600    }
1601
1602    public boolean readerSupportsThumbnails() {
1603        return true;
1604    }
1605
1606    public int getNumThumbnails(int imageIndex) throws IOException {
1607        setThreadLock();
1608        try {
1609            cbLock.check();
1610
1611            getImageMetadata(imageIndex);  // checks iis state for us
1612            // Now check the jfif segments
1613            JFIFMarkerSegment jfif =
1614                (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1615                (JFIFMarkerSegment.class, true);
1616            int retval = 0;
1617            if (jfif != null) {
1618                retval = (jfif.thumb == null) ? 0 : 1;
1619                retval += jfif.extSegments.size();
1620            }
1621            return retval;
1622        } finally {
1623            clearThreadLock();
1624        }
1625    }
1626
1627    public int getThumbnailWidth(int imageIndex, int thumbnailIndex)
1628        throws IOException {
1629        setThreadLock();
1630        try {
1631            cbLock.check();
1632
1633            if ((thumbnailIndex < 0)
1634                || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1635                throw new IndexOutOfBoundsException("No such thumbnail");
1636            }
1637            // Now we know that there is a jfif segment
1638            JFIFMarkerSegment jfif =
1639                (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1640                (JFIFMarkerSegment.class, true);
1641            return  jfif.getThumbnailWidth(thumbnailIndex);
1642        } finally {
1643            clearThreadLock();
1644        }
1645    }
1646
1647    public int getThumbnailHeight(int imageIndex, int thumbnailIndex)
1648        throws IOException {
1649        setThreadLock();
1650        try {
1651            cbLock.check();
1652
1653            if ((thumbnailIndex < 0)
1654                || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1655                throw new IndexOutOfBoundsException("No such thumbnail");
1656            }
1657            // Now we know that there is a jfif segment
1658            JFIFMarkerSegment jfif =
1659                (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1660                (JFIFMarkerSegment.class, true);
1661            return  jfif.getThumbnailHeight(thumbnailIndex);
1662        } finally {
1663            clearThreadLock();
1664        }
1665    }
1666
1667    public BufferedImage readThumbnail(int imageIndex,
1668                                       int thumbnailIndex)
1669        throws IOException {
1670        setThreadLock();
1671        try {
1672            cbLock.check();
1673
1674            if ((thumbnailIndex < 0)
1675                || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1676                throw new IndexOutOfBoundsException("No such thumbnail");
1677            }
1678            // Now we know that there is a jfif segment and that iis is good
1679            JFIFMarkerSegment jfif =
1680                (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1681                (JFIFMarkerSegment.class, true);
1682            return  jfif.getThumbnail(iis, thumbnailIndex, this);
1683        } finally {
1684            clearThreadLock();
1685        }
1686    }
1687
1688    private void resetInternalState() {
1689        // reset C structures
1690        resetReader(structPointer);
1691
1692        // reset local Java structures
1693        numImages = 0;
1694        imagePositions = new ArrayList<>();
1695        currentImage = -1;
1696        image = null;
1697        raster = null;
1698        target = null;
1699        buffer = null;
1700        destROI = null;
1701        destinationBands = null;
1702        streamMetadata = null;
1703        imageMetadata = null;
1704        imageMetadataIndex = -1;
1705        haveSeeked = false;
1706        tablesOnlyChecked = false;
1707        iccCS = null;
1708        initProgressData();
1709    }
1710
1711    public void reset() {
1712        setThreadLock();
1713        try {
1714            cbLock.check();
1715            super.reset();
1716        } finally {
1717            clearThreadLock();
1718        }
1719    }
1720
1721    private native void resetReader(long structPointer);
1722
1723    public void dispose() {
1724        setThreadLock();
1725        try {
1726            cbLock.check();
1727
1728            if (structPointer != 0) {
1729                disposerRecord.dispose();
1730                structPointer = 0;
1731            }
1732        } finally {
1733            clearThreadLock();
1734        }
1735    }
1736
1737    private static native void disposeReader(long structPointer);
1738
1739    private static class JPEGReaderDisposerRecord implements DisposerRecord {
1740        private long pData;
1741
1742        public JPEGReaderDisposerRecord(long pData) {
1743            this.pData = pData;
1744        }
1745
1746        public synchronized void dispose() {
1747            if (pData != 0) {
1748                disposeReader(pData);
1749                pData = 0;
1750            }
1751        }
1752    }
1753
1754    private Thread theThread = null;
1755    private int theLockCount = 0;
1756
1757    private synchronized void setThreadLock() {
1758        Thread currThread = Thread.currentThread();
1759        if (theThread != null) {
1760            if (theThread != currThread) {
1761                // it looks like that this reader instance is used
1762                // by multiple threads.
1763                throw new IllegalStateException("Attempt to use instance of " +
1764                                                this + " locked on thread " +
1765                                                theThread + " from thread " +
1766                                                currThread);
1767            } else {
1768                theLockCount ++;
1769            }
1770        } else {
1771            theThread = currThread;
1772            theLockCount = 1;
1773        }
1774    }
1775
1776    private synchronized void clearThreadLock() {
1777        Thread currThread = Thread.currentThread();
1778        if (theThread == null || theThread != currThread) {
1779            throw new IllegalStateException("Attempt to clear thread lock " +
1780                                            " form wrong thread." +
1781                                            " Locked thread: " + theThread +
1782                                            "; current thread: " + currThread);
1783        }
1784        theLockCount --;
1785        if (theLockCount == 0) {
1786            theThread = null;
1787        }
1788    }
1789
1790    private CallBackLock cbLock = new CallBackLock();
1791
1792    private static class CallBackLock {
1793
1794        private State lockState;
1795
1796        CallBackLock() {
1797            lockState = State.Unlocked;
1798        }
1799
1800        void check() {
1801            if (lockState != State.Unlocked) {
1802                throw new IllegalStateException("Access to the reader is not allowed");
1803            }
1804        }
1805
1806        private void lock() {
1807            lockState = State.Locked;
1808        }
1809
1810        private void unlock() {
1811            lockState = State.Unlocked;
1812        }
1813
1814        private static enum State {
1815            Unlocked,
1816            Locked
1817        }
1818    }
1819}
1820
1821/**
1822 * An internal helper class that wraps producer's iterator
1823 * and extracts specifier instances on demand.
1824 */
1825class ImageTypeIterator implements Iterator<ImageTypeSpecifier> {
1826     private Iterator<ImageTypeProducer> producers;
1827     private ImageTypeSpecifier theNext = null;
1828
1829     public ImageTypeIterator(Iterator<ImageTypeProducer> producers) {
1830         this.producers = producers;
1831     }
1832
1833     public boolean hasNext() {
1834         if (theNext != null) {
1835             return true;
1836         }
1837         if (!producers.hasNext()) {
1838             return false;
1839         }
1840         do {
1841             theNext = producers.next().getType();
1842         } while (theNext == null && producers.hasNext());
1843
1844         return (theNext != null);
1845     }
1846
1847     public ImageTypeSpecifier next() {
1848         if (theNext != null || hasNext()) {
1849             ImageTypeSpecifier t = theNext;
1850             theNext = null;
1851             return t;
1852         } else {
1853             throw new NoSuchElementException();
1854         }
1855     }
1856
1857     public void remove() {
1858         producers.remove();
1859     }
1860}
1861
1862/**
1863 * An internal helper class that provides means for deferred creation
1864 * of ImageTypeSpecifier instance required to describe available
1865 * destination types.
1866 *
1867 * This implementation only supports standard
1868 * jpeg color spaces (defined by corresponding JCS color space code).
1869 *
1870 * To support other color spaces one can override produce() method to
1871 * return custom instance of ImageTypeSpecifier.
1872 */
1873class ImageTypeProducer {
1874
1875    private ImageTypeSpecifier type = null;
1876    boolean failed = false;
1877    private int csCode;
1878
1879    public ImageTypeProducer(int csCode) {
1880        this.csCode = csCode;
1881    }
1882
1883    public ImageTypeProducer() {
1884        csCode = -1; // undefined
1885    }
1886
1887    public synchronized ImageTypeSpecifier getType() {
1888        if (!failed && type == null) {
1889            try {
1890                type = produce();
1891            } catch (Throwable e) {
1892                failed = true;
1893            }
1894        }
1895        return type;
1896    }
1897
1898    private static final ImageTypeProducer [] defaultTypes =
1899            new ImageTypeProducer [JPEG.NUM_JCS_CODES];
1900
1901    public static synchronized ImageTypeProducer getTypeProducer(int csCode) {
1902        if (csCode < 0 || csCode >= JPEG.NUM_JCS_CODES) {
1903            return null;
1904        }
1905        if (defaultTypes[csCode] == null) {
1906            defaultTypes[csCode] = new ImageTypeProducer(csCode);
1907        }
1908        return defaultTypes[csCode];
1909    }
1910
1911    protected ImageTypeSpecifier produce() {
1912        switch (csCode) {
1913            case JPEG.JCS_GRAYSCALE:
1914                return ImageTypeSpecifier.createFromBufferedImageType
1915                        (BufferedImage.TYPE_BYTE_GRAY);
1916            case JPEG.JCS_YCbCr:
1917            //there is no YCbCr raw type so by default we assume it as RGB
1918            case JPEG.JCS_RGB:
1919                return ImageTypeSpecifier.createInterleaved(JPEG.JCS.sRGB,
1920                        JPEG.bOffsRGB,
1921                        DataBuffer.TYPE_BYTE,
1922                        false,
1923                        false);
1924            case JPEG.JCS_RGBA:
1925                return ImageTypeSpecifier.createPacked(JPEG.JCS.sRGB,
1926                        0xff000000,
1927                        0x00ff0000,
1928                        0x0000ff00,
1929                        0x000000ff,
1930                        DataBuffer.TYPE_INT,
1931                        false);
1932            case JPEG.JCS_YCC:
1933                if (JPEG.JCS.getYCC() != null) {
1934                    return ImageTypeSpecifier.createInterleaved(
1935                            JPEG.JCS.getYCC(),
1936                        JPEG.bandOffsets[2],
1937                        DataBuffer.TYPE_BYTE,
1938                        false,
1939                        false);
1940                } else {
1941                    return null;
1942                }
1943            case JPEG.JCS_YCCA:
1944                if (JPEG.JCS.getYCC() != null) {
1945                    return ImageTypeSpecifier.createInterleaved(
1946                            JPEG.JCS.getYCC(),
1947                        JPEG.bandOffsets[3],
1948                        DataBuffer.TYPE_BYTE,
1949                        true,
1950                        false);
1951                } else {
1952                    return null;
1953                }
1954            default:
1955                return null;
1956        }
1957    }
1958}
1959