From 0a7720cce4068a08a5b9a06cefa537de846fa099 Mon Sep 17 00:00:00 2001 From: Ruben Brunk Date: Fri, 28 Jun 2013 20:02:54 -0700 Subject: Added jpeg streaming classes. - Provides streaming operations for decompressing/compressing JPEG files. - Allows pixel operations to be performed on large JPEG images without holding the entire bitmap in memory. Change-Id: I597ddf282b59d2ba6d6bca4722208121e3728f94 --- jni_jpegstream/Android.mk | 41 +++ jni_jpegstream/src/error_codes.h | 26 ++ jni_jpegstream/src/inputstream_wrapper.cpp | 69 +++++ jni_jpegstream/src/inputstream_wrapper.h | 38 +++ jni_jpegstream/src/jerr_hook.cpp | 52 ++++ jni_jpegstream/src/jerr_hook.h | 43 ++++ jni_jpegstream/src/jni_defines.h | 29 +++ jni_jpegstream/src/jpeg_config.h | 31 +++ jni_jpegstream/src/jpeg_hook.cpp | 198 +++++++++++++++ jni_jpegstream/src/jpeg_hook.h | 76 ++++++ jni_jpegstream/src/jpeg_reader.cpp | 254 +++++++++++++++++++ jni_jpegstream/src/jpeg_reader.h | 88 +++++++ jni_jpegstream/src/jpeg_writer.cpp | 218 ++++++++++++++++ jni_jpegstream/src/jpeg_writer.h | 83 ++++++ jni_jpegstream/src/jpegstream.cpp | 377 ++++++++++++++++++++++++++++ jni_jpegstream/src/outputstream_wrapper.cpp | 49 ++++ jni_jpegstream/src/outputstream_wrapper.h | 35 +++ jni_jpegstream/src/stream_wrapper.cpp | 97 +++++++ jni_jpegstream/src/stream_wrapper.h | 44 ++++ 19 files changed, 1848 insertions(+) create mode 100644 jni_jpegstream/Android.mk create mode 100644 jni_jpegstream/src/error_codes.h create mode 100644 jni_jpegstream/src/inputstream_wrapper.cpp create mode 100644 jni_jpegstream/src/inputstream_wrapper.h create mode 100644 jni_jpegstream/src/jerr_hook.cpp create mode 100644 jni_jpegstream/src/jerr_hook.h create mode 100644 jni_jpegstream/src/jni_defines.h create mode 100644 jni_jpegstream/src/jpeg_config.h create mode 100644 jni_jpegstream/src/jpeg_hook.cpp create mode 100644 jni_jpegstream/src/jpeg_hook.h create mode 100644 jni_jpegstream/src/jpeg_reader.cpp create mode 100644 jni_jpegstream/src/jpeg_reader.h create mode 100644 jni_jpegstream/src/jpeg_writer.cpp create mode 100644 jni_jpegstream/src/jpeg_writer.h create mode 100644 jni_jpegstream/src/jpegstream.cpp create mode 100644 jni_jpegstream/src/outputstream_wrapper.cpp create mode 100644 jni_jpegstream/src/outputstream_wrapper.h create mode 100644 jni_jpegstream/src/stream_wrapper.cpp create mode 100644 jni_jpegstream/src/stream_wrapper.h (limited to 'jni_jpegstream') diff --git a/jni_jpegstream/Android.mk b/jni_jpegstream/Android.mk new file mode 100644 index 000000000..de11733f8 --- /dev/null +++ b/jni_jpegstream/Android.mk @@ -0,0 +1,41 @@ +LOCAL_PATH:= $(call my-dir) + +# Jpeg Streaming native + +include $(CLEAR_VARS) + +LOCAL_MODULE := libjni_jpegstream + +LOCAL_NDK_STL_VARIANT := stlport_static + +LOCAL_C_INCLUDES := $(LOCAL_PATH) \ + $(LOCAL_PATH)/src \ + external/jpeg + +LOCAL_SHARED_LIBRARIES := libjpeg +ifeq (,$(TARGET_BUILD_APPS)) + # platform build + LOCAL_SHARED_LIBRARIES := libcutils +endif + +LOCAL_LDFLAGS := -llog +LOCAL_SDK_VERSION := 9 +LOCAL_ARM_MODE := arm + +LOCAL_CFLAGS += -ffast-math -O3 -funroll-loops +LOCAL_CPPFLAGS += $(JNI_CFLAGS) + + +LOCAL_CPP_EXTENSION := .cpp +LOCAL_SRC_FILES := \ + src/inputstream_wrapper.cpp \ + src/jpegstream.cpp \ + src/jerr_hook.cpp \ + src/jpeg_hook.cpp \ + src/jpeg_writer.cpp \ + src/jpeg_reader.cpp \ + src/outputstream_wrapper.cpp \ + src/stream_wrapper.cpp + + +include $(BUILD_SHARED_LIBRARY) diff --git a/jni_jpegstream/src/error_codes.h b/jni_jpegstream/src/error_codes.h new file mode 100644 index 000000000..be55a0099 --- /dev/null +++ b/jni_jpegstream/src/error_codes.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#ifndef JPEG_ERROR_CODES_H_ +#define JPEG_ERROR_CODES_H_ + +#define J_DONE -4 +#define J_EXCEPTION -3 +#define J_ERROR_BAD_ARGS -2 +#define J_ERROR_FATAL -1 +#define J_SUCCESS 0 + +#endif // JPEG_ERROR_CODES_H_ diff --git a/jni_jpegstream/src/inputstream_wrapper.cpp b/jni_jpegstream/src/inputstream_wrapper.cpp new file mode 100644 index 000000000..98721b066 --- /dev/null +++ b/jni_jpegstream/src/inputstream_wrapper.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#include "inputstream_wrapper.h" +#include "error_codes.h" + +jmethodID InputStreamWrapper::sReadID = NULL; +jmethodID InputStreamWrapper::sSkipID = NULL; + +int32_t InputStreamWrapper::read(int32_t length, int32_t offset) { + if (offset < 0 || length < 0 || (offset + length) > getBufferSize()) { + return J_ERROR_BAD_ARGS; + } + int32_t bytesRead = 0; + mEnv->ReleaseByteArrayElements(mByteArray, mBytes, JNI_COMMIT); + mBytes = NULL; + if (mEnv->ExceptionCheck()) { + return J_EXCEPTION; + } + bytesRead = static_cast(mEnv->CallIntMethod(mStream, sReadID, + mByteArray, offset, length)); + if (mEnv->ExceptionCheck()) { + return J_EXCEPTION; + } + mBytes = mEnv->GetByteArrayElements(mByteArray, NULL); + if (mBytes == NULL || mEnv->ExceptionCheck()) { + return J_EXCEPTION; + } + if (bytesRead == END_OF_STREAM) { + return J_DONE; + } + return bytesRead; +} + +int64_t InputStreamWrapper::skip(int64_t count) { + int64_t bytesSkipped = 0; + bytesSkipped = static_cast(mEnv->CallLongMethod(mStream, sSkipID, + static_cast(count))); + if (mEnv->ExceptionCheck() || bytesSkipped < 0) { + return J_EXCEPTION; + } + return bytesSkipped; +} + +// Acts like a read call that returns the End Of Image marker for a JPEG file. +int32_t InputStreamWrapper::forceReadEOI() { + mBytes[0] = (jbyte) 0xFF; + mBytes[1] = (jbyte) 0xD9; + return 2; +} + +void InputStreamWrapper::setReadSkipMethodIDs(jmethodID readID, + jmethodID skipID) { + sReadID = readID; + sSkipID = skipID; +} diff --git a/jni_jpegstream/src/inputstream_wrapper.h b/jni_jpegstream/src/inputstream_wrapper.h new file mode 100644 index 000000000..ed9942bab --- /dev/null +++ b/jni_jpegstream/src/inputstream_wrapper.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#ifndef INPUTSTREAM_WRAPPER_H_ +#define INPUTSTREAM_WRAPPER_H_ + +#include "jni_defines.h" +#include "stream_wrapper.h" + +#include + +class InputStreamWrapper : public StreamWrapper { +public: + virtual int32_t read(int32_t length, int32_t offset); + virtual int64_t skip(int64_t count); + virtual int32_t forceReadEOI(); + + // Call this in JNI_OnLoad to cache read/skip method IDs + static void setReadSkipMethodIDs(jmethodID readID, jmethodID skipID); +protected: + static jmethodID sReadID; + static jmethodID sSkipID; +}; + +#endif // INPUTSTREAM_WRAPPER_H_ diff --git a/jni_jpegstream/src/jerr_hook.cpp b/jni_jpegstream/src/jerr_hook.cpp new file mode 100644 index 000000000..f8f864f78 --- /dev/null +++ b/jni_jpegstream/src/jerr_hook.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + + +#include "jerr_hook.h" +#include "jni_defines.h" + +/** + * Replaces libjpeg's error_exit function, returns control to + * the point + */ +void ErrExit(j_common_ptr cinfo) { + ErrManager* mgr = reinterpret_cast(cinfo->err); + (*cinfo->err->output_message) (cinfo); + // Returns control to error handling in jpeg_writer + longjmp(mgr->setjmp_buf, 1); +} + +/** + * Replaces libjpeg's output_message function, writes message + * to logcat's error log. + */ +void ErrOutput(j_common_ptr cinfo) { + ErrManager* mgr = reinterpret_cast(cinfo->err); + char buf[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message) (cinfo, buf); + buf[JMSG_LENGTH_MAX - 1] = '\0'; // Force null terminator + // Output error message in ndk logcat. + LOGE("%s\n", buf); +} + +void SetupErrMgr(j_common_ptr cinfo, ErrManager* errMgr) { + jpeg_std_error(&(errMgr->mgr)); + errMgr->mgr.error_exit = ErrExit; + errMgr->mgr.output_message = ErrOutput; + cinfo->err = reinterpret_cast(errMgr); +} + + diff --git a/jni_jpegstream/src/jerr_hook.h b/jni_jpegstream/src/jerr_hook.h new file mode 100644 index 000000000..f2ba7cd03 --- /dev/null +++ b/jni_jpegstream/src/jerr_hook.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#ifndef JERR_HOOK_H_ +#define JERR_HOOK_H_ + +extern "C" { +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" +} + +#include + +/** + * ErrManager replaces libjpeg's default error handling with + * the following behavior: + * - libjpeg function calls return to the position set by + * setjmp for error cleanup. + * - libjpeg error and warning messages are printed to + * logcat's error output. + */ +typedef struct { + struct jpeg_error_mgr mgr; + jmp_buf setjmp_buf; +} ErrManager; + +void SetupErrMgr(j_common_ptr cinfo, ErrManager* errMgr); + +#endif // JERR_HOOK_H_ diff --git a/jni_jpegstream/src/jni_defines.h b/jni_jpegstream/src/jni_defines.h new file mode 100644 index 000000000..8c9bd0430 --- /dev/null +++ b/jni_jpegstream/src/jni_defines.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#ifndef JNIDEFINES_H +#define JNIDEFINES_H + + +#include +#include +#include + +#define LOGV(msg...) __android_log_print(ANDROID_LOG_VERBOSE, "Native_JPEGStream", msg) +#define LOGE(msg...) __android_log_print(ANDROID_LOG_ERROR, "Native_JPEGStream", msg) +#define LOGW(msg...) __android_log_print(ANDROID_LOG_WARN, "Native_JPEGStream", msg) + +#endif // JNIDEFINES_H diff --git a/jni_jpegstream/src/jpeg_config.h b/jni_jpegstream/src/jpeg_config.h new file mode 100644 index 000000000..a99755275 --- /dev/null +++ b/jni_jpegstream/src/jpeg_config.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#ifndef JPEG_CONFIG_H_ +#define JPEG_CONFIG_H_ +namespace Jpeg_Config { + +// Pixel format +enum Format { + FORMAT_GRAYSCALE = 0x001, // 1 byte/pixel + FORMAT_RGB = 0x003, // 3 bytes/pixel RGBRGBRGBRGB... + FORMAT_RGBA = 0x004, // 4 bytes/pixel RGBARGBARGBARGBA... + FORMAT_ABGR = 0x104 // 4 bytes/pixel ABGRABGRABGR... +}; + +} // end namespace Jpeg_Config + +#endif // JPEG_CONFIG_H_ diff --git a/jni_jpegstream/src/jpeg_hook.cpp b/jni_jpegstream/src/jpeg_hook.cpp new file mode 100644 index 000000000..cca54e405 --- /dev/null +++ b/jni_jpegstream/src/jpeg_hook.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#include "error_codes.h" +#include "jni_defines.h" +#include "jpeg_hook.h" + +#include +#include + +void Mgr_init_destination_fcn(j_compress_ptr cinfo) { + DestManager *dst = reinterpret_cast(cinfo->dest); + dst->mgr.next_output_byte = reinterpret_cast(dst->outStream->getBufferPtr()); + dst->mgr.free_in_buffer = dst->outStream->getBufferSize(); +} + +boolean Mgr_empty_output_buffer_fcn(j_compress_ptr cinfo) { + DestManager *dst = reinterpret_cast(cinfo->dest); + int32_t len = dst->outStream->getBufferSize(); + if (dst->outStream->write(len, 0) != J_SUCCESS) { + ERREXIT(cinfo, JERR_FILE_WRITE); + } + dst->mgr.next_output_byte = reinterpret_cast(dst->outStream->getBufferPtr()); + dst->mgr.free_in_buffer = len; + return TRUE; +} + +void Mgr_term_destination_fcn(j_compress_ptr cinfo) { + DestManager *dst = reinterpret_cast(cinfo->dest); + int32_t remaining = dst->outStream->getBufferSize() - dst->mgr.free_in_buffer; + if (dst->outStream->write(remaining, 0) != J_SUCCESS) { + ERREXIT(cinfo, JERR_FILE_WRITE); + } +} + +int32_t MakeDst(j_compress_ptr cinfo, JNIEnv *env, jobject outStream) { + if (cinfo->dest != NULL) { + LOGE("DestManager already exists, cannot allocate!"); + return J_ERROR_FATAL; + } else { + size_t size = sizeof(DestManager); + cinfo->dest = (struct jpeg_destination_mgr *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_PERMANENT, size); + if (cinfo->dest == NULL) { + LOGE("Could not allocate memory for DestManager."); + return J_ERROR_FATAL; + } + memset(cinfo->dest, '0', size); + } + DestManager *d = reinterpret_cast(cinfo->dest); + d->mgr.init_destination = Mgr_init_destination_fcn; + d->mgr.empty_output_buffer = Mgr_empty_output_buffer_fcn; + d->mgr.term_destination = Mgr_term_destination_fcn; + d->outStream = new OutputStreamWrapper(); + if(d->outStream->init(env, outStream)) { + return J_SUCCESS; + } + return J_ERROR_FATAL; +} + +void UpdateDstEnv(j_compress_ptr cinfo, JNIEnv* env) { + DestManager* d = reinterpret_cast(cinfo->dest); + d->outStream->updateEnv(env); +} + +void CleanDst(j_compress_ptr cinfo) { + if (cinfo != NULL && cinfo->dest != NULL) { + DestManager *d = reinterpret_cast(cinfo->dest); + if (d->outStream != NULL) { + delete d->outStream; + d->outStream = NULL; + } + } +} + +boolean Mgr_fill_input_buffer_fcn(j_decompress_ptr cinfo) { + SourceManager *src = reinterpret_cast(cinfo->src); + int32_t bytesRead = src->inStream->read(src->inStream->getBufferSize(), 0); + if (bytesRead == J_DONE) { + if (src->start_of_file == TRUE) { + ERREXIT(cinfo, JERR_INPUT_EMPTY); + } + WARNMS(cinfo, JWRN_JPEG_EOF); + bytesRead = src->inStream->forceReadEOI(); + } else if (bytesRead < 0) { + ERREXIT(cinfo, JERR_FILE_READ); + } else if (bytesRead == 0) { + LOGW("read 0 bytes from InputStream."); + } + src->mgr.next_input_byte = reinterpret_cast(src->inStream->getBufferPtr()); + src->mgr.bytes_in_buffer = bytesRead; + if (bytesRead != 0) { + src->start_of_file = FALSE; + } + return TRUE; +} + +void Mgr_init_source_fcn(j_decompress_ptr cinfo) { + SourceManager *s = reinterpret_cast(cinfo->src); + s->start_of_file = TRUE; + Mgr_fill_input_buffer_fcn(cinfo); +} + +void Mgr_skip_input_data_fcn(j_decompress_ptr cinfo, long num_bytes) { + // Cannot skip negative or 0 bytes. + if (num_bytes <= 0) { + LOGW("skipping 0 bytes in InputStream"); + return; + } + SourceManager *src = reinterpret_cast(cinfo->src); + if (src->mgr.bytes_in_buffer >= num_bytes) { + src->mgr.bytes_in_buffer -= num_bytes; + src->mgr.next_input_byte += num_bytes; + } else { + // if skipping more bytes than remain in buffer, set skip_bytes + int64_t skip = num_bytes - src->mgr.bytes_in_buffer; + src->mgr.next_input_byte += src->mgr.bytes_in_buffer; + src->mgr.bytes_in_buffer = 0; + int64_t actual = src->inStream->skip(skip); + if (actual < 0) { + ERREXIT(cinfo, JERR_FILE_READ); + } + skip -= actual; + while (skip > 0) { + actual = src->inStream->skip(skip); + if (actual < 0) { + ERREXIT(cinfo, JERR_FILE_READ); + } + skip -= actual; + if (actual == 0) { + // Multiple zero byte skips, likely EOF + WARNMS(cinfo, JWRN_JPEG_EOF); + return; + } + } + } +} + +void Mgr_term_source_fcn(j_decompress_ptr cinfo) { + //noop +} + +int32_t MakeSrc(j_decompress_ptr cinfo, JNIEnv *env, jobject inStream){ + if (cinfo->src != NULL) { + LOGE("SourceManager already exists, cannot allocate!"); + return J_ERROR_FATAL; + } else { + size_t size = sizeof(SourceManager); + cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_PERMANENT, size); + if (cinfo->src == NULL) { + // Could not allocate memory. + LOGE("Could not allocate memory for SourceManager."); + return J_ERROR_FATAL; + } + memset(cinfo->src, '0', size); + } + SourceManager *s = reinterpret_cast(cinfo->src); + s->start_of_file = TRUE; + s->mgr.init_source = Mgr_init_source_fcn; + s->mgr.fill_input_buffer = Mgr_fill_input_buffer_fcn; + s->mgr.skip_input_data = Mgr_skip_input_data_fcn; + s->mgr.resync_to_restart = jpeg_resync_to_restart; // use default restart + s->mgr.term_source = Mgr_term_source_fcn; + s->inStream = new InputStreamWrapper(); + if(s->inStream->init(env, inStream)) { + return J_SUCCESS; + } + return J_ERROR_FATAL; +} + +void UpdateSrcEnv(j_decompress_ptr cinfo, JNIEnv* env) { + SourceManager* s = reinterpret_cast(cinfo->src); + s->inStream->updateEnv(env); +} + +void CleanSrc(j_decompress_ptr cinfo) { + if (cinfo != NULL && cinfo->src != NULL) { + SourceManager *s = reinterpret_cast(cinfo->src); + if (s->inStream != NULL) { + delete s->inStream; + s->inStream = NULL; + } + } +} diff --git a/jni_jpegstream/src/jpeg_hook.h b/jni_jpegstream/src/jpeg_hook.h new file mode 100644 index 000000000..b02bb34c0 --- /dev/null +++ b/jni_jpegstream/src/jpeg_hook.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#ifndef LIBJPEG_HOOK_H_ +#define LIBJPEG_HOOK_H_ + +extern "C" { +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" +} + +#include "inputstream_wrapper.h" +#include "outputstream_wrapper.h" + +#include + +/** + * DestManager holds the libjpeg destination manager struct and + * a holder with a java OutputStream. + */ +typedef struct { + struct jpeg_destination_mgr mgr; + OutputStreamWrapper *outStream; +} DestManager; + +// Initializes the DestManager struct, sets up the jni refs +int32_t MakeDst(j_compress_ptr cinfo, JNIEnv *env, jobject outStream); + +/** + * Updates the jni env pointer. This should be called in the beginning of any + * JNI method in jpegstream.cpp before CleanDst or any of the libjpeg functions + * that can trigger a call to an OutputStreamWrapper method. + */ +void UpdateDstEnv(j_compress_ptr cinfo, JNIEnv* env); + +// Cleans the jni refs. To wipe the compress object call jpeg_destroy_compress +void CleanDst(j_compress_ptr cinfo); + +/** + * SourceManager holds the libjpeg source manager struct and a + * holder with a java InputStream. + */ +typedef struct { + struct jpeg_source_mgr mgr; + boolean start_of_file; + InputStreamWrapper *inStream; +} SourceManager; + +// Initializes the SourceManager struct, sets up the jni refs +int32_t MakeSrc(j_decompress_ptr cinfo, JNIEnv *env, jobject inStream); + +/** + * Updates the jni env pointer. This should be called in the beginning of any + * JNI method in jpegstream.cpp before CleanSrc or any of the libjpeg functions + * that can trigger a call to an InputStreamWrapper method. + */ +void UpdateSrcEnv(j_decompress_ptr cinfo, JNIEnv* env); + +// Cleans the jni refs. To wipe the decompress object, call jpeg_destroy_decompress +void CleanSrc(j_decompress_ptr cinfo); + +#endif // LIBJPEG_HOOK_H_ diff --git a/jni_jpegstream/src/jpeg_reader.cpp b/jni_jpegstream/src/jpeg_reader.cpp new file mode 100644 index 000000000..4726b6426 --- /dev/null +++ b/jni_jpegstream/src/jpeg_reader.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#include "jpeg_reader.h" +#include "error_codes.h" +#include "jpeg_hook.h" + +#include + +JpegReader::JpegReader() : + mInfo(), + mErrorManager(), + mScanlineBuf(NULL), + mScanlineIter(NULL), + mScanlineBuflen(0), + mScanlineUnformattedBuflen(0), + mScanlineBytesRemaining(0), + mFormat(), + mFinished(false), + mSetup(false) {} + +JpegReader::~JpegReader() { + if (reset() != J_SUCCESS) { + LOGE("Failed to destroy compress object, JpegReader may leak memory."); + } +} + +int32_t JpegReader::setup(JNIEnv *env, jobject in, int32_t* width, int32_t* height, + Jpeg_Config::Format format) { + if (mFinished || mSetup) { + return J_ERROR_FATAL; + } + if (env->ExceptionCheck()) { + return J_EXCEPTION; + } + + // Setup error handler + SetupErrMgr(reinterpret_cast(&mInfo), &mErrorManager); + // Set jump address for error handling + if (setjmp(mErrorManager.setjmp_buf)) { + return J_ERROR_FATAL; + } + + // Call libjpeg setup + jpeg_create_decompress(&mInfo); + + // Setup our data source object, this allocates java global references + int32_t flags = MakeSrc(&mInfo, env, in); + if (flags != J_SUCCESS) { + LOGE("Failed to make source with error code: %d ", flags); + return flags; + } + + // Reads jpeg file header + jpeg_read_header(&mInfo, TRUE); + jpeg_calc_output_dimensions(&mInfo); + + const int components = (static_cast(format) & 0xff); + + // Do setup for input format + switch (components) { + case 1: + mInfo.out_color_space = JCS_GRAYSCALE; + mScanlineUnformattedBuflen = mInfo.output_width; + break; + case 3: + case 4: + mScanlineUnformattedBuflen = mInfo.output_width * components; + if (mInfo.jpeg_color_space == JCS_CMYK + || mInfo.jpeg_color_space == JCS_YCCK) { + // Always use cmyk for output in a 4 channel jpeg. + // libjpeg has a builtin cmyk->rgb decoder. + mScanlineUnformattedBuflen = mInfo.output_width * 4; + mInfo.out_color_space = JCS_CMYK; + } else { + mInfo.out_color_space = JCS_RGB; + } + break; + default: + return J_ERROR_BAD_ARGS; + } + + mScanlineBuflen = mInfo.output_width * components; + mScanlineBytesRemaining = mScanlineBuflen; + mScanlineBuf = (JSAMPLE *) (mInfo.mem->alloc_small)( + reinterpret_cast(&mInfo), JPOOL_PERMANENT, + mScanlineUnformattedBuflen * sizeof(JSAMPLE)); + mScanlineIter = mScanlineBuf; + jpeg_start_decompress(&mInfo); + + // Output image dimensions + if (width != NULL) { + *width = mInfo.output_width; + } + if (height != NULL) { + *height = mInfo.output_height; + } + + mFormat = format; + mSetup = true; + return J_SUCCESS; +} + +int32_t JpegReader::read(int8_t* bytes, int32_t offset, int32_t count) { + if (!mSetup) { + return J_ERROR_FATAL; + } + if (mFinished) { + return J_DONE; + } + // Set jump address for error handling + if (setjmp(mErrorManager.setjmp_buf)) { + return J_ERROR_FATAL; + } + if (count <= 0) { + return J_ERROR_BAD_ARGS; + } + int32_t total_length = count; + while (mInfo.output_scanline < mInfo.output_height) { + if (count < mScanlineBytesRemaining) { + // read partial scanline and return + if (bytes != NULL) { + // Treat NULL bytes as a skip + memcpy((void*) (bytes + offset), (void*) mScanlineIter, + count * sizeof(int8_t)); + } + mScanlineBytesRemaining -= count; + mScanlineIter += count; + return total_length; + } else if (count > 0) { + // read full scanline + if (bytes != NULL) { + // Treat NULL bytes as a skip + memcpy((void*) (bytes + offset), (void*) mScanlineIter, + mScanlineBytesRemaining * sizeof(int8_t)); + bytes += mScanlineBytesRemaining; + } + count -= mScanlineBytesRemaining; + mScanlineBytesRemaining = 0; + } + // Scanline buffer exhausted, read next scanline + if (jpeg_read_scanlines(&mInfo, &mScanlineBuf, 1) != 1) { + // Always read full scanline, no IO suspension + return J_ERROR_FATAL; + } + // Do in-place pixel formatting + formatPixels(static_cast(mScanlineBuf), + mScanlineUnformattedBuflen); + + // Reset iterators + mScanlineIter = mScanlineBuf; + mScanlineBytesRemaining = mScanlineBuflen; + } + + // Read all of the scanlines + jpeg_finish_decompress(&mInfo); + mFinished = true; + return total_length - count; +} + +void JpegReader::updateEnv(JNIEnv *env) { + UpdateSrcEnv(&mInfo, env); +} + +// Does in-place pixel formatting +void JpegReader::formatPixels(uint8_t* buf, int32_t len) { + uint8_t *iter = buf; + + // Do cmyk->rgb conversion if necessary + switch (mInfo.out_color_space) { + case JCS_CMYK: + // Convert CMYK to RGB + int r, g, b, c, m, y, k; + for (int i = 0; i < len; i += 4) { + c = buf[i + 0]; + m = buf[i + 1]; + y = buf[i + 2]; + k = buf[i + 3]; + // Handle fmt for weird photoshop markers + if (mInfo.saw_Adobe_marker) { + r = (k * c) / 255; + g = (k * m) / 255; + b = (k * y) / 255; + } else { + r = (255 - k) * (255 - c) / 255; + g = (255 - k) * (255 - m) / 255; + b = (255 - k) * (255 - y) / 255; + } + *iter++ = r; + *iter++ = g; + *iter++ = b; + } + break; + case JCS_RGB: + iter += (len * 3 / 4); + break; + case JCS_GRAYSCALE: + default: + return; + } + + // Do endianness and alpha for output format + if (mFormat == Jpeg_Config::FORMAT_RGBA) { + // Set alphas to 255 + uint8_t* end = buf + len - 1; + for (int i = len - 1; i >= 0; i -= 4) { + buf[i] = 255; + buf[i - 1] = *--iter; + buf[i - 2] = *--iter; + buf[i - 3] = *--iter; + } + } else if (mFormat == Jpeg_Config::FORMAT_ABGR) { + // Reverse endianness and set alphas to 255 + uint8_t* end = buf + len - 1; + int r, g, b; + for (int i = len - 1; i >= 0; i -= 4) { + b = *--iter; + g = *--iter; + r = *--iter; + buf[i] = r; + buf[i - 1] = g; + buf[i - 2] = b; + buf[i - 3] = 255; + } + } +} + +int32_t JpegReader::reset() { + // Set jump address for error handling + if (setjmp(mErrorManager.setjmp_buf)) { + return J_ERROR_FATAL; + } + // Clean up global java references + CleanSrc(&mInfo); + // Wipe decompress struct, free memory pools + jpeg_destroy_decompress(&mInfo); + mFinished = false; + mSetup = false; + return J_SUCCESS; +} + diff --git a/jni_jpegstream/src/jpeg_reader.h b/jni_jpegstream/src/jpeg_reader.h new file mode 100644 index 000000000..afde27b93 --- /dev/null +++ b/jni_jpegstream/src/jpeg_reader.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ +#ifndef JPEG_READER_H_ +#define JPEG_READER_H_ + +#include "jerr_hook.h" +#include "jni_defines.h" +#include "jpeg_config.h" + +#include + +/** + * JpegReader wraps libjpeg's decompression functionality and a + * java InputStream object. Read calls return data from the + * InputStream that has been decompressed. + */ +class JpegReader { +public: + JpegReader(); + ~JpegReader(); + + /** + * Call setup with a valid InputStream reference and pixel format. + * If this method is successful, the contents of width and height will + * be set to the dimensions of the bitmap to be read. + * + * ***This method will result in the jpeg file header being read + * from the InputStream*** + * + * Returns J_SUCCESS on success or a negative error code. + */ + int32_t setup(JNIEnv *env, jobject in, int32_t* width, int32_t* height, + Jpeg_Config::Format format); + + /** + * Decompresses bytes from the InputStream and writes at most count + * bytes into the buffer, bytes, starting at some offset. Passing a + * NULL as the bytes pointer effectively skips those bytes. + * + * ***This method will result in bytes being read from the InputStream*** + * + * Returns the number of bytes written into the input buffer or a + * negative error code. + */ + int32_t read(int8_t * bytes, int32_t offset, int32_t count); + + /** + * Updates the environment pointer. Call this before read or reset + * in any jni function call. + */ + void updateEnv(JNIEnv *env); + + /** + * Frees any java global references held by the JpegReader, destroys + * the decompress structure, and frees allocations in libjpeg's pools. + */ + int32_t reset(); + +private: + void formatPixels(uint8_t* buf, int32_t len); + struct jpeg_decompress_struct mInfo; + ErrManager mErrorManager; + + JSAMPLE* mScanlineBuf; + JSAMPLE* mScanlineIter; + int32_t mScanlineBuflen; + int32_t mScanlineUnformattedBuflen; + int32_t mScanlineBytesRemaining; + + Jpeg_Config::Format mFormat; + bool mFinished; + bool mSetup; +}; + +#endif // JPEG_READER_H_ diff --git a/jni_jpegstream/src/jpeg_writer.cpp b/jni_jpegstream/src/jpeg_writer.cpp new file mode 100644 index 000000000..4f7891778 --- /dev/null +++ b/jni_jpegstream/src/jpeg_writer.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#include "jpeg_hook.h" +#include "jpeg_writer.h" +#include "error_codes.h" + +#include +#include + +JpegWriter::JpegWriter() : mInfo(), + mErrorManager(), + mScanlineBuf(NULL), + mScanlineIter(NULL), + mScanlineBuflen(0), + mScanlineBytesRemaining(0), + mFormat(), + mFinished(false), + mSetup(false) {} + +JpegWriter::~JpegWriter() { + if (reset() != J_SUCCESS) { + LOGE("Failed to destroy compress object, may leak memory."); + } +} + +const int32_t JpegWriter::DEFAULT_X_DENSITY = 300; +const int32_t JpegWriter::DEFAULT_Y_DENSITY = 300; +const int32_t JpegWriter::DEFAULT_DENSITY_UNIT = 1; + +int32_t JpegWriter::setup(JNIEnv *env, jobject out, int32_t width, int32_t height, + Jpeg_Config::Format format, int32_t quality) { + if (mFinished || mSetup) { + return J_ERROR_FATAL; + } + if (env->ExceptionCheck()) { + return J_EXCEPTION; + } + if (height <= 0 || width <= 0 || quality <= 0 || quality > 100) { + return J_ERROR_BAD_ARGS; + } + // Setup error handler + SetupErrMgr(reinterpret_cast(&mInfo), &mErrorManager); + + // Set jump address for error handling + if (setjmp(mErrorManager.setjmp_buf)) { + return J_ERROR_FATAL; + } + + // Setup cinfo struct + jpeg_create_compress(&mInfo); + + // Setup global java refs + int32_t flags = MakeDst(&mInfo, env, out); + if (flags != J_SUCCESS) { + return flags; + } + + // Initialize width, height, and color space + mInfo.image_width = width; + mInfo.image_height = height; + const int components = (static_cast(format) & 0xff); + switch (components) { + case 1: + mInfo.input_components = 1; + mInfo.in_color_space = JCS_GRAYSCALE; + break; + case 3: + case 4: + mInfo.input_components = 3; + mInfo.in_color_space = JCS_RGB; + break; + default: + return J_ERROR_BAD_ARGS; + } + + // Set defaults + jpeg_set_defaults(&mInfo); + mInfo.density_unit = DEFAULT_DENSITY_UNIT; // JFIF code for pixel size units: + // 1 = in, 2 = cm + mInfo.X_density = DEFAULT_X_DENSITY; // Horizontal pixel density + mInfo.Y_density = DEFAULT_Y_DENSITY; // Vertical pixel density + + // Set compress quality + jpeg_set_quality(&mInfo, quality, TRUE); + + mFormat = format; + + // Setup scanline buffer + mScanlineBuflen = width * components; + mScanlineBytesRemaining = mScanlineBuflen; + mScanlineBuf = (JSAMPLE *) (mInfo.mem->alloc_small)( + reinterpret_cast(&mInfo), JPOOL_PERMANENT, + mScanlineBuflen * sizeof(JSAMPLE)); + mScanlineIter = mScanlineBuf; + + // Start compression + jpeg_start_compress(&mInfo, TRUE); + mSetup = true; + return J_SUCCESS; +} + +int32_t JpegWriter::write(int8_t* bytes, int32_t length) { + if (!mSetup) { + return J_ERROR_FATAL; + } + if (mFinished) { + return 0; + } + // Set jump address for error handling + if (setjmp(mErrorManager.setjmp_buf)) { + return J_ERROR_FATAL; + } + if (length < 0 || bytes == NULL) { + return J_ERROR_BAD_ARGS; + } + + int32_t total_length = length; + JSAMPROW row_pointer[1]; + while (mInfo.next_scanline < mInfo.image_height) { + if (length < mScanlineBytesRemaining) { + // read partial scanline and return + memcpy((void*) mScanlineIter, (void*) bytes, + length * sizeof(int8_t)); + mScanlineBytesRemaining -= length; + mScanlineIter += length; + return total_length; + } else if (length > 0) { + // read full scanline + memcpy((void*) mScanlineIter, (void*) bytes, + mScanlineBytesRemaining * sizeof(int8_t)); + bytes += mScanlineBytesRemaining; + length -= mScanlineBytesRemaining; + mScanlineBytesRemaining = 0; + } + // Do in-place pixel formatting + formatPixels(static_cast(mScanlineBuf), mScanlineBuflen); + row_pointer[0] = mScanlineBuf; + // Do compression + if (jpeg_write_scanlines(&mInfo, row_pointer, 1) != 1) { + return J_ERROR_FATAL; + } + // Reset scanline buffer + mScanlineBytesRemaining = mScanlineBuflen; + mScanlineIter = mScanlineBuf; + } + jpeg_finish_compress(&mInfo); + mFinished = true; + return total_length - length; +} + +// Does in-place pixel formatting +void JpegWriter::formatPixels(uint8_t* buf, int32_t len) { + // Assumes len is a multiple of 4 for RGBA and ABGR pixels. + assert((len % 4) == 0); + uint8_t* d = buf; + switch (mFormat) { + case Jpeg_Config::FORMAT_RGBA: { + // Strips alphas + for (int i = 0; i < len / 4; ++i, buf += 4) { + *d++ = buf[0]; + *d++ = buf[1]; + *d++ = buf[2]; + } + break; + } + case Jpeg_Config::FORMAT_ABGR: { + // Strips alphas and flips endianness + if (len / 4 >= 1) { + *d++ = buf[3]; + uint8_t tmp = *d; + *d++ = buf[2]; + *d++ = tmp; + } + for (int i = 1; i < len / 4; ++i, buf += 4) { + *d++ = buf[3]; + *d++ = buf[2]; + *d++ = buf[1]; + } + break; + } + default: { + // Do nothing + break; + } + } +} + +void JpegWriter::updateEnv(JNIEnv *env) { + UpdateDstEnv(&mInfo, env); +} + +int32_t JpegWriter::reset() { + // Set jump address for error handling + if (setjmp(mErrorManager.setjmp_buf)) { + return J_ERROR_FATAL; + } + // Clean up global java references + CleanDst(&mInfo); + // Wipe compress struct, free memory pools + jpeg_destroy_compress(&mInfo); + mFinished = false; + mSetup = false; + return J_SUCCESS; +} diff --git a/jni_jpegstream/src/jpeg_writer.h b/jni_jpegstream/src/jpeg_writer.h new file mode 100644 index 000000000..bd9a42d32 --- /dev/null +++ b/jni_jpegstream/src/jpeg_writer.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ +#ifndef JPEG_WRITER_H_ +#define JPEG_WRITER_H_ + +#include "jerr_hook.h" +#include "jni_defines.h" +#include "jpeg_config.h" + +#include + +/** + * JpegWriter wraps libjpeg's compression functionality and a + * java OutputStream object. Write calls result in input data + * being compressed and written to the OuputStream. + */ +class JpegWriter { +public: + JpegWriter(); + ~JpegWriter(); + + /** + * Call setup with a valid OutputStream reference, bitmap height and + * width, pixel format, and compression quality in range (0, 100]. + * + * Returns J_SUCCESS on success or a negative error code. + */ + int32_t setup(JNIEnv *env, jobject out, int32_t width, int32_t height, + Jpeg_Config::Format format, int32_t quality); + + /** + * Compresses bytes from the input buffer. + * + * ***This method will result in bytes being written to the OutputStream*** + * + * Returns J_SUCCESS on success or a negative error code. + */ + int32_t write(int8_t* bytes, int32_t length); + + /** + * Updates the environment pointer. Call this before write or reset + * in any jni function call. + */ + void updateEnv(JNIEnv *env); + + /** + * Frees any java global references held by the JpegWriter, destroys + * the compress structure, and frees allocations in libjpeg's pools. + */ + int32_t reset(); + + static const int32_t DEFAULT_X_DENSITY; + static const int32_t DEFAULT_Y_DENSITY; + static const int32_t DEFAULT_DENSITY_UNIT; +private: + void formatPixels(uint8_t* buf, int32_t len); + struct jpeg_compress_struct mInfo; + ErrManager mErrorManager; + + JSAMPLE* mScanlineBuf; + JSAMPLE* mScanlineIter; + int32_t mScanlineBuflen; + int32_t mScanlineBytesRemaining; + + Jpeg_Config::Format mFormat; + bool mFinished; + bool mSetup; +}; + +#endif // JPEG_WRITER_H_ diff --git a/jni_jpegstream/src/jpegstream.cpp b/jni_jpegstream/src/jpegstream.cpp new file mode 100644 index 000000000..3b9a6830b --- /dev/null +++ b/jni_jpegstream/src/jpegstream.cpp @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#include "error_codes.h" +#include "jni_defines.h" +#include "jpeg_writer.h" +#include "jpeg_reader.h" +#include "jpeg_config.h" +#include "outputstream_wrapper.h" +#include "inputstream_wrapper.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static jint OutputStream_setup(JNIEnv* env, jobject thiz, jobject out, + jint width, jint height, jint format, jint quality) { + // Get a reference to this object's class + jclass thisClass = env->GetObjectClass(thiz); + if (env->ExceptionCheck() || thisClass == NULL) { + return J_EXCEPTION; + } + // Get field for storing C pointer + jfieldID fidNumber = env->GetFieldID(thisClass, "JNIPointer", "J"); + if (NULL == fidNumber || env->ExceptionCheck()) { + return J_EXCEPTION; + } + + // Check size + if (width <= 0 || height <= 0) { + return J_ERROR_BAD_ARGS; + } + Jpeg_Config::Format fmt = static_cast(format); + // Check format + switch (fmt) { + case Jpeg_Config::FORMAT_GRAYSCALE: + case Jpeg_Config::FORMAT_RGB: + case Jpeg_Config::FORMAT_RGBA: + case Jpeg_Config::FORMAT_ABGR: + break; + default: + return J_ERROR_BAD_ARGS; + } + + uint32_t w = static_cast(width); + uint32_t h = static_cast(height); + int32_t q = static_cast(quality); + // Clamp quality to (0, 100] + q = (q > 100) ? 100 : ((q < 1) ? 1 : q); + + JpegWriter* w_ptr = new JpegWriter(); + + // Do JpegWriter setup. + int32_t errorFlag = w_ptr->setup(env, out, w, h, fmt, q); + if (env->ExceptionCheck() || errorFlag != J_SUCCESS) { + delete w_ptr; + return errorFlag; + } + + // Store C pointer for writer + env->SetLongField(thiz, fidNumber, reinterpret_cast(w_ptr)); + if (env->ExceptionCheck()) { + delete w_ptr; + return J_EXCEPTION; + } + return J_SUCCESS; +} + +static jint InputStream_setup(JNIEnv* env, jobject thiz, jobject dimens, + jobject in, jint format) { + // Get a reference to this object's class + jclass thisClass = env->GetObjectClass(thiz); + if (env->ExceptionCheck() || thisClass == NULL) { + return J_EXCEPTION; + } + jmethodID setMethod = NULL; + + // Get dimensions object setter method + if (dimens != NULL) { + jclass pointClass = env->GetObjectClass(dimens); + if (env->ExceptionCheck() || pointClass == NULL) { + return J_EXCEPTION; + } + setMethod = env->GetMethodID(pointClass, "set", "(II)V"); + if (env->ExceptionCheck() || setMethod == NULL) { + return J_EXCEPTION; + } + } + // Get field for storing C pointer + jfieldID fidNumber = env->GetFieldID(thisClass, "JNIPointer", "J"); + if (NULL == fidNumber || env->ExceptionCheck()) { + return J_EXCEPTION; + } + Jpeg_Config::Format fmt = static_cast(format); + // Check format + switch (fmt) { + case Jpeg_Config::FORMAT_GRAYSCALE: + case Jpeg_Config::FORMAT_RGB: + case Jpeg_Config::FORMAT_RGBA: + case Jpeg_Config::FORMAT_ABGR: + break; + default: + return J_ERROR_BAD_ARGS; + } + + JpegReader* r_ptr = new JpegReader(); + int32_t w = 0, h = 0; + // Do JpegReader setup. + int32_t errorFlag = r_ptr->setup(env, in, &w, &h, fmt); + if (env->ExceptionCheck() || errorFlag != J_SUCCESS) { + delete r_ptr; + return errorFlag; + } + + // Set dimensions to return + if (dimens != NULL) { + env->CallVoidMethod(dimens, setMethod, static_cast(w), + static_cast(h)); + if (env->ExceptionCheck()) { + delete r_ptr; + return J_EXCEPTION; + } + } + // Store C pointer for reader + env->SetLongField(thiz, fidNumber, reinterpret_cast(r_ptr)); + if (env->ExceptionCheck()) { + delete r_ptr; + return J_EXCEPTION; + } + return J_SUCCESS; +} + +static JpegWriter* getWPtr(JNIEnv* env, jobject thiz, jfieldID* fid) { + jclass thisClass = env->GetObjectClass(thiz); + if (env->ExceptionCheck() || thisClass == NULL) { + return NULL; + } + jfieldID fidNumber = env->GetFieldID(thisClass, "JNIPointer", "J"); + if (NULL == fidNumber || env->ExceptionCheck()) { + return NULL; + } + jlong ptr = env->GetLongField(thiz, fidNumber); + if (env->ExceptionCheck()) { + return NULL; + } + // Get writer C pointer out of java field. + JpegWriter* w_ptr = reinterpret_cast(ptr); + if (fid != NULL) { + *fid = fidNumber; + } + return w_ptr; +} + +static JpegReader* getRPtr(JNIEnv* env, jobject thiz, jfieldID* fid) { + jclass thisClass = env->GetObjectClass(thiz); + if (env->ExceptionCheck() || thisClass == NULL) { + return NULL; + } + jfieldID fidNumber = env->GetFieldID(thisClass, "JNIPointer", "J"); + if (NULL == fidNumber || env->ExceptionCheck()) { + return NULL; + } + jlong ptr = env->GetLongField(thiz, fidNumber); + if (env->ExceptionCheck()) { + return NULL; + } + // Get reader C pointer out of java field. + JpegReader* r_ptr = reinterpret_cast(ptr); + if (fid != NULL) { + *fid = fidNumber; + } + return r_ptr; +} + +static void OutputStream_cleanup(JNIEnv* env, jobject thiz) { + jfieldID fidNumber = NULL; + JpegWriter* w_ptr = getWPtr(env, thiz, &fidNumber); + if (w_ptr == NULL) { + return; + } + // Update environment + w_ptr->updateEnv(env); + // Destroy writer object + delete w_ptr; + w_ptr = NULL; + // Set the java field to null + env->SetLongField(thiz, fidNumber, reinterpret_cast(w_ptr)); +} + +static void InputStream_cleanup(JNIEnv* env, jobject thiz) { + jfieldID fidNumber = NULL; + JpegReader* r_ptr = getRPtr(env, thiz, &fidNumber); + if (r_ptr == NULL) { + return; + } + // Update environment + r_ptr->updateEnv(env); + // Destroy the reader object + delete r_ptr; + r_ptr = NULL; + // Set the java field to null + env->SetLongField(thiz, fidNumber, reinterpret_cast(r_ptr)); +} + +static jint OutputStream_writeInputBytes(JNIEnv* env, jobject thiz, + jbyteArray inBuffer, jint offset, jint inCount) { + JpegWriter* w_ptr = getWPtr(env, thiz, NULL); + if (w_ptr == NULL) { + return J_EXCEPTION; + } + // Pin input buffer + jbyte* in_buf = (jbyte*) env->GetByteArrayElements(inBuffer, 0); + if (env->ExceptionCheck() || in_buf == NULL) { + return J_EXCEPTION; + } + + int8_t* in_bytes = static_cast(in_buf); + int32_t in_len = static_cast(inCount); + int32_t off = static_cast(offset); + in_bytes += off; + int32_t written = 0; + + // Update environment + w_ptr->updateEnv(env); + // Write out and unpin buffer. + written = w_ptr->write(in_bytes, in_len); + env->ReleaseByteArrayElements(inBuffer, in_buf, JNI_ABORT); + return written; +} + +static jint InputStream_readDecodedBytes(JNIEnv* env, jobject thiz, + jbyteArray inBuffer, jint offset, jint inCount) { + JpegReader* r_ptr = getRPtr(env, thiz, NULL); + if (r_ptr == NULL) { + return J_EXCEPTION; + } + // Pin input buffer + jbyte* in_buf = (jbyte*) env->GetByteArrayElements(inBuffer, 0); + if (env->ExceptionCheck() || in_buf == NULL) { + return J_EXCEPTION; + } + int8_t* in_bytes = static_cast(in_buf); + int32_t in_len = static_cast(inCount); + int32_t off = static_cast(offset); + int32_t read = 0; + + // Update environment + r_ptr->updateEnv(env); + // Read into buffer + read = r_ptr->read(in_bytes, off, in_len); + + // Unpin buffer + if (read < 0) { + env->ReleaseByteArrayElements(inBuffer, in_buf, JNI_ABORT); + } else { + env->ReleaseByteArrayElements(inBuffer, in_buf, JNI_COMMIT); + } + return read; +} + +static jint InputStream_skipDecodedBytes(JNIEnv* env, jobject thiz, + jint bytes) { + if (bytes <= 0) { + return J_ERROR_BAD_ARGS; + } + JpegReader* r_ptr = getRPtr(env, thiz, NULL); + if (r_ptr == NULL) { + return J_EXCEPTION; + } + + // Update environment + r_ptr->updateEnv(env); + int32_t skip = 0; + // Read with null buffer to skip + skip = r_ptr->read(NULL, 0, bytes); + return skip; +} + +static const char *outClassPathName = + "com/android/gallery3d/jpegstream/JPEGOutputStream"; +static const char *inClassPathName = + "com/android/gallery3d/jpegstream/JPEGInputStream"; + +static JNINativeMethod writeMethods[] = { { "setup", + "(Ljava/io/OutputStream;IIII)I", (void*) OutputStream_setup }, { + "cleanup", "()V", (void*) OutputStream_cleanup }, { "writeInputBytes", + "([BII)I", (void*) OutputStream_writeInputBytes } }; + +static JNINativeMethod readMethods[] = { { "setup", + "(Landroid/graphics/Point;Ljava/io/InputStream;I)I", + (void*) InputStream_setup }, { "cleanup", "()V", + (void*) InputStream_cleanup }, { "readDecodedBytes", "([BII)I", + (void*) InputStream_readDecodedBytes }, { "skipDecodedBytes", "(I)I", + (void*) InputStream_skipDecodedBytes } }; + +static int registerNativeMethods(JNIEnv* env, const char* className, + JNINativeMethod* gMethods, int numMethods) { + jclass clazz; + clazz = env->FindClass(className); + if (clazz == NULL) { + LOGE("Native registration unable to find class '%s'", className); + return JNI_FALSE; + } + if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { + LOGE("RegisterNatives failed for '%s'", className); + return JNI_FALSE; + } + return JNI_TRUE; +} + +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + LOGE("Error: GetEnv failed in JNI_OnLoad"); + return -1; + } + if (!registerNativeMethods(env, outClassPathName, writeMethods, + sizeof(writeMethods) / sizeof(writeMethods[0]))) { + LOGE("Error: could not register native methods for JPEGOutputStream"); + return -1; + } + if (!registerNativeMethods(env, inClassPathName, readMethods, + sizeof(readMethods) / sizeof(readMethods[0]))) { + LOGE("Error: could not register native methods for JPEGInputStream"); + return -1; + } + // cache method IDs for OutputStream + jclass outCls = env->FindClass("java/io/OutputStream"); + if (outCls == NULL) { + LOGE("Unable to find class 'OutputStream'"); + return -1; + } + jmethodID cachedWriteFun = env->GetMethodID(outCls, "write", "([BII)V"); + if (cachedWriteFun == NULL) { + LOGE("Unable to find write function in class 'OutputStream'"); + return -1; + } + OutputStreamWrapper::setWriteMethodID(cachedWriteFun); + + // cache method IDs for InputStream + jclass inCls = env->FindClass("java/io/InputStream"); + if (inCls == NULL) { + LOGE("Unable to find class 'InputStream'"); + return -1; + } + jmethodID cachedReadFun = env->GetMethodID(inCls, "read", "([BII)I"); + if (cachedReadFun == NULL) { + LOGE("Unable to find read function in class 'InputStream'"); + return -1; + } + jmethodID cachedSkipFun = env->GetMethodID(inCls, "skip", "(J)J"); + if (cachedSkipFun == NULL) { + LOGE("Unable to find skip function in class 'InputStream'"); + return -1; + } + InputStreamWrapper::setReadSkipMethodIDs(cachedReadFun, cachedSkipFun); + return JNI_VERSION_1_6; +} + +#ifdef __cplusplus +} +#endif diff --git a/jni_jpegstream/src/outputstream_wrapper.cpp b/jni_jpegstream/src/outputstream_wrapper.cpp new file mode 100644 index 000000000..0639b6eea --- /dev/null +++ b/jni_jpegstream/src/outputstream_wrapper.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#include "outputstream_wrapper.h" +#include "error_codes.h" + +jmethodID OutputStreamWrapper::sWriteID = NULL; + +int32_t OutputStreamWrapper::write(int32_t length, int32_t offset) { + if (offset < 0 || length < 0 || (offset + length) > getBufferSize()) { + return J_ERROR_BAD_ARGS; + } + mEnv->ReleaseByteArrayElements(mByteArray, mBytes, JNI_COMMIT); + mBytes = NULL; + if (mEnv->ExceptionCheck()) { + return J_EXCEPTION; + } + if (sWriteID == NULL) { + LOGE("Uninitialized method ID for OutputStream write function."); + return J_ERROR_FATAL; + } + // Call OutputStream write with byte array. + mEnv->CallVoidMethod(mStream, sWriteID, mByteArray, offset, length); + if (mEnv->ExceptionCheck()) { + return J_EXCEPTION; + } + mBytes = mEnv->GetByteArrayElements(mByteArray, NULL); + if (mBytes == NULL || mEnv->ExceptionCheck()) { + return J_EXCEPTION; + } + return J_SUCCESS; +} + +void OutputStreamWrapper::setWriteMethodID(jmethodID id) { + sWriteID = id; +} diff --git a/jni_jpegstream/src/outputstream_wrapper.h b/jni_jpegstream/src/outputstream_wrapper.h new file mode 100644 index 000000000..9b8b0071b --- /dev/null +++ b/jni_jpegstream/src/outputstream_wrapper.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#ifndef OUTPUTSTREAM_WRAPPER_H_ +#define OUTPUTSTREAM_WRAPPER_H_ + +#include "jni_defines.h" +#include "stream_wrapper.h" + +#include + +class OutputStreamWrapper : public StreamWrapper { +public: + virtual int32_t write(int32_t length, int32_t offset); + + // Call this in JNI_OnLoad to cache write method + static void setWriteMethodID(jmethodID id); +protected: + static jmethodID sWriteID; +}; + +#endif // OUTPUTSTREAM_WRAPPER_H_ diff --git a/jni_jpegstream/src/stream_wrapper.cpp b/jni_jpegstream/src/stream_wrapper.cpp new file mode 100644 index 000000000..049d84f67 --- /dev/null +++ b/jni_jpegstream/src/stream_wrapper.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#include "stream_wrapper.h" + +const int32_t StreamWrapper::END_OF_STREAM = -1; +const int32_t StreamWrapper::DEFAULT_BUFFER_SIZE = 1 << 16; // 64Kb + +StreamWrapper::StreamWrapper() : mEnv(NULL), + mStream(NULL), + mByteArray(NULL), + mBytes(NULL), + mByteArrayLen(0) {} + +StreamWrapper::~StreamWrapper() { + cleanup(); +} + +void StreamWrapper::updateEnv(JNIEnv *env) { + if (env == NULL) { + LOGE("Cannot update StreamWrapper with a null JNIEnv pointer!"); + return; + } + mEnv = env; +} + +bool StreamWrapper::init(JNIEnv *env, jobject stream) { + if (mEnv != NULL) { + LOGW("StreamWrapper already initialized!"); + return false; + } + mEnv = env; + mStream = env->NewGlobalRef(stream); + if (mStream == NULL || env->ExceptionCheck()) { + cleanup(); + return false; + } + mByteArrayLen = DEFAULT_BUFFER_SIZE; + jbyteArray tmp = env->NewByteArray(getBufferSize()); + if (tmp == NULL || env->ExceptionCheck()){ + cleanup(); + return false; + } + mByteArray = reinterpret_cast(env->NewGlobalRef(tmp)); + if (mByteArray == NULL || env->ExceptionCheck()){ + cleanup(); + return false; + } + mBytes = env->GetByteArrayElements(mByteArray, NULL); + if (mBytes == NULL || env->ExceptionCheck()){ + cleanup(); + return false; + } + return true; +} + +void StreamWrapper::cleanup() { + if (mEnv != NULL) { + if (mStream != NULL) { + mEnv->DeleteGlobalRef(mStream); + mStream = NULL; + } + if (mByteArray != NULL) { + if (mBytes != NULL) { + mEnv->ReleaseByteArrayElements(mByteArray, mBytes, JNI_ABORT); + mBytes = NULL; + } + mEnv->DeleteGlobalRef(mByteArray); + mByteArray = NULL; + } else { + mBytes = NULL; + } + mByteArrayLen = 0; + mEnv = NULL; + } +} + +int32_t StreamWrapper::getBufferSize() { + return mByteArrayLen; +} + +jbyte* StreamWrapper::getBufferPtr() { + return mBytes; +} diff --git a/jni_jpegstream/src/stream_wrapper.h b/jni_jpegstream/src/stream_wrapper.h new file mode 100644 index 000000000..e036a9145 --- /dev/null +++ b/jni_jpegstream/src/stream_wrapper.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed 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. + */ + +#ifndef STREAM_WRAPPER_H_ +#define STREAM_WRAPPER_H_ + +#include "jni_defines.h" + +#include + +class StreamWrapper { +public: + StreamWrapper(); + virtual ~StreamWrapper(); + virtual void updateEnv(JNIEnv *env); + virtual bool init(JNIEnv *env, jobject stream); + virtual void cleanup(); + virtual int32_t getBufferSize(); + virtual jbyte* getBufferPtr(); + + const static int32_t DEFAULT_BUFFER_SIZE; + const static int32_t END_OF_STREAM; +protected: + JNIEnv *mEnv; + jobject mStream; + jbyteArray mByteArray; + jbyte* mBytes; + int32_t mByteArrayLen; +}; + +#endif // STREAM_WRAPPER_H_ -- cgit v1.2.3