8374058: Enhance JPEG handling

Reviewed-by: mschoene, rhalade, psadhukhan, prr
This commit is contained in:
Jayathirth D V 2026-03-04 17:01:12 +00:00 committed by bchristi
parent 1b0aeb0940
commit d72538f9a5
3 changed files with 472 additions and 230 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -120,6 +120,7 @@ typedef struct streamBufferStruct {
size_t bufferLength; // Allocated, nut just used
int suspendable; // Set to true to suspend input
long remaining_skip; // Used only on input
jboolean isCopy; // GetByteArrayElements copied/pinned the Java array
} streamBuffer, *streamBufferPtr;
/*
@ -200,7 +201,8 @@ static void destroyStreamBuffer(JNIEnv *env, streamBufferPtr sb) {
// Forward reference
static void unpinStreamBuffer(JNIEnv *env,
streamBufferPtr sb,
const JOCTET *next_byte);
const JOCTET *next_byte,
int streamReleaseMode);
/*
* Resets the state of a streamBuffer object that has been in use.
* The global reference to the stream is released, but the reference
@ -212,15 +214,16 @@ static void resetStreamBuffer(JNIEnv *env, streamBufferPtr sb) {
(*env)->DeleteWeakGlobalRef(env, sb->ioRef);
sb->ioRef = NULL;
}
unpinStreamBuffer(env, sb, NULL);
unpinStreamBuffer(env, sb, NULL, JNI_ABORT);
sb->bufferOffset = NO_DATA;
sb->suspendable = FALSE;
sb->remaining_skip = 0;
}
/*
* Pins the data buffer associated with this stream. Returns OK on
* success, NOT_OK on failure, as GetPrimitiveArrayCritical may fail.
* Pins/copies the data buffer associated with this stream. Returns OK on
* success, NOT_OK on failure, as GetByteArrayElements
* may fail.
*/
static int pinStreamBuffer(JNIEnv *env,
streamBufferPtr sb,
@ -228,9 +231,9 @@ static int pinStreamBuffer(JNIEnv *env,
if (sb->hstreamBuffer != NULL) {
assert(sb->buf == NULL);
sb->buf =
(JOCTET *)(*env)->GetPrimitiveArrayCritical(env,
sb->hstreamBuffer,
NULL);
(JOCTET *)(*env)->GetByteArrayElements(env,
sb->hstreamBuffer,
&sb->isCopy);
if (sb->buf == NULL) {
return NOT_OK;
}
@ -242,11 +245,12 @@ static int pinStreamBuffer(JNIEnv *env,
}
/*
* Unpins the data buffer associated with this stream.
* Unpins/releases the data buffer associated with this stream.
*/
static void unpinStreamBuffer(JNIEnv *env,
streamBufferPtr sb,
const JOCTET *next_byte) {
const JOCTET *next_byte,
int streamReleaseMode) {
if (sb->buf != NULL) {
assert(sb->hstreamBuffer != NULL);
if (next_byte == NULL) {
@ -254,11 +258,13 @@ static void unpinStreamBuffer(JNIEnv *env,
} else {
sb->bufferOffset = next_byte - sb->buf;
}
(*env)->ReleasePrimitiveArrayCritical(env,
sb->hstreamBuffer,
sb->buf,
0);
sb->buf = NULL;
(*env)->ReleaseByteArrayElements(env,
sb->hstreamBuffer,
(jbyte *)sb->buf,
streamReleaseMode);
if (streamReleaseMode != JNI_COMMIT) {
sb->buf = NULL;
}
}
}
@ -276,6 +282,7 @@ static void clearStreamBuffer(streamBufferPtr sb) {
typedef struct pixelBufferStruct {
jobject hpixelObject; // Usually a DataBuffer bank as a byte array
unsigned int byteBufferLength;
jboolean isCopy; // GetByteArrayElements copied/pinned the Java array
union pixptr {
INT32 *ip; // Pinned buffer pointer, as 32-bit ints
unsigned char *bp; // Pinned buffer pointer, as bytes
@ -309,7 +316,7 @@ static int setPixelBuffer(JNIEnv *env, pixelBufferPtr pb, jobject obj) {
}
// Forward reference
static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb);
static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb, int pixelReleaseMode);
/*
* Resets a pixel buffer to its initial state. Unpins any pixel buffer,
@ -318,7 +325,7 @@ static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb);
*/
static void resetPixelBuffer(JNIEnv *env, pixelBufferPtr pb) {
if (pb->hpixelObject != NULL) {
unpinPixelBuffer(env, pb);
unpinPixelBuffer(env, pb, JNI_ABORT);
(*env)->DeleteGlobalRef(env, pb->hpixelObject);
pb->hpixelObject = NULL;
pb->byteBufferLength = 0;
@ -326,13 +333,13 @@ static void resetPixelBuffer(JNIEnv *env, pixelBufferPtr pb) {
}
/*
* Pins the data buffer. Returns OK on success, NOT_OK on failure.
* Pins/copies the data buffer. Returns OK on success, NOT_OK on failure.
*/
static int pinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) {
if (pb->hpixelObject != NULL) {
assert(pb->buf.ip == NULL);
pb->buf.bp = (unsigned char *)(*env)->GetPrimitiveArrayCritical
(env, pb->hpixelObject, NULL);
pb->buf.bp = (unsigned char *)(*env)->GetByteArrayElements
(env, pb->hpixelObject, &pb->isCopy);
if (pb->buf.bp == NULL) {
return NOT_OK;
}
@ -341,17 +348,19 @@ static int pinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) {
}
/*
* Unpins the data buffer.
* Unpins/releases the pixel buffer.
*/
static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) {
static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb, int pixelReleaseMode) {
if (pb->buf.ip != NULL) {
assert(pb->hpixelObject != NULL);
(*env)->ReleasePrimitiveArrayCritical(env,
pb->hpixelObject,
pb->buf.ip,
0);
pb->buf.ip = NULL;
(*env)->ReleaseByteArrayElements(env,
pb->hpixelObject,
(jbyte *)pb->buf.ip,
pixelReleaseMode);
if (pixelReleaseMode != JNI_COMMIT) {
pb->buf.ip = NULL;
}
}
}
@ -468,34 +477,28 @@ static j_common_ptr destroyImageioData(JNIEnv *env, imageIODataPtr data) {
/******************** Java array pinning and unpinning *****************/
/* We use Get/ReleasePrimitiveArrayCritical functions to avoid
* the need to copy array elements for the above two objects.
*
* MAKE SURE TO:
*
* - carefully insert pairs of RELEASE_ARRAYS and GET_ARRAYS around
* callbacks to Java.
* - call RELEASE_ARRAYS before returning to Java.
*
* Otherwise things will go horribly wrong. There may be memory leaks,
* excessive pinning, or even VM crashes!
*
* Note that GetPrimitiveArrayCritical may fail!
/*
* We use Get/ReleaseByteArrayElements functions for access stream
* and pixel information from Java level arrays.
* If we receive reference to copy of Java array make sure you update
* Java array also when the latest information is needed at Java level.
* Also we use specific release modes for performance optimizations.
*/
/*
* Release (unpin) all the arrays in use during a read.
* Release (unpin) both stream and pixel arrays.
*/
static void RELEASE_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET *next_byte)
static void RELEASE_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET *next_byte,
int streamReleaseMode, int pixelReleaseMode)
{
unpinStreamBuffer(env, &data->streamBuf, next_byte);
unpinStreamBuffer(env, &data->streamBuf, next_byte, streamReleaseMode);
unpinPixelBuffer(env, &data->pixelBuf);
unpinPixelBuffer(env, &data->pixelBuf, pixelReleaseMode);
}
/*
* Get (pin) all the arrays in use during a read.
* Get (pin) both stream and pixel arrays.
*/
static int GET_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET **next_byte) {
if (pinStreamBuffer(env, &data->streamBuf, next_byte) == NOT_OK) {
@ -503,7 +506,7 @@ static int GET_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET **next_byte
}
if (pinPixelBuffer(env, &data->pixelBuf) == NOT_OK) {
RELEASE_ARRAYS(env, data, *next_byte);
RELEASE_ARRAYS(env, data, *next_byte, JNI_ABORT, JNI_ABORT);
return NOT_OK;
}
return OK;
@ -570,26 +573,16 @@ sun_jpeg_output_message (j_common_ptr cinfo)
theObject = data->imageIOobj;
if (cinfo->is_decompressor) {
struct jpeg_source_mgr *src = ((j_decompress_ptr)cinfo)->src;
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env, theObject,
JPEGImageReader_warningWithMessageID,
string);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
cinfo->err->error_exit(cinfo);
}
} else {
struct jpeg_destination_mgr *dest = ((j_compress_ptr)cinfo)->dest;
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
(*env)->CallVoidMethod(env, theObject,
JPEGImageWriter_warningWithMessageID,
string);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data,
(const JOCTET **)(&dest->next_output_byte))) {
cinfo->err->error_exit(cinfo);
}
}
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit(cinfo);
}
}
@ -941,7 +934,7 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo)
#ifdef DEBUG_IIO_JPEG
printf("Filling input buffer, remaining skip is %ld, ",
sb->remaining_skip);
printf("Buffer length is %d\n", sb->bufferLength);
printf("Buffer length is %zu\n", sb->bufferLength);
#endif
/*
@ -956,8 +949,15 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo)
/*
* Now fill a complete buffer, or as much of one as the stream
* will give us if we are near the end.
*
* The native copy of java array is not valid anymore so we just
* release it and get new copy, if we don't have native copy we rely
* on JVM to maintain the pinned handle of java array.
*/
RELEASE_ARRAYS(env, data, src->next_input_byte);
jboolean isCopy = sb->isCopy;
if (isCopy) {
unpinStreamBuffer(env, &data->streamBuf, src->next_input_byte, JNI_ABORT);
}
GET_IO_REF(input);
@ -969,9 +969,12 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo)
if ((ret > 0) && ((unsigned int)ret > sb->bufferLength)) {
ret = (int)sb->bufferLength;
}
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
if ((*env)->ExceptionCheck(env) ||
(isCopy && (pinStreamBuffer(env,
&data->streamBuf,
&(src->next_input_byte)) == NOT_OK))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
#ifdef DEBUG_IIO_JPEG
@ -988,12 +991,10 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo)
#ifdef DEBUG_IIO_JPEG
printf("YO! Early EOI! ret = %d\n", ret);
#endif
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env, reader,
JPEGImageReader_warningOccurredID,
READ_NO_EOI);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
@ -1008,97 +1009,6 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo)
return TRUE;
}
/*
* With I/O suspension turned on, the JPEG library requires that all
* buffer filling be done at the top application level, using this
* function. Due to the way that backtracking works, this procedure
* saves all of the data that was left in the buffer when suspension
* occurred and read new data only at the end.
*/
GLOBAL(void)
imageio_fill_suspended_buffer(j_decompress_ptr cinfo)
{
struct jpeg_source_mgr *src = cinfo->src;
imageIODataPtr data = (imageIODataPtr) cinfo->client_data;
streamBufferPtr sb = &data->streamBuf;
JNIEnv *env = (JNIEnv *)JNU_GetEnv(the_jvm, JNI_VERSION_1_2);
jint ret;
size_t offset, buflen;
jobject input = NULL;
/*
* The original (jpegdecoder.c) had code here that called
* InputStream.available and just returned if the number of bytes
* available was less than any remaining skip. Presumably this was
* to avoid blocking, although the benefit was unclear, as no more
* decompression can take place until more data is available, so
* the code would block on input a little further along anyway.
* ImageInputStreams don't have an available method, so we'll just
* block in the skip if we have to.
*/
if (sb->remaining_skip) {
src->skip_input_data(cinfo, 0);
}
/* Save the data currently in the buffer */
offset = src->bytes_in_buffer;
if (src->next_input_byte > sb->buf) {
memcpy(sb->buf, src->next_input_byte, offset);
}
RELEASE_ARRAYS(env, data, src->next_input_byte);
GET_IO_REF(input);
buflen = sb->bufferLength - offset;
if (buflen <= 0) {
if (!GET_ARRAYS(env, data, &(src->next_input_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
RELEASE_ARRAYS(env, data, src->next_input_byte);
return;
}
ret = (*env)->CallIntMethod(env, input,
JPEGImageReader_readInputDataID,
sb->hstreamBuffer,
offset, buflen);
if ((ret > 0) && ((unsigned int)ret > buflen)) ret = (int)buflen;
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
/*
* If we have reached the end of the stream, then the EOI marker
* is missing. We accept such streams but generate a warning.
* The image is likely to be corrupted, though everything through
* the end of the last complete MCU should be usable.
*/
if (ret <= 0) {
jobject reader = data->imageIOobj;
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env, reader,
JPEGImageReader_warningOccurredID,
READ_NO_EOI);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
sb->buf[offset] = (JOCTET) 0xFF;
sb->buf[offset + 1] = (JOCTET) JPEG_EOI;
ret = 2;
}
src->next_input_byte = sb->buf;
src->bytes_in_buffer = ret + offset;
return;
}
/*
* Skip num_bytes worth of data. The buffer pointer and count are
* advanced over num_bytes input bytes, using the input stream
@ -1160,16 +1070,13 @@ imageio_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
return;
}
RELEASE_ARRAYS(env, data, src->next_input_byte);
GET_IO_REF(input);
ret = (*env)->CallLongMethod(env,
input,
JPEGImageReader_skipInputBytesID,
(jlong) num_bytes);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
@ -1181,15 +1088,12 @@ imageio_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
*/
if (ret <= 0) {
reader = data->imageIOobj;
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env,
reader,
JPEGImageReader_warningOccurredID,
READ_NO_EOI);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
sb->buf[0] = (JOCTET) 0xFF;
sb->buf[1] = (JOCTET) JPEG_EOI;
@ -1215,7 +1119,7 @@ imageio_term_source(j_decompress_ptr cinfo)
JNIEnv *env = (JNIEnv *)JNU_GetEnv(the_jvm, JNI_VERSION_1_2);
jobject reader = data->imageIOobj;
if (src->bytes_in_buffer > 0) {
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0);
(*env)->CallVoidMethod(env,
reader,
JPEGImageReader_pushBackID,
@ -1659,7 +1563,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader
if (setjmp(jerr->setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error
while reading the header. */
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT);
if (!(*env)->ExceptionCheck(env)) {
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message) ((struct jpeg_common_struct *) cinfo,
@ -1678,7 +1582,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader
(*env)->ExceptionClear(env);
JNU_ThrowByName(env,
"javax/imageio/IIOException",
"Array pin failed");
"Get array elements failed");
return retval;
}
@ -1701,7 +1605,11 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader
printf("just read tables-only image; q table 0 at %p\n",
cinfo->quant_tbl_ptrs[0]);
#endif
RELEASE_ARRAYS(env, data, src->next_input_byte);
/*
* readImageHeader can be called independently, so
* we release the arrays when we return back.
*/
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0);
} else {
/*
* Now adjust the jpeg_color_space variable, which was set in
@ -1802,7 +1710,6 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader
/* Leave the output space as CMYK */
}
}
RELEASE_ARRAYS(env, data, src->next_input_byte);
/* read icc profile data */
profileData = read_icc_profile(env, cinfo);
@ -1819,14 +1726,17 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader
cinfo->out_color_space,
cinfo->num_components,
profileData);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
if (reset) {
jpeg_abort_decompress(cinfo);
}
RELEASE_ARRAYS(env, data, src->next_input_byte);
/*
* readImageHeader can be called independently, so
* we release the arrays when we return back.
*/
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0);
}
return retval;
@ -1987,7 +1897,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
if (setjmp(jerr->setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error
while reading. */
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT);
if (!(*env)->ExceptionCheck(env)) {
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message) ((struct jpeg_common_struct *) cinfo,
@ -2005,7 +1915,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
(*env)->ExceptionClear(env);
JNU_ThrowByName(env,
"javax/imageio/IIOException",
"Array pin failed");
"Get array elements failed");
return data->abortFlag;
}
@ -2037,7 +1947,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
jpeg_start_decompress(cinfo);
if (numBands != cinfo->output_components) {
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT);
JNU_ThrowByName(env, "javax/imageio/IIOException",
"Invalid argument to native readImage");
return data->abortFlag;
@ -2046,7 +1956,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
if (cinfo->output_components <= 0 ||
cinfo->image_width > (0xffffffffu / (unsigned int)cinfo->output_components))
{
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT);
JNU_ThrowByName(env, "javax/imageio/IIOException",
"Invalid number of output components");
return data->abortFlag;
@ -2055,7 +1965,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
// Allocate a 1-scanline buffer
scanLinePtr = (JSAMPROW)malloc(cinfo->image_width*cinfo->output_components);
if (scanLinePtr == NULL) {
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT);
JNU_ThrowByName( env,
"java/lang/OutOfMemoryError",
"Reading JPEG Stream");
@ -2070,22 +1980,18 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
// the first interesting pass.
jpeg_start_output(cinfo, cinfo->input_scan_number);
if (wantUpdates) {
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env, this,
JPEGImageReader_passStartedID,
cinfo->input_scan_number-1);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
}
} else if (wantUpdates) {
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env, this,
JPEGImageReader_passStartedID,
0);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
}
@ -2136,16 +2042,20 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
}
}
// And call it back to Java
RELEASE_ARRAYS(env, data, src->next_input_byte);
/*
* Optimisation to just commit the native pixel buffer
* content back to java array without releasing the
* native buffer.
*/
if (pb->isCopy) {
unpinPixelBuffer(env, pb, JNI_COMMIT);
}
(*env)->CallVoidMethod(env,
this,
JPEGImageReader_acceptPixelsID,
targetLine++,
progressive);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
@ -2175,11 +2085,9 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
done = TRUE;
}
if (wantUpdates) {
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env, this,
JPEGImageReader_passCompleteID);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
}
@ -2204,13 +2112,16 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
this,
JPEGImageReader_skipPastImageID,
imageIndex);
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
} else {
jpeg_finish_decompress(cinfo);
}
free(scanLinePtr);
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0);
return data->abortFlag;
}
@ -2405,8 +2316,16 @@ imageio_empty_output_buffer (j_compress_ptr cinfo)
JNIEnv *env = (JNIEnv *)JNU_GetEnv(the_jvm, JNI_VERSION_1_2);
jobject output = NULL;
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
/*
* Optimization to not delete the native copy of stream buffer,
* but just commit the content back to the Java array.
* In case where we don't have a copy, we rely on JVM to maintain
* the native reference of Java array.
*/
jboolean isCopy = sb->isCopy;
if (isCopy) {
unpinStreamBuffer(env, sb, dest->next_output_byte, JNI_COMMIT);
}
GET_IO_REF(output);
(*env)->CallVoidMethod(env,
@ -2415,10 +2334,8 @@ imageio_empty_output_buffer (j_compress_ptr cinfo)
sb->hstreamBuffer,
0,
sb->bufferLength);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data,
(const JOCTET **)(&dest->next_output_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
dest->next_output_byte = sb->buf;
@ -2447,7 +2364,16 @@ imageio_term_destination (j_compress_ptr cinfo)
if (datacount != 0) {
jobject output = NULL;
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
/*
* Optimization to not delete the native copy of stream buffer,
* but just commit the content back to the Java array.
* In case where we don't have a copy, we rely on JVM to maintain
* the native reference of Java array.
*/
jboolean isCopy = sb->isCopy;
if (isCopy) {
unpinStreamBuffer(env, sb, dest->next_output_byte, JNI_COMMIT);
}
GET_IO_REF(output);
@ -2457,17 +2383,13 @@ imageio_term_destination (j_compress_ptr cinfo)
sb->hstreamBuffer,
0,
datacount);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data,
(const JOCTET **)(&dest->next_output_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
}
dest->next_output_byte = NULL;
dest->free_in_buffer = 0;
}
/*
@ -2668,7 +2590,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables
if (setjmp(jerr->setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error
while writing. */
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte), JNI_ABORT, JNI_ABORT);
if (!(*env)->ExceptionCheck(env)) {
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message) ((j_common_ptr) cinfo,
@ -2683,7 +2605,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables
(*env)->ExceptionClear(env);
JNU_ThrowByName(env,
"javax/imageio/IIOException",
"Array pin failed");
"Get array elements failed");
return;
}
@ -2703,7 +2625,15 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables
}
jpeg_write_tables(cinfo); // Flushes the buffer for you
RELEASE_ARRAYS(env, data, NULL);
/*
* writeTables can be called independently, so
* we release the arrays when we return back.
* Also the table content in output_buffer is
* already flushed, so no need to commit the
* native copy of stream content back to the
* Java array.
*/
RELEASE_ARRAYS(env, data, NULL, JNI_ABORT, 0);
}
static void freeArray(UINT8** arr, jint size) {
@ -2766,7 +2696,6 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
UINT8** scale = NULL;
boolean success = TRUE;
/* verify the inputs */
if (data == NULL) {
@ -2891,7 +2820,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
if (setjmp(jerr->setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error
while writing. */
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte), JNI_ABORT, JNI_ABORT);
if (!(*env)->ExceptionCheck(env)) {
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message) ((j_common_ptr) cinfo,
@ -2973,7 +2902,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
free(scanLinePtr);
JNU_ThrowByName(env,
"javax/imageio/IIOException",
"Array pin failed");
"Get array elements failed");
return data->abortFlag;
}
@ -3006,7 +2935,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
scanptr = (int *) cinfo->script_space;
scanData = (*env)->GetIntArrayElements(env, scanInfo, NULL);
if (scanData == NULL) {
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte), JNI_ABORT, JNI_ABORT);
freeArray(scale, numBands);
free(scanLinePtr);
return data->abortFlag;
@ -3034,16 +2963,13 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
if (haveMetadata) {
// Flush the buffer
imageio_flush_destination(cinfo);
// Call Java to write the metadata
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
// Call Java to write the metadata.
(*env)->CallVoidMethod(env,
this,
JPEGImageWriter_writeMetadataID);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data,
(const JOCTET **)(&dest->next_output_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
}
targetLine = 0;
@ -3053,20 +2979,29 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
// for each line in destHeight
while ((data->abortFlag == JNI_FALSE)
&& (cinfo->next_scanline < cinfo->image_height)) {
// get the line from Java
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
/*
* Get a line of pixel data from Java.
* In case where we have native copy of Java pixel array,
* we need to just use JNI_ABORT to exclude any copy operation
* and then get new copy for next scanline.
*
* If we have direct reference to Java array, we rely on
* JVM to maintain the reference appropriately.
*/
jboolean isCopy = pb->isCopy;
if (isCopy) {
unpinPixelBuffer(env, pb, JNI_ABORT);
}
(*env)->CallVoidMethod(env,
this,
JPEGImageWriter_grabPixelsID,
targetLine);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data,
(const JOCTET **)(&dest->next_output_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
if ((*env)->ExceptionCheck(env) ||
(isCopy && (pinPixelBuffer(env, pb) == NOT_OK))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
// subsample it into our buffer
in = data->pixelBuf.buf.bp;
out = scanLinePtr;
pixelLimit = in + ((pixelBufferSize > data->pixelBuf.byteBufferLength) ?
@ -3108,7 +3043,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
freeArray(scale, numBands);
free(scanLinePtr);
RELEASE_ARRAYS(env, data, NULL);
RELEASE_ARRAYS(env, data, NULL, 0, JNI_ABORT);
return data->abortFlag;
}

View File

@ -0,0 +1,166 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.javax.imageio.plugins.jpeg;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.stream.ImageInputStream;
/**
* Measure time taken to read large jpeg image
* make test TEST="micro:javax.imageio.plugins.jpeg.LargeJpegReadWithProgressBench"
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(3)
@State(Scope.Benchmark)
public class LargeJpegReadWithProgressBench {
private static final File pwd = new File(".");
private static ImageReader reader;
@Setup
public void setup() throws IOException {
BufferedImage src = createSource();
ImageInputStream iis = prepareInput(src);
reader = null;
Iterator<ImageReader> it = ImageIO.getImageReadersByFormatName("jpeg");
if (it.hasNext()) {
reader = (ImageReader)it.next();
} else {
throw new RuntimeException("Could not find JPEG reader");
}
reader.setInput(iis);
ImageReadProgressListener listener = new ImageReadProgressListener();
reader.addIIOReadProgressListener(listener);
}
@Benchmark
public void readLargeJpegImage(Blackhole bh) throws IOException {
reader.read(0);
}
private static BufferedImage createSource() {
int width = 2000;
int height = 2000;
int squareSize = 20;
Color red = Color.RED;
Color green = Color.GREEN;
BufferedImage image = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (((x / squareSize) + (y / squareSize)) % 2 == 0) {
image.setRGB(x, y, red.getRGB());
} else {
image.setRGB(x, y, green.getRGB());
}
}
}
return image;
}
private static ImageInputStream prepareInput(BufferedImage src)
throws IOException {
File f = File.createTempFile("src_", ".jpeg", pwd);
if (ImageIO.write(src, "jpeg", f)) {
ImageInputStream iis = ImageIO.createImageInputStream(f);
f.deleteOnExit();
return iis;
} else {
throw new RuntimeException("Unable to write jpeg image");
}
}
}
class ImageReadProgressListener implements IIOReadProgressListener {
// This class is a no-op, it is added just to have a progress listener
@Override
public void sequenceStarted(ImageReader source, int minIndex) {
}
@Override
public void sequenceComplete(ImageReader source) {
}
@Override
public void imageStarted(ImageReader source, int imageIndex) {
}
@Override
public void imageProgress(ImageReader source, float percentageDone) {
}
@Override
public void imageComplete(ImageReader source) {
}
@Override
public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
}
@Override
public void thumbnailProgress(ImageReader source, float percentageDone) {
}
@Override
public void thumbnailComplete(ImageReader source) {
}
@Override
public void readAborted(ImageReader source) {
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.javax.imageio.plugins.jpeg;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
/**
* Measure time taken to read large jpeg image
* make test TEST="micro:javax.imageio.plugins.jpeg.LargeJpegReadWriteBench"
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(3)
@State(Scope.Benchmark)
public class LargeJpegReadWriteBench {
private static final File pwd = new File(".");
private static ImageReader reader;
private static ImageWriter writer;
private static BufferedImage src;
@Setup
public void setup() throws IOException {
src = createSource();
ImageInputStream iis = prepareInput(src);
reader = null;
Iterator<ImageReader> readerIterator = ImageIO.getImageReadersByFormatName("jpeg");
if (readerIterator.hasNext()) {
reader = readerIterator.next();
} else {
throw new RuntimeException("Could not find JPEG reader");
}
reader.setInput(iis);
ImageOutputStream ios = prepareOutput(src);
writer = null;
Iterator<ImageWriter> writerIterator = ImageIO.getImageWritersByFormatName("jpeg");
if (writerIterator.hasNext()) {
writer = writerIterator.next();
} else {
throw new RuntimeException("Could not find JPEG writer");
}
writer.setOutput(ios);
}
@Benchmark
public void readLargeJpegImage(Blackhole bh) throws IOException {
reader.read(0);
}
@Benchmark
public void writeLargeJpegImage(Blackhole bh) throws IOException {
writer.write(src);
}
private static BufferedImage createSource() {
int width = 2000;
int height = 2000;
int squareSize = 20;
Color red = Color.RED;
Color green = Color.GREEN;
BufferedImage image = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (((x / squareSize) + (y / squareSize)) % 2 == 0) {
image.setRGB(x, y, red.getRGB());
} else {
image.setRGB(x, y, green.getRGB());
}
}
}
return image;
}
private static ImageInputStream prepareInput(BufferedImage src)
throws IOException {
File f = File.createTempFile("src_", ".jpeg", pwd);
if (ImageIO.write(src, "jpeg", f)) {
ImageInputStream iis = ImageIO.createImageInputStream(f);
f.deleteOnExit();
return iis;
} else {
throw new RuntimeException("Unable to write jpeg image");
}
}
private static ImageOutputStream prepareOutput(BufferedImage src) throws IOException {
File f = File.createTempFile("dest_", ".jpeg", pwd);
ImageOutputStream ios = ImageIO.createImageOutputStream(f);
f.deleteOnExit();
return ios;
}
}