1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "error_codes.h"
18#include "jni_defines.h"
19#include "jpeg_writer.h"
20#include "jpeg_reader.h"
21#include "jpeg_config.h"
22#include "outputstream_wrapper.h"
23#include "inputstream_wrapper.h"
24
25#include <stdint.h>
26
27#ifdef __cplusplus
28extern "C" {
29#endif
30
31static jint OutputStream_setup(JNIEnv* env, jobject thiz, jobject out,
32        jint width, jint height, jint format, jint quality) {
33    // Get a reference to this object's class
34    jclass thisClass = env->GetObjectClass(thiz);
35    if (env->ExceptionCheck() || thisClass == NULL) {
36        return J_EXCEPTION;
37    }
38    // Get field for storing C pointer
39    jfieldID fidNumber = env->GetFieldID(thisClass, "JNIPointer", "J");
40    if (NULL == fidNumber || env->ExceptionCheck()) {
41        return J_EXCEPTION;
42    }
43
44    // Check size
45    if (width <= 0 || height <= 0) {
46        return J_ERROR_BAD_ARGS;
47    }
48    Jpeg_Config::Format fmt = static_cast<Jpeg_Config::Format>(format);
49    // Check format
50    switch (fmt) {
51    case Jpeg_Config::FORMAT_GRAYSCALE:
52    case Jpeg_Config::FORMAT_RGB:
53    case Jpeg_Config::FORMAT_RGBA:
54    case Jpeg_Config::FORMAT_ABGR:
55        break;
56    default:
57        return J_ERROR_BAD_ARGS;
58    }
59
60    uint32_t w = static_cast<uint32_t>(width);
61    uint32_t h = static_cast<uint32_t>(height);
62    int32_t q = static_cast<int32_t>(quality);
63    // Clamp quality to (0, 100]
64    q = (q > 100) ? 100 : ((q < 1) ? 1 : q);
65
66    JpegWriter* w_ptr = new JpegWriter();
67
68    // Do JpegWriter setup.
69    int32_t errorFlag = w_ptr->setup(env, out, w, h, fmt, q);
70    if (env->ExceptionCheck() || errorFlag != J_SUCCESS) {
71        delete w_ptr;
72        return errorFlag;
73    }
74
75    // Store C pointer for writer
76    env->SetLongField(thiz, fidNumber, reinterpret_cast<jlong>(w_ptr));
77    if (env->ExceptionCheck()) {
78        delete w_ptr;
79        return J_EXCEPTION;
80    }
81    return J_SUCCESS;
82}
83
84static jint InputStream_setup(JNIEnv* env, jobject thiz, jobject dimens,
85        jobject in, jint format) {
86    // Get a reference to this object's class
87    jclass thisClass = env->GetObjectClass(thiz);
88    if (env->ExceptionCheck() || thisClass == NULL) {
89        return J_EXCEPTION;
90    }
91    jmethodID setMethod = NULL;
92
93    // Get dimensions object setter method
94    if (dimens != NULL) {
95        jclass pointClass = env->GetObjectClass(dimens);
96        if (env->ExceptionCheck() || pointClass == NULL) {
97            return J_EXCEPTION;
98        }
99        setMethod = env->GetMethodID(pointClass, "set", "(II)V");
100        if (env->ExceptionCheck() || setMethod == NULL) {
101            return J_EXCEPTION;
102        }
103    }
104    // Get field for storing C pointer
105    jfieldID fidNumber = env->GetFieldID(thisClass, "JNIPointer", "J");
106    if (NULL == fidNumber || env->ExceptionCheck()) {
107        return J_EXCEPTION;
108    }
109    Jpeg_Config::Format fmt = static_cast<Jpeg_Config::Format>(format);
110    // Check format
111    switch (fmt) {
112    case Jpeg_Config::FORMAT_GRAYSCALE:
113    case Jpeg_Config::FORMAT_RGB:
114    case Jpeg_Config::FORMAT_RGBA:
115    case Jpeg_Config::FORMAT_ABGR:
116        break;
117    default:
118        return J_ERROR_BAD_ARGS;
119    }
120
121    JpegReader* r_ptr = new JpegReader();
122    int32_t w = 0, h = 0;
123    // Do JpegReader setup.
124    int32_t errorFlag = r_ptr->setup(env, in, &w, &h, fmt);
125    if (env->ExceptionCheck() || errorFlag != J_SUCCESS) {
126        delete r_ptr;
127        return errorFlag;
128    }
129
130    // Set dimensions to return
131    if (dimens != NULL) {
132        env->CallVoidMethod(dimens, setMethod, static_cast<jint>(w),
133                static_cast<jint>(h));
134        if (env->ExceptionCheck()) {
135            delete r_ptr;
136            return J_EXCEPTION;
137        }
138    }
139    // Store C pointer for reader
140    env->SetLongField(thiz, fidNumber, reinterpret_cast<jlong>(r_ptr));
141    if (env->ExceptionCheck()) {
142        delete r_ptr;
143        return J_EXCEPTION;
144    }
145    return J_SUCCESS;
146}
147
148static JpegWriter* getWPtr(JNIEnv* env, jobject thiz, jfieldID* fid) {
149    jclass thisClass = env->GetObjectClass(thiz);
150    if (env->ExceptionCheck() || thisClass == NULL) {
151        return NULL;
152    }
153    jfieldID fidNumber = env->GetFieldID(thisClass, "JNIPointer", "J");
154    if (NULL == fidNumber || env->ExceptionCheck()) {
155        return NULL;
156    }
157    jlong ptr = env->GetLongField(thiz, fidNumber);
158    if (env->ExceptionCheck()) {
159        return NULL;
160    }
161    // Get writer C pointer out of java field.
162    JpegWriter* w_ptr = reinterpret_cast<JpegWriter*>(ptr);
163    if (fid != NULL) {
164        *fid = fidNumber;
165    }
166    return w_ptr;
167}
168
169static JpegReader* getRPtr(JNIEnv* env, jobject thiz, jfieldID* fid) {
170    jclass thisClass = env->GetObjectClass(thiz);
171    if (env->ExceptionCheck() || thisClass == NULL) {
172        return NULL;
173    }
174    jfieldID fidNumber = env->GetFieldID(thisClass, "JNIPointer", "J");
175    if (NULL == fidNumber || env->ExceptionCheck()) {
176        return NULL;
177    }
178    jlong ptr = env->GetLongField(thiz, fidNumber);
179    if (env->ExceptionCheck()) {
180        return NULL;
181    }
182    // Get reader C pointer out of java field.
183    JpegReader* r_ptr = reinterpret_cast<JpegReader*>(ptr);
184    if (fid != NULL) {
185        *fid = fidNumber;
186    }
187    return r_ptr;
188}
189
190static void OutputStream_cleanup(JNIEnv* env, jobject thiz) {
191    jfieldID fidNumber = NULL;
192    JpegWriter* w_ptr = getWPtr(env, thiz, &fidNumber);
193    if (w_ptr == NULL) {
194        return;
195    }
196    // Update environment
197    w_ptr->updateEnv(env);
198    // Destroy writer object
199    delete w_ptr;
200    w_ptr = NULL;
201    // Set the java field to null
202    env->SetLongField(thiz, fidNumber, reinterpret_cast<jlong>(w_ptr));
203}
204
205static void InputStream_cleanup(JNIEnv* env, jobject thiz) {
206    jfieldID fidNumber = NULL;
207    JpegReader* r_ptr = getRPtr(env, thiz, &fidNumber);
208    if (r_ptr == NULL) {
209        return;
210    }
211    // Update environment
212    r_ptr->updateEnv(env);
213    // Destroy the reader object
214    delete r_ptr;
215    r_ptr = NULL;
216    // Set the java field to null
217    env->SetLongField(thiz, fidNumber, reinterpret_cast<jlong>(r_ptr));
218}
219
220static jint OutputStream_writeInputBytes(JNIEnv* env, jobject thiz,
221        jbyteArray inBuffer, jint offset, jint inCount) {
222    JpegWriter* w_ptr = getWPtr(env, thiz, NULL);
223    if (w_ptr == NULL) {
224        return J_EXCEPTION;
225    }
226    // Pin input buffer
227    jbyte* in_buf = (jbyte*) env->GetByteArrayElements(inBuffer, 0);
228    if (env->ExceptionCheck() || in_buf == NULL) {
229        return J_EXCEPTION;
230    }
231
232    int8_t* in_bytes = static_cast<int8_t*>(in_buf);
233    int32_t in_len = static_cast<int32_t>(inCount);
234    int32_t off = static_cast<int32_t>(offset);
235    in_bytes += off;
236    int32_t written = 0;
237
238    // Update environment
239    w_ptr->updateEnv(env);
240    // Write out and unpin buffer.
241    written = w_ptr->write(in_bytes, in_len);
242    env->ReleaseByteArrayElements(inBuffer, in_buf, JNI_ABORT);
243    return written;
244}
245
246static jint InputStream_readDecodedBytes(JNIEnv* env, jobject thiz,
247        jbyteArray inBuffer, jint offset, jint inCount) {
248    JpegReader* r_ptr = getRPtr(env, thiz, NULL);
249    if (r_ptr == NULL) {
250        return J_EXCEPTION;
251    }
252    // Pin input buffer
253    jbyte* in_buf = (jbyte*) env->GetByteArrayElements(inBuffer, 0);
254    if (env->ExceptionCheck() || in_buf == NULL) {
255        return J_EXCEPTION;
256    }
257    int8_t* in_bytes = static_cast<int8_t*>(in_buf);
258    int32_t in_len = static_cast<int32_t>(inCount);
259    int32_t off = static_cast<int32_t>(offset);
260    int32_t read = 0;
261
262    // Update environment
263    r_ptr->updateEnv(env);
264    // Read into buffer
265    read = r_ptr->read(in_bytes, off, in_len);
266
267    // Unpin buffer
268    if (read < 0) {
269        env->ReleaseByteArrayElements(inBuffer, in_buf, JNI_ABORT);
270    } else {
271        env->ReleaseByteArrayElements(inBuffer, in_buf, JNI_COMMIT);
272    }
273    return read;
274}
275
276static jint InputStream_skipDecodedBytes(JNIEnv* env, jobject thiz,
277        jint bytes) {
278    if (bytes <= 0) {
279        return J_ERROR_BAD_ARGS;
280    }
281    JpegReader* r_ptr = getRPtr(env, thiz, NULL);
282    if (r_ptr == NULL) {
283        return J_EXCEPTION;
284    }
285
286    // Update environment
287    r_ptr->updateEnv(env);
288    int32_t skip = 0;
289    // Read with null buffer to skip
290    skip = r_ptr->read(NULL, 0, bytes);
291    return skip;
292}
293
294static const char *outClassPathName =
295        "com/android/gallery3d/jpegstream/JPEGOutputStream";
296static const char *inClassPathName =
297        "com/android/gallery3d/jpegstream/JPEGInputStream";
298
299static JNINativeMethod writeMethods[] = { { "setup",
300        "(Ljava/io/OutputStream;IIII)I", (void*) OutputStream_setup }, {
301        "cleanup", "()V", (void*) OutputStream_cleanup }, { "writeInputBytes",
302        "([BII)I", (void*) OutputStream_writeInputBytes } };
303
304static JNINativeMethod readMethods[] = { { "setup",
305        "(Landroid/graphics/Point;Ljava/io/InputStream;I)I",
306        (void*) InputStream_setup }, { "cleanup", "()V",
307        (void*) InputStream_cleanup }, { "readDecodedBytes", "([BII)I",
308        (void*) InputStream_readDecodedBytes }, { "skipDecodedBytes", "(I)I",
309        (void*) InputStream_skipDecodedBytes } };
310
311static int registerNativeMethods(JNIEnv* env, const char* className,
312        JNINativeMethod* gMethods, int numMethods) {
313    jclass clazz;
314    clazz = env->FindClass(className);
315    if (clazz == NULL) {
316        LOGE("Native registration unable to find class '%s'", className);
317        return JNI_FALSE;
318    }
319    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
320        LOGE("RegisterNatives failed for '%s'", className);
321        return JNI_FALSE;
322    }
323    return JNI_TRUE;
324}
325
326jint JNI_OnLoad(JavaVM* vm, void* reserved __unused) {
327    JNIEnv* env;
328    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
329        LOGE("Error: GetEnv failed in JNI_OnLoad");
330        return -1;
331    }
332    if (!registerNativeMethods(env, outClassPathName, writeMethods,
333            sizeof(writeMethods) / sizeof(writeMethods[0]))) {
334        LOGE("Error: could not register native methods for JPEGOutputStream");
335        return -1;
336    }
337    if (!registerNativeMethods(env, inClassPathName, readMethods,
338            sizeof(readMethods) / sizeof(readMethods[0]))) {
339        LOGE("Error: could not register native methods for JPEGInputStream");
340        return -1;
341    }
342    // cache method IDs for OutputStream
343    jclass outCls = env->FindClass("java/io/OutputStream");
344    if (outCls == NULL) {
345        LOGE("Unable to find class 'OutputStream'");
346        return -1;
347    }
348    jmethodID cachedWriteFun = env->GetMethodID(outCls, "write", "([BII)V");
349    if (cachedWriteFun == NULL) {
350        LOGE("Unable to find write function in class 'OutputStream'");
351        return -1;
352    }
353    OutputStreamWrapper::setWriteMethodID(cachedWriteFun);
354
355    // cache method IDs for InputStream
356    jclass inCls = env->FindClass("java/io/InputStream");
357    if (inCls == NULL) {
358        LOGE("Unable to find class 'InputStream'");
359        return -1;
360    }
361    jmethodID cachedReadFun = env->GetMethodID(inCls, "read", "([BII)I");
362    if (cachedReadFun == NULL) {
363        LOGE("Unable to find read function in class 'InputStream'");
364        return -1;
365    }
366    jmethodID cachedSkipFun = env->GetMethodID(inCls, "skip", "(J)J");
367    if (cachedSkipFun == NULL) {
368        LOGE("Unable to find skip function in class 'InputStream'");
369        return -1;
370    }
371    InputStreamWrapper::setReadSkipMethodIDs(cachedReadFun, cachedSkipFun);
372    return JNI_VERSION_1_6;
373}
374
375#ifdef __cplusplus
376}
377#endif
378