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