1 /*
2  * Copyright 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 //#define LOG_NDEBUG 0
18 #define LOG_TAG "ImageReader_JNI"
19 #define ATRACE_TAG ATRACE_TAG_CAMERA
20 #include "android_media_Utils.h"
21 #include <cutils/atomic.h>
22 #include <utils/Log.h>
23 #include <utils/misc.h>
24 #include <utils/List.h>
25 #include <utils/Trace.h>
26 #include <utils/String8.h>
27 
28 #include <cstdio>
29 
30 #include <gui/BufferItemConsumer.h>
31 #include <gui/Surface.h>
32 
33 #include <android_runtime/AndroidRuntime.h>
34 #include <android_runtime/android_view_Surface.h>
35 #include <android_runtime/android_graphics_GraphicBuffer.h>
36 #include <android_runtime/android_hardware_HardwareBuffer.h>
37 #include <grallocusage/GrallocUsageConversion.h>
38 
39 #include <private/android/AHardwareBufferHelpers.h>
40 
41 #include <jni.h>
42 #include <nativehelper/JNIHelp.h>
43 
44 #include <stdint.h>
45 #include <inttypes.h>
46 #include <android/hardware_buffer_jni.h>
47 
48 #include <ui/Rect.h>
49 
50 #define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID       "mNativeContext"
51 #define ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID   "mNativeBuffer"
52 #define ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID       "mTimestamp"
53 #define ANDROID_MEDIA_SURFACEIMAGE_DS_JNI_ID       "mDataSpace"
54 #define ANDROID_MEDIA_SURFACEIMAGE_TF_JNI_ID       "mTransform"
55 #define ANDROID_MEDIA_SURFACEIMAGE_SM_JNI_ID       "mScalingMode"
56 
57 #define CONSUMER_BUFFER_USAGE_UNKNOWN              0;
58 // ----------------------------------------------------------------------------
59 
60 using namespace android;
61 
62 
63 enum {
64     ACQUIRE_SUCCESS = 0,
65     ACQUIRE_NO_BUFFERS = 1,
66     ACQUIRE_MAX_IMAGES = 2,
67 };
68 
69 static struct {
70     jfieldID mNativeContext;
71     jmethodID postEventFromNative;
72 } gImageReaderClassInfo;
73 
74 static struct {
75     jfieldID mNativeBuffer;
76     jfieldID mTimestamp;
77     jfieldID mDataSpace;
78     jfieldID mTransform;
79     jfieldID mScalingMode;
80     jfieldID mPlanes;
81 } gSurfaceImageClassInfo;
82 
83 static struct {
84     jclass clazz;
85     jmethodID ctor;
86 } gSurfacePlaneClassInfo;
87 
88 static struct {
89     jclass clazz;
90     jmethodID ctor;
91 } gImagePlaneClassInfo;
92 
93 // Get an ID that's unique within this process.
createProcessUniqueId()94 static int32_t createProcessUniqueId() {
95     static volatile int32_t globalCounter = 0;
96     return android_atomic_inc(&globalCounter);
97 }
98 
99 // ----------------------------------------------------------------------------
100 
101 class JNIImageReaderContext : public ConsumerBase::FrameAvailableListener
102 {
103 public:
104     JNIImageReaderContext(JNIEnv* env, jobject weakThiz, jclass clazz, int maxImages);
105 
106     virtual ~JNIImageReaderContext();
107 
108     virtual void onFrameAvailable(const BufferItem& item);
109 
110     BufferItem* getBufferItem();
111     void returnBufferItem(BufferItem* buffer);
112 
113 
setBufferConsumer(const sp<BufferItemConsumer> & consumer)114     void setBufferConsumer(const sp<BufferItemConsumer>& consumer) { mConsumer = consumer; }
getBufferConsumer()115     BufferItemConsumer* getBufferConsumer() { return mConsumer.get(); }
116 
setProducer(const sp<IGraphicBufferProducer> & producer)117     void setProducer(const sp<IGraphicBufferProducer>& producer) { mProducer = producer; }
getProducer()118     IGraphicBufferProducer* getProducer() { return mProducer.get(); }
119 
setBufferFormat(int format)120     void setBufferFormat(int format) { mFormat = format; }
getBufferFormat()121     int getBufferFormat() { return mFormat; }
122 
setBufferDataspace(android_dataspace dataSpace)123     void setBufferDataspace(android_dataspace dataSpace) { mDataSpace = dataSpace; }
getBufferDataspace()124     android_dataspace getBufferDataspace() { return mDataSpace; }
125 
setBufferWidth(int width)126     void setBufferWidth(int width) { mWidth = width; }
getBufferWidth()127     int getBufferWidth() { return mWidth; }
128 
setBufferHeight(int height)129     void setBufferHeight(int height) { mHeight = height; }
getBufferHeight()130     int getBufferHeight() { return mHeight; }
131 
132 private:
133     static JNIEnv* getJNIEnv(bool* needsDetach);
134     static void detachJNI();
135 
136     List<BufferItem*> mBuffers;
137     sp<BufferItemConsumer> mConsumer;
138     sp<IGraphicBufferProducer> mProducer;
139     jobject mWeakThiz;
140     jclass mClazz;
141     int mFormat;
142     android_dataspace mDataSpace;
143     int mWidth;
144     int mHeight;
145 };
146 
JNIImageReaderContext(JNIEnv * env,jobject weakThiz,jclass clazz,int maxImages)147 JNIImageReaderContext::JNIImageReaderContext(JNIEnv* env,
148         jobject weakThiz, jclass clazz, int maxImages) :
149     mWeakThiz(env->NewGlobalRef(weakThiz)),
150     mClazz((jclass)env->NewGlobalRef(clazz)),
151     mFormat(0),
152     mDataSpace(HAL_DATASPACE_UNKNOWN),
153     mWidth(-1),
154     mHeight(-1) {
155     for (int i = 0; i < maxImages; i++) {
156         BufferItem* buffer = new BufferItem;
157         mBuffers.push_back(buffer);
158     }
159 }
160 
getJNIEnv(bool * needsDetach)161 JNIEnv* JNIImageReaderContext::getJNIEnv(bool* needsDetach) {
162     LOG_ALWAYS_FATAL_IF(needsDetach == NULL, "needsDetach is null!!!");
163     *needsDetach = false;
164     JNIEnv* env = AndroidRuntime::getJNIEnv();
165     if (env == NULL) {
166         JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL};
167         JavaVM* vm = AndroidRuntime::getJavaVM();
168         int result = vm->AttachCurrentThread(&env, (void*) &args);
169         if (result != JNI_OK) {
170             ALOGE("thread attach failed: %#x", result);
171             return NULL;
172         }
173         *needsDetach = true;
174     }
175     return env;
176 }
177 
detachJNI()178 void JNIImageReaderContext::detachJNI() {
179     JavaVM* vm = AndroidRuntime::getJavaVM();
180     int result = vm->DetachCurrentThread();
181     if (result != JNI_OK) {
182         ALOGE("thread detach failed: %#x", result);
183     }
184 }
185 
getBufferItem()186 BufferItem* JNIImageReaderContext::getBufferItem() {
187     if (mBuffers.empty()) {
188         return NULL;
189     }
190     // Return a BufferItem pointer and remove it from the list
191     List<BufferItem*>::iterator it = mBuffers.begin();
192     BufferItem* buffer = *it;
193     mBuffers.erase(it);
194     return buffer;
195 }
196 
returnBufferItem(BufferItem * buffer)197 void JNIImageReaderContext::returnBufferItem(BufferItem* buffer) {
198     buffer->mGraphicBuffer = nullptr;
199     mBuffers.push_back(buffer);
200 }
201 
~JNIImageReaderContext()202 JNIImageReaderContext::~JNIImageReaderContext() {
203     bool needsDetach = false;
204     JNIEnv* env = getJNIEnv(&needsDetach);
205     if (env != NULL) {
206         env->DeleteGlobalRef(mWeakThiz);
207         env->DeleteGlobalRef(mClazz);
208     } else {
209         ALOGW("leaking JNI object references");
210     }
211     if (needsDetach) {
212         detachJNI();
213     }
214 
215     // Delete buffer items.
216     for (List<BufferItem *>::iterator it = mBuffers.begin();
217             it != mBuffers.end(); it++) {
218         delete *it;
219     }
220 
221     if (mConsumer != 0) {
222         mConsumer.clear();
223     }
224 }
225 
onFrameAvailable(const BufferItem &)226 void JNIImageReaderContext::onFrameAvailable(const BufferItem& /*item*/)
227 {
228     ATRACE_CALL();
229     ALOGV("%s: frame available", __FUNCTION__);
230     bool needsDetach = false;
231     JNIEnv* env = getJNIEnv(&needsDetach);
232     if (env != NULL) {
233         env->CallStaticVoidMethod(mClazz, gImageReaderClassInfo.postEventFromNative, mWeakThiz);
234     } else {
235         ALOGW("onFrameAvailable event will not posted");
236     }
237     if (needsDetach) {
238         detachJNI();
239     }
240 }
241 
242 // ----------------------------------------------------------------------------
243 
244 extern "C" {
245 
ImageReader_getContext(JNIEnv * env,jobject thiz)246 static JNIImageReaderContext* ImageReader_getContext(JNIEnv* env, jobject thiz)
247 {
248     JNIImageReaderContext *ctx;
249     ctx = reinterpret_cast<JNIImageReaderContext *>
250               (env->GetLongField(thiz, gImageReaderClassInfo.mNativeContext));
251     return ctx;
252 }
253 
ImageReader_getProducer(JNIEnv * env,jobject thiz)254 static IGraphicBufferProducer* ImageReader_getProducer(JNIEnv* env, jobject thiz)
255 {
256     ALOGV("%s:", __FUNCTION__);
257     JNIImageReaderContext* const ctx = ImageReader_getContext(env, thiz);
258     if (ctx == NULL) {
259         jniThrowRuntimeException(env, "ImageReaderContext is not initialized");
260         return NULL;
261     }
262 
263     return ctx->getProducer();
264 }
265 
ImageReader_setNativeContext(JNIEnv * env,jobject thiz,sp<JNIImageReaderContext> ctx)266 static void ImageReader_setNativeContext(JNIEnv* env,
267         jobject thiz, sp<JNIImageReaderContext> ctx)
268 {
269     ALOGV("%s:", __FUNCTION__);
270     JNIImageReaderContext* const p = ImageReader_getContext(env, thiz);
271     if (ctx != 0) {
272         ctx->incStrong((void*)ImageReader_setNativeContext);
273     }
274     if (p) {
275         p->decStrong((void*)ImageReader_setNativeContext);
276     }
277     env->SetLongField(thiz, gImageReaderClassInfo.mNativeContext,
278             reinterpret_cast<jlong>(ctx.get()));
279 }
280 
ImageReader_getBufferConsumer(JNIEnv * env,jobject thiz)281 static BufferItemConsumer* ImageReader_getBufferConsumer(JNIEnv* env, jobject thiz)
282 {
283     ALOGV("%s:", __FUNCTION__);
284     JNIImageReaderContext* const ctx = ImageReader_getContext(env, thiz);
285     if (ctx == NULL) {
286         jniThrowRuntimeException(env, "ImageReaderContext is not initialized");
287         return NULL;
288     }
289 
290     return ctx->getBufferConsumer();
291 }
292 
Image_setBufferItem(JNIEnv * env,jobject thiz,const BufferItem * buffer)293 static void Image_setBufferItem(JNIEnv* env, jobject thiz,
294         const BufferItem* buffer)
295 {
296     env->SetLongField(thiz, gSurfaceImageClassInfo.mNativeBuffer, reinterpret_cast<jlong>(buffer));
297 }
298 
Image_getBufferItem(JNIEnv * env,jobject image)299 static BufferItem* Image_getBufferItem(JNIEnv* env, jobject image)
300 {
301     return reinterpret_cast<BufferItem*>(
302             env->GetLongField(image, gSurfaceImageClassInfo.mNativeBuffer));
303 }
304 
305 
306 // ----------------------------------------------------------------------------
307 
ImageReader_classInit(JNIEnv * env,jclass clazz)308 static void ImageReader_classInit(JNIEnv* env, jclass clazz)
309 {
310     ALOGV("%s:", __FUNCTION__);
311 
312     jclass imageClazz = env->FindClass("android/media/ImageReader$SurfaceImage");
313     LOG_ALWAYS_FATAL_IF(imageClazz == NULL,
314                         "can't find android/graphics/ImageReader$SurfaceImage");
315     gSurfaceImageClassInfo.mNativeBuffer = env->GetFieldID(
316             imageClazz, ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID, "J");
317     LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mNativeBuffer == NULL,
318                         "can't find android/graphics/ImageReader.%s",
319                         ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID);
320 
321     gSurfaceImageClassInfo.mTimestamp = env->GetFieldID(
322             imageClazz, ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID, "J");
323     LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mTimestamp == NULL,
324                         "can't find android/graphics/ImageReader.%s",
325                         ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID);
326 
327     gSurfaceImageClassInfo.mDataSpace = env->GetFieldID(
328             imageClazz, ANDROID_MEDIA_SURFACEIMAGE_DS_JNI_ID, "I");
329     LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mDataSpace == NULL,
330                         "can't find android/graphics/ImageReader.%s",
331                         ANDROID_MEDIA_SURFACEIMAGE_DS_JNI_ID);
332 
333     gSurfaceImageClassInfo.mTransform = env->GetFieldID(
334             imageClazz, ANDROID_MEDIA_SURFACEIMAGE_TF_JNI_ID, "I");
335     LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mTransform == NULL,
336                         "can't find android/graphics/ImageReader.%s",
337                         ANDROID_MEDIA_SURFACEIMAGE_TF_JNI_ID);
338 
339     gSurfaceImageClassInfo.mScalingMode = env->GetFieldID(
340             imageClazz, ANDROID_MEDIA_SURFACEIMAGE_SM_JNI_ID, "I");
341     LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mScalingMode == NULL,
342                         "can't find android/graphics/ImageReader.%s",
343                         ANDROID_MEDIA_SURFACEIMAGE_SM_JNI_ID);
344 
345     gSurfaceImageClassInfo.mPlanes = env->GetFieldID(
346             imageClazz, "mPlanes", "[Landroid/media/ImageReader$SurfaceImage$SurfacePlane;");
347     LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mPlanes == NULL,
348             "can't find android/media/ImageReader$ReaderSurfaceImage.mPlanes");
349 
350     gImageReaderClassInfo.mNativeContext = env->GetFieldID(
351             clazz, ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID, "J");
352     LOG_ALWAYS_FATAL_IF(gImageReaderClassInfo.mNativeContext == NULL,
353                         "can't find android/graphics/ImageReader.%s",
354                           ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID);
355 
356     gImageReaderClassInfo.postEventFromNative = env->GetStaticMethodID(
357             clazz, "postEventFromNative", "(Ljava/lang/Object;)V");
358     LOG_ALWAYS_FATAL_IF(gImageReaderClassInfo.postEventFromNative == NULL,
359                         "can't find android/graphics/ImageReader.postEventFromNative");
360 
361     jclass planeClazz = env->FindClass("android/media/ImageReader$SurfaceImage$SurfacePlane");
362     LOG_ALWAYS_FATAL_IF(planeClazz == NULL, "Can not find SurfacePlane class");
363     // FindClass only gives a local reference of jclass object.
364     gSurfacePlaneClassInfo.clazz = (jclass) env->NewGlobalRef(planeClazz);
365     gSurfacePlaneClassInfo.ctor = env->GetMethodID(gSurfacePlaneClassInfo.clazz, "<init>",
366             "(Landroid/media/ImageReader$SurfaceImage;IILjava/nio/ByteBuffer;)V");
367     LOG_ALWAYS_FATAL_IF(gSurfacePlaneClassInfo.ctor == NULL,
368             "Can not find SurfacePlane constructor");
369 
370     planeClazz = env->FindClass("android/media/ImageReader$ImagePlane");
371     LOG_ALWAYS_FATAL_IF(planeClazz == NULL, "Can not find ImagePlane class");
372     // FindClass only gives a local reference of jclass object.
373     gImagePlaneClassInfo.clazz = (jclass) env->NewGlobalRef(planeClazz);
374     gImagePlaneClassInfo.ctor = env->GetMethodID(gImagePlaneClassInfo.clazz, "<init>",
375             "(IILjava/nio/ByteBuffer;)V");
376     LOG_ALWAYS_FATAL_IF(gImagePlaneClassInfo.ctor == NULL,
377             "Can not find ImagePlane constructor");
378 }
379 
ImageReader_init(JNIEnv * env,jobject thiz,jobject weakThiz,jint width,jint height,jint maxImages,jlong ndkUsage,jint nativeHalFormat,jint dataSpace)380 static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint width, jint height,
381                              jint maxImages, jlong ndkUsage, jint nativeHalFormat, jint dataSpace) {
382     status_t res;
383 
384     ALOGV("%s: width:%d, height: %d, nativeHalFormat: %d, maxImages:%d",
385           __FUNCTION__, width, height, nativeHalFormat, maxImages);
386 
387     android_dataspace nativeDataspace = static_cast<android_dataspace>(dataSpace);
388 
389     jclass clazz = env->GetObjectClass(thiz);
390     if (clazz == NULL) {
391         jniThrowRuntimeException(env, "Can't find android/graphics/ImageReader");
392         return;
393     }
394     sp<JNIImageReaderContext> ctx(new JNIImageReaderContext(env, weakThiz, clazz, maxImages));
395 
396     sp<IGraphicBufferProducer> gbProducer;
397     sp<IGraphicBufferConsumer> gbConsumer;
398     BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
399     sp<BufferItemConsumer> bufferConsumer;
400     String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d",
401             width, height, nativeHalFormat, maxImages, getpid(),
402             createProcessUniqueId());
403     uint64_t consumerUsage =
404             android_hardware_HardwareBuffer_convertToGrallocUsageBits(ndkUsage);
405 
406     bufferConsumer = new BufferItemConsumer(gbConsumer, consumerUsage, maxImages,
407             /*controlledByApp*/true);
408     if (bufferConsumer == nullptr) {
409         jniThrowExceptionFmt(env, "java/lang/RuntimeException",
410                 "Failed to allocate native buffer consumer for hal format 0x%x and usage 0x%x",
411                 nativeHalFormat, consumerUsage);
412         return;
413     }
414 
415     if (consumerUsage & GRALLOC_USAGE_PROTECTED) {
416         gbConsumer->setConsumerIsProtected(true);
417     }
418 
419     ctx->setBufferConsumer(bufferConsumer);
420     bufferConsumer->setName(consumerName);
421 
422     ctx->setProducer(gbProducer);
423     bufferConsumer->setFrameAvailableListener(ctx);
424     ImageReader_setNativeContext(env, thiz, ctx);
425     ctx->setBufferFormat(nativeHalFormat);
426     ctx->setBufferDataspace(nativeDataspace);
427     ctx->setBufferWidth(width);
428     ctx->setBufferHeight(height);
429 
430     // Set the width/height/format/dataspace to the bufferConsumer.
431     res = bufferConsumer->setDefaultBufferSize(width, height);
432     if (res != OK) {
433         jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
434                           "Failed to set buffer consumer default size (%dx%d) for Hal format 0x%x",
435                           width, height, nativeHalFormat);
436         return;
437     }
438     res = bufferConsumer->setDefaultBufferFormat(nativeHalFormat);
439     if (res != OK) {
440         jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
441                           "Failed to set buffer consumer default Halformat 0x%x", nativeHalFormat);
442         return;
443     }
444     res = bufferConsumer->setDefaultBufferDataSpace(nativeDataspace);
445     if (res != OK) {
446         jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
447                           "Failed to set buffer consumer default dataSpace 0x%x", nativeDataspace);
448         return;
449     }
450 }
451 
ImageReader_close(JNIEnv * env,jobject thiz)452 static void ImageReader_close(JNIEnv* env, jobject thiz)
453 {
454     ALOGV("%s:", __FUNCTION__);
455 
456     JNIImageReaderContext* const ctx = ImageReader_getContext(env, thiz);
457     if (ctx == NULL) {
458         // ImageReader is already closed.
459         return;
460     }
461 
462     BufferItemConsumer* consumer = NULL;
463     consumer = ImageReader_getBufferConsumer(env, thiz);
464 
465     if (consumer != NULL) {
466         consumer->abandon();
467         consumer->setFrameAvailableListener(NULL);
468     }
469     ImageReader_setNativeContext(env, thiz, NULL);
470 }
471 
Image_unlockIfLocked(JNIEnv * env,jobject image)472 static sp<Fence> Image_unlockIfLocked(JNIEnv* env, jobject image) {
473     ALOGV("%s", __FUNCTION__);
474     BufferItem* buffer = Image_getBufferItem(env, image);
475     if (buffer == NULL) {
476         jniThrowException(env, "java/lang/IllegalStateException",
477                 "Image is not initialized");
478         return Fence::NO_FENCE;
479     }
480 
481     // Is locked?
482     bool wasBufferLocked = false;
483     jobject planes = NULL;
484     if (!isFormatOpaque(buffer->mGraphicBuffer->getPixelFormat())) {
485         planes = env->GetObjectField(image, gSurfaceImageClassInfo.mPlanes);
486     }
487     wasBufferLocked = (planes != NULL);
488     if (wasBufferLocked) {
489         status_t res = OK;
490         int fenceFd = -1;
491         if (wasBufferLocked) {
492             res = buffer->mGraphicBuffer->unlockAsync(&fenceFd);
493             if (res != OK) {
494                 jniThrowRuntimeException(env, "unlock buffer failed");
495                 return Fence::NO_FENCE;
496             }
497         }
498         sp<Fence> releaseFence = new Fence(fenceFd);
499         return releaseFence;
500         ALOGV("Successfully unlocked the image");
501     }
502     return Fence::NO_FENCE;
503 }
504 
ImageReader_imageRelease(JNIEnv * env,jobject thiz,jobject image)505 static void ImageReader_imageRelease(JNIEnv* env, jobject thiz, jobject image)
506 {
507     ALOGV("%s:", __FUNCTION__);
508     JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz);
509     if (ctx == NULL) {
510         ALOGW("ImageReader#close called before Image#close, consider calling Image#close first");
511         return;
512     }
513 
514     BufferItemConsumer* bufferConsumer = ctx->getBufferConsumer();
515     BufferItem* buffer = Image_getBufferItem(env, image);
516     if (buffer == nullptr) {
517         // Release an already closed image is harmless.
518         return;
519     }
520 
521     sp<Fence> releaseFence = Image_unlockIfLocked(env, image);
522     bufferConsumer->releaseBuffer(*buffer, releaseFence);
523     Image_setBufferItem(env, image, NULL);
524     ctx->returnBufferItem(buffer);
525     ALOGV("%s: Image (format: 0x%x) has been released", __FUNCTION__, ctx->getBufferFormat());
526 }
527 
ImageReader_imageSetup(JNIEnv * env,jobject thiz,jobject image)528 static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, jobject image) {
529     ALOGV("%s:", __FUNCTION__);
530     JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz);
531     if (ctx == NULL) {
532         jniThrowException(env, "java/lang/IllegalStateException",
533                 "ImageReader is not initialized or was already closed");
534         return -1;
535     }
536 
537     BufferItemConsumer* bufferConsumer = ctx->getBufferConsumer();
538     BufferItem* buffer = ctx->getBufferItem();
539     if (buffer == NULL) {
540         ALOGW("Unable to acquire a buffer item, very likely client tried to acquire more than"
541             " maxImages buffers");
542         return ACQUIRE_MAX_IMAGES;
543     }
544 
545     status_t res = bufferConsumer->acquireBuffer(buffer, 0);
546     if (res != OK) {
547         ctx->returnBufferItem(buffer);
548         if (res != BufferQueue::NO_BUFFER_AVAILABLE) {
549             if (res == INVALID_OPERATION) {
550                 // Max number of images were already acquired.
551                 ALOGE("%s: Max number of buffers allowed are already acquired : %s (%d)",
552                         __FUNCTION__, strerror(-res), res);
553                 return ACQUIRE_MAX_IMAGES;
554             } else {
555                 ALOGE("%s: Acquire image failed with some unknown error: %s (%d)",
556                         __FUNCTION__, strerror(-res), res);
557                 jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
558                         "Unknown error (%d) when we tried to acquire an image.",
559                                           res);
560                 return ACQUIRE_NO_BUFFERS;
561             }
562         }
563         // This isn't really an error case, as the application may acquire buffer at any time.
564         return ACQUIRE_NO_BUFFERS;
565     }
566 
567     // Add some extra checks for non-opaque formats.
568     if (!isFormatOpaque(ctx->getBufferFormat())) {
569         // Check if the left-top corner of the crop rect is origin, we currently assume this point is
570         // zero, will revisit this once this assumption turns out problematic.
571         Point lt = buffer->mCrop.leftTop();
572         if (lt.x != 0 || lt.y != 0) {
573             jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException",
574                     "crop left top corner [%d, %d] need to be at origin", lt.x, lt.y);
575             return -1;
576         }
577 
578         // Check if the producer buffer configurations match what ImageReader configured.
579         int outputWidth = getBufferWidth(buffer);
580         int outputHeight = getBufferHeight(buffer);
581 
582         int imgReaderHalFmt = ctx->getBufferFormat();
583         int imageReaderWidth = ctx->getBufferWidth();
584         int imageReaderHeight = ctx->getBufferHeight();
585         int bufferFormat = buffer->mGraphicBuffer->getPixelFormat();
586         if ((bufferFormat != HAL_PIXEL_FORMAT_BLOB) && (imgReaderHalFmt != HAL_PIXEL_FORMAT_BLOB) &&
587                 (imageReaderWidth != outputWidth || imageReaderHeight != outputHeight)) {
588             ALOGV("%s: Producer buffer size: %dx%d, doesn't match ImageReader configured size: %dx%d",
589                     __FUNCTION__, outputWidth, outputHeight, imageReaderWidth, imageReaderHeight);
590         }
591         if (imgReaderHalFmt != bufferFormat) {
592             if (imgReaderHalFmt == HAL_PIXEL_FORMAT_YCbCr_420_888 &&
593                     isPossiblyYUV(bufferFormat)) {
594                 // Treat formats that are compatible with flexible YUV
595                 // (HAL_PIXEL_FORMAT_YCbCr_420_888) as HAL_PIXEL_FORMAT_YCbCr_420_888.
596                 ALOGV("%s: Treat buffer format to 0x%x as HAL_PIXEL_FORMAT_YCbCr_420_888",
597                         __FUNCTION__, bufferFormat);
598             } else if (imgReaderHalFmt == HAL_PIXEL_FORMAT_YCBCR_P010 &&
599                     isPossibly10BitYUV(bufferFormat)) {
600                 // Treat formats that are compatible with flexible 10-bit YUV
601                 // (HAL_PIXEL_FORMAT_YCBCR_P010) as HAL_PIXEL_FORMAT_YCBCR_P010.
602                 ALOGV("%s: Treat buffer format to 0x%x as HAL_PIXEL_FORMAT_YCBCR_P010",
603                         __FUNCTION__, bufferFormat);
604             } else if (imgReaderHalFmt == HAL_PIXEL_FORMAT_BLOB &&
605                     bufferFormat == HAL_PIXEL_FORMAT_RGBA_8888) {
606                 // Using HAL_PIXEL_FORMAT_RGBA_8888 Gralloc buffers containing JPEGs to get around
607                 // SW write limitations for (b/17379185).
608                 ALOGV("%s: Receiving JPEG in HAL_PIXEL_FORMAT_RGBA_8888 buffer.", __FUNCTION__);
609             } else {
610                 // Return the buffer to the queue. No need to provide fence, as this buffer wasn't
611                 // used anywhere yet.
612                 bufferConsumer->releaseBuffer(*buffer);
613                 ctx->returnBufferItem(buffer);
614 
615                 // Throw exception
616                 ALOGE("Producer output buffer format: 0x%x, ImageReader configured format: 0x%x",
617                         bufferFormat, ctx->getBufferFormat());
618                 String8 msg;
619                 msg.appendFormat("The producer output buffer format 0x%x doesn't "
620                         "match the ImageReader's configured buffer format 0x%x.",
621                         bufferFormat, ctx->getBufferFormat());
622                 jniThrowException(env, "java/lang/UnsupportedOperationException",
623                         msg.c_str());
624                 return -1;
625             }
626         }
627 
628     }
629 
630     // Set SurfaceImage instance member variables
631     Image_setBufferItem(env, image, buffer);
632     env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp,
633             static_cast<jlong>(buffer->mTimestamp));
634     env->SetIntField(image, gSurfaceImageClassInfo.mDataSpace,
635             static_cast<jint>(buffer->mDataSpace));
636     auto transform = buffer->mTransform;
637     if (buffer->mTransformToDisplayInverse) {
638         transform |= NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
639     }
640     env->SetIntField(image, gSurfaceImageClassInfo.mTransform,
641             static_cast<jint>(transform));
642     env->SetIntField(image, gSurfaceImageClassInfo.mScalingMode,
643             static_cast<jint>(buffer->mScalingMode));
644 
645     return ACQUIRE_SUCCESS;
646 }
647 
ImageReader_detachImage(JNIEnv * env,jobject thiz,jobject image,jboolean throwISEOnly)648 static jint ImageReader_detachImage(JNIEnv* env, jobject thiz, jobject image,
649                                     jboolean throwISEOnly) {
650     ALOGV("%s:", __FUNCTION__);
651     JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz);
652     if (ctx == NULL) {
653         jniThrowException(env, "java/lang/IllegalStateException", "ImageReader was already closed");
654         return -1;
655     }
656 
657     BufferItemConsumer* bufferConsumer = ctx->getBufferConsumer();
658     BufferItem* buffer = Image_getBufferItem(env, image);
659     if (!buffer) {
660         ALOGE(
661                 "Image already released and can not be detached from ImageReader!!!");
662         jniThrowException(env, "java/lang/IllegalStateException",
663                 "Image detach from ImageReader failed: buffer was already released");
664         return -1;
665     }
666 
667     status_t res = OK;
668     Image_unlockIfLocked(env, image);
669     res = bufferConsumer->detachBuffer(buffer->mSlot);
670     if (res != OK) {
671         ALOGE("Image detach failed: %s (%d)!!!", strerror(-res), res);
672         if ((bool) throwISEOnly) {
673             jniThrowException(env, "java/lang/IllegalStateException",
674                     "nativeDetachImage failed for image!!!");
675         } else {
676              jniThrowRuntimeException(env, "nativeDetachImage failed for image!!!");
677         }
678         return res;
679     }
680     return OK;
681 }
682 
ImageReader_discardFreeBuffers(JNIEnv * env,jobject thiz)683 static void ImageReader_discardFreeBuffers(JNIEnv* env, jobject thiz) {
684     ALOGV("%s:", __FUNCTION__);
685     JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz);
686     if (ctx == NULL) {
687         jniThrowException(env, "java/lang/IllegalStateException", "ImageReader was already closed");
688         return;
689     }
690 
691     BufferItemConsumer* bufferConsumer = ctx->getBufferConsumer();
692     status_t res = bufferConsumer->discardFreeBuffers();
693     if (res != OK) {
694         ALOGE("Buffer discard failed: %s (%d)", strerror(-res), res);
695         jniThrowRuntimeException(env,
696                 "nativeDicardFreebuffers failed");
697     }
698 }
699 
ImageReader_getSurface(JNIEnv * env,jobject thiz)700 static jobject ImageReader_getSurface(JNIEnv* env, jobject thiz)
701 {
702     ALOGV("%s: ", __FUNCTION__);
703 
704     IGraphicBufferProducer* gbp = ImageReader_getProducer(env, thiz);
705     if (gbp == NULL) {
706         jniThrowRuntimeException(env, "Buffer consumer is uninitialized");
707         return NULL;
708     }
709 
710     // Wrap the IGBP in a Java-language Surface.
711     return android_view_Surface_createFromIGraphicBufferProducer(env, gbp);
712 }
713 
Image_getLockedImage(JNIEnv * env,jobject thiz,LockedImage * image,uint64_t ndkReaderUsage)714 static void Image_getLockedImage(JNIEnv* env, jobject thiz, LockedImage *image,
715         uint64_t ndkReaderUsage) {
716     ALOGV("%s", __FUNCTION__);
717     BufferItem* buffer = Image_getBufferItem(env, thiz);
718     if (buffer == NULL) {
719         jniThrowException(env, "java/lang/IllegalStateException",
720                 "Image is not initialized");
721         return;
722     }
723 
724     uint32_t lockUsage;
725     if ((ndkReaderUsage & (AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY
726             | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN)) != 0) {
727         lockUsage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN;
728     } else {
729         lockUsage = GRALLOC_USAGE_SW_READ_OFTEN;
730     }
731 
732     status_t res = lockImageFromBuffer(buffer, lockUsage, buffer->mFence->dup(), image);
733 
734     if (res != OK) {
735         jniThrowExceptionFmt(env, "java/lang/RuntimeException",
736                 "lock buffer failed for format 0x%x",
737                 buffer->mGraphicBuffer->getPixelFormat());
738         return;
739     }
740 
741     // Carry over some fields from BufferItem.
742     image->crop        = buffer->mCrop;
743     image->transform   = buffer->mTransform;
744     image->scalingMode = buffer->mScalingMode;
745     image->timestamp   = buffer->mTimestamp;
746     image->dataSpace   = buffer->mDataSpace;
747     image->frameNumber = buffer->mFrameNumber;
748 
749     ALOGV("%s: Successfully locked the image", __FUNCTION__);
750     // crop, transform, scalingMode, timestamp, and frameNumber should be set by producer,
751     // and we don't set them here.
752 }
753 
Image_getLockedImageInfo(JNIEnv * env,LockedImage * buffer,int idx,int32_t writerFormat,uint8_t ** base,uint32_t * size,int * pixelStride,int * rowStride)754 static bool Image_getLockedImageInfo(JNIEnv* env, LockedImage* buffer, int idx,
755         int32_t writerFormat, uint8_t **base, uint32_t *size, int *pixelStride, int *rowStride) {
756     ALOGV("%s", __FUNCTION__);
757 
758     status_t res = getLockedImageInfo(buffer, idx, writerFormat, base, size,
759             pixelStride, rowStride);
760     if (res != OK) {
761         jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException",
762                              "Pixel format: 0x%x is unsupported", buffer->flexFormat);
763         return false;
764     }
765     return true;
766 }
767 
ImageReader_unlockGraphicBuffer(JNIEnv * env,jobject,jobject buffer)768 static void ImageReader_unlockGraphicBuffer(JNIEnv* env, jobject /*thiz*/,
769         jobject buffer) {
770     sp<GraphicBuffer> graphicBuffer =
771             android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, buffer);
772     if (graphicBuffer.get() == NULL) {
773         jniThrowRuntimeException(env, "Invalid graphic buffer!");
774         return;
775     }
776 
777     status_t res = graphicBuffer->unlock();
778     if (res != OK) {
779         jniThrowRuntimeException(env, "unlock buffer failed");
780     }
781 }
782 
ImageReader_createImagePlanes(JNIEnv * env,jobject,int numPlanes,jobject buffer,int fenceFd,int format,int cropLeft,int cropTop,int cropRight,int cropBottom)783 static jobjectArray ImageReader_createImagePlanes(JNIEnv* env, jobject /*thiz*/,
784         int numPlanes, jobject buffer, int fenceFd, int format, int cropLeft, int cropTop,
785         int cropRight, int cropBottom)
786 {
787     ALOGV("%s: create ImagePlane array with size %d", __FUNCTION__, numPlanes);
788     int rowStride = 0;
789     int pixelStride = 0;
790     uint8_t *pData = NULL;
791     uint32_t dataSize = 0;
792     jobject byteBuffer = NULL;
793 
794     PublicFormat publicReaderFormat = static_cast<PublicFormat>(format);
795     int halReaderFormat = mapPublicFormatToHalFormat(publicReaderFormat);
796 
797     if (isFormatOpaque(halReaderFormat) && numPlanes > 0) {
798         String8 msg;
799         msg.appendFormat("Format 0x%x is opaque, thus not writable, the number of planes (%d)"
800                 " must be 0", halReaderFormat, numPlanes);
801         jniThrowException(env, "java/lang/IllegalArgumentException", msg.c_str());
802         return NULL;
803     }
804 
805     jobjectArray imagePlanes = env->NewObjectArray(numPlanes, gImagePlaneClassInfo.clazz,
806             /*initial_element*/NULL);
807     if (imagePlanes == NULL) {
808         jniThrowRuntimeException(env, "Failed to create ImagePlane arrays,"
809                 " probably out of memory");
810         return NULL;
811     }
812     if (isFormatOpaque(halReaderFormat)) {
813         // Return 0 element surface array.
814         return imagePlanes;
815     }
816 
817     LockedImage lockedImg = LockedImage();
818     uint32_t lockUsage = GRALLOC_USAGE_SW_READ_OFTEN;
819 
820     Rect cropRect(cropLeft, cropTop, cropRight, cropBottom);
821     status_t res = lockImageFromBuffer(
822             android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, buffer), lockUsage,
823             cropRect, fenceFd, &lockedImg);
824     if (res != OK) {
825         jniThrowExceptionFmt(env, "java/lang/RuntimeException",
826                 "lock buffer failed for format 0x%x", format);
827         return nullptr;
828     }
829 
830     // Create all ImagePlanes
831     for (int i = 0; i < numPlanes; i++) {
832         if (!Image_getLockedImageInfo(env, &lockedImg, i, halReaderFormat,
833                 &pData, &dataSize, &pixelStride, &rowStride)) {
834             return NULL;
835         }
836         byteBuffer = env->NewDirectByteBuffer(pData, dataSize);
837         if ((byteBuffer == NULL) && (env->ExceptionCheck() == false)) {
838             jniThrowException(env, "java/lang/IllegalStateException",
839                     "Failed to allocate ByteBuffer");
840             return NULL;
841         }
842 
843         // Finally, create this ImagePlane.
844         jobject imagePlane = env->NewObject(gImagePlaneClassInfo.clazz,
845                     gImagePlaneClassInfo.ctor, rowStride, pixelStride, byteBuffer);
846         env->SetObjectArrayElement(imagePlanes, i, imagePlane);
847     }
848 
849     return imagePlanes;
850 }
851 
Image_createSurfacePlanes(JNIEnv * env,jobject thiz,int numPlanes,int halReaderFormat,uint64_t ndkReaderUsage)852 static jobjectArray Image_createSurfacePlanes(JNIEnv* env, jobject thiz,
853         int numPlanes, int halReaderFormat, uint64_t ndkReaderUsage)
854 {
855     ALOGV("%s: create SurfacePlane array with size %d", __FUNCTION__, numPlanes);
856     int rowStride = 0;
857     int pixelStride = 0;
858     uint8_t *pData = NULL;
859     uint32_t dataSize = 0;
860     jobject byteBuffer = NULL;
861 
862     if (isFormatOpaque(halReaderFormat) && numPlanes > 0) {
863         String8 msg;
864         msg.appendFormat("Format 0x%x is opaque, thus not writable, the number of planes (%d)"
865                 " must be 0", halReaderFormat, numPlanes);
866         jniThrowException(env, "java/lang/IllegalArgumentException", msg.c_str());
867         return NULL;
868     }
869 
870     jobjectArray surfacePlanes = env->NewObjectArray(numPlanes, gSurfacePlaneClassInfo.clazz,
871             /*initial_element*/NULL);
872     if (surfacePlanes == NULL) {
873         jniThrowRuntimeException(env, "Failed to create SurfacePlane arrays,"
874                 " probably out of memory");
875         return NULL;
876     }
877     if (isFormatOpaque(halReaderFormat)) {
878         // Return 0 element surface array.
879         return surfacePlanes;
880     }
881 
882     LockedImage lockedImg = LockedImage();
883     Image_getLockedImage(env, thiz, &lockedImg, ndkReaderUsage);
884     if (env->ExceptionCheck()) {
885         return NULL;
886     }
887     // Create all SurfacePlanes
888     for (int i = 0; i < numPlanes; i++) {
889         if (!Image_getLockedImageInfo(env, &lockedImg, i, halReaderFormat,
890                 &pData, &dataSize, &pixelStride, &rowStride)) {
891             return NULL;
892         }
893         byteBuffer = env->NewDirectByteBuffer(pData, dataSize);
894         if ((byteBuffer == NULL) && (env->ExceptionCheck() == false)) {
895             jniThrowException(env, "java/lang/IllegalStateException",
896                     "Failed to allocate ByteBuffer");
897             return NULL;
898         }
899 
900         // Finally, create this SurfacePlane.
901         jobject surfacePlane = env->NewObject(gSurfacePlaneClassInfo.clazz,
902                     gSurfacePlaneClassInfo.ctor, thiz, rowStride, pixelStride, byteBuffer);
903         env->SetObjectArrayElement(surfacePlanes, i, surfacePlane);
904     }
905 
906     return surfacePlanes;
907 }
908 
Image_getWidth(JNIEnv * env,jobject thiz)909 static jint Image_getWidth(JNIEnv* env, jobject thiz)
910 {
911     BufferItem* buffer = Image_getBufferItem(env, thiz);
912     return getBufferWidth(buffer);
913 }
914 
Image_getHeight(JNIEnv * env,jobject thiz)915 static jint Image_getHeight(JNIEnv* env, jobject thiz)
916 {
917     BufferItem* buffer = Image_getBufferItem(env, thiz);
918     return getBufferHeight(buffer);
919 }
920 
Image_getFenceFd(JNIEnv * env,jobject thiz)921 static jint Image_getFenceFd(JNIEnv* env, jobject thiz)
922 {
923     BufferItem* buffer = Image_getBufferItem(env, thiz);
924     if ((buffer != NULL) && (buffer->mFence.get() != NULL)){
925         return buffer->mFence->get();
926     }
927 
928     return -1;
929 }
930 
Image_getFormat(JNIEnv * env,jobject thiz,jint readerFormat)931 static jint Image_getFormat(JNIEnv* env, jobject thiz, jint readerFormat)
932 {
933     if (isFormatOpaque(readerFormat)) {
934         // Assuming opaque reader produce opaque images.
935         return static_cast<jint>(PublicFormat::PRIVATE);
936     } else {
937         BufferItem* buffer = Image_getBufferItem(env, thiz);
938         int readerHalFormat = mapPublicFormatToHalFormat(static_cast<PublicFormat>(readerFormat));
939         int32_t fmt = applyFormatOverrides(
940                 buffer->mGraphicBuffer->getPixelFormat(), readerHalFormat);
941         // Override the image format to HAL_PIXEL_FORMAT_YCbCr_420_888 if the actual format is
942         // NV21 or YV12. This could only happen when the Gralloc HAL version is v0.1 thus doesn't
943         // support lockycbcr(), the CpuConsumer need to use the lock() method in the
944         // lockNextBuffer() call. For Gralloc HAL v0.2 or newer, this format should already be
945         // overridden to HAL_PIXEL_FORMAT_YCbCr_420_888 for the flexible YUV compatible formats.
946         if (isPossiblyYUV(fmt)) {
947             fmt = HAL_PIXEL_FORMAT_YCbCr_420_888;
948         }
949         PublicFormat publicFmt = mapHalFormatDataspaceToPublicFormat(fmt, buffer->mDataSpace);
950         return static_cast<jint>(publicFmt);
951     }
952 }
953 
Image_getHardwareBuffer(JNIEnv * env,jobject thiz)954 static jobject Image_getHardwareBuffer(JNIEnv* env, jobject thiz) {
955     BufferItem* buffer = Image_getBufferItem(env, thiz);
956     AHardwareBuffer* b = AHardwareBuffer_from_GraphicBuffer(buffer->mGraphicBuffer.get());
957     // don't user the public AHardwareBuffer_toHardwareBuffer() because this would force us
958     // to link against libandroid.so
959     return android_hardware_HardwareBuffer_createFromAHardwareBuffer(env, b);
960 }
961 
962 } // extern "C"
963 
964 // ----------------------------------------------------------------------------
965 
966 static const JNINativeMethod gImageReaderMethods[] = {
967     {"nativeClassInit",        "()V",                        (void*)ImageReader_classInit },
968     {"nativeInit",             "(Ljava/lang/Object;IIIJII)V",   (void*)ImageReader_init },
969     {"nativeClose",            "()V",                        (void*)ImageReader_close },
970     {"nativeReleaseImage",     "(Landroid/media/Image;)V",   (void*)ImageReader_imageRelease },
971     {"nativeImageSetup",       "(Landroid/media/Image;)I",   (void*)ImageReader_imageSetup },
972     {"nativeGetSurface",       "()Landroid/view/Surface;",   (void*)ImageReader_getSurface },
973     {"nativeDetachImage",      "(Landroid/media/Image;Z)I",   (void*)ImageReader_detachImage },
974     {"nativeCreateImagePlanes",
975         "(ILandroid/graphics/GraphicBuffer;IIIIII)[Landroid/media/ImageReader$ImagePlane;",
976                                                              (void*)ImageReader_createImagePlanes },
977     {"nativeUnlockGraphicBuffer",
978         "(Landroid/graphics/GraphicBuffer;)V",             (void*)ImageReader_unlockGraphicBuffer },
979     {"nativeDiscardFreeBuffers", "()V",                      (void*)ImageReader_discardFreeBuffers }
980 };
981 
982 static const JNINativeMethod gImageMethods[] = {
983     {"nativeCreatePlanes",      "(IIJ)[Landroid/media/ImageReader$SurfaceImage$SurfacePlane;",
984                                                              (void*)Image_createSurfacePlanes },
985     {"nativeGetWidth",          "()I",                       (void*)Image_getWidth },
986     {"nativeGetHeight",         "()I",                       (void*)Image_getHeight },
987     {"nativeGetFormat",         "(I)I",                      (void*)Image_getFormat },
988     {"nativeGetFenceFd",        "()I",                       (void*)Image_getFenceFd },
989     {"nativeGetHardwareBuffer", "()Landroid/hardware/HardwareBuffer;",
990                                                              (void*)Image_getHardwareBuffer },
991 };
992 
register_android_media_ImageReader(JNIEnv * env)993 int register_android_media_ImageReader(JNIEnv *env) {
994 
995     int ret1 = AndroidRuntime::registerNativeMethods(env,
996                    "android/media/ImageReader", gImageReaderMethods, NELEM(gImageReaderMethods));
997 
998     int ret2 = AndroidRuntime::registerNativeMethods(env,
999                    "android/media/ImageReader$SurfaceImage", gImageMethods, NELEM(gImageMethods));
1000 
1001     return (ret1 || ret2);
1002 }
1003