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