diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..94d6e1b
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{#android.push_field_title}}
+
+ {{/android.push_field_title}}
+ {{#android.push_field_text}}
+
+ {{/android.push_field_text}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/src/android/camera.java b/camera/src/android/camera.java
new file mode 100644
index 0000000..5736f09
--- /dev/null
+++ b/camera/src/android/camera.java
@@ -0,0 +1,381 @@
+package com.defold.android.camera;
+
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+
+import android.hardware.Camera;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+import android.app.Activity;
+import android.media.ImageReader;
+import android.view.Surface;
+import android.view.SurfaceView;
+import android.view.SurfaceHolder;
+import android.view.ViewGroup;
+import android.view.View;
+import android.app.Activity;
+import android.graphics.ImageFormat;
+
+import java.util.Arrays;
+import java.util.List;
+import java.lang.Runnable;
+import java.lang.Math;
+
+
+
+class CameraExtension implements Camera.PreviewCallback {
+
+ private static final String TAG = "defold";
+
+ private static final String[] PERMISSIONS = { Manifest.permission.CAMERA };
+
+ private Camera camera;
+ private Camera.Size pictureSize;
+ private Camera.Size previewSize;
+ private Camera.Parameters parameters;
+ private SurfaceView surfaceView;
+
+ private byte[] previewPixels;
+ private byte[] picturePixels;
+
+ private static CameraExtension instance;
+
+ private CameraExtension() {
+
+ }
+
+ private static CameraExtension getInstance() {
+ if (instance == null) {
+ Log.v(TAG, "No instance, creating");
+ instance = new CameraExtension();
+ }
+ return instance;
+ }
+
+ private static boolean hasAllPermissions(Context context, String... permissions) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context != null && permissions != null) {
+ for (String permission : permissions) {
+ if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static void requestPermissions(Context context, String... permissions) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (!hasAllPermissions(context, permissions)) {
+ ((Activity)context).requestPermissions(permissions, 1);
+ }
+ }
+ }
+
+
+ private static Camera openCamera(int facing) {
+ Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+ int cameraCount = Camera.getNumberOfCameras();
+ for (int i = 0; i < cameraCount; i++) {
+ Camera.getCameraInfo(i, cameraInfo);
+ if (cameraInfo.facing == facing) {
+ return Camera.open(i);
+ }
+ }
+ return null;
+ }
+
+ private static Camera openFrontCamera() {
+ return openCamera(Camera.CameraInfo.CAMERA_FACING_FRONT);
+ }
+
+ private static Camera openBackCamera() {
+ return openCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
+ }
+
+ private native void helloworld();
+
+ private native void sendPicture(byte[] picture);
+
+ private native void handleCameraFrame(byte[] cameraFrame, int width, int height);
+
+ //Method from Ketai project!
+ private void decodeYUV420SP(byte[] rgb, byte[] yuv420sp, int width, int height) {
+ final int frameSize = width * height;
+
+ for (int j = 0, yp = 0; j < height; j++) {
+ int uvp = frameSize + (j >> 1) * width;
+ int u = 0;
+ int v = 0;
+ for (int i = 0; i < width; i++, yp++) {
+ int y = (0xff & ((int) yuv420sp[yp])) - 16;
+ if (y < 0) {
+ y = 0;
+ }
+ if ((i & 1) == 0) {
+ v = (0xff & yuv420sp[uvp++]) - 128;
+ u = (0xff & yuv420sp[uvp++]) - 128;
+ }
+
+ int y1192 = 1192 * y;
+ int r = (y1192 + 1634 * v);
+ int g = (y1192 - 833 * v - 400 * u);
+ int b = (y1192 + 2066 * u);
+
+ if (r < 0) {
+ r = 0;
+ }
+ else if (r > 262143) {
+ r = 262143;
+ }
+
+ if (g < 0) {
+ g = 0;
+ }
+ else if (g > 262143) {
+ g = 262143;
+ }
+
+ if (b < 0) {
+ b = 0;
+ }
+ else if (b > 262143) {
+ b = 262143;
+ }
+
+ int pixel = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
+ rgb[(yp * 3)+0] = (byte)(pixel & 0x00ff0000);
+ rgb[(yp * 3)+1] = (byte)(pixel & 0x0000ff00);
+ rgb[(yp * 3)+2] = (byte)(pixel & 0x000000ff);
+ }
+ }
+ }
+
+ /**
+ * Repeatedly take pictures
+ */
+ private void takePicture(final Context context) {
+ ((Activity)context).runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (camera != null) {
+ Log.v(TAG, "Start preview");
+ surfaceView.setVisibility(View.VISIBLE);
+ camera.startPreview();
+ surfaceView.setVisibility(View.INVISIBLE);
+
+ Log.v(TAG, "Take picture");
+ camera.takePicture(null, null, new Camera.PictureCallback() {
+ @Override
+ public void onPictureTaken(byte[] data, Camera camera) {
+ Log.v(TAG, "onPictureTaken");
+ if (data != null) {
+ sendPicture(data);
+ }
+ else {
+ Log.v(TAG, "onPictureTaken - data is null");
+ }
+ takePicture(context);
+ }
+ });
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onPreviewFrame(byte[] data, Camera camera) {
+ Log.v(TAG, "onPreviewFrame " + previewSize.width + "x" + previewSize.height);
+ if (data != null) {
+ handleCameraFrame(data, previewSize.width, previewSize.height);
+ surfaceView.setVisibility(View.VISIBLE);
+ camera.startPreview();
+ surfaceView.setVisibility(View.INVISIBLE);
+ }
+ else {
+ Log.v(TAG, "onPreviewFrame - data is null");
+ }
+ /*decodeYUV420SP(previewPixels, data, previewSize.width, previewSize.height);
+ sendPicture(previewPixels);
+ surfaceView.setVisibility(View.VISIBLE);
+ camera.startPreview();
+ surfaceView.setVisibility(View.INVISIBLE);*/
+ }
+
+
+ private Camera.Size getPreferredPreviewSize() {
+ List previewSizes = parameters.getSupportedPreviewSizes();
+ return previewSizes.get((int)Math.ceil(previewSizes.size() / 2));
+ }
+
+
+ private boolean prepareCapture(final Context context, int facing) {
+ Log.v(TAG, "prepareCapture");
+ if (!hasAllPermissions(context, PERMISSIONS)) {
+ requestPermissions(context, PERMISSIONS);
+ return false;
+ }
+
+ if (facing == 0) {
+ camera = openBackCamera();
+ }
+ else {
+ camera = openFrontCamera();
+ }
+ if (camera == null) {
+ Log.v(TAG, "Unable to open camera");
+ return false;
+ }
+
+ try {
+ parameters = camera.getParameters();
+ for(Camera.Size size : parameters.getSupportedPictureSizes()) {
+ Log.v(TAG, "Supported picture size: " + size.width + "x" + size.height);
+ }
+ for(Camera.Size size : parameters.getSupportedPreviewSizes()) {
+ Log.v(TAG, "Supported preview size: " + size.width + "x" + size.height);
+ }
+ for(int format : parameters.getSupportedPictureFormats()) {
+ Log.v(TAG, "Supported picture format: " + format);
+ }
+ for(int format : parameters.getSupportedPreviewFormats()) {
+ Log.v(TAG, "Supported preview format: " + format);
+ }
+ Log.v(TAG, "Current preview format:" + parameters.getPreviewFormat());
+
+ pictureSize = parameters.getPictureSize();
+ previewSize = getPreferredPreviewSize();
+ parameters.setPreviewSize(previewSize.width, previewSize.height);
+ Log.v(TAG, "Camera parameters. picture size: " + pictureSize.width + "x" + pictureSize.height + " preview size: " + previewSize.width + "x" + previewSize.height);
+ //parameters.setPictureFormat(ImageFormat.RGB_565);
+ //parameters.setPictureSize(previewSize.width, previewSize.height);
+ camera.setParameters(parameters);
+
+ previewPixels = new byte[previewSize.width * previewSize.height * 3];
+ picturePixels = new byte[pictureSize.width * pictureSize.height * 3];
+
+ //camera.setDisplayOrientation() // use utility function from Google example
+ }
+ catch(Exception e) {
+ Log.e(TAG, e.toString());
+ }
+ return true;
+ }
+
+ private int getWidth() {
+ return (previewSize != null) ? previewSize.width : 0;
+ }
+
+ private int getHeight() {
+ return (previewSize != null) ? previewSize.height : 0;
+ }
+
+ private void startCapture(final Context context) {
+ Log.v(TAG, "startCapture");
+ ((Activity)context).runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Log.v(TAG, "runOnUiThread");
+
+ try {
+ Log.v(TAG, "Create surface");
+ surfaceView = new SurfaceView(context);
+ Activity activity = (Activity)context;
+ ViewGroup viewGroup = (ViewGroup)activity.findViewById(android.R.id.content);
+ viewGroup.addView(surfaceView);
+
+ final SurfaceHolder surfaceHolder = surfaceView.getHolder();
+ surfaceHolder.addCallback(new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Log.v(TAG, "surfaceChanged");
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ Log.v(TAG, "surfaceCreated");
+ try {
+ camera.stopPreview();
+ camera.setPreviewCallback(CameraExtension.this);
+ camera.setPreviewDisplay(surfaceHolder);
+ surfaceView.setVisibility(View.VISIBLE);
+ camera.startPreview();
+ surfaceView.setVisibility(View.INVISIBLE);
+ /*Log.v(TAG, "Take picture");
+ camera.takePicture(null, new Camera.PictureCallback() {
+ @Override
+ public void onPictureTaken(byte[] data, Camera camera) {
+ if (data != null) {
+ Log.v(TAG, "onPictureTaken");
+ sendPicture(data);
+ }
+ else {
+ Log.v(TAG, "onPictureTaken - data is null");
+ }
+ surfaceView.setVisibility(View.VISIBLE);
+ }
+ }, null);*/
+ }
+ catch(Exception e) {
+ Log.e(TAG, e.toString());
+ }
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ Log.v(TAG, "surfaceDestroyed");
+ }
+ });
+
+ }
+ catch(Exception e) {
+ Log.e(TAG, e.toString());
+ }
+ }
+ });
+ }
+
+ private void stopCapture() {
+ if (camera != null) {
+ camera.stopPreview();
+ camera.release();
+ camera = null;
+ }
+ if (surfaceView != null) {
+ ((ViewGroup)surfaceView.getParent()).removeView(surfaceView);
+ surfaceView = null;
+ }
+ }
+
+ public static boolean PrepareCapture(final Context context, int facing) {
+ Log.v(TAG, "PrepareCapture " + facing);
+ return CameraExtension.getInstance().prepareCapture(context, facing);
+ }
+
+ public static void StartCapture(final Context context) {
+ Log.v(TAG, "StartCapture");
+ CameraExtension.getInstance().startCapture(context);
+ }
+
+ public static void StopCapture(final Context context) {
+ Log.v(TAG, "StopCapture");
+ CameraExtension.getInstance().stopCapture();
+ }
+
+ public static int GetWidth() {
+ Log.v(TAG, "GetWidth");
+ return CameraExtension.getInstance().getWidth();
+ }
+
+ public static int GetHeight() {
+ Log.v(TAG, "GetHeight");
+ return CameraExtension.getInstance().getHeight();
+ }
+
+}
diff --git a/camera/src/camera.cpp b/camera/src/camera.cpp
index fe90517..5c41572 100644
--- a/camera/src/camera.cpp
+++ b/camera/src/camera.cpp
@@ -11,7 +11,7 @@
#define DLIB_LOG_DOMAIN LIB_NAME
#include
-#if defined(DM_PLATFORM_IOS) || defined(DM_PLATFORM_OSX)
+#if defined(DM_PLATFORM_IOS) || defined(DM_PLATFORM_OSX) || defined(DM_PLATFORM_ANDROID)
#include "camera_private.h"
@@ -94,7 +94,7 @@ static int GetInfo(lua_State* L)
static int GetFrame(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 1);
- lua_rawgeti(L,LUA_REGISTRYINDEX, g_DefoldCamera.m_VideoBufferLuaRef);
+ lua_rawgeti(L,LUA_REGISTRYINDEX, g_DefoldCamera.m_VideoBufferLuaRef);
return 1;
}
diff --git a/camera/src/camera_android.cpp b/camera/src/camera_android.cpp
new file mode 100644
index 0000000..f838236
--- /dev/null
+++ b/camera/src/camera_android.cpp
@@ -0,0 +1,223 @@
+#include
+
+#if defined(DM_PLATFORM_ANDROID)
+
+#include "camera_private.h"
+
+static JNIEnv* Attach()
+{
+ JNIEnv* env;
+ JavaVM* vm = dmGraphics::GetNativeAndroidJavaVM();
+ vm->AttachCurrentThread(&env, NULL);
+ return env;
+}
+
+static bool Detach(JNIEnv* env)
+{
+ bool exception = (bool) env->ExceptionCheck();
+ env->ExceptionClear();
+ JavaVM* vm = dmGraphics::GetNativeAndroidJavaVM();
+ vm->DetachCurrentThread();
+ return !exception;
+}
+
+namespace {
+ struct AttachScope
+ {
+ JNIEnv* m_Env;
+ AttachScope() : m_Env(Attach())
+ {
+ }
+ ~AttachScope()
+ {
+ Detach(m_Env);
+ }
+ };
+}
+
+static jclass GetClass(JNIEnv* env, const char* classname)
+{
+ jclass activity_class = env->FindClass("android/app/NativeActivity");
+ jmethodID get_class_loader = env->GetMethodID(activity_class,"getClassLoader", "()Ljava/lang/ClassLoader;");
+ jobject cls = env->CallObjectMethod(dmGraphics::GetNativeAndroidActivity(), get_class_loader);
+ jclass class_loader = env->FindClass("java/lang/ClassLoader");
+ jmethodID find_class = env->GetMethodID(class_loader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+
+ jstring str_class_name = env->NewStringUTF(classname);
+ jclass outcls = (jclass)env->CallObjectMethod(cls, find_class, str_class_name);
+ env->DeleteLocalRef(str_class_name);
+ return outcls;
+}
+
+
+struct AndroidCamera {
+ dmBuffer::HBuffer m_VideoBuffer;
+
+ AndroidCamera() : m_VideoBuffer(0)
+ {
+ }
+};
+
+AndroidCamera g_Camera;
+
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT void JNICALL Java_com_defold_android_camera_CameraExtension_helloworld(JNIEnv *env, jobject obj) {
+ dmLogError("Hello World!\n");
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_defold_android_camera_CameraExtension_sendPicture(JNIEnv *env, jobject obj, jbyteArray array) {
+ dmLogError("send Picture!\n");
+
+ uint8_t* data = 0;
+ uint32_t datasize = 0;
+ dmBuffer::GetBytes(g_Camera.m_VideoBuffer, (void**)&data, &datasize);
+
+ int len = env->GetArrayLength(array);
+ dmLogError("sendPicture received array length %d expected %d", len, datasize);
+ //unsigned char* buf = new unsigned char[len];
+ env->GetByteArrayRegion (array, 0, len, reinterpret_cast(data));
+
+ dmBuffer::ValidateBuffer(g_Camera.m_VideoBuffer);
+ return;
+}
+
+
+
+JNIEXPORT void JNICALL Java_com_defold_android_camera_CameraExtension_handleCameraFrame(JNIEnv *env, jobject obj, jbyteArray yuv420sp, jint width, jint height) {
+ uint8_t* data = 0;
+ uint32_t datasize = 0;
+ dmBuffer::GetBytes(g_Camera.m_VideoBuffer, (void**)&data, &datasize);
+
+ int i;
+ int j;
+ int Y;
+ int Cr = 0;
+ int Cb = 0;
+ int pixPtr = 0;
+ int jDiv2 = 0;
+ int R = 0;
+ int G = 0;
+ int B = 0;
+ int cOff;
+ int w = width;
+ int h = height;
+ int sz = w * h;
+
+ jbyte* yuv = (jbyte*) (env->GetPrimitiveArrayCritical(yuv420sp, 0));
+
+ for(j = 0; j < h; j++) {
+ pixPtr = j * w;
+ jDiv2 = j >> 1;
+ for(i = 0; i < w; i++) {
+ Y = yuv[pixPtr];
+ if(Y < 0) Y += 255;
+ if((i & 0x1) != 1) {
+ cOff = sz + jDiv2 * w + (i >> 1) * 2;
+ Cb = yuv[cOff];
+ if(Cb < 0) Cb += 127; else Cb -= 128;
+ Cr = yuv[cOff + 1];
+ if(Cr < 0) Cr += 127; else Cr -= 128;
+ }
+
+ //ITU-R BT.601 conversion
+ //
+ //R = 1.164*(Y-16) + 2.018*(Cr-128);
+ //G = 1.164*(Y-16) - 0.813*(Cb-128) - 0.391*(Cr-128);
+ //B = 1.164*(Y-16) + 1.596*(Cb-128);
+ //
+ Y = Y + (Y >> 3) + (Y >> 5) + (Y >> 7);
+ R = Y + (Cr << 1) + (Cr >> 6);
+ if(R < 0) R = 0; else if(R > 255) R = 255;
+ G = Y - Cb + (Cb >> 3) + (Cb >> 4) - (Cr >> 1) + (Cr >> 3);
+ if(G < 0) G = 0; else if(G > 255) G = 255;
+ B = Y + Cb + (Cb >> 1) + (Cb >> 4) + (Cb >> 5);
+ if(B < 0) B = 0; else if(B > 255) B = 255;
+
+ data[0] = R;
+ data++;
+ data[0] = G;
+ data++;
+ data[0] = B;
+ data++;
+ //data[(pixPtr * 3) + 0] = R;
+ //data[(pixPtr * 3) + 1] = G;
+ //data[(pixPtr * 3) + 2] = B;
+ //data[pixPtr++] = 0xff000000 + (R << 16) + (G << 8) + B;
+ }
+ }
+
+ env->ReleasePrimitiveArrayCritical(yuv420sp, yuv, 0);
+
+ dmBuffer::ValidateBuffer(g_Camera.m_VideoBuffer);
+}
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+int CameraPlatform_StartCapture(dmBuffer::HBuffer* buffer, CameraType type, CaptureQuality quality, CameraInfo& outparams)
+{
+ dmLogError("Android start capture");
+
+ int facing = (type == CAMERA_TYPE_BACK) ? 0 : 1;
+
+ // prepare JNI
+ AttachScope attachscope;
+ JNIEnv* env = attachscope.m_Env;
+ jclass cls = GetClass(env, "com.defold.android.camera.CameraExtension");
+
+ // prepare camera for capture and get width and height
+ jmethodID prepare_capture = env->GetStaticMethodID(cls, "PrepareCapture", "(Landroid/content/Context;I)Z");
+ jboolean prepare_success = env->CallStaticBooleanMethod(cls, prepare_capture, dmGraphics::GetNativeAndroidActivity(), facing);
+ jint width = env->CallStaticIntMethod(cls, env->GetStaticMethodID(cls, "GetWidth", "()I"));
+ jint height = env->CallStaticIntMethod(cls, env->GetStaticMethodID(cls, "GetHeight", "()I"));
+
+ // set out parameters and create video buffer
+ outparams.m_Width = (uint32_t)width;
+ outparams.m_Height = (uint32_t)height;
+ uint32_t size = outparams.m_Width * outparams.m_Height;
+ dmBuffer::StreamDeclaration streams_decl[] = {
+ {dmHashString64("rgb"), dmBuffer::VALUE_TYPE_UINT8, 3}
+ };
+ dmBuffer::Create(size, streams_decl, 1, buffer);
+ g_Camera.m_VideoBuffer = *buffer;
+
+ // Start capture
+ if (prepare_success == JNI_TRUE) {
+ jmethodID start_capture = env->GetStaticMethodID(cls, "StartCapture", "(Landroid/content/Context;)V");
+ env->CallStaticBooleanMethod(cls, start_capture, dmGraphics::GetNativeAndroidActivity());
+ }
+
+ return (prepare_success == JNI_TRUE) ? 1 : 0;
+}
+
+int CameraPlatform_StopCapture()
+{
+ dmLogError("Android stop capture");
+
+ // stop Android camera
+ AttachScope attachscope;
+ JNIEnv* env = attachscope.m_Env;
+ jclass cls = GetClass(env, "com.defold.android.camera.CameraExtension");
+ jmethodID method = env->GetStaticMethodID(cls, "StopCapture", "(Landroid/content/Context;)V");
+ dmLogError("Android stop capture - calling java");
+ env->CallStaticVoidMethod(cls, method, dmGraphics::GetNativeAndroidActivity());
+
+ // destroy the video buffer
+ dmBuffer::Destroy(g_Camera.m_VideoBuffer);
+ g_Camera.m_VideoBuffer = 0;
+
+ return 1;
+}
+
+
+#endif // DM_PLATFORM_ANDROID
diff --git a/camera/src/camera_private.h b/camera/src/camera_private.h
index f222aa0..109a579 100644
--- a/camera/src/camera_private.h
+++ b/camera/src/camera_private.h
@@ -19,7 +19,7 @@ struct CameraInfo
{
uint32_t m_Width;
uint32_t m_Height;
- CameraType m_Type;
+ CameraType m_Type;
};
extern int CameraPlatform_StartCapture(dmBuffer::HBuffer* buffer, CameraType type, CaptureQuality quality, CameraInfo& outparams);
diff --git a/game.project b/game.project
index 421ce8a..bbd5248 100644
--- a/game.project
+++ b/game.project
@@ -23,6 +23,7 @@ bundle_identifier = com.defold.camera
[android]
package = com.defold.camera
+manifest = /AndroidManifest.xml
[osx]
bundle_identifier = com.defold.camera
diff --git a/main/logo.atlas b/main/logo.atlas
index 44e703e..a72a2d8 100644
--- a/main/logo.atlas
+++ b/main/logo.atlas
@@ -1,3 +1,6 @@
images {
image: "/main/images/logo.png"
}
+margin: 0
+extrude_borders: 0
+inner_padding: 0