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