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