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