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