blob: c70ff9b1c19281f008e102ab19f5d39dfe4282ec [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 Rustem Rafikov
*/
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "org_apache_harmony_x_imageio_plugins_jpeg_JPEGImageWriter.h"
#include "jpeglib.h"
#include "setjmp.h"
#include "exceptions.h"
#define MAX_BUFFER 32768
typedef struct {
struct jpeg_error_mgr base;
jmp_buf jmp_buffer;
} enc_error_mgr, * enc_error_mgr_ptr;
typedef struct {
JOCTET *jpeg_buffer;
jobject ios;
} enc_client_data, * enc_client_data_ptr;
static jmethodID IOSwriteID;
static jmethodID getScanlineID;
static jmethodID QTableGetTableID;
JavaVM *jvm;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
jvm = vm;
return JNI_VERSION_1_2;
}
static JNIEnv* get_env() {
JNIEnv *e;
int rt = (*jvm)->GetEnv(jvm, (void **)&e, JNI_VERSION_1_2);
return e;
}
/**
* Sets DQT tables
*/
static void setupDQTs(JNIEnv *env, j_compress_ptr cinfo, jobjectArray dqts) {
JQUANT_TBL *quant_ptr;
jint len;
int i, j;
jobject slot;
jint *arrPtr;
if (!dqts) {
//-- TODO: write warning
return;
}
len = (*env)->GetArrayLength(env, dqts);
for (i = 0; i < len; i++) {
slot = (*env)->GetObjectArrayElement(env, dqts, i);
arrPtr = (*env)->GetPrimitiveArrayCritical(env, slot, NULL);
if (cinfo->quant_tbl_ptrs[i] == NULL) {
cinfo->quant_tbl_ptrs[i] = jpeg_alloc_quant_table((j_common_ptr)cinfo);
}
quant_ptr = cinfo->quant_tbl_ptrs[i];
for (j = 0; j < 64; j++) {
quant_ptr->quantval[j] = arrPtr[j];
}
quant_ptr->sent_table = FALSE;
(*env)->ReleasePrimitiveArrayCritical(env, slot, arrPtr, 0);
(*env)->DeleteLocalRef(env, slot);
}
}
/*
* Initialize destination --- called by jpeg_start_compress
* before any data is actually written.
*/
METHODDEF(void) ios_init_destination(j_compress_ptr cinfo) {
}
/**
* Called when output buffer becomes filled
*/
METHODDEF(boolean) ios_empty_output_buffer(j_compress_ptr cinfo) {
struct jpeg_compress_struct * cs;
enc_client_data_ptr cdata;
enc_error_mgr_ptr err_mgr;
jbyteArray java_buffer;
JOCTET *native_buffer;
JNIEnv *env;
cs = (struct jpeg_compress_struct *) cinfo;
cdata = (enc_client_data_ptr) cs->client_data;
err_mgr = (enc_error_mgr_ptr) cs->err;
env = get_env();
java_buffer = (*env)->NewByteArray(env, MAX_BUFFER);
native_buffer = (JOCTET *)(*env)->GetPrimitiveArrayCritical(env, java_buffer, NULL);
memcpy(native_buffer, cdata->jpeg_buffer, MAX_BUFFER);
(*env)->ReleasePrimitiveArrayCritical(env, java_buffer, native_buffer, 0);
(*env)->CallVoidMethod(env, cdata->ios, IOSwriteID, java_buffer, 0, MAX_BUFFER);
// checking for an exception in the java method
if ((*env)->ExceptionOccurred(env)) {
err_mgr->base.error_exit((j_common_ptr) cinfo);
}
cs->dest->next_output_byte = cdata->jpeg_buffer;
cs->dest->free_in_buffer = MAX_BUFFER;
return TRUE;
}
/*
* Terminate destination --- called by jpeg_finish_compress
*/
METHODDEF(void) ios_term_destination(j_compress_ptr cinfo) {
struct jpeg_compress_struct * cs;
enc_client_data_ptr cdata;
enc_error_mgr_ptr err_mgr;
jbyteArray java_buffer;
JOCTET *native_buffer;
JNIEnv *env;
cs = (struct jpeg_compress_struct *) cinfo;
cdata = (enc_client_data_ptr) cs->client_data;
err_mgr = (enc_error_mgr_ptr) cs->err;
env = get_env();
java_buffer = (*env)->NewByteArray(env, MAX_BUFFER);
native_buffer = (JOCTET *)(*env)->GetPrimitiveArrayCritical(env, java_buffer, NULL);
memcpy(native_buffer, cdata->jpeg_buffer, MAX_BUFFER);
(*env)->ReleasePrimitiveArrayCritical(env, java_buffer, native_buffer, 0);
(*env)->CallVoidMethod(env, cdata->ios, IOSwriteID, java_buffer, 0, MAX_BUFFER - cs->dest->free_in_buffer);
// checking for an exception in the java method
if ((*env)->ExceptionOccurred(env)) {
err_mgr->base.error_exit((j_common_ptr) cinfo);
}
cs->dest->next_output_byte = NULL;
cs->dest->free_in_buffer = 0;
}
/**
* The error handler
*/
METHODDEF(void) ios_jpeg_error_exit(j_common_ptr cinfo) {
enc_error_mgr_ptr err_mgr = (enc_error_mgr_ptr) cinfo->err;
#ifdef DEBUG
printf("in jpeg_error_exit()\n");
#endif //-- DEBUG
longjmp(err_mgr->jmp_buffer, 1);
}
/**
* It is called from initCompressionObjects
*/
//GLOBAL(CompressStruct*) ios_create_compress(JNIEnv *env) {
GLOBAL(struct jpeg_compress_struct *) ios_create_compress(JNIEnv *env) {
struct jpeg_compress_struct * cinfo;
struct jpeg_destination_mgr * dest_mgr;
enc_error_mgr_ptr err_mgr;
enc_client_data_ptr client_data;
//-- create compress struct
cinfo = malloc(sizeof(struct jpeg_compress_struct));
if (!cinfo) {
throwNewOutOfMemoryError(env, "Unable to allocate memory for IJG structures");
return 0;
}
//-- create error manager
err_mgr = malloc(sizeof(enc_error_mgr));
if (!err_mgr) {
free(cinfo);
throwNewOutOfMemoryError(env, "Unable to allocate memory for IJG structures");
return 0;
}
cinfo->err = jpeg_std_error(&(err_mgr->base));
err_mgr->base.error_exit = ios_jpeg_error_exit;
//-- TODO setjmp before every call to libjpeg
jpeg_create_compress(cinfo);
dest_mgr = malloc(sizeof(struct jpeg_destination_mgr));
if (!dest_mgr) {
free(cinfo);
free(err_mgr);
throwNewOutOfMemoryError(env, "Unable to allocate memory for IJG structures");
return 0;
}
cinfo->dest = dest_mgr;
client_data = malloc(sizeof(enc_client_data));
if (!client_data) {
free(cinfo);
free(err_mgr);
free(dest_mgr);
throwNewOutOfMemoryError(env, "Unable to allocate memory for IJG structures");
return 0;
}
cinfo->client_data = client_data;
client_data->ios = NULL;
client_data->jpeg_buffer = malloc(MAX_BUFFER);
if (!client_data->jpeg_buffer) {
free(cinfo);
free(err_mgr);
free(dest_mgr);
free(client_data);
throwNewOutOfMemoryError(env, "Unable to allocate memory for IJG structures");
return 0;
}
dest_mgr->next_output_byte = client_data->jpeg_buffer;
dest_mgr->free_in_buffer = MAX_BUFFER;
dest_mgr->init_destination = ios_init_destination;
dest_mgr->empty_output_buffer = ios_empty_output_buffer;
dest_mgr->term_destination = ios_term_destination;
return cinfo;
}
/*
* Class: org_apache_harmony_x_imageio_plugins_jpeg_JPEGImageWriter
* Method: dispose
* Signature: (J)V
*/
JNIEXPORT void JNICALL
Java_org_apache_harmony_x_imageio_plugins_jpeg_JPEGImageWriter_dispose(JNIEnv *env, jobject obj, jlong handle) {
struct jpeg_compress_struct * cinfo = (struct jpeg_compress_struct *) (IDATA)handle;
enc_client_data_ptr cdata = (enc_client_data_ptr) cinfo->client_data;
if (cdata->ios != NULL) {
(*env)->DeleteGlobalRef(env, cdata->ios);
cdata->ios = NULL;
}
if (cdata->jpeg_buffer != NULL) {
free(cdata->jpeg_buffer);
}
free(cinfo->dest);
free(cinfo->client_data);
free(cinfo->err);
jpeg_destroy_compress(cinfo);
free(cinfo);
}
/*
* Class: org_apache_harmony_x_imageio_plugins_jpeg_JPEGImageWriter
* Method: initWriterIds
* Signature: (Ljava/lang/Class;)V
*/
JNIEXPORT void JNICALL
Java_org_apache_harmony_x_imageio_plugins_jpeg_JPEGImageWriter_initWriterIds(JNIEnv *env, jclass encoderClass, jclass iosClass) {
//-- ImageOutputStream.write(byte[], int, int)
IOSwriteID = (*env)->GetMethodID(env, iosClass, "write", "([BII)V");
//-- call back to getScanLine(int);
getScanlineID = (*env)->GetMethodID(env, encoderClass, "getScanLine", "(I)V");
//-- init jvm var
(*env)->GetJavaVM(env, &jvm);
}
/*
* Class: org_apache_harmony_x_imageio_plugins_jpeg_JPEGImageWriter
* Method: initCompressionObj
* Signature: ()J
*/
JNIEXPORT jlong JNICALL
Java_org_apache_harmony_x_imageio_plugins_jpeg_JPEGImageWriter_initCompressionObj(JNIEnv *env, jobject encoder) {
return (jlong) (IDATA)ios_create_compress(env);
}
/*
* Class: org_apache_harmony_x_imageio_plugins_jpeg_JPEGImageWriter
* Method: setIOS
* Signature: (Ljavax/imageio/stream/ImageOutputStream;J)V
*/
JNIEXPORT void JNICALL
Java_org_apache_harmony_x_imageio_plugins_jpeg_JPEGImageWriter_setIOS(JNIEnv *env, jobject encoder, jobject iosObj, jlong handle) {
struct jpeg_compress_struct * cinfo = (struct jpeg_compress_struct *) (IDATA)handle;
enc_client_data_ptr cdata = (enc_client_data_ptr) cinfo->client_data;
if (cdata->ios != NULL) {
(*env)->DeleteGlobalRef(env, cdata->ios);
cdata->ios = NULL;
}
cdata->ios = (*env)->NewGlobalRef(env, iosObj);
if (cdata->ios == NULL) {
throwNewOutOfMemoryError(env, "Unable to allocate memory for IJG structures");
return;
}
cinfo->dest->next_output_byte = cdata->jpeg_buffer;
cinfo->dest->free_in_buffer = MAX_BUFFER;
}
/*
* Class: org_apache_harmony_x_imageio_plugins_jpeg_JPEGImageWriter
* Method: encode
* Signature: ([BIIIIIIIZ[[IJ)Z
*/
JNIEXPORT jboolean JNICALL
Java_org_apache_harmony_x_imageio_plugins_jpeg_JPEGImageWriter_encode(JNIEnv *env,
jobject callerObj, jbyteArray arr, jint srcWidth, jint width, jint height, jint deltaX,
jint in_cs, jint out_cs, jint numBands, jboolean progressive, jobjectArray dqts, jlong handle)
{
JSAMPROW row_pointer;
struct jpeg_compress_struct * cinfo;
enc_error_mgr_ptr err_mgr;
int i, j;
int cur_scanline;
unsigned char * native_buffer;
unsigned char * rowPtr;
jboolean optimizeHuffman = FALSE;
row_pointer = (JSAMPROW) malloc(width * numBands);
if (!row_pointer) {
throwNewOutOfMemoryError(env, "Unable to allocate memory for IJG structures");
return FALSE;
}
cinfo = (struct jpeg_compress_struct *) (IDATA)handle;
err_mgr = (enc_error_mgr_ptr) cinfo->err;
if (setjmp(err_mgr->jmp_buffer)) {
if (!(*env)->ExceptionOccurred(env)) {
char msg_buffer[JMSG_LENGTH_MAX];
cinfo->err->format_message((j_common_ptr)cinfo, msg_buffer);
throwNewExceptionByName(env, "javax/imageio/IIOException",
msg_buffer);
}
if (row_pointer) {
free(row_pointer);
}
return FALSE;
}
cinfo->image_width = width;
cinfo->image_height = height;
cinfo->input_components = numBands;
cinfo->in_color_space = in_cs;
jpeg_set_defaults(cinfo);
jpeg_set_colorspace(cinfo, out_cs);
cinfo->optimize_coding = optimizeHuffman;
//-- TRUE - for pure abbrivated images (wo tables) creation
//-- if you want to write some tables set "sent_table = FALSE" after
//-- this call for the table to be emitted.
//jpeg_suppress_tables(&cinfo, TRUE);
jpeg_suppress_tables(cinfo, FALSE);
setupDQTs(env, cinfo, dqts);
//-- only simple progression sequence
if (progressive) {
jpeg_simple_progression(cinfo);
}
//-- TRUE forces all "sent_table = FALSE" so all tables will be written
jpeg_start_compress(cinfo, TRUE);
//-- use this for tables-only files and abbrivated images.
//-- If using jpeg_suppress_tables(&cinfo, TRUE):
//-- Only DQT sent_table = FALSE right now -> so DQT will be written but not DHT.
//-- uncomment when custom huffman tables be used.
//jpeg_start_compress(&cinfo, FALSE);
cur_scanline = 0;
while (cinfo->next_scanline < cinfo->image_height) {
(*env)->CallVoidMethod(env, callerObj, getScanlineID, cur_scanline);
// checking for an exception in the java method
if ((*env)->ExceptionOccurred(env)) {
//c_struct->exception_in_callback = TRUE;
cinfo->err->error_exit((j_common_ptr) cinfo);
}
native_buffer = (JOCTET*) (*env)->GetPrimitiveArrayCritical(env, arr, NULL);
// subsampling and copying to internal array
rowPtr = row_pointer;
for (i = 0; i < srcWidth * numBands; i += numBands * deltaX) {
for (j = 0; j < numBands; j++) {
*rowPtr++ = native_buffer[i + j];
}
}
(*env)->ReleasePrimitiveArrayCritical(env, arr, native_buffer, 0);
jpeg_write_scanlines(cinfo, &row_pointer, 1);
cur_scanline++;
}
jpeg_finish_compress(cinfo);
free(row_pointer);
return TRUE;
}