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