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