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