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