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