264 private static native void initReaderIDs(Class iisClass,
265 Class qTableClass,
266 Class huffClass);
267
268 public JPEGImageReader(ImageReaderSpi originator) {
269 super(originator);
270 structPointer = initJPEGImageReader();
271 disposerRecord = new JPEGReaderDisposerRecord(structPointer);
272 Disposer.addRecord(disposerReferent, disposerRecord);
273 }
274
275 /** Sets up per-reader C structure and returns a pointer to it. */
276 private native long initJPEGImageReader();
277
278 /**
279 * Called by the native code or other classes to signal a warning.
280 * The code is used to lookup a localized message to be used when
281 * sending warnings to listeners.
282 */
283 protected void warningOccurred(int code) {
284 if ((code < 0) || (code > MAX_WARNING)){
285 throw new InternalError("Invalid warning index");
286 }
287 processWarningOccurred
288 ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
289 Integer.toString(code));
290 }
291
292 /**
293 * The library has it's own error facility that emits warning messages.
294 * This routine is called by the native code when it has already
295 * formatted a string for output.
296 * XXX For truly complete localization of all warning messages,
297 * the sun_jpeg_output_message routine in the native code should
298 * send only the codes and parameters to a method here in Java,
299 * which will then format and send the warnings, using localized
300 * strings. This method will have to deal with all the parameters
301 * and formats (%u with possibly large numbers, %02d, %02x, etc.)
302 * that actually occur in the JPEG library. For now, this prevents
303 * library warnings from being printed to stderr.
304 */
305 protected void warningWithMessage(String msg) {
306 processWarningOccurred(msg);
307 }
308
309 public void setInput(Object input,
310 boolean seekForwardOnly,
311 boolean ignoreMetadata)
312 {
313 setThreadLock();
314 try {
315 super.setInput(input, seekForwardOnly, ignoreMetadata);
316 this.ignoreMetadata = ignoreMetadata;
317 resetInternalState();
318 iis = (ImageInputStream) input; // Always works
319 setSource(structPointer, iis);
320 } finally {
321 clearThreadLock();
322 }
323 }
324
325 private native void setSource(long structPointer,
326 ImageInputStream source);
327
328 private void checkTablesOnly() throws IOException {
329 if (debug) {
330 System.out.println("Checking for tables-only image");
331 }
332 long savePos = iis.getStreamPosition();
333 if (debug) {
334 System.out.println("saved pos is " + savePos);
335 System.out.println("length is " + iis.length());
336 }
337 // Read the first header
338 boolean tablesOnly = readNativeHeader(true);
339 if (tablesOnly) {
340 if (debug) {
341 System.out.println("tables-only image found");
342 long pos = iis.getStreamPosition();
343 System.out.println("pos after return from native is " + pos);
344 }
345 // This reads the tables-only image twice, once from C
346 // and once from Java, but only if ignoreMetadata is false
358 // Now we are at the first image if there are any, so add it
359 // to the list
360 if (hasNextImage()) {
361 imagePositions.add(new Long(iis.getStreamPosition()));
362 }
363 } else { // Not tables only, so add original pos to the list
364 imagePositions.add(new Long(savePos));
365 // And set current image since we've read it now
366 currentImage = 0;
367 }
368 if (seekForwardOnly) {
369 Long pos = (Long) imagePositions.get(imagePositions.size()-1);
370 iis.flushBefore(pos.longValue());
371 }
372 tablesOnlyChecked = true;
373 }
374
375 public int getNumImages(boolean allowSearch) throws IOException {
376 setThreadLock();
377 try { // locked thread
378 return getNumImagesOnThread(allowSearch);
379 } finally {
380 clearThreadLock();
381 }
382 }
383
384 private int getNumImagesOnThread(boolean allowSearch)
385 throws IOException {
386 if (numImages != 0) {
387 return numImages;
388 }
389 if (iis == null) {
390 throw new IllegalStateException("Input not set");
391 }
392 if (allowSearch == true) {
393 if (seekForwardOnly) {
394 throw new IllegalStateException(
395 "seekForwardOnly and allowSearch can't both be true!");
396 }
397 // Otherwise we have to read the entire stream
557 }
558 foundFF = (byteval == 0xff) ? true : false;
559 }
560 // We hit the end of the stream before we hit an SOI, so no image
561 iis.reset();
562 if (debug) {
563 System.out.println("false");
564 }
565 return false;
566 }
567
568 /**
569 * Push back the given number of bytes to the input stream.
570 * Called by the native code at the end of each image so
571 * that the next one can be identified from Java.
572 */
573 private void pushBack(int num) throws IOException {
574 if (debug) {
575 System.out.println("pushing back " + num + " bytes");
576 }
577 iis.seek(iis.getStreamPosition()-num);
578 // The buffer is clear after this, so no need to set haveSeeked.
579 }
580
581 /**
582 * Reads header information for the given image, if possible.
583 */
584 private void readHeader(int imageIndex, boolean reset)
585 throws IOException {
586 gotoImage(imageIndex);
587 readNativeHeader(reset); // Ignore return
588 currentImage = imageIndex;
589 }
590
591 private boolean readNativeHeader(boolean reset) throws IOException {
592 boolean retval = false;
593 retval = readImageHeader(structPointer, haveSeeked, reset);
594 haveSeeked = false;
595 return retval;
596 }
597
598 /**
628 this.width = width;
629 this.height = height;
630 this.colorSpaceCode = colorSpaceCode;
631 this.outColorSpaceCode = outColorSpaceCode;
632 this.numComponents = numComponents;
633
634 if (iccData == null) {
635 iccCS = null;
636 return;
637 }
638
639 ICC_Profile newProfile = null;
640 try {
641 newProfile = ICC_Profile.getInstance(iccData);
642 } catch (IllegalArgumentException e) {
643 /*
644 * Color profile data seems to be invalid.
645 * Ignore this profile.
646 */
647 iccCS = null;
648 warningOccurred(WARNING_IGNORE_INVALID_ICC);
649
650 return;
651 }
652 byte[] newData = newProfile.getData();
653
654 ICC_Profile oldProfile = null;
655 if (iccCS instanceof ICC_ColorSpace) {
656 oldProfile = ((ICC_ColorSpace)iccCS).getProfile();
657 }
658 byte[] oldData = null;
659 if (oldProfile != null) {
660 oldData = oldProfile.getData();
661 }
662
663 /*
664 * At the moment we can't rely on the ColorSpace.equals()
665 * and ICC_Profile.equals() because they do not detect
666 * the case when two profiles are created from same data.
667 *
668 * So, we have to do data comparison in order to avoid
669 * creation of different ColorSpace instances for the same
670 * embedded data.
671 */
672 if (oldData == null ||
673 !java.util.Arrays.equals(oldData, newData))
674 {
675 iccCS = new ICC_ColorSpace(newProfile);
676 }
677 }
678
679 public int getWidth(int imageIndex) throws IOException {
680 setThreadLock();
681 try {
682 if (currentImage != imageIndex) {
683 readHeader(imageIndex, true);
684 }
685 return width;
686 } finally {
687 clearThreadLock();
688 }
689 }
690
691 public int getHeight(int imageIndex) throws IOException {
692 setThreadLock();
693 try {
694 if (currentImage != imageIndex) {
695 readHeader(imageIndex, true);
696 }
697 return height;
698 } finally {
699 clearThreadLock();
700 }
701 }
702
703 /////////// Color Conversion and Image Types
704
705 /**
706 * Return an ImageTypeSpecifier corresponding to the given
707 * color space code, or null if the color space is unsupported.
708 */
709 private ImageTypeSpecifier getImageType(int code) {
710 ImageTypeSpecifier ret = null;
711
712 if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) {
713 ret = defaultTypes[code];
714 }
715 return ret;
716 }
717
718 public ImageTypeSpecifier getRawImageType(int imageIndex)
719 throws IOException {
720 setThreadLock();
721 try {
722 if (currentImage != imageIndex) {
723 readHeader(imageIndex, true);
724 }
725
726 // Returns null if it can't be represented
727 return getImageType(colorSpaceCode);
728 } finally {
729 clearThreadLock();
730 }
731 }
732
733 public Iterator getImageTypes(int imageIndex)
734 throws IOException {
735 setThreadLock();
736 try {
737 return getImageTypesOnThread(imageIndex);
738 } finally {
739 clearThreadLock();
740 }
741 }
742
743 private Iterator getImageTypesOnThread(int imageIndex)
744 throws IOException {
745 if (currentImage != imageIndex) {
746 readHeader(imageIndex, true);
747 }
748
749 // We return an iterator containing the default, any
750 // conversions that the library provides, and
751 // all the other default types with the same number
752 // of components, as we can do these as a post-process.
753 // As we convert Rasters rather than images, images
754 // with alpha cannot be converted in a post-process.
755
756 // If this image can't be interpreted, this method
757 // returns an empty Iterator.
758
759 // Get the raw ITS, if there is one. Note that this
760 // won't always be the same as the default.
761 ImageTypeSpecifier raw = getImageType(colorSpaceCode);
762
763 // Given the encoded colorspace, build a list of ITS's
764 // representing outputs you could handle starting
765 // with the default.
927 throw new IIOException("Incompatible color conversion");
928 }
929 }
930
931 /**
932 * Set the IJG output space to the given value. The library will
933 * perform the appropriate colorspace conversions.
934 */
935 private native void setOutColorSpace(long structPointer, int id);
936
937 /////// End of Color Conversion & Image Types
938
939 public ImageReadParam getDefaultReadParam() {
940 return new JPEGImageReadParam();
941 }
942
943 public IIOMetadata getStreamMetadata() throws IOException {
944 setThreadLock();
945 try {
946 if (!tablesOnlyChecked) {
947 checkTablesOnly();
948 }
949 return streamMetadata;
950 } finally {
951 clearThreadLock();
952 }
953 }
954
955 public IIOMetadata getImageMetadata(int imageIndex)
956 throws IOException {
957 setThreadLock();
958 try {
959 // imageMetadataIndex will always be either a valid index or
960 // -1, in which case imageMetadata will not be null.
961 // So we can leave checking imageIndex for gotoImage.
962 if ((imageMetadataIndex == imageIndex)
963 && (imageMetadata != null)) {
964 return imageMetadata;
965 }
966
967 gotoImage(imageIndex);
968
969 imageMetadata = new JPEGMetadata(false, false, iis, this);
970
971 imageMetadataIndex = imageIndex;
972
973 return imageMetadata;
974 } finally {
975 clearThreadLock();
976 }
977 }
978
979 public BufferedImage read(int imageIndex, ImageReadParam param)
980 throws IOException {
981 setThreadLock();
982 try {
983 try {
984 readInternal(imageIndex, param, false);
985 } catch (RuntimeException e) {
986 resetLibraryState(structPointer);
987 throw e;
988 } catch (IOException e) {
989 resetLibraryState(structPointer);
990 throw e;
991 }
992
993 BufferedImage ret = image;
994 image = null; // don't keep a reference here
995 return ret;
996 } finally {
997 clearThreadLock();
998 }
999 }
1000
1001 private Raster readInternal(int imageIndex,
1002 ImageReadParam param,
1192 } else {
1193 processImageComplete();
1194 }
1195
1196 return target;
1197
1198 }
1199
1200 /**
1201 * This method is called back from C when the intermediate Raster
1202 * is full. The parameter indicates the scanline in the target
1203 * Raster to which the intermediate Raster should be copied.
1204 * After the copy, we notify update listeners.
1205 */
1206 private void acceptPixels(int y, boolean progressive) {
1207 if (convert != null) {
1208 convert.filter(raster, raster);
1209 }
1210 target.setRect(destROI.x, destROI.y + y, raster);
1211
1212 processImageUpdate(image,
1213 destROI.x, destROI.y+y,
1214 raster.getWidth(), 1,
1215 1, 1,
1216 destinationBands);
1217 if ((y > 0) && (y%progInterval == 0)) {
1218 int height = target.getHeight()-1;
1219 float percentOfPass = ((float)y)/height;
1220 if (progressive) {
1221 if (knownPassCount != UNKNOWN) {
1222 processImageProgress((pass + percentOfPass)*100.0F
1223 / knownPassCount);
1224 } else if (maxProgressivePass != Integer.MAX_VALUE) {
1225 // Use the range of allowed progressive passes
1226 processImageProgress((pass + percentOfPass)*100.0F
1227 / (maxProgressivePass - minProgressivePass + 1));
1228 } else {
1229 // Assume there are a minimum of MIN_ESTIMATED_PASSES
1230 // and that there is always one more pass
1231 // Compute the percentage as the percentage at the end
1245 * (percentOfPass)/remainingPasses;
1246 if (debug) {
1247 System.out.print("pass= " + pass);
1248 System.out.print(", y= " + y);
1249 System.out.print(", progInt= " + progInterval);
1250 System.out.print(", % of pass: " + percentOfPass);
1251 System.out.print(", rem. passes: "
1252 + remainingPasses);
1253 System.out.print(", prev%: "
1254 + previousPassPercentage);
1255 System.out.print(", %ToDate: " + percentToDate);
1256 System.out.print(" ");
1257 }
1258 processImageProgress(percentToDate*100.0F);
1259 }
1260 }
1261 } else {
1262 processImageProgress(percentOfPass * 100.0F);
1263 }
1264 }
1265 }
1266
1267 private void initProgressData() {
1268 knownPassCount = UNKNOWN;
1269 pass = 0;
1270 percentToDate = 0.0F;
1271 previousPassPercentage = 0.0F;
1272 progInterval = 0;
1273 }
1274
1275 private void passStarted (int pass) {
1276 this.pass = pass;
1277 previousPassPercentage = percentToDate;
1278 processPassStarted(image,
1279 pass,
1280 minProgressivePass,
1281 maxProgressivePass,
1282 0, 0,
1283 1,1,
1284 destinationBands);
1285 }
1286
1287 private void passComplete () {
1288 processPassComplete(image);
1289 }
1290
1291 void thumbnailStarted(int thumbnailIndex) {
1292 processThumbnailStarted(currentImage, thumbnailIndex);
1293 }
1294
1295 // Provide access to protected superclass method
1296 void thumbnailProgress(float percentageDone) {
1297 processThumbnailProgress(percentageDone);
1298 }
1299
1300 // Provide access to protected superclass method
1301 void thumbnailComplete() {
1302 processThumbnailComplete();
1303 }
1304
1305 /**
1306 * Returns <code>true</code> if the read was aborted.
1307 */
1308 private native boolean readImage(long structPointer,
1309 byte [] buffer,
1310 int numRasterBands,
1311 int [] srcBands,
1312 int [] bandSizes,
1313 int sourceXOffset, int sourceYOffset,
1314 int sourceWidth, int sourceHeight,
1315 int periodX, int periodY,
1316 JPEGQTable [] abbrevQTables,
1317 JPEGHuffmanTable [] abbrevDCHuffmanTables,
1318 JPEGHuffmanTable [] abbrevACHuffmanTables,
1319 int minProgressivePass,
1320 int maxProgressivePass,
1321 boolean wantUpdates);
1322
1323 public void abort() {
1324 setThreadLock();
1325 try {
1326 super.abort();
1327 abortRead(structPointer);
1328 } finally {
1329 clearThreadLock();
1330 }
1331 }
1332
1333 /** Set the C level abort flag. Keep it atomic for thread safety. */
1334 private native void abortRead(long structPointer);
1335
1336 /** Resets library state when an exception occurred during a read. */
1337 private native void resetLibraryState(long structPointer);
1338
1339 public boolean canReadRaster() {
1340 return true;
1341 }
1342
1343 public Raster readRaster(int imageIndex, ImageReadParam param)
1344 throws IOException {
1345 setThreadLock();
1346 Raster retval = null;
1347 try {
1348 /*
1349 * This could be further optimized by not resetting the dest.
1350 * offset and creating a translated raster in readInternal()
1351 * (see bug 4994702 for more info).
1352 */
1353
1354 // For Rasters, destination offset is logical, not physical, so
1355 // set it to 0 before calling computeRegions, so that the destination
1356 // region is not clipped.
1357 Point saveDestOffset = null;
1358 if (param != null) {
1359 saveDestOffset = param.getDestinationOffset();
1360 param.setDestinationOffset(new Point(0, 0));
1361 }
1362 retval = readInternal(imageIndex, param, true);
1363 // Apply the destination offset, if any, as a logical offset
1364 if (saveDestOffset != null) {
1365 target = target.createWritableTranslatedChild(saveDestOffset.x,
1366 saveDestOffset.y);
1367 }
1368 } catch (RuntimeException e) {
1369 resetLibraryState(structPointer);
1370 throw e;
1371 } catch (IOException e) {
1372 resetLibraryState(structPointer);
1373 throw e;
1374 } finally {
1375 clearThreadLock();
1376 }
1377 return retval;
1378 }
1379
1380 public boolean readerSupportsThumbnails() {
1381 return true;
1382 }
1383
1384 public int getNumThumbnails(int imageIndex) throws IOException {
1385 setThreadLock();
1386 try {
1387 getImageMetadata(imageIndex); // checks iis state for us
1388 // Now check the jfif segments
1389 JFIFMarkerSegment jfif =
1390 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1391 (JFIFMarkerSegment.class, true);
1392 int retval = 0;
1393 if (jfif != null) {
1394 retval = (jfif.thumb == null) ? 0 : 1;
1395 retval += jfif.extSegments.size();
1396 }
1397 return retval;
1398 } finally {
1399 clearThreadLock();
1400 }
1401 }
1402
1403 public int getThumbnailWidth(int imageIndex, int thumbnailIndex)
1404 throws IOException {
1405 setThreadLock();
1406 try {
1407 if ((thumbnailIndex < 0)
1408 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1409 throw new IndexOutOfBoundsException("No such thumbnail");
1410 }
1411 // Now we know that there is a jfif segment
1412 JFIFMarkerSegment jfif =
1413 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1414 (JFIFMarkerSegment.class, true);
1415 return jfif.getThumbnailWidth(thumbnailIndex);
1416 } finally {
1417 clearThreadLock();
1418 }
1419 }
1420
1421 public int getThumbnailHeight(int imageIndex, int thumbnailIndex)
1422 throws IOException {
1423 setThreadLock();
1424 try {
1425 if ((thumbnailIndex < 0)
1426 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1427 throw new IndexOutOfBoundsException("No such thumbnail");
1428 }
1429 // Now we know that there is a jfif segment
1430 JFIFMarkerSegment jfif =
1431 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1432 (JFIFMarkerSegment.class, true);
1433 return jfif.getThumbnailHeight(thumbnailIndex);
1434 } finally {
1435 clearThreadLock();
1436 }
1437 }
1438
1439 public BufferedImage readThumbnail(int imageIndex,
1440 int thumbnailIndex)
1441 throws IOException {
1442 setThreadLock();
1443 try {
1444 if ((thumbnailIndex < 0)
1445 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1446 throw new IndexOutOfBoundsException("No such thumbnail");
1447 }
1448 // Now we know that there is a jfif segment and that iis is good
1449 JFIFMarkerSegment jfif =
1450 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1451 (JFIFMarkerSegment.class, true);
1452 return jfif.getThumbnail(iis, thumbnailIndex, this);
1453 } finally {
1454 clearThreadLock();
1455 }
1456 }
1457
1458 private void resetInternalState() {
1459 // reset C structures
1460 resetReader(structPointer);
1461
1462 // reset local Java structures
1463 numImages = 0;
1464 imagePositions = new ArrayList();
1465 currentImage = -1;
1466 image = null;
1467 raster = null;
1468 target = null;
1469 buffer = null;
1470 destROI = null;
1471 destinationBands = null;
1472 streamMetadata = null;
1473 imageMetadata = null;
1474 imageMetadataIndex = -1;
1475 haveSeeked = false;
1476 tablesOnlyChecked = false;
1477 iccCS = null;
1478 initProgressData();
1479 }
1480
1481 public void reset() {
1482 setThreadLock();
1483 try {
1484 super.reset();
1485 } finally {
1486 clearThreadLock();
1487 }
1488 }
1489
1490 private native void resetReader(long structPointer);
1491
1492 public void dispose() {
1493 setThreadLock();
1494 try {
1495 if (structPointer != 0) {
1496 disposerRecord.dispose();
1497 structPointer = 0;
1498 }
1499 } finally {
1500 clearThreadLock();
1501 }
1502 }
1503
1504 private static native void disposeReader(long structPointer);
1505
1506 private static class JPEGReaderDisposerRecord implements DisposerRecord {
1507 private long pData;
1508
1509 public JPEGReaderDisposerRecord(long pData) {
1510 this.pData = pData;
1511 }
1512
1513 public synchronized void dispose() {
1514 if (pData != 0) {
1534 } else {
1535 theLockCount ++;
1536 }
1537 } else {
1538 theThread = currThread;
1539 theLockCount = 1;
1540 }
1541 }
1542
1543 private synchronized void clearThreadLock() {
1544 Thread currThread = Thread.currentThread();
1545 if (theThread == null || theThread != currThread) {
1546 throw new IllegalStateException("Attempt to clear thread lock " +
1547 " form wrong thread." +
1548 " Locked thread: " + theThread +
1549 "; current thread: " + currThread);
1550 }
1551 theLockCount --;
1552 if (theLockCount == 0) {
1553 theThread = null;
1554 }
1555 }
1556 }
|
264 private static native void initReaderIDs(Class iisClass,
265 Class qTableClass,
266 Class huffClass);
267
268 public JPEGImageReader(ImageReaderSpi originator) {
269 super(originator);
270 structPointer = initJPEGImageReader();
271 disposerRecord = new JPEGReaderDisposerRecord(structPointer);
272 Disposer.addRecord(disposerReferent, disposerRecord);
273 }
274
275 /** Sets up per-reader C structure and returns a pointer to it. */
276 private native long initJPEGImageReader();
277
278 /**
279 * Called by the native code or other classes to signal a warning.
280 * The code is used to lookup a localized message to be used when
281 * sending warnings to listeners.
282 */
283 protected void warningOccurred(int code) {
284 cbLock.lock();
285 try {
286 if ((code < 0) || (code > MAX_WARNING)){
287 throw new InternalError("Invalid warning index");
288 }
289 processWarningOccurred
290 ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
291 Integer.toString(code));
292 } finally {
293 cbLock.unlock();
294 }
295 }
296
297 /**
298 * The library has it's own error facility that emits warning messages.
299 * This routine is called by the native code when it has already
300 * formatted a string for output.
301 * XXX For truly complete localization of all warning messages,
302 * the sun_jpeg_output_message routine in the native code should
303 * send only the codes and parameters to a method here in Java,
304 * which will then format and send the warnings, using localized
305 * strings. This method will have to deal with all the parameters
306 * and formats (%u with possibly large numbers, %02d, %02x, etc.)
307 * that actually occur in the JPEG library. For now, this prevents
308 * library warnings from being printed to stderr.
309 */
310 protected void warningWithMessage(String msg) {
311 cbLock.lock();
312 try {
313 processWarningOccurred(msg);
314 } finally {
315 cbLock.unlock();
316 }
317 }
318
319 public void setInput(Object input,
320 boolean seekForwardOnly,
321 boolean ignoreMetadata)
322 {
323 setThreadLock();
324 try {
325 cbLock.check();
326
327 super.setInput(input, seekForwardOnly, ignoreMetadata);
328 this.ignoreMetadata = ignoreMetadata;
329 resetInternalState();
330 iis = (ImageInputStream) input; // Always works
331 setSource(structPointer);
332 } finally {
333 clearThreadLock();
334 }
335 }
336
337 /**
338 * This method is called from native code in order to fill
339 * native input buffer.
340 *
341 * We block any attempt to change the reading state during this
342 * method, in order to prevent a corruption of the native decoder
343 * state.
344 *
345 * @return number of bytes read from the stream.
346 */
347 private int readInputData(byte[] buf, int off, int len) throws IOException {
348 cbLock.lock();
349 try {
350 return iis.read(buf, off, len);
351 } finally {
352 cbLock.unlock();
353 }
354 }
355
356 /**
357 * This method is called from the native code in order to
358 * skip requested number of bytes in the input stream.
359 *
360 * @param n
361 * @return
362 * @throws IOException
363 */
364 private long skipInputBytes(long n) throws IOException {
365 cbLock.lock();
366 try {
367 return iis.skipBytes(n);
368 } finally {
369 cbLock.unlock();
370 }
371 }
372
373 private native void setSource(long structPointer);
374
375 private void checkTablesOnly() throws IOException {
376 if (debug) {
377 System.out.println("Checking for tables-only image");
378 }
379 long savePos = iis.getStreamPosition();
380 if (debug) {
381 System.out.println("saved pos is " + savePos);
382 System.out.println("length is " + iis.length());
383 }
384 // Read the first header
385 boolean tablesOnly = readNativeHeader(true);
386 if (tablesOnly) {
387 if (debug) {
388 System.out.println("tables-only image found");
389 long pos = iis.getStreamPosition();
390 System.out.println("pos after return from native is " + pos);
391 }
392 // This reads the tables-only image twice, once from C
393 // and once from Java, but only if ignoreMetadata is false
405 // Now we are at the first image if there are any, so add it
406 // to the list
407 if (hasNextImage()) {
408 imagePositions.add(new Long(iis.getStreamPosition()));
409 }
410 } else { // Not tables only, so add original pos to the list
411 imagePositions.add(new Long(savePos));
412 // And set current image since we've read it now
413 currentImage = 0;
414 }
415 if (seekForwardOnly) {
416 Long pos = (Long) imagePositions.get(imagePositions.size()-1);
417 iis.flushBefore(pos.longValue());
418 }
419 tablesOnlyChecked = true;
420 }
421
422 public int getNumImages(boolean allowSearch) throws IOException {
423 setThreadLock();
424 try { // locked thread
425 cbLock.check();
426
427 return getNumImagesOnThread(allowSearch);
428 } finally {
429 clearThreadLock();
430 }
431 }
432
433 private int getNumImagesOnThread(boolean allowSearch)
434 throws IOException {
435 if (numImages != 0) {
436 return numImages;
437 }
438 if (iis == null) {
439 throw new IllegalStateException("Input not set");
440 }
441 if (allowSearch == true) {
442 if (seekForwardOnly) {
443 throw new IllegalStateException(
444 "seekForwardOnly and allowSearch can't both be true!");
445 }
446 // Otherwise we have to read the entire stream
606 }
607 foundFF = (byteval == 0xff) ? true : false;
608 }
609 // We hit the end of the stream before we hit an SOI, so no image
610 iis.reset();
611 if (debug) {
612 System.out.println("false");
613 }
614 return false;
615 }
616
617 /**
618 * Push back the given number of bytes to the input stream.
619 * Called by the native code at the end of each image so
620 * that the next one can be identified from Java.
621 */
622 private void pushBack(int num) throws IOException {
623 if (debug) {
624 System.out.println("pushing back " + num + " bytes");
625 }
626 cbLock.lock();
627 try {
628 iis.seek(iis.getStreamPosition()-num);
629 // The buffer is clear after this, so no need to set haveSeeked.
630 } finally {
631 cbLock.unlock();
632 }
633 }
634
635 /**
636 * Reads header information for the given image, if possible.
637 */
638 private void readHeader(int imageIndex, boolean reset)
639 throws IOException {
640 gotoImage(imageIndex);
641 readNativeHeader(reset); // Ignore return
642 currentImage = imageIndex;
643 }
644
645 private boolean readNativeHeader(boolean reset) throws IOException {
646 boolean retval = false;
647 retval = readImageHeader(structPointer, haveSeeked, reset);
648 haveSeeked = false;
649 return retval;
650 }
651
652 /**
682 this.width = width;
683 this.height = height;
684 this.colorSpaceCode = colorSpaceCode;
685 this.outColorSpaceCode = outColorSpaceCode;
686 this.numComponents = numComponents;
687
688 if (iccData == null) {
689 iccCS = null;
690 return;
691 }
692
693 ICC_Profile newProfile = null;
694 try {
695 newProfile = ICC_Profile.getInstance(iccData);
696 } catch (IllegalArgumentException e) {
697 /*
698 * Color profile data seems to be invalid.
699 * Ignore this profile.
700 */
701 iccCS = null;
702 cbLock.lock();
703 try {
704 warningOccurred(WARNING_IGNORE_INVALID_ICC);
705 } finally {
706 cbLock.unlock();
707 }
708
709 return;
710 }
711 byte[] newData = newProfile.getData();
712
713 ICC_Profile oldProfile = null;
714 if (iccCS instanceof ICC_ColorSpace) {
715 oldProfile = ((ICC_ColorSpace)iccCS).getProfile();
716 }
717 byte[] oldData = null;
718 if (oldProfile != null) {
719 oldData = oldProfile.getData();
720 }
721
722 /*
723 * At the moment we can't rely on the ColorSpace.equals()
724 * and ICC_Profile.equals() because they do not detect
725 * the case when two profiles are created from same data.
726 *
727 * So, we have to do data comparison in order to avoid
728 * creation of different ColorSpace instances for the same
729 * embedded data.
730 */
731 if (oldData == null ||
732 !java.util.Arrays.equals(oldData, newData))
733 {
734 iccCS = new ICC_ColorSpace(newProfile);
735 }
736 }
737
738 public int getWidth(int imageIndex) throws IOException {
739 setThreadLock();
740 try {
741 if (currentImage != imageIndex) {
742 cbLock.check();
743 readHeader(imageIndex, true);
744 }
745 return width;
746 } finally {
747 clearThreadLock();
748 }
749 }
750
751 public int getHeight(int imageIndex) throws IOException {
752 setThreadLock();
753 try {
754 if (currentImage != imageIndex) {
755 cbLock.check();
756 readHeader(imageIndex, true);
757 }
758 return height;
759 } finally {
760 clearThreadLock();
761 }
762 }
763
764 /////////// Color Conversion and Image Types
765
766 /**
767 * Return an ImageTypeSpecifier corresponding to the given
768 * color space code, or null if the color space is unsupported.
769 */
770 private ImageTypeSpecifier getImageType(int code) {
771 ImageTypeSpecifier ret = null;
772
773 if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) {
774 ret = defaultTypes[code];
775 }
776 return ret;
777 }
778
779 public ImageTypeSpecifier getRawImageType(int imageIndex)
780 throws IOException {
781 setThreadLock();
782 try {
783 if (currentImage != imageIndex) {
784 cbLock.check();
785
786 readHeader(imageIndex, true);
787 }
788
789 // Returns null if it can't be represented
790 return getImageType(colorSpaceCode);
791 } finally {
792 clearThreadLock();
793 }
794 }
795
796 public Iterator getImageTypes(int imageIndex)
797 throws IOException {
798 setThreadLock();
799 try {
800 return getImageTypesOnThread(imageIndex);
801 } finally {
802 clearThreadLock();
803 }
804 }
805
806 private Iterator getImageTypesOnThread(int imageIndex)
807 throws IOException {
808 if (currentImage != imageIndex) {
809 cbLock.check();
810 readHeader(imageIndex, true);
811 }
812
813 // We return an iterator containing the default, any
814 // conversions that the library provides, and
815 // all the other default types with the same number
816 // of components, as we can do these as a post-process.
817 // As we convert Rasters rather than images, images
818 // with alpha cannot be converted in a post-process.
819
820 // If this image can't be interpreted, this method
821 // returns an empty Iterator.
822
823 // Get the raw ITS, if there is one. Note that this
824 // won't always be the same as the default.
825 ImageTypeSpecifier raw = getImageType(colorSpaceCode);
826
827 // Given the encoded colorspace, build a list of ITS's
828 // representing outputs you could handle starting
829 // with the default.
991 throw new IIOException("Incompatible color conversion");
992 }
993 }
994
995 /**
996 * Set the IJG output space to the given value. The library will
997 * perform the appropriate colorspace conversions.
998 */
999 private native void setOutColorSpace(long structPointer, int id);
1000
1001 /////// End of Color Conversion & Image Types
1002
1003 public ImageReadParam getDefaultReadParam() {
1004 return new JPEGImageReadParam();
1005 }
1006
1007 public IIOMetadata getStreamMetadata() throws IOException {
1008 setThreadLock();
1009 try {
1010 if (!tablesOnlyChecked) {
1011 cbLock.check();
1012 checkTablesOnly();
1013 }
1014 return streamMetadata;
1015 } finally {
1016 clearThreadLock();
1017 }
1018 }
1019
1020 public IIOMetadata getImageMetadata(int imageIndex)
1021 throws IOException {
1022 setThreadLock();
1023 try {
1024 // imageMetadataIndex will always be either a valid index or
1025 // -1, in which case imageMetadata will not be null.
1026 // So we can leave checking imageIndex for gotoImage.
1027 if ((imageMetadataIndex == imageIndex)
1028 && (imageMetadata != null)) {
1029 return imageMetadata;
1030 }
1031
1032 cbLock.check();
1033
1034 gotoImage(imageIndex);
1035
1036 imageMetadata = new JPEGMetadata(false, false, iis, this);
1037
1038 imageMetadataIndex = imageIndex;
1039
1040 return imageMetadata;
1041 } finally {
1042 clearThreadLock();
1043 }
1044 }
1045
1046 public BufferedImage read(int imageIndex, ImageReadParam param)
1047 throws IOException {
1048 setThreadLock();
1049 try {
1050 cbLock.check();
1051 try {
1052 readInternal(imageIndex, param, false);
1053 } catch (RuntimeException e) {
1054 resetLibraryState(structPointer);
1055 throw e;
1056 } catch (IOException e) {
1057 resetLibraryState(structPointer);
1058 throw e;
1059 }
1060
1061 BufferedImage ret = image;
1062 image = null; // don't keep a reference here
1063 return ret;
1064 } finally {
1065 clearThreadLock();
1066 }
1067 }
1068
1069 private Raster readInternal(int imageIndex,
1070 ImageReadParam param,
1260 } else {
1261 processImageComplete();
1262 }
1263
1264 return target;
1265
1266 }
1267
1268 /**
1269 * This method is called back from C when the intermediate Raster
1270 * is full. The parameter indicates the scanline in the target
1271 * Raster to which the intermediate Raster should be copied.
1272 * After the copy, we notify update listeners.
1273 */
1274 private void acceptPixels(int y, boolean progressive) {
1275 if (convert != null) {
1276 convert.filter(raster, raster);
1277 }
1278 target.setRect(destROI.x, destROI.y + y, raster);
1279
1280 cbLock.lock();
1281 try {
1282 processImageUpdate(image,
1283 destROI.x, destROI.y+y,
1284 raster.getWidth(), 1,
1285 1, 1,
1286 destinationBands);
1287 if ((y > 0) && (y%progInterval == 0)) {
1288 int height = target.getHeight()-1;
1289 float percentOfPass = ((float)y)/height;
1290 if (progressive) {
1291 if (knownPassCount != UNKNOWN) {
1292 processImageProgress((pass + percentOfPass)*100.0F
1293 / knownPassCount);
1294 } else if (maxProgressivePass != Integer.MAX_VALUE) {
1295 // Use the range of allowed progressive passes
1296 processImageProgress((pass + percentOfPass)*100.0F
1297 / (maxProgressivePass - minProgressivePass + 1));
1298 } else {
1299 // Assume there are a minimum of MIN_ESTIMATED_PASSES
1300 // and that there is always one more pass
1301 // Compute the percentage as the percentage at the end
1315 * (percentOfPass)/remainingPasses;
1316 if (debug) {
1317 System.out.print("pass= " + pass);
1318 System.out.print(", y= " + y);
1319 System.out.print(", progInt= " + progInterval);
1320 System.out.print(", % of pass: " + percentOfPass);
1321 System.out.print(", rem. passes: "
1322 + remainingPasses);
1323 System.out.print(", prev%: "
1324 + previousPassPercentage);
1325 System.out.print(", %ToDate: " + percentToDate);
1326 System.out.print(" ");
1327 }
1328 processImageProgress(percentToDate*100.0F);
1329 }
1330 }
1331 } else {
1332 processImageProgress(percentOfPass * 100.0F);
1333 }
1334 }
1335 } finally {
1336 cbLock.unlock();
1337 }
1338 }
1339
1340 private void initProgressData() {
1341 knownPassCount = UNKNOWN;
1342 pass = 0;
1343 percentToDate = 0.0F;
1344 previousPassPercentage = 0.0F;
1345 progInterval = 0;
1346 }
1347
1348 private void passStarted (int pass) {
1349 cbLock.lock();
1350 try {
1351 this.pass = pass;
1352 previousPassPercentage = percentToDate;
1353 processPassStarted(image,
1354 pass,
1355 minProgressivePass,
1356 maxProgressivePass,
1357 0, 0,
1358 1,1,
1359 destinationBands);
1360 } finally {
1361 cbLock.unlock();
1362 }
1363 }
1364
1365 private void passComplete () {
1366 cbLock.lock();
1367 try {
1368 processPassComplete(image);
1369 } finally {
1370 cbLock.unlock();
1371 }
1372 }
1373
1374 void thumbnailStarted(int thumbnailIndex) {
1375 cbLock.lock();
1376 try {
1377 processThumbnailStarted(currentImage, thumbnailIndex);
1378 } finally {
1379 cbLock.unlock();
1380 }
1381 }
1382
1383 // Provide access to protected superclass method
1384 void thumbnailProgress(float percentageDone) {
1385 cbLock.lock();
1386 try {
1387 processThumbnailProgress(percentageDone);
1388 } finally {
1389 cbLock.unlock();
1390 }
1391 }
1392
1393 // Provide access to protected superclass method
1394 void thumbnailComplete() {
1395 cbLock.lock();
1396 try {
1397 processThumbnailComplete();
1398 } finally {
1399 cbLock.unlock();
1400 }
1401 }
1402
1403 /**
1404 * Returns <code>true</code> if the read was aborted.
1405 */
1406 private native boolean readImage(long structPointer,
1407 byte [] buffer,
1408 int numRasterBands,
1409 int [] srcBands,
1410 int [] bandSizes,
1411 int sourceXOffset, int sourceYOffset,
1412 int sourceWidth, int sourceHeight,
1413 int periodX, int periodY,
1414 JPEGQTable [] abbrevQTables,
1415 JPEGHuffmanTable [] abbrevDCHuffmanTables,
1416 JPEGHuffmanTable [] abbrevACHuffmanTables,
1417 int minProgressivePass,
1418 int maxProgressivePass,
1419 boolean wantUpdates);
1420
1421 public void abort() {
1422 setThreadLock();
1423 try {
1424 /**
1425 * NB: we do not check the call back lock here,
1426 * we allow to abort the reader any time.
1427 */
1428
1429 super.abort();
1430 abortRead(structPointer);
1431 } finally {
1432 clearThreadLock();
1433 }
1434 }
1435
1436 /** Set the C level abort flag. Keep it atomic for thread safety. */
1437 private native void abortRead(long structPointer);
1438
1439 /** Resets library state when an exception occurred during a read. */
1440 private native void resetLibraryState(long structPointer);
1441
1442 public boolean canReadRaster() {
1443 return true;
1444 }
1445
1446 public Raster readRaster(int imageIndex, ImageReadParam param)
1447 throws IOException {
1448 setThreadLock();
1449 Raster retval = null;
1450 try {
1451 cbLock.check();
1452 /*
1453 * This could be further optimized by not resetting the dest.
1454 * offset and creating a translated raster in readInternal()
1455 * (see bug 4994702 for more info).
1456 */
1457
1458 // For Rasters, destination offset is logical, not physical, so
1459 // set it to 0 before calling computeRegions, so that the destination
1460 // region is not clipped.
1461 Point saveDestOffset = null;
1462 if (param != null) {
1463 saveDestOffset = param.getDestinationOffset();
1464 param.setDestinationOffset(new Point(0, 0));
1465 }
1466 retval = readInternal(imageIndex, param, true);
1467 // Apply the destination offset, if any, as a logical offset
1468 if (saveDestOffset != null) {
1469 target = target.createWritableTranslatedChild(saveDestOffset.x,
1470 saveDestOffset.y);
1471 }
1472 } catch (RuntimeException e) {
1473 resetLibraryState(structPointer);
1474 throw e;
1475 } catch (IOException e) {
1476 resetLibraryState(structPointer);
1477 throw e;
1478 } finally {
1479 clearThreadLock();
1480 }
1481 return retval;
1482 }
1483
1484 public boolean readerSupportsThumbnails() {
1485 return true;
1486 }
1487
1488 public int getNumThumbnails(int imageIndex) throws IOException {
1489 setThreadLock();
1490 try {
1491 cbLock.check();
1492
1493 getImageMetadata(imageIndex); // checks iis state for us
1494 // Now check the jfif segments
1495 JFIFMarkerSegment jfif =
1496 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1497 (JFIFMarkerSegment.class, true);
1498 int retval = 0;
1499 if (jfif != null) {
1500 retval = (jfif.thumb == null) ? 0 : 1;
1501 retval += jfif.extSegments.size();
1502 }
1503 return retval;
1504 } finally {
1505 clearThreadLock();
1506 }
1507 }
1508
1509 public int getThumbnailWidth(int imageIndex, int thumbnailIndex)
1510 throws IOException {
1511 setThreadLock();
1512 try {
1513 cbLock.check();
1514
1515 if ((thumbnailIndex < 0)
1516 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1517 throw new IndexOutOfBoundsException("No such thumbnail");
1518 }
1519 // Now we know that there is a jfif segment
1520 JFIFMarkerSegment jfif =
1521 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1522 (JFIFMarkerSegment.class, true);
1523 return jfif.getThumbnailWidth(thumbnailIndex);
1524 } finally {
1525 clearThreadLock();
1526 }
1527 }
1528
1529 public int getThumbnailHeight(int imageIndex, int thumbnailIndex)
1530 throws IOException {
1531 setThreadLock();
1532 try {
1533 cbLock.check();
1534
1535 if ((thumbnailIndex < 0)
1536 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1537 throw new IndexOutOfBoundsException("No such thumbnail");
1538 }
1539 // Now we know that there is a jfif segment
1540 JFIFMarkerSegment jfif =
1541 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1542 (JFIFMarkerSegment.class, true);
1543 return jfif.getThumbnailHeight(thumbnailIndex);
1544 } finally {
1545 clearThreadLock();
1546 }
1547 }
1548
1549 public BufferedImage readThumbnail(int imageIndex,
1550 int thumbnailIndex)
1551 throws IOException {
1552 setThreadLock();
1553 try {
1554 cbLock.check();
1555
1556 if ((thumbnailIndex < 0)
1557 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1558 throw new IndexOutOfBoundsException("No such thumbnail");
1559 }
1560 // Now we know that there is a jfif segment and that iis is good
1561 JFIFMarkerSegment jfif =
1562 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1563 (JFIFMarkerSegment.class, true);
1564 return jfif.getThumbnail(iis, thumbnailIndex, this);
1565 } finally {
1566 clearThreadLock();
1567 }
1568 }
1569
1570 private void resetInternalState() {
1571 // reset C structures
1572 resetReader(structPointer);
1573
1574 // reset local Java structures
1575 numImages = 0;
1576 imagePositions = new ArrayList();
1577 currentImage = -1;
1578 image = null;
1579 raster = null;
1580 target = null;
1581 buffer = null;
1582 destROI = null;
1583 destinationBands = null;
1584 streamMetadata = null;
1585 imageMetadata = null;
1586 imageMetadataIndex = -1;
1587 haveSeeked = false;
1588 tablesOnlyChecked = false;
1589 iccCS = null;
1590 initProgressData();
1591 }
1592
1593 public void reset() {
1594 setThreadLock();
1595 try {
1596 cbLock.check();
1597 super.reset();
1598 } finally {
1599 clearThreadLock();
1600 }
1601 }
1602
1603 private native void resetReader(long structPointer);
1604
1605 public void dispose() {
1606 setThreadLock();
1607 try {
1608 cbLock.check();
1609
1610 if (structPointer != 0) {
1611 disposerRecord.dispose();
1612 structPointer = 0;
1613 }
1614 } finally {
1615 clearThreadLock();
1616 }
1617 }
1618
1619 private static native void disposeReader(long structPointer);
1620
1621 private static class JPEGReaderDisposerRecord implements DisposerRecord {
1622 private long pData;
1623
1624 public JPEGReaderDisposerRecord(long pData) {
1625 this.pData = pData;
1626 }
1627
1628 public synchronized void dispose() {
1629 if (pData != 0) {
1649 } else {
1650 theLockCount ++;
1651 }
1652 } else {
1653 theThread = currThread;
1654 theLockCount = 1;
1655 }
1656 }
1657
1658 private synchronized void clearThreadLock() {
1659 Thread currThread = Thread.currentThread();
1660 if (theThread == null || theThread != currThread) {
1661 throw new IllegalStateException("Attempt to clear thread lock " +
1662 " form wrong thread." +
1663 " Locked thread: " + theThread +
1664 "; current thread: " + currThread);
1665 }
1666 theLockCount --;
1667 if (theLockCount == 0) {
1668 theThread = null;
1669 }
1670 }
1671
1672 private CallBackLock cbLock = new CallBackLock();
1673
1674 private static class CallBackLock {
1675
1676 private State lockState;
1677
1678 CallBackLock() {
1679 lockState = State.Unlocked;
1680 }
1681
1682 void check() {
1683 if (lockState != State.Unlocked) {
1684 throw new IllegalStateException("Access to the reader is not allowed");
1685 }
1686 }
1687
1688 private void lock() {
1689 lockState = State.Locked;
1690 }
1691
1692 private void unlock() {
1693 lockState = State.Unlocked;
1694 }
1695
1696 private static enum State {
1697 Unlocked,
1698 Locked
1699 }
1700 }
1701 }
|