1 /* 2 * Copyright (c) 2000, 2014, 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 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 = 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(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 = 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<ImageTypeSpecifier> getImageTypes(int imageIndex) 770 throws IOException { 771 setThreadLock(); 772 try { 773 return getImageTypesOnThread(imageIndex); 774 } finally { 775 clearThreadLock(); 776 } 777 } 778 779 private Iterator<ImageTypeSpecifier> 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<ImageTypeSpecifier> 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<MarkerSegment> 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 }