8007667: Better image reading

Reviewed-by: prr, jgodinez, mschoene
This commit is contained in:
Andrew Brygin 2013-02-26 00:41:40 +04:00
parent 243470f47e
commit 57d870834f
2 changed files with 231 additions and 87 deletions

View File

@ -243,12 +243,17 @@ public class JPEGImageReader extends ImageReader {
* sending warnings to listeners. * sending warnings to listeners.
*/ */
protected void warningOccurred(int code) { protected void warningOccurred(int code) {
if ((code < 0) || (code > MAX_WARNING)){ cbLock.lock();
throw new InternalError("Invalid warning index"); try {
if ((code < 0) || (code > MAX_WARNING)){
throw new InternalError("Invalid warning index");
}
processWarningOccurred
("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
Integer.toString(code));
} finally {
cbLock.unlock();
} }
processWarningOccurred
("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
Integer.toString(code));
} }
/** /**
@ -265,7 +270,12 @@ public class JPEGImageReader extends ImageReader {
* library warnings from being printed to stderr. * library warnings from being printed to stderr.
*/ */
protected void warningWithMessage(String msg) { protected void warningWithMessage(String msg) {
processWarningOccurred(msg); cbLock.lock();
try {
processWarningOccurred(msg);
} finally {
cbLock.unlock();
}
} }
public void setInput(Object input, public void setInput(Object input,
@ -274,18 +284,55 @@ public class JPEGImageReader extends ImageReader {
{ {
setThreadLock(); setThreadLock();
try { try {
cbLock.check();
super.setInput(input, seekForwardOnly, ignoreMetadata); super.setInput(input, seekForwardOnly, ignoreMetadata);
this.ignoreMetadata = ignoreMetadata; this.ignoreMetadata = ignoreMetadata;
resetInternalState(); resetInternalState();
iis = (ImageInputStream) input; // Always works iis = (ImageInputStream) input; // Always works
setSource(structPointer, iis); setSource(structPointer);
} finally { } finally {
clearThreadLock(); clearThreadLock();
} }
} }
private native void setSource(long structPointer, /**
ImageInputStream source); * This method is called from native code in order to fill
* native input buffer.
*
* We block any attempt to change the reading state during this
* method, in order to prevent a corruption of the native decoder
* state.
*
* @return number of bytes read from the stream.
*/
private int readInputData(byte[] buf, int off, int len) throws IOException {
cbLock.lock();
try {
return iis.read(buf, off, len);
} finally {
cbLock.unlock();
}
}
/**
* This method is called from the native code in order to
* skip requested number of bytes in the input stream.
*
* @param n
* @return
* @throws IOException
*/
private long skipInputBytes(long n) throws IOException {
cbLock.lock();
try {
return iis.skipBytes(n);
} finally {
cbLock.unlock();
}
}
private native void setSource(long structPointer);
private void checkTablesOnly() throws IOException { private void checkTablesOnly() throws IOException {
if (debug) { if (debug) {
@ -337,6 +384,8 @@ public class JPEGImageReader extends ImageReader {
public int getNumImages(boolean allowSearch) throws IOException { public int getNumImages(boolean allowSearch) throws IOException {
setThreadLock(); setThreadLock();
try { // locked thread try { // locked thread
cbLock.check();
return getNumImagesOnThread(allowSearch); return getNumImagesOnThread(allowSearch);
} finally { } finally {
clearThreadLock(); clearThreadLock();
@ -536,8 +585,13 @@ public class JPEGImageReader extends ImageReader {
if (debug) { if (debug) {
System.out.println("pushing back " + num + " bytes"); System.out.println("pushing back " + num + " bytes");
} }
iis.seek(iis.getStreamPosition()-num); cbLock.lock();
// The buffer is clear after this, so no need to set haveSeeked. try {
iis.seek(iis.getStreamPosition()-num);
// The buffer is clear after this, so no need to set haveSeeked.
} finally {
cbLock.unlock();
}
} }
/** /**
@ -644,7 +698,12 @@ public class JPEGImageReader extends ImageReader {
* Ignore this profile. * Ignore this profile.
*/ */
iccCS = null; iccCS = null;
warningOccurred(WARNING_IGNORE_INVALID_ICC); cbLock.lock();
try {
warningOccurred(WARNING_IGNORE_INVALID_ICC);
} finally {
cbLock.unlock();
}
} }
} }
} }
@ -653,6 +712,7 @@ public class JPEGImageReader extends ImageReader {
setThreadLock(); setThreadLock();
try { try {
if (currentImage != imageIndex) { if (currentImage != imageIndex) {
cbLock.check();
readHeader(imageIndex, true); readHeader(imageIndex, true);
} }
return width; return width;
@ -665,6 +725,7 @@ public class JPEGImageReader extends ImageReader {
setThreadLock(); setThreadLock();
try { try {
if (currentImage != imageIndex) { if (currentImage != imageIndex) {
cbLock.check();
readHeader(imageIndex, true); readHeader(imageIndex, true);
} }
return height; return height;
@ -693,6 +754,8 @@ public class JPEGImageReader extends ImageReader {
setThreadLock(); setThreadLock();
try { try {
if (currentImage != imageIndex) { if (currentImage != imageIndex) {
cbLock.check();
readHeader(imageIndex, true); readHeader(imageIndex, true);
} }
@ -716,6 +779,7 @@ public class JPEGImageReader extends ImageReader {
private Iterator getImageTypesOnThread(int imageIndex) private Iterator getImageTypesOnThread(int imageIndex)
throws IOException { throws IOException {
if (currentImage != imageIndex) { if (currentImage != imageIndex) {
cbLock.check();
readHeader(imageIndex, true); readHeader(imageIndex, true);
} }
@ -931,6 +995,7 @@ public class JPEGImageReader extends ImageReader {
setThreadLock(); setThreadLock();
try { try {
if (!tablesOnlyChecked) { if (!tablesOnlyChecked) {
cbLock.check();
checkTablesOnly(); checkTablesOnly();
} }
return streamMetadata; return streamMetadata;
@ -951,6 +1016,8 @@ public class JPEGImageReader extends ImageReader {
return imageMetadata; return imageMetadata;
} }
cbLock.check();
gotoImage(imageIndex); gotoImage(imageIndex);
imageMetadata = new JPEGMetadata(false, false, iis, this); imageMetadata = new JPEGMetadata(false, false, iis, this);
@ -967,6 +1034,7 @@ public class JPEGImageReader extends ImageReader {
throws IOException { throws IOException {
setThreadLock(); setThreadLock();
try { try {
cbLock.check();
try { try {
readInternal(imageIndex, param, false); readInternal(imageIndex, param, false);
} catch (RuntimeException e) { } catch (RuntimeException e) {
@ -1196,58 +1264,63 @@ public class JPEGImageReader extends ImageReader {
} }
target.setRect(destROI.x, destROI.y + y, raster); target.setRect(destROI.x, destROI.y + y, raster);
processImageUpdate(image, cbLock.lock();
destROI.x, destROI.y+y, try {
raster.getWidth(), 1, processImageUpdate(image,
1, 1, destROI.x, destROI.y+y,
destinationBands); raster.getWidth(), 1,
if ((y > 0) && (y%progInterval == 0)) { 1, 1,
int height = target.getHeight()-1; destinationBands);
float percentOfPass = ((float)y)/height; if ((y > 0) && (y%progInterval == 0)) {
if (progressive) { int height = target.getHeight()-1;
if (knownPassCount != UNKNOWN) { float percentOfPass = ((float)y)/height;
processImageProgress((pass + percentOfPass)*100.0F if (progressive) {
/ knownPassCount); if (knownPassCount != UNKNOWN) {
} else if (maxProgressivePass != Integer.MAX_VALUE) { processImageProgress((pass + percentOfPass)*100.0F
// Use the range of allowed progressive passes / knownPassCount);
processImageProgress((pass + percentOfPass)*100.0F } else if (maxProgressivePass != Integer.MAX_VALUE) {
/ (maxProgressivePass - minProgressivePass + 1)); // Use the range of allowed progressive passes
} else { processImageProgress((pass + percentOfPass)*100.0F
// Assume there are a minimum of MIN_ESTIMATED_PASSES / (maxProgressivePass - minProgressivePass + 1));
// and that there is always one more pass } else {
// Compute the percentage as the percentage at the end // Assume there are a minimum of MIN_ESTIMATED_PASSES
// of the previous pass, plus the percentage of this // and that there is always one more pass
// pass scaled to be the percentage of the total remaining, // Compute the percentage as the percentage at the end
// assuming a minimum of MIN_ESTIMATED_PASSES passes and // of the previous pass, plus the percentage of this
// that there is always one more pass. This is monotonic // pass scaled to be the percentage of the total remaining,
// and asymptotic to 1.0, which is what we need. // assuming a minimum of MIN_ESTIMATED_PASSES passes and
int remainingPasses = // including this one // that there is always one more pass. This is monotonic
Math.max(2, MIN_ESTIMATED_PASSES-pass); // and asymptotic to 1.0, which is what we need.
int totalPasses = pass + remainingPasses-1; int remainingPasses = // including this one
progInterval = Math.max(height/20*totalPasses, Math.max(2, MIN_ESTIMATED_PASSES-pass);
totalPasses); int totalPasses = pass + remainingPasses-1;
if (y%progInterval == 0) { progInterval = Math.max(height/20*totalPasses,
percentToDate = previousPassPercentage + totalPasses);
(1.0F - previousPassPercentage) if (y%progInterval == 0) {
* (percentOfPass)/remainingPasses; percentToDate = previousPassPercentage +
if (debug) { (1.0F - previousPassPercentage)
System.out.print("pass= " + pass); * (percentOfPass)/remainingPasses;
System.out.print(", y= " + y); if (debug) {
System.out.print(", progInt= " + progInterval); System.out.print("pass= " + pass);
System.out.print(", % of pass: " + percentOfPass); System.out.print(", y= " + y);
System.out.print(", rem. passes: " System.out.print(", progInt= " + progInterval);
+ remainingPasses); System.out.print(", % of pass: " + percentOfPass);
System.out.print(", prev%: " System.out.print(", rem. passes: "
+ previousPassPercentage); + remainingPasses);
System.out.print(", %ToDate: " + percentToDate); System.out.print(", prev%: "
System.out.print(" "); + previousPassPercentage);
System.out.print(", %ToDate: " + percentToDate);
System.out.print(" ");
}
processImageProgress(percentToDate*100.0F);
} }
processImageProgress(percentToDate*100.0F);
} }
} else {
processImageProgress(percentOfPass * 100.0F);
} }
} else {
processImageProgress(percentOfPass * 100.0F);
} }
} finally {
cbLock.unlock();
} }
} }
@ -1260,33 +1333,58 @@ public class JPEGImageReader extends ImageReader {
} }
private void passStarted (int pass) { private void passStarted (int pass) {
this.pass = pass; cbLock.lock();
previousPassPercentage = percentToDate; try {
processPassStarted(image, this.pass = pass;
pass, previousPassPercentage = percentToDate;
minProgressivePass, processPassStarted(image,
maxProgressivePass, pass,
0, 0, minProgressivePass,
1,1, maxProgressivePass,
destinationBands); 0, 0,
1,1,
destinationBands);
} finally {
cbLock.unlock();
}
} }
private void passComplete () { private void passComplete () {
processPassComplete(image); cbLock.lock();
try {
processPassComplete(image);
} finally {
cbLock.unlock();
}
} }
void thumbnailStarted(int thumbnailIndex) { void thumbnailStarted(int thumbnailIndex) {
processThumbnailStarted(currentImage, thumbnailIndex); cbLock.lock();
try {
processThumbnailStarted(currentImage, thumbnailIndex);
} finally {
cbLock.unlock();
}
} }
// Provide access to protected superclass method // Provide access to protected superclass method
void thumbnailProgress(float percentageDone) { void thumbnailProgress(float percentageDone) {
processThumbnailProgress(percentageDone); cbLock.lock();
try {
processThumbnailProgress(percentageDone);
} finally {
cbLock.unlock();
}
} }
// Provide access to protected superclass method // Provide access to protected superclass method
void thumbnailComplete() { void thumbnailComplete() {
processThumbnailComplete(); cbLock.lock();
try {
processThumbnailComplete();
} finally {
cbLock.unlock();
}
} }
/** /**
@ -1310,6 +1408,11 @@ public class JPEGImageReader extends ImageReader {
public void abort() { public void abort() {
setThreadLock(); setThreadLock();
try { try {
/**
* NB: we do not check the call back lock here,
* we allow to abort the reader any time.
*/
super.abort(); super.abort();
abortRead(structPointer); abortRead(structPointer);
} finally { } finally {
@ -1332,6 +1435,7 @@ public class JPEGImageReader extends ImageReader {
setThreadLock(); setThreadLock();
Raster retval = null; Raster retval = null;
try { try {
cbLock.check();
/* /*
* This could be further optimized by not resetting the dest. * This could be further optimized by not resetting the dest.
* offset and creating a translated raster in readInternal() * offset and creating a translated raster in readInternal()
@ -1371,6 +1475,8 @@ public class JPEGImageReader extends ImageReader {
public int getNumThumbnails(int imageIndex) throws IOException { public int getNumThumbnails(int imageIndex) throws IOException {
setThreadLock(); setThreadLock();
try { try {
cbLock.check();
getImageMetadata(imageIndex); // checks iis state for us getImageMetadata(imageIndex); // checks iis state for us
// Now check the jfif segments // Now check the jfif segments
JFIFMarkerSegment jfif = JFIFMarkerSegment jfif =
@ -1391,6 +1497,8 @@ public class JPEGImageReader extends ImageReader {
throws IOException { throws IOException {
setThreadLock(); setThreadLock();
try { try {
cbLock.check();
if ((thumbnailIndex < 0) if ((thumbnailIndex < 0)
|| (thumbnailIndex >= getNumThumbnails(imageIndex))) { || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
throw new IndexOutOfBoundsException("No such thumbnail"); throw new IndexOutOfBoundsException("No such thumbnail");
@ -1409,6 +1517,8 @@ public class JPEGImageReader extends ImageReader {
throws IOException { throws IOException {
setThreadLock(); setThreadLock();
try { try {
cbLock.check();
if ((thumbnailIndex < 0) if ((thumbnailIndex < 0)
|| (thumbnailIndex >= getNumThumbnails(imageIndex))) { || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
throw new IndexOutOfBoundsException("No such thumbnail"); throw new IndexOutOfBoundsException("No such thumbnail");
@ -1428,6 +1538,8 @@ public class JPEGImageReader extends ImageReader {
throws IOException { throws IOException {
setThreadLock(); setThreadLock();
try { try {
cbLock.check();
if ((thumbnailIndex < 0) if ((thumbnailIndex < 0)
|| (thumbnailIndex >= getNumThumbnails(imageIndex))) { || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
throw new IndexOutOfBoundsException("No such thumbnail"); throw new IndexOutOfBoundsException("No such thumbnail");
@ -1468,6 +1580,7 @@ public class JPEGImageReader extends ImageReader {
public void reset() { public void reset() {
setThreadLock(); setThreadLock();
try { try {
cbLock.check();
super.reset(); super.reset();
} finally { } finally {
clearThreadLock(); clearThreadLock();
@ -1479,6 +1592,8 @@ public class JPEGImageReader extends ImageReader {
public void dispose() { public void dispose() {
setThreadLock(); setThreadLock();
try { try {
cbLock.check();
if (structPointer != 0) { if (structPointer != 0) {
disposerRecord.dispose(); disposerRecord.dispose();
structPointer = 0; structPointer = 0;
@ -1540,6 +1655,36 @@ public class JPEGImageReader extends ImageReader {
theThread = null; theThread = null;
} }
} }
private CallBackLock cbLock = new CallBackLock();
private static class CallBackLock {
private State lockState;
CallBackLock() {
lockState = State.Unlocked;
}
void check() {
if (lockState != State.Unlocked) {
throw new IllegalStateException("Access to the reader is not allowed");
}
}
private void lock() {
lockState = State.Locked;
}
private void unlock() {
lockState = State.Unlocked;
}
private static enum State {
Unlocked,
Locked
}
}
} }
/** /**

View File

@ -57,8 +57,8 @@
#define MAX(a,b) ((a) > (b) ? (a) : (b)) #define MAX(a,b) ((a) > (b) ? (a) : (b))
/* Cached Java method ids */ /* Cached Java method ids */
static jmethodID ImageInputStream_readID; static jmethodID JPEGImageReader_readInputDataID;
static jmethodID ImageInputStream_skipBytesID; static jmethodID JPEGImageReader_skipInputBytesID;
static jmethodID JPEGImageReader_warningOccurredID; static jmethodID JPEGImageReader_warningOccurredID;
static jmethodID JPEGImageReader_warningWithMessageID; static jmethodID JPEGImageReader_warningWithMessageID;
static jmethodID JPEGImageReader_setImageDataID; static jmethodID JPEGImageReader_setImageDataID;
@ -923,7 +923,7 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo)
RELEASE_ARRAYS(env, data, src->next_input_byte); RELEASE_ARRAYS(env, data, src->next_input_byte);
ret = (*env)->CallIntMethod(env, ret = (*env)->CallIntMethod(env,
sb->stream, sb->stream,
ImageInputStream_readID, JPEGImageReader_readInputDataID,
sb->hstreamBuffer, 0, sb->hstreamBuffer, 0,
sb->bufferLength); sb->bufferLength);
if ((*env)->ExceptionOccurred(env) if ((*env)->ExceptionOccurred(env)
@ -1013,7 +1013,7 @@ imageio_fill_suspended_buffer(j_decompress_ptr cinfo)
} }
ret = (*env)->CallIntMethod(env, sb->stream, ret = (*env)->CallIntMethod(env, sb->stream,
ImageInputStream_readID, JPEGImageReader_readInputDataID,
sb->hstreamBuffer, sb->hstreamBuffer,
offset, buflen); offset, buflen);
if ((*env)->ExceptionOccurred(env) if ((*env)->ExceptionOccurred(env)
@ -1107,7 +1107,7 @@ imageio_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
RELEASE_ARRAYS(env, data, src->next_input_byte); RELEASE_ARRAYS(env, data, src->next_input_byte);
ret = (*env)->CallLongMethod(env, ret = (*env)->CallLongMethod(env,
sb->stream, sb->stream,
ImageInputStream_skipBytesID, JPEGImageReader_skipInputBytesID,
(jlong) num_bytes); (jlong) num_bytes);
if ((*env)->ExceptionOccurred(env) if ((*env)->ExceptionOccurred(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) { || !GET_ARRAYS(env, data, &(src->next_input_byte))) {
@ -1382,13 +1382,13 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_initReaderIDs
jclass qTableClass, jclass qTableClass,
jclass huffClass) { jclass huffClass) {
ImageInputStream_readID = (*env)->GetMethodID(env, JPEGImageReader_readInputDataID = (*env)->GetMethodID(env,
ImageInputStreamClass, cls,
"read", "readInputData",
"([BII)I"); "([BII)I");
ImageInputStream_skipBytesID = (*env)->GetMethodID(env, JPEGImageReader_skipInputBytesID = (*env)->GetMethodID(env,
ImageInputStreamClass, cls,
"skipBytes", "skipInputBytes",
"(J)J"); "(J)J");
JPEGImageReader_warningOccurredID = (*env)->GetMethodID(env, JPEGImageReader_warningOccurredID = (*env)->GetMethodID(env,
cls, cls,
@ -1531,8 +1531,7 @@ JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_setSource Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_setSource
(JNIEnv *env, (JNIEnv *env,
jobject this, jobject this,
jlong ptr, jlong ptr) {
jobject source) {
imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr); imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);
j_common_ptr cinfo; j_common_ptr cinfo;
@ -1546,7 +1545,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_setSource
cinfo = data->jpegObj; cinfo = data->jpegObj;
imageio_set_stream(env, cinfo, data, source); imageio_set_stream(env, cinfo, data, this);
imageio_init_source((j_decompress_ptr) cinfo); imageio_init_source((j_decompress_ptr) cinfo);
} }