rev 9343 : 8033716: Fix raw and unchecked lint warnings in com.sun.imageio
Reviewed-by: darcy, prr, bae

   1 /*
   2  * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.imageio.plugins.jpeg;
  27 
  28 import javax.imageio.IIOException;
  29 import javax.imageio.ImageReader;
  30 import javax.imageio.ImageReadParam;
  31 import javax.imageio.ImageTypeSpecifier;
  32 import javax.imageio.metadata.IIOMetadata;
  33 import javax.imageio.spi.ImageReaderSpi;
  34 import javax.imageio.stream.ImageInputStream;
  35 import javax.imageio.plugins.jpeg.JPEGImageReadParam;
  36 import javax.imageio.plugins.jpeg.JPEGQTable;
  37 import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
  38 
  39 import java.awt.Point;
  40 import java.awt.Rectangle;
  41 import java.awt.color.ColorSpace;
  42 import java.awt.color.ICC_Profile;
  43 import java.awt.color.ICC_ColorSpace;
  44 import java.awt.color.CMMException;
  45 import java.awt.image.BufferedImage;
  46 import java.awt.image.Raster;
  47 import java.awt.image.WritableRaster;
  48 import java.awt.image.DataBuffer;
  49 import java.awt.image.DataBufferByte;
  50 import java.awt.image.ColorModel;
  51 import java.awt.image.IndexColorModel;
  52 import java.awt.image.ColorConvertOp;
  53 import java.io.IOException;
  54 import java.util.List;
  55 import java.util.Iterator;
  56 import java.util.ArrayList;
  57 import java.util.NoSuchElementException;
  58 
  59 import sun.java2d.Disposer;
  60 import sun.java2d.DisposerRecord;
  61 
  62 public class JPEGImageReader extends ImageReader {
  63 
  64     private boolean debug = false;
  65 
  66     /**
  67      * The following variable contains a pointer to the IJG library
  68      * structure for this reader.  It is assigned in the constructor
  69      * and then is passed in to every native call.  It is set to 0
  70      * by dispose to avoid disposing twice.
  71      */
  72     private long structPointer = 0;
  73 
  74     /** The input stream we read from */
  75     private ImageInputStream iis = null;
  76 
  77     /**
  78      * List of stream positions for images, reinitialized every time
  79      * a new input source is set.
  80      */
  81     private List imagePositions = null;
  82 
  83     /**
  84      * The number of images in the stream, or 0.
  85      */
  86     private int numImages = 0;
  87 
  88     static {
  89         java.security.AccessController.doPrivileged(
  90             new java.security.PrivilegedAction<Void>() {
  91                 public Void run() {
  92                     System.loadLibrary("jpeg");
  93                     return null;
  94                 }
  95             });
  96         initReaderIDs(ImageInputStream.class,
  97                       JPEGQTable.class,
  98                       JPEGHuffmanTable.class);
  99     }
 100 
 101     // The following warnings are converted to strings when used
 102     // as keys to get localized resources from JPEGImageReaderResources
 103     // and its children.
 104 
 105     /**
 106      * Warning code to be passed to warningOccurred to indicate
 107      * that the EOI marker is missing from the end of the stream.
 108      * This usually signals that the stream is corrupted, but
 109      * everything up to the last MCU should be usable.
 110      */
 111     protected static final int WARNING_NO_EOI = 0;
 112 
 113     /**
 114      * Warning code to be passed to warningOccurred to indicate
 115      * that a JFIF segment was encountered inside a JFXX JPEG
 116      * thumbnail and is being ignored.
 117      */
 118     protected static final int WARNING_NO_JFIF_IN_THUMB = 1;
 119 
 120     /**
 121      * Warning code to be passed to warningOccurred to indicate
 122      * that embedded ICC profile is invalid and will be ignored.
 123      */
 124     protected static final int WARNING_IGNORE_INVALID_ICC = 2;
 125 
 126     private static final int MAX_WARNING = WARNING_IGNORE_INVALID_ICC;
 127 
 128     /**
 129      * Image index of image for which header information
 130      * is available.
 131      */
 132     private int currentImage = -1;
 133 
 134     // The following is copied out from C after reading the header.
 135     // Unlike metadata, which may never be retrieved, we need this
 136     // if we are to read an image at all.
 137 
 138     /** Set by setImageData native code callback */
 139     private int width;
 140     /** Set by setImageData native code callback */
 141     private int height;
 142     /**
 143      * Set by setImageData native code callback.  A modified
 144      * IJG+NIFTY colorspace code.
 145      */
 146     private int colorSpaceCode;
 147     /**
 148      * Set by setImageData native code callback.  A modified
 149      * IJG+NIFTY colorspace code.
 150      */
 151     private int outColorSpaceCode;
 152     /** Set by setImageData native code callback */
 153     private int numComponents;
 154     /** Set by setImageData native code callback */
 155     private ColorSpace iccCS = null;
 156 
 157 
 158     /** If we need to post-convert in Java, convert with this op */
 159     private ColorConvertOp convert = null;
 160 
 161     /** The image we are going to fill */
 162     private BufferedImage image = null;
 163 
 164     /** An intermediate Raster to hold decoded data */
 165     private WritableRaster raster = null;
 166 
 167     /** A view of our target Raster that we can setRect to */
 168     private WritableRaster target = null;
 169 
 170     /** The databuffer for the above Raster */
 171     private DataBufferByte buffer = null;
 172 
 173     /** The region in the destination where we will write pixels */
 174     private Rectangle destROI = null;
 175 
 176     /** The list of destination bands, if any */
 177     private int [] destinationBands = null;
 178 
 179     /** Stream metadata, cached, even when the stream is changed. */
 180     private JPEGMetadata streamMetadata = null;
 181 
 182     /** Image metadata, valid for the imageMetadataIndex only. */
 183     private JPEGMetadata imageMetadata = null;
 184     private int imageMetadataIndex = -1;
 185 
 186     /**
 187      * Set to true every time we seek in the stream; used to
 188      * invalidate the native buffer contents in C.
 189      */
 190     private boolean haveSeeked = false;
 191 
 192     /**
 193      * Tables that have been read from a tables-only image at the
 194      * beginning of a stream.
 195      */
 196     private JPEGQTable [] abbrevQTables = null;
 197     private JPEGHuffmanTable[] abbrevDCHuffmanTables = null;
 198     private JPEGHuffmanTable[] abbrevACHuffmanTables = null;
 199 
 200     private int minProgressivePass = 0;
 201     private int maxProgressivePass = Integer.MAX_VALUE;
 202 
 203     /**
 204      * Variables used by progress monitoring.
 205      */
 206     private static final int UNKNOWN = -1;  // Number of passes
 207     private static final int MIN_ESTIMATED_PASSES = 10; // IJG default
 208     private int knownPassCount = UNKNOWN;
 209     private int pass = 0;
 210     private float percentToDate = 0.0F;
 211     private float previousPassPercentage = 0.0F;
 212     private int progInterval = 0;
 213 
 214     /**
 215      * Set to true once stream has been checked for stream metadata
 216      */
 217     private boolean tablesOnlyChecked = false;
 218 
 219     /** The referent to be registered with the Disposer. */
 220     private Object disposerReferent = new Object();
 221 
 222     /** The DisposerRecord that handles the actual disposal of this reader. */
 223     private DisposerRecord disposerRecord;
 224 
 225     /** Sets up static C structures. */
 226     private static native void initReaderIDs(Class iisClass,
 227                                              Class qTableClass,
 228                                              Class huffClass);
 229 
 230     public JPEGImageReader(ImageReaderSpi originator) {
 231         super(originator);
 232         structPointer = initJPEGImageReader();
 233         disposerRecord = new JPEGReaderDisposerRecord(structPointer);
 234         Disposer.addRecord(disposerReferent, disposerRecord);
 235     }
 236 
 237     /** Sets up per-reader C structure and returns a pointer to it. */
 238     private native long initJPEGImageReader();
 239 
 240     /**
 241      * Called by the native code or other classes to signal a warning.
 242      * The code is used to lookup a localized message to be used when
 243      * sending warnings to listeners.
 244      */
 245     protected void warningOccurred(int code) {
 246         cbLock.lock();
 247         try {
 248             if ((code < 0) || (code > MAX_WARNING)){
 249                 throw new InternalError("Invalid warning index");
 250             }
 251             processWarningOccurred
 252                 ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
 253                  Integer.toString(code));
 254         } finally {
 255             cbLock.unlock();
 256         }
 257     }
 258 
 259     /**
 260      * The library has it's own error facility that emits warning messages.
 261      * This routine is called by the native code when it has already
 262      * formatted a string for output.
 263      * XXX  For truly complete localization of all warning messages,
 264      * the sun_jpeg_output_message routine in the native code should
 265      * send only the codes and parameters to a method here in Java,
 266      * which will then format and send the warnings, using localized
 267      * strings.  This method will have to deal with all the parameters
 268      * and formats (%u with possibly large numbers, %02d, %02x, etc.)
 269      * that actually occur in the JPEG library.  For now, this prevents
 270      * library warnings from being printed to stderr.
 271      */
 272     protected void warningWithMessage(String msg) {
 273         cbLock.lock();
 274         try {
 275             processWarningOccurred(msg);
 276         } finally {
 277             cbLock.unlock();
 278         }
 279     }
 280 
 281     public void setInput(Object input,
 282                          boolean seekForwardOnly,
 283                          boolean ignoreMetadata)
 284     {
 285         setThreadLock();
 286         try {
 287             cbLock.check();
 288 
 289             super.setInput(input, seekForwardOnly, ignoreMetadata);
 290             this.ignoreMetadata = ignoreMetadata;
 291             resetInternalState();
 292             iis = (ImageInputStream) input; // Always works
 293             setSource(structPointer);
 294         } finally {
 295             clearThreadLock();
 296         }
 297     }
 298 
 299     /**
 300      * This method is called from native code in order to fill
 301      * native input buffer.
 302      *
 303      * We block any attempt to change the reading state during this
 304      * method, in order to prevent a corruption of the native decoder
 305      * state.
 306      *
 307      * @return number of bytes read from the stream.
 308      */
 309     private int readInputData(byte[] buf, int off, int len) throws IOException {
 310         cbLock.lock();
 311         try {
 312             return iis.read(buf, off, len);
 313         } finally {
 314             cbLock.unlock();
 315         }
 316     }
 317 
 318     /**
 319      * This method is called from the native code in order to
 320      * skip requested number of bytes in the input stream.
 321      *
 322      * @param n
 323      * @return
 324      * @throws IOException
 325      */
 326     private long skipInputBytes(long n) throws IOException {
 327         cbLock.lock();
 328         try {
 329             return iis.skipBytes(n);
 330         } finally {
 331             cbLock.unlock();
 332         }
 333     }
 334 
 335     private native void setSource(long structPointer);
 336 
 337     private void checkTablesOnly() throws IOException {
 338         if (debug) {
 339             System.out.println("Checking for tables-only image");
 340         }
 341         long savePos = iis.getStreamPosition();
 342         if (debug) {
 343             System.out.println("saved pos is " + savePos);
 344             System.out.println("length is " + iis.length());
 345         }
 346         // Read the first header
 347         boolean tablesOnly = readNativeHeader(true);
 348         if (tablesOnly) {
 349             if (debug) {
 350                 System.out.println("tables-only image found");
 351                 long pos = iis.getStreamPosition();
 352                 System.out.println("pos after return from native is " + pos);
 353             }
 354             // This reads the tables-only image twice, once from C
 355             // and once from Java, but only if ignoreMetadata is false
 356             if (ignoreMetadata == false) {
 357                 iis.seek(savePos);
 358                 haveSeeked = true;
 359                 streamMetadata = new JPEGMetadata(true, false,
 360                                                   iis, this);
 361                 long pos = iis.getStreamPosition();
 362                 if (debug) {
 363                     System.out.println
 364                         ("pos after constructing stream metadata is " + pos);
 365                 }
 366             }
 367             // Now we are at the first image if there are any, so add it
 368             // to the list
 369             if (hasNextImage()) {
 370                 imagePositions.add(new Long(iis.getStreamPosition()));
 371             }
 372         } else { // Not tables only, so add original pos to the list
 373             imagePositions.add(new Long(savePos));
 374             // And set current image since we've read it now
 375             currentImage = 0;
 376         }
 377         if (seekForwardOnly) {
 378             Long pos = (Long) imagePositions.get(imagePositions.size()-1);
 379             iis.flushBefore(pos.longValue());
 380         }
 381         tablesOnlyChecked = true;
 382     }
 383 
 384     public int getNumImages(boolean allowSearch) throws IOException {
 385         setThreadLock();
 386         try { // locked thread
 387             cbLock.check();
 388 
 389             return getNumImagesOnThread(allowSearch);
 390         } finally {
 391             clearThreadLock();
 392         }
 393     }
 394 

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