summaryrefslogtreecommitdiffstats
path: root/jni_jpegstream
diff options
context:
space:
mode:
authorRuben Brunk <rubenbrunk@google.com>2013-06-29 03:02:54 (GMT)
committerRuben Brunk <rubenbrunk@google.com>2013-07-11 20:41:29 (GMT)
commita8221bbdb8ece9b02dbf7b72565f9fbc5b314f7c (patch)
tree0fd84567e4f8a8d2bff0e7dcd696c88d518251cb /jni_jpegstream
parent0c1b4c6422a4d2d9b81cc0946d1c9675440a94e2 (diff)
downloadandroid_packages_apps_Gallery2-a8221bbdb8ece9b02dbf7b72565f9fbc5b314f7c.zip
android_packages_apps_Gallery2-a8221bbdb8ece9b02dbf7b72565f9fbc5b314f7c.tar.gz
android_packages_apps_Gallery2-a8221bbdb8ece9b02dbf7b72565f9fbc5b314f7c.tar.bz2
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
Diffstat (limited to 'jni_jpegstream')
-rw-r--r--jni_jpegstream/Android.mk41
-rw-r--r--jni_jpegstream/src/error_codes.h26
-rw-r--r--jni_jpegstream/src/inputstream_wrapper.cpp69
-rw-r--r--jni_jpegstream/src/inputstream_wrapper.h38
-rw-r--r--jni_jpegstream/src/jerr_hook.cpp52
-rw-r--r--jni_jpegstream/src/jerr_hook.h43
-rw-r--r--jni_jpegstream/src/jni_defines.h29
-rw-r--r--jni_jpegstream/src/jpeg_config.h31
-rw-r--r--jni_jpegstream/src/jpeg_hook.cpp198
-rw-r--r--jni_jpegstream/src/jpeg_hook.h76
-rw-r--r--jni_jpegstream/src/jpeg_reader.cpp254
-rw-r--r--jni_jpegstream/src/jpeg_reader.h88
-rw-r--r--jni_jpegstream/src/jpeg_writer.cpp218
-rw-r--r--jni_jpegstream/src/jpeg_writer.h83
-rw-r--r--jni_jpegstream/src/jpegstream.cpp377
-rw-r--r--jni_jpegstream/src/outputstream_wrapper.cpp49
-rw-r--r--jni_jpegstream/src/outputstream_wrapper.h35
-rw-r--r--jni_jpegstream/src/stream_wrapper.cpp97
-rw-r--r--jni_jpegstream/src/stream_wrapper.h44
19 files changed, 1848 insertions, 0 deletions
diff --git a/jni_jpegstream/Android.mk b/jni_jpegstream/Android.mk
new file mode 100644
index 0000000..de11733
--- /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 0000000..be55a00
--- /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 0000000..98721b0
--- /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<int32_t>(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<int64_t>(mEnv->CallLongMethod(mStream, sSkipID,
+ static_cast<jlong>(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 0000000..ed9942b
--- /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 <stdint.h>
+
+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 0000000..f8f864f
--- /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<ErrManager*>(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<ErrManager*>(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<struct jpeg_error_mgr*>(errMgr);
+}
+
+
diff --git a/jni_jpegstream/src/jerr_hook.h b/jni_jpegstream/src/jerr_hook.h
new file mode 100644
index 0000000..f2ba7cd
--- /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 <setjmp.h>
+
+/**
+ * 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 0000000..8c9bd04
--- /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 <jni.h>
+#include <string.h>
+#include <android/log.h>
+
+#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 0000000..a997552
--- /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 0000000..cca54e4
--- /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 <stddef.h>
+#include <string.h>
+
+void Mgr_init_destination_fcn(j_compress_ptr cinfo) {
+ DestManager *dst = reinterpret_cast<DestManager*>(cinfo->dest);
+ dst->mgr.next_output_byte = reinterpret_cast<JOCTET*>(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<DestManager*>(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<JOCTET*>(dst->outStream->getBufferPtr());
+ dst->mgr.free_in_buffer = len;
+ return TRUE;
+}
+
+void Mgr_term_destination_fcn(j_compress_ptr cinfo) {
+ DestManager *dst = reinterpret_cast<DestManager*>(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<DestManager*>(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<DestManager*>(cinfo->dest);
+ d->outStream->updateEnv(env);
+}
+
+void CleanDst(j_compress_ptr cinfo) {
+ if (cinfo != NULL && cinfo->dest != NULL) {
+ DestManager *d = reinterpret_cast<DestManager*>(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<SourceManager*>(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<JOCTET*>(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<SourceManager*>(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<SourceManager*>(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<SourceManager*>(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<SourceManager*>(cinfo->src);
+ s->inStream->updateEnv(env);
+}
+
+void CleanSrc(j_decompress_ptr cinfo) {
+ if (cinfo != NULL && cinfo->src != NULL) {
+ SourceManager *s = reinterpret_cast<SourceManager*>(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 0000000..b02bb34
--- /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 <stdint.h>
+
+/**
+ * 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 0000000..4726b64
--- /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 <setjmp.h>
+
+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<j_common_ptr>(&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<int>(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<j_common_ptr>(&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<uint8_t*>(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 0000000..afde27b
--- /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 <stdint.h>
+
+/**
+ * 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 0000000..4f78917
--- /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 <setjmp.h>
+#include <assert.h>
+
+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<j_common_ptr>(&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<int>(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<j_common_ptr>(&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<uint8_t*>(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 0000000..bd9a42d
--- /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 <stdint.h>
+
+/**
+ * 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 0000000..3b9a683
--- /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 <stdint.h>
+
+#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<Jpeg_Config::Format>(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<uint32_t>(width);
+ uint32_t h = static_cast<uint32_t>(height);
+ int32_t q = static_cast<int32_t>(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<jlong>(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<Jpeg_Config::Format>(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<jint>(w),
+ static_cast<jint>(h));
+ if (env->ExceptionCheck()) {
+ delete r_ptr;
+ return J_EXCEPTION;
+ }
+ }
+ // Store C pointer for reader
+ env->SetLongField(thiz, fidNumber, reinterpret_cast<jlong>(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<JpegWriter*>(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<JpegReader*>(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<jlong>(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<jlong>(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<int8_t*>(in_buf);
+ int32_t in_len = static_cast<int32_t>(inCount);
+ int32_t off = static_cast<int32_t>(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<int8_t*>(in_buf);
+ int32_t in_len = static_cast<int32_t>(inCount);
+ int32_t off = static_cast<int32_t>(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<void**>(&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 0000000..0639b6e
--- /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 0000000..9b8b007
--- /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 <stdint.h>
+
+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 0000000..049d84f
--- /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<jbyteArray>(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 0000000..e036a91
--- /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 <stdint.h>
+
+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_