blob: d8fffa4a6d406e4f1358535611b5af6a2ca9e25b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Oleg V. Khaschansky
*
*/
#include "JPEGDecoder.h"
typedef enum TAG_DECODER_STATE{
INIT,
START_DECOMPRESS,
DECOMPRESS_STARTED,
CONSUME_INPUT,
PREPARE_OUTPUT_SCAN,
DO_OUTPUT_SCAN,
READ_DONE,
DECOMPRESS_DESTROYED
} DECODER_STATE;
// Error manager
typedef struct tag_gl_jpeg_error_mgr{
struct jpeg_error_mgr base;
jmp_buf jmp_buffer;
} gl_jpeg_error_mgr;
METHODDEF(void) gl_jpeg_error_exit(j_common_ptr cinfo) {
gl_jpeg_error_mgr* myerr = (gl_jpeg_error_mgr*) cinfo->err;
/*
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message)(cinfo, buffer);
Print message if needed here
*/
// Now we are skipping errors silently
longjmp(myerr->jmp_buffer, 1);
}
typedef struct tag_jpeg_source_mgr {
struct jpeg_source_mgr base;
JOCTET *jpeg_buffer;//[MAX_BUFFER];
int valid_buffer_length;
int buffer_size;
size_t skip_input_bytes;
boolean at_eof;
boolean final_pass;
boolean decoding_done;
boolean do_progressive;
} gl_jpeg_source_mgr;
METHODDEF(void) gl_jpeg_dummy_decompress(j_decompress_ptr cinfo) {}
METHODDEF(boolean) gl_jpeg_fill_input_buffer(j_decompress_ptr cinfo) {
gl_jpeg_source_mgr* srcmgr = (gl_jpeg_source_mgr*) cinfo->src;
if ( srcmgr->at_eof ) {
// Insert a fake EOI marker - as jpeglib advises
srcmgr->jpeg_buffer[0] = (JOCTET) 0xFF;
srcmgr->jpeg_buffer[1] = (JOCTET) JPEG_EOI;
srcmgr->base.bytes_in_buffer = 2;
srcmgr->base.next_input_byte = (JOCTET *) srcmgr->jpeg_buffer;
return TRUE;
} else {
return FALSE; // I/O suspension mode on
}
}
METHODDEF(void) gl_jpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
gl_jpeg_source_mgr* srcmgr;
size_t skipbytes;
if(num_bytes <= 0) return; // required noop
srcmgr = (gl_jpeg_source_mgr*) cinfo->src;
srcmgr->skip_input_bytes += num_bytes;
skipbytes = MIN(srcmgr->base.bytes_in_buffer, srcmgr->skip_input_bytes);
if(skipbytes < srcmgr->base.bytes_in_buffer) {
memmove(srcmgr->jpeg_buffer,
srcmgr->base.next_input_byte + skipbytes,
srcmgr->base.bytes_in_buffer - skipbytes);
}
srcmgr->base.bytes_in_buffer -= skipbytes;
srcmgr->valid_buffer_length = (int) srcmgr->base.bytes_in_buffer;
srcmgr->skip_input_bytes -= skipbytes;
/* adjust data for jpeglib */
cinfo->src->next_input_byte = (JOCTET *) srcmgr->jpeg_buffer;
cinfo->src->bytes_in_buffer = (size_t) srcmgr->valid_buffer_length;
}
GLOBAL(boolean) gl_jpeg_init_source_mgr(j_decompress_ptr cinfo, int bufferSize) {
int perfBufferSize;
gl_jpeg_source_mgr* mgr = (gl_jpeg_source_mgr*) cinfo->src;
// jpeg_source_mgr fields
mgr->base.init_source = gl_jpeg_dummy_decompress;
mgr->base.fill_input_buffer = gl_jpeg_fill_input_buffer;
mgr->base.skip_input_data = gl_jpeg_skip_input_data;
mgr->base.resync_to_restart = jpeg_resync_to_restart; // Use default
mgr->base.term_source = gl_jpeg_dummy_decompress;
perfBufferSize = bufferSize * 8;
if (perfBufferSize < MIN_BUFFER) {
mgr->buffer_size = MIN_BUFFER;
} else if (perfBufferSize > MAX_BUFFER){
mgr->buffer_size = MAX_BUFFER;
} else {
mgr->buffer_size = perfBufferSize;
}
mgr->jpeg_buffer = malloc(mgr->buffer_size);
if(!mgr->jpeg_buffer) {
return FALSE;
}
mgr->base.bytes_in_buffer = 0;
mgr->base.next_input_byte = mgr->jpeg_buffer;
// gl_jpeg_source_mgr fields
mgr->valid_buffer_length = 0;
mgr->skip_input_bytes = 0;
mgr->at_eof = 0;
mgr->final_pass = FALSE;
mgr->decoding_done = FALSE;
return TRUE;
}
typedef struct tag_gl_decompress_struct {
struct jpeg_decompress_struct decompress;
gl_jpeg_error_mgr errorMgr;
gl_jpeg_source_mgr srcMgr;
DECODER_STATE decoderState;
// Pointer to output buffer
JSAMPARRAY outBuffer;
int outBufferSize;
size_t scanlineSize;
int maxScanlines;
} gl_decompress_struct;
GLOBAL(void) gl_decompress_struct_destroy(gl_decompress_struct* glDecompress);
void outOfMemory(JNIEnv *env, jobject obj, gl_decompress_struct *glDecompress) {
if(glDecompress) {
gl_decompress_struct_destroy(glDecompress);
free(glDecompress);
}
(*env)->SetIntField(env, obj, img_JPEG_bytesConsumedID, -1);
(*env)->SetLongField(env, obj, img_JPEG_hNativeDecoderID, 0);
throwNewExceptionByName(env, "java/lang/OutOfMemoryError", "Out of memory");
}
GLOBAL(void) gl_decompress_struct_init(gl_decompress_struct** glDecompressPtr, int bufferSize) {
gl_decompress_struct* glDecompress = *glDecompressPtr;
if(glDecompress == NULL) { // Allocate new decompress struct
glDecompress = malloc(sizeof(gl_decompress_struct));
if(!glDecompress) // Out of memory
return;
*glDecompressPtr = glDecompress;
}
// Clear standard jpeg decompress struct
memset(&(glDecompress->decompress), 0, sizeof(glDecompress->decompress));
// Set up error manager
glDecompress->decompress.err = jpeg_std_error(&(glDecompress->errorMgr.base));
glDecompress->errorMgr.base.error_exit = gl_jpeg_error_exit;
// Init jpeg decompress struct
jpeg_create_decompress(&glDecompress->decompress);
// Set up source manager
glDecompress->decompress.src = &(glDecompress->srcMgr.base);
if(!gl_jpeg_init_source_mgr(&glDecompress->decompress, bufferSize)) { // Out of memory
free(glDecompress);
*glDecompressPtr = NULL;
return;
}
//glDecompress->decompress.UseIPP = FALSE;
glDecompress->decoderState = INIT;
}
GLOBAL(void) gl_decompress_struct_destroy(gl_decompress_struct* glDecompress) {
int i;
jpeg_destroy_decompress(&glDecompress->decompress);
if(glDecompress->outBuffer) {
for(i=0; i<glDecompress->outBufferSize; i++) {
free(glDecompress->outBuffer[i]);
}
free(glDecompress->outBuffer);
glDecompress->outBuffer = NULL;
}
// Cleanup input buffer
if(glDecompress->srcMgr.jpeg_buffer)
free(glDecompress->srcMgr.jpeg_buffer);
}
/*
* Class: org_apache_harmony_awt_gl_image_JpegDecoder
* Method: initIDs
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_org_apache_harmony_awt_gl_image_JpegDecoder_initIDs
(JNIEnv *env, jclass cls) {
img_JPEG_imageWidthID = (*env)->GetFieldID(env, cls, "imageWidth", "I");
if(img_JPEG_imageWidthID == NULL) {
throwNPException(env, "Unable to get field ID");
}
img_JPEG_imageHeightID = (*env)->GetFieldID(env, cls, "imageHeight", "I");
if(img_JPEG_imageHeightID == NULL) {
throwNPException(env, "Unable to get field ID");
}
img_JPEG_progressiveID = (*env)->GetFieldID(env, cls, "progressive", "Z");
if(img_JPEG_progressiveID == NULL) {
throwNPException(env, "Unable to get field ID");
}
img_JPEG_jpegColorSpaceID = (*env)->GetFieldID(env, cls, "jpegColorSpace", "I");
if(img_JPEG_jpegColorSpaceID == NULL) {
throwNPException(env, "Unable to get field ID");
}
img_JPEG_bytesConsumedID = (*env)->GetFieldID(env, cls, "bytesConsumed", "I");
if(img_JPEG_bytesConsumedID == NULL) {
throwNPException(env, "Unable to get field ID");
}
img_JPEG_currScanlineID = (*env)->GetFieldID(env, cls, "currScanline", "I");
if(img_JPEG_currScanlineID == NULL) {
throwNPException(env, "Unable to get field ID");
}
img_JPEG_hNativeDecoderID = (*env)->GetFieldID(env, cls, "hNativeDecoder", "J");
if(img_JPEG_hNativeDecoderID == NULL) {
throwNPException(env, "Unable to get field ID");
}
}
// Utility function to perform fast buffer format conversion
static void toIntRGB(unsigned char *in, unsigned char *out, int lengthInPixels) {
// To reverse representation faster go in backward direction
unsigned char *inCurrPtr = in + lengthInPixels*3 - 1; // end of the input buffer
// end of the output buffer
unsigned int *outCurrPtr = (unsigned int *)(out + lengthInPixels*4) - 1;
unsigned int val;
while (inCurrPtr > in) {
val = *(inCurrPtr--); // B
val |= (*(inCurrPtr--))<<8; // G
val |= (*(inCurrPtr--))<<16; // R
val |= 0xFF000000; // Set alpha to 255
*(outCurrPtr--) = val;
}
}
/*
* Class: org_apache_harmony_awt_gl_image_JpegDecoder
* Method: releaseNativeDecoder
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_org_apache_harmony_awt_gl_image_JpegDecoder_releaseNativeDecoder
(JNIEnv *env, jclass cls, jlong hglDecompress) {
// Cleanup if image was truncated
gl_decompress_struct *glDecompress = (gl_decompress_struct*) ((IDATA)hglDecompress);
if(glDecompress) {
gl_decompress_struct_destroy(glDecompress);
free(glDecompress);
}
}
#define RETURN_JAVA_ARRAY return intOut == NULL ? byteOut : intOut
/*
* Class: org_apache_harmony_awt_gl_image_JpegDecoder
* Method: decode
* Signature: ([BIJ)Ljava/lang/Object;
*/
JNIEXPORT jobject JNICALL Java_org_apache_harmony_awt_gl_image_JpegDecoder_decode
(JNIEnv *env,
jobject obj,
jbyteArray jbuffer,
jint bytesInBuffer,
jlong hglDecompress
) {
int consumed, freeInJpegBuffer;
unsigned char* buffer;
jbyteArray byteOut = NULL;
jintArray intOut = NULL;
gl_decompress_struct *glDecompress =
(gl_decompress_struct*) ((IDATA)hglDecompress);
if(glDecompress == NULL) {
gl_decompress_struct_init(&glDecompress, bytesInBuffer);
if(glDecompress == NULL) { // Out of memory
outOfMemory(env, obj, NULL);
return 0;
}
}
// Silently skip data if EOF already encountered
if(glDecompress->srcMgr.at_eof) {
(*env)->SetIntField(env, obj, img_JPEG_bytesConsumedID, bytesInBuffer);
(*env)->SetLongField(env, obj, img_JPEG_hNativeDecoderID, (jlong) ((IDATA)glDecompress));
RETURN_JAVA_ARRAY;
}
if(setjmp(glDecompress->errorMgr.jmp_buffer)) {
// this is fatal
(*env)->SetIntField(env, obj, img_JPEG_bytesConsumedID, -1);
(*env)->SetLongField(env, obj, img_JPEG_hNativeDecoderID, 0);
RETURN_JAVA_ARRAY;
}
freeInJpegBuffer = glDecompress->srcMgr.buffer_size - glDecompress->srcMgr.valid_buffer_length;
// Grow jpeg input buffer (if needed) when buffered-image mode is used
if(glDecompress->decompress.buffered_image) {
if(freeInJpegBuffer < bytesInBuffer) {
JOCTET *tmp;
glDecompress->srcMgr.buffer_size += MAX_BUFFER;
if((tmp = realloc(glDecompress->srcMgr.jpeg_buffer, glDecompress->srcMgr.buffer_size))) {
glDecompress->srcMgr.jpeg_buffer = tmp;
freeInJpegBuffer =
glDecompress->srcMgr.buffer_size - glDecompress->srcMgr.valid_buffer_length;
} else {
outOfMemory(env, obj, glDecompress);
return 0;
}
}
}
consumed = MIN(bytesInBuffer, freeInJpegBuffer);
// filling buffer with the new data
buffer = (*env)->GetPrimitiveArrayCritical(env, jbuffer, 0);
memcpy(glDecompress->srcMgr.jpeg_buffer + glDecompress->srcMgr.valid_buffer_length, buffer, consumed);
// move the unconsumed data to the beginning of the java buffer
if(consumed < bytesInBuffer && consumed > 0) {
memcpy(buffer, buffer+consumed, bytesInBuffer-consumed);
}
(*env)->ReleasePrimitiveArrayCritical(env, jbuffer, buffer, 0);
glDecompress->srcMgr.valid_buffer_length += consumed;
if(glDecompress->srcMgr.skip_input_bytes) {
int skipbytes = (int) MIN((size_t) glDecompress->srcMgr.valid_buffer_length, glDecompress->srcMgr.skip_input_bytes);
if(skipbytes < glDecompress->srcMgr.valid_buffer_length) {
memmove(glDecompress->srcMgr.jpeg_buffer,
glDecompress->srcMgr.jpeg_buffer + skipbytes,
glDecompress->srcMgr.valid_buffer_length - skipbytes);
}
glDecompress->srcMgr.valid_buffer_length -= skipbytes;
glDecompress->srcMgr.skip_input_bytes -= skipbytes;
// still more bytes to skip
if(glDecompress->srcMgr.skip_input_bytes) {
if(consumed <= 0)
assert(0); // This is illegal
(*env)->SetIntField(env, obj, img_JPEG_bytesConsumedID, consumed);
(*env)->SetLongField(env, obj, img_JPEG_hNativeDecoderID, (jlong) ((IDATA)glDecompress));
RETURN_JAVA_ARRAY;
}
} // if(glDecompress->srcMgr.skip_input_bytes)
glDecompress->srcMgr.base.next_input_byte = (JOCTET *) glDecompress->srcMgr.jpeg_buffer;
glDecompress->srcMgr.base.bytes_in_buffer = (size_t) glDecompress->srcMgr.valid_buffer_length;
if(glDecompress->decoderState == INIT) {
if(jpeg_read_header(&glDecompress->decompress, TRUE) != JPEG_SUSPENDED) {
int width, height;
width = glDecompress->decompress.image_width / glDecompress->decompress.scale_denom;
height = glDecompress->decompress.image_height / glDecompress->decompress.scale_denom;
(*env)->SetIntField(env, obj, img_JPEG_imageWidthID, width);
(*env)->SetIntField(env, obj, img_JPEG_imageHeightID, height);
glDecompress->decoderState = START_DECOMPRESS;
}
}
if(glDecompress->decoderState == START_DECOMPRESS) {
int i;
glDecompress->srcMgr.do_progressive =
jpeg_has_multiple_scans( &glDecompress->decompress );
// Pass to java progressive or not
(*env)->SetBooleanField(env, obj,
img_JPEG_progressiveID,
glDecompress->srcMgr.do_progressive);
glDecompress->decompress.buffered_image = glDecompress->srcMgr.do_progressive;
// setup image sizes
jpeg_calc_output_dimensions( &glDecompress->decompress );
// Accept 2 output color spaces: GRAY and RGB.
// Alpha channel will be probably supported in the future...
if (glDecompress->decompress.out_color_space != JCS_GRAYSCALE) {
glDecompress->decompress.out_color_space = JCS_RGB;
glDecompress->scanlineSize = glDecompress->decompress.image_width * 3;
} else {
glDecompress->scanlineSize = glDecompress->decompress.image_width;
}
// Pass output color space to java
(*env)->SetIntField(env, obj,
img_JPEG_jpegColorSpaceID,
glDecompress->decompress.out_color_space);
glDecompress->decompress.do_fancy_upsampling = TRUE;
glDecompress->decompress.do_block_smoothing = FALSE;
glDecompress->decompress.quantize_colors = FALSE;
glDecompress->decompress.dct_method = JDCT_FASTEST;
// false: IO suspension
if(jpeg_start_decompress(&glDecompress->decompress)) {
glDecompress->decoderState =
glDecompress->srcMgr.do_progressive ? DECOMPRESS_STARTED : DO_OUTPUT_SCAN;
}
// Calculate output buffer size and allocate memory for it
glDecompress->maxScanlines = MAX((int)(MAX_BUFFER / glDecompress->scanlineSize), 1);
glDecompress->outBufferSize = glDecompress->maxScanlines; // * glDecompress->scanlineSize;
glDecompress->outBuffer = malloc(glDecompress->outBufferSize * sizeof(JSAMPROW));
if(!glDecompress->outBuffer) { // Out of memory
outOfMemory(env, obj, glDecompress);
return 0;
}
for(i = 0; i < glDecompress->outBufferSize; i++) {
if(!(glDecompress->outBuffer[i] = malloc(glDecompress->scanlineSize))) {
if(i == 0) { // Cannot allocate buffer for one scanline
free(glDecompress->outBuffer);
outOfMemory(env, obj, glDecompress);
return 0;
} else { // Proceed with smaller buffer
glDecompress->maxScanlines = glDecompress->outBufferSize = i - 1;
break;
}
}
}
}
if(glDecompress->decoderState == DECOMPRESS_STARTED) {
glDecompress->decoderState = CONSUME_INPUT;
}
if(glDecompress->decoderState == CONSUME_INPUT) {
int retval;
do {
retval = jpeg_consume_input(&glDecompress->decompress);
} while (retval != JPEG_SUSPENDED && retval != JPEG_REACHED_EOI && retval != JPEG_REACHED_SOS);
if( glDecompress->srcMgr.final_pass
|| retval == JPEG_REACHED_EOI
|| retval == JPEG_REACHED_SOS) {
glDecompress->decoderState = PREPARE_OUTPUT_SCAN;
}
}
if(glDecompress->decoderState == PREPARE_OUTPUT_SCAN) {
if ( jpeg_start_output(
&glDecompress->decompress,
glDecompress->decompress.input_scan_number)
) {
glDecompress->decoderState = DO_OUTPUT_SCAN;
}
}
if(glDecompress->decoderState == DO_OUTPUT_SCAN) {
int oldoutput_scanline, completed_scanlines, lengthPixels, scanlines_read;
JSAMPARRAY outbufferPtr;
if(glDecompress->srcMgr.decoding_done) { // Decoding done, just keep eating input data
(*env)->SetIntField(env, obj, img_JPEG_bytesConsumedID, consumed);
(*env)->SetLongField(env, obj, img_JPEG_hNativeDecoderID, (jlong) ((IDATA)glDecompress));
RETURN_JAVA_ARRAY;
}
oldoutput_scanline = glDecompress->decompress.output_scanline;
completed_scanlines = 0;
outbufferPtr = glDecompress->outBuffer;
// here decoding goes
while(glDecompress->decompress.output_scanline < glDecompress->decompress.output_height &&
(scanlines_read = jpeg_read_scanlines(
&glDecompress->decompress,
(JSAMPARRAY)(outbufferPtr),
glDecompress->maxScanlines - completed_scanlines))) {
completed_scanlines = glDecompress->decompress.output_scanline - oldoutput_scanline;
outbufferPtr+=scanlines_read;//glDecompress->scanlineSize;
if(glDecompress->maxScanlines - completed_scanlines <= 0)
break;
}
(*env)->SetIntField(env, obj, img_JPEG_currScanlineID, glDecompress->decompress.output_scanline);
// Create java array and fill it with data
lengthPixels = completed_scanlines * glDecompress->decompress.image_width;
if(glDecompress->decompress.out_color_space == JCS_GRAYSCALE) {
unsigned char *outputBuffer;
int i, offset;
byteOut = (*env)->NewByteArray(env, lengthPixels);
outputBuffer = (unsigned char*) (*env)->GetPrimitiveArrayCritical(env, byteOut, 0);
for(i=0, offset=0; i<completed_scanlines;i++, offset+=glDecompress->decompress.image_width) {
JSAMPROW outBufferPtr = glDecompress->outBuffer[i];
memcpy(outputBuffer+offset, outBufferPtr, glDecompress->decompress.image_width);
}
(*env)->ReleasePrimitiveArrayCritical(env, byteOut, outputBuffer, 0);
} else { // JCS_RGB
unsigned char *outputBuffer;
int i, offset;
intOut = (*env)->NewIntArray(env, lengthPixels);
outputBuffer = (unsigned char*)(*env)->GetPrimitiveArrayCritical(env, intOut, 0);
// Expand 24->32 bpp for each scanline.
for(i=0, offset=0; i<completed_scanlines;i++, offset+=glDecompress->decompress.image_width*4/*sizeof java integer*/) {
JSAMPROW outBufferPtr = glDecompress->outBuffer[i];
toIntRGB(outBufferPtr, outputBuffer + offset, glDecompress->decompress.image_width);
}
(*env)->ReleasePrimitiveArrayCritical(env, intOut, outputBuffer, 0);
}
if(glDecompress->decompress.output_scanline >= glDecompress->decompress.output_height) {
if ( glDecompress->srcMgr.do_progressive ) {
jpeg_finish_output(&glDecompress->decompress);
glDecompress->srcMgr.final_pass = jpeg_input_complete(&glDecompress->decompress);
glDecompress->srcMgr.decoding_done =
glDecompress->srcMgr.final_pass &&
glDecompress->decompress.input_scan_number ==
glDecompress->decompress.output_scan_number;
} else {
glDecompress->srcMgr.decoding_done = TRUE;
}
if(!glDecompress->srcMgr.decoding_done) {
// don't return until necessary!
glDecompress->decoderState = DECOMPRESS_STARTED;
}
}
if(glDecompress->decoderState == DO_OUTPUT_SCAN && glDecompress->srcMgr.decoding_done) {
glDecompress->srcMgr.at_eof = TRUE;
(void) jpeg_finish_decompress(&glDecompress->decompress);
(void) gl_decompress_struct_destroy(glDecompress);
glDecompress->decoderState = READ_DONE;
free(glDecompress);
(*env)->SetIntField(env, obj, img_JPEG_bytesConsumedID, 0);
(*env)->SetLongField(env, obj, img_JPEG_hNativeDecoderID, 0/*(jlong) glDecompress*/);
RETURN_JAVA_ARRAY;
}
}
if(glDecompress->srcMgr.base.bytes_in_buffer &&
glDecompress->srcMgr.jpeg_buffer != glDecompress->srcMgr.base.next_input_byte
) {
memmove(glDecompress->srcMgr.jpeg_buffer,
glDecompress->srcMgr.base.next_input_byte,
glDecompress->srcMgr.base.bytes_in_buffer);
}
glDecompress->srcMgr.valid_buffer_length = (int) glDecompress->srcMgr.base.bytes_in_buffer;
(*env)->SetIntField(env, obj, img_JPEG_bytesConsumedID, consumed);
(*env)->SetLongField(env, obj, img_JPEG_hNativeDecoderID, (jlong) ((IDATA)glDecompress));
RETURN_JAVA_ARRAY;
}