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