From eeb94d4de94bfd4e01f3a716803f77a530f5b92c Mon Sep 17 00:00:00 2001 From: mbansal Date: Tue, 2 Aug 2011 11:31:28 -0400 Subject: Updates for mosaic preview rendering to happen using native GLES2.0 code. 1) Added new subfolder mosaic_renderer in feature_mos/src with FrameBuffer and WarpRenderer classes. 2) Added mosaic_renderer_jni.h|cpp files to the jni folder to perform GL calls for rendering on the GL thread. 3) Updated code in feature_mos_jni.cpp to connect with mosaic_renderer_jni.cpp. 4) Added new java files in com.android.camera.panorama to encapsulate the GL JNI interface, a GLSurfaceView for display and a GLSurfaceView.Renderer for rendering. 5) Modified APP code to enable the new GL-based rendering and made relevant changes to the UI (in pano_views.xml). 6) Fixed a GL bug which was preventing the rendering from working properly after hitting the back button once. 7) Preview rendering now displays in the current frame coordinate system. 8) Fixed the ghost preview rendering bug. Change-Id: Ieb64ee3fd4a9a2c6563a311a4930ddc85ce2247c --- jni/Android.mk | 6 +- .../src/mosaic_renderer/FrameBuffer.cpp | 99 ++++++ jni/feature_mos/src/mosaic_renderer/FrameBuffer.h | 31 ++ .../src/mosaic_renderer/WarpRenderer.cpp | 378 +++++++++++++++++++++ jni/feature_mos/src/mosaic_renderer/WarpRenderer.h | 80 +++++ jni/feature_mos_jni.cpp | 93 ++++- jni/mosaic_renderer_jni.cpp | 338 ++++++++++++++++++ jni/mosaic_renderer_jni.h | 23 ++ 8 files changed, 1034 insertions(+), 14 deletions(-) create mode 100755 jni/feature_mos/src/mosaic_renderer/FrameBuffer.cpp create mode 100755 jni/feature_mos/src/mosaic_renderer/FrameBuffer.h create mode 100755 jni/feature_mos/src/mosaic_renderer/WarpRenderer.cpp create mode 100755 jni/feature_mos/src/mosaic_renderer/WarpRenderer.h create mode 100644 jni/mosaic_renderer_jni.cpp create mode 100644 jni/mosaic_renderer_jni.h (limited to 'jni') diff --git a/jni/Android.mk b/jni/Android.mk index 9d04bf5..d653573 100755 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -13,6 +13,7 @@ LOCAL_CFLAGS := -O3 -DNDEBUG LOCAL_SRC_FILES := \ feature_mos_jni.cpp \ + mosaic_renderer_jni.cpp \ feature_mos/src/mosaic/trsMatrix.cpp \ feature_mos/src/mosaic/AlignFeatures.cpp \ feature_mos/src/mosaic/Blend.cpp \ @@ -20,6 +21,8 @@ LOCAL_SRC_FILES := \ feature_mos/src/mosaic/ImageUtils.cpp \ feature_mos/src/mosaic/Mosaic.cpp \ feature_mos/src/mosaic/Pyramid.cpp \ + feature_mos/src/mosaic_renderer/WarpRenderer.cpp \ + feature_mos/src/mosaic_renderer/FrameBuffer.cpp \ feature_stab/db_vlvm/db_feature_detection.cpp \ feature_stab/db_vlvm/db_feature_matching.cpp \ feature_stab/db_vlvm/db_framestitching.cpp \ @@ -34,7 +37,8 @@ LOCAL_SRC_FILES := \ feature_stab/src/dbreg/dbstabsmooth.cpp \ feature_stab/src/dbreg/vp_motionmodel.c -LOCAL_SHARED_LIBRARIES := liblog libnativehelper +LOCAL_SHARED_LIBRARIES := liblog libnativehelper libGLESv2 +#LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -ldl -llog -lGLESv2 -L$(TARGET_OUT) LOCAL_MODULE_TAGS := optional diff --git a/jni/feature_mos/src/mosaic_renderer/FrameBuffer.cpp b/jni/feature_mos/src/mosaic_renderer/FrameBuffer.cpp new file mode 100755 index 0000000..9a07e49 --- /dev/null +++ b/jni/feature_mos/src/mosaic_renderer/FrameBuffer.cpp @@ -0,0 +1,99 @@ +#include "FrameBuffer.h" + +FrameBuffer::FrameBuffer() +{ + Reset(); +} + +FrameBuffer::~FrameBuffer() { +} + +void FrameBuffer::Reset() { + mFrameBufferName = -1; + mTextureName = -1; + mWidth = 0; + mHeight = 0; + mFormat = -1; +} + +bool FrameBuffer::InitializeGLContext() { + Reset(); + return CreateBuffers(); +} + +bool FrameBuffer::Init(int width, int height, GLenum format) { + if (mFrameBufferName == (GLuint)-1) { + if (!CreateBuffers()) { + return false; + } + } + glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferName); + glBindTexture(GL_TEXTURE_2D, mTextureName); + + glTexImage2D(GL_TEXTURE_2D, + 0, + format, + width, + height, + 0, + format, + GL_UNSIGNED_BYTE, + NULL); + if (!checkGlError("bind/teximage")) { + return false; + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + // This is necessary to work with user-generated frame buffers with + // dimensions that are NOT powers of 2. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Attach texture to frame buffer. + glFramebufferTexture2D(GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + mTextureName, + 0); + + if (!checkGlError("texture setup")) { + return false; + } + mWidth = width; + mHeight = height; + mFormat = format; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return true; +} + +bool FrameBuffer::CreateBuffers() { + glGenFramebuffers(1, &mFrameBufferName); + glGenTextures(1, &mTextureName); + if (!checkGlError("texture generation")) { + return false; + } + return true; +} + +GLuint FrameBuffer::GetTextureName() const { + return mTextureName; +} + +GLuint FrameBuffer::GetFrameBufferName() const { + return mFrameBufferName; +} + +GLenum FrameBuffer::GetFormat() const { + return mFormat; +} + +int FrameBuffer::GetWidth() const { + return mWidth; +} + +int FrameBuffer::GetHeight() const { + return mHeight; +} + + + diff --git a/jni/feature_mos/src/mosaic_renderer/FrameBuffer.h b/jni/feature_mos/src/mosaic_renderer/FrameBuffer.h new file mode 100755 index 0000000..b6a20ad --- /dev/null +++ b/jni/feature_mos/src/mosaic_renderer/FrameBuffer.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +extern bool checkGlError(const char* op); + +class FrameBuffer { + public: + FrameBuffer(); + virtual ~FrameBuffer(); + + bool InitializeGLContext(); + bool Init(int width, int height, GLenum format); + GLuint GetTextureName() const; + GLuint GetFrameBufferName() const; + GLenum GetFormat() const; + + int GetWidth() const; + int GetHeight() const; + + private: + void Reset(); + bool CreateBuffers(); + GLuint mFrameBufferName; + GLuint mTextureName; + int mWidth; + int mHeight; + GLenum mFormat; +}; diff --git a/jni/feature_mos/src/mosaic_renderer/WarpRenderer.cpp b/jni/feature_mos/src/mosaic_renderer/WarpRenderer.cpp new file mode 100755 index 0000000..9274721 --- /dev/null +++ b/jni/feature_mos/src/mosaic_renderer/WarpRenderer.cpp @@ -0,0 +1,378 @@ +#include "WarpRenderer.h" + +#include + +#include +#define LOG_TAG "WarpRenderer" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) + + +static const char gVertexShader[] = +"uniform mat4 u_affinetrans; \n" +"uniform mat4 u_viewporttrans; \n" +"uniform mat4 u_scalingtrans; \n" +"attribute vec4 a_position; \n" +"attribute vec2 a_texCoord; \n" +"varying vec2 v_texCoord; \n" +"void main() \n" +"{ \n" +" gl_Position = u_scalingtrans * u_viewporttrans * u_affinetrans * a_position; \n" +" v_texCoord = a_texCoord; \n" +"} \n"; + +static const char gFragmentShader[] = +"precision mediump float; \n" +"varying vec2 v_texCoord; \n" +"uniform sampler2D s_texture; \n" +"void main() \n" +"{ \n" +" vec4 color; \n" +" color = texture2D(s_texture, v_texCoord); \n" +" gl_FragColor = color; \n" +"} \n"; + + +const GLfloat g_vVertices[] = { + -1.f, -1.f, 0.0f, 1.0f, // Position 0 + 0.0f, 1.0f, // TexCoord 0 + 1.f, -1.f, 0.0f, 1.0f, // Position 1 + 1.0f, 1.0f, // TexCoord 1 + -1.f, 1.f, 0.0f, 1.0f, // Position 2 + 0.0f, 0.0f, // TexCoord 2 + 1.f, 1.f, 0.0f, 1.0f, // Position 3 + 1.0f, 0.0f // TexCoord 3 +}; + +const int VERTEX_STRIDE = 6 * sizeof(GLfloat); + +GLushort g_iIndices[] = { 0, 1, 2, 3 }; + +WarpRenderer::WarpRenderer() + : mGlProgram(0), + mInputTextureName(-1), + mInputTextureWidth(0), + mInputTextureHeight(0), + mSurfaceWidth(0), + mSurfaceHeight(0) + { + InitializeGLContext(); +} + +WarpRenderer::~WarpRenderer() { +} + +GLuint WarpRenderer::loadShader(GLenum shaderType, const char* pSource) { + GLuint shader = glCreateShader(shaderType); + if (shader) { + glShaderSource(shader, 1, &pSource, NULL); + glCompileShader(shader); + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + if (infoLen) { + char* buf = (char*) malloc(infoLen); + if (buf) { + glGetShaderInfoLog(shader, infoLen, NULL, buf); + LOGE("Could not compile shader %d:\n%s\n", + shaderType, buf); + free(buf); + } + glDeleteShader(shader); + shader = 0; + } + } + } + return shader; +} + +GLuint WarpRenderer::createProgram(const char* pVertexSource, const char* pFragmentSource) +{ + GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource); + if (!vertexShader) + { + return 0; + } + LOGI("VertexShader Loaded!"); + + GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource); + if (!pixelShader) + { + return 0; + } + LOGI("FragmentShader Loaded!"); + + GLuint program = glCreateProgram(); + if (program) + { + glAttachShader(program, vertexShader); + checkGlError("glAttachShader"); + glAttachShader(program, pixelShader); + checkGlError("glAttachShader"); + + LOGI("Shaders Attached!"); + + glLinkProgram(program); + GLint linkStatus = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + + LOGI("Program Linked!"); + + if (linkStatus != GL_TRUE) + { + GLint bufLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); + if (bufLength) + { + char* buf = (char*) malloc(bufLength); + if (buf) + { + glGetProgramInfoLog(program, bufLength, NULL, buf); + LOGE("Could not link program:\n%s\n", buf); + free(buf); + } + } + glDeleteProgram(program); + program = 0; + } + } + return program; +} + +bool WarpRenderer::InitializeGLProgram() +{ + bool succeeded = false; + do { + GLuint glProgram; + glProgram = createProgram(VertexShaderSource(), + FragmentShaderSource()); + if (!glProgram) { + break; + } + + glUseProgram(glProgram); + if (!checkGlError("glUseProgram")) break; + + // Get attribute locations + mPositionLoc = glGetAttribLocation(glProgram, "a_position"); + mAffinetransLoc = glGetUniformLocation(glProgram, "u_affinetrans"); + mViewporttransLoc = glGetUniformLocation(glProgram, "u_viewporttrans"); + mScalingtransLoc = glGetUniformLocation(glProgram, "u_scalingtrans"); + mTexCoordLoc = glGetAttribLocation(glProgram, "a_texCoord"); + + // Get sampler location + mSamplerLoc = glGetUniformLocation(glProgram, "s_texture"); + + mGlProgram = glProgram; + succeeded = true; + } while (false); + + if (!succeeded && (mGlProgram != 0)) + { + glDeleteProgram(mGlProgram); + checkGlError("glDeleteProgram"); + mGlProgram = 0; + } + return succeeded; +} + +void WarpRenderer::SetViewportMatrix(int w, int h, int W, int H) +{ + for(int i=0; i<16; i++) + { + mViewportMatrix[i] = 0.0f; + } + + mViewportMatrix[0] = float(w)/float(W); + mViewportMatrix[5] = float(h)/float(H); + mViewportMatrix[10] = 1.0f; + mViewportMatrix[12] = -1.0f + float(w)/float(W); + mViewportMatrix[13] = -1.0f + float(h)/float(H); + mViewportMatrix[15] = 1.0f; +} + +void WarpRenderer::SetScalingMatrix(float xscale, float yscale) +{ + for(int i=0; i<16; i++) + { + mScalingMatrix[i] = 0.0f; + } + + mScalingMatrix[0] = xscale; + mScalingMatrix[5] = yscale; + mScalingMatrix[10] = 1.0f; + mScalingMatrix[15] = 1.0f; +} + +// Set this renderer to use the default frame-buffer (screen) and +// set the viewport size to be the given width and height (pixels). +bool WarpRenderer::SetupGraphics(int width, int height) +{ + bool succeeded = false; + do { + if (mGlProgram == 0) + { + if (!InitializeGLProgram()) + { + break; + } + } + glUseProgram(mGlProgram); + if (!checkGlError("glUseProgram")) break; + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + mFrameBuffer = NULL; + mSurfaceWidth = width; + mSurfaceHeight = height; + + glViewport(0, 0, mSurfaceWidth, mSurfaceHeight); + if (!checkGlError("glViewport")) break; + succeeded = true; + } while (false); + + return succeeded; +} + + +// Set this renderer to use the specified FBO and +// set the viewport size to be the width and height of this FBO. +bool WarpRenderer::SetupGraphics(FrameBuffer* buffer) +{ + bool succeeded = false; + do { + if (mGlProgram == 0) + { + if (!InitializeGLProgram()) + { + break; + } + } + glUseProgram(mGlProgram); + if (!checkGlError("glUseProgram")) break; + + glBindFramebuffer(GL_FRAMEBUFFER, buffer->GetFrameBufferName()); + + mFrameBuffer = buffer; + mSurfaceWidth = mFrameBuffer->GetWidth(); + mSurfaceHeight = mFrameBuffer->GetHeight(); + + glViewport(0, 0, mSurfaceWidth, mSurfaceHeight); + if (!checkGlError("glViewport")) break; + succeeded = true; + } while (false); + + return succeeded; +} + +bool WarpRenderer::Clear(float r, float g, float b, float a) +{ + bool succeeded = false; + do { + bool rt = (mFrameBuffer == NULL)? + SetupGraphics(mSurfaceWidth, mSurfaceHeight) : + SetupGraphics(mFrameBuffer); + + if(!rt) + break; + + glClearColor(r, g, b, a); + glClear(GL_COLOR_BUFFER_BIT); + + succeeded = true; + } while (false); + return succeeded; + +} + +bool WarpRenderer::DrawTexture(GLfloat *affine) +{ + bool succeeded = false; + do { + bool rt = (mFrameBuffer == NULL)? + SetupGraphics(mSurfaceWidth, mSurfaceHeight) : + SetupGraphics(mFrameBuffer); + + if(!rt) + break; + + glDisable(GL_BLEND); + + glActiveTexture(GL_TEXTURE0); + if (!checkGlError("glActiveTexture")) break; + + const GLenum texture_type = InputTextureType(); + glBindTexture(texture_type, mInputTextureName); + if (!checkGlError("glBindTexture")) break; + + // Set the sampler texture unit to 0 + glUniform1i(mSamplerLoc, 0); + + // Load the vertex position + glVertexAttribPointer(mPositionLoc, 4, GL_FLOAT, + GL_FALSE, VERTEX_STRIDE, g_vVertices); + + // Load the texture coordinate + glVertexAttribPointer(mTexCoordLoc, 2, GL_FLOAT, + GL_FALSE, VERTEX_STRIDE, &g_vVertices[4]); + + glEnableVertexAttribArray(mPositionLoc); + glEnableVertexAttribArray(mTexCoordLoc); + + // pass matrix information to the vertex shader + glUniformMatrix4fv(mAffinetransLoc, 1, GL_FALSE, affine); + glUniformMatrix4fv(mViewporttransLoc, 1, GL_FALSE, mViewportMatrix); + glUniformMatrix4fv(mScalingtransLoc, 1, GL_FALSE, mScalingMatrix); + + // And, finally, execute the GL draw command. + glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, g_iIndices); + + checkGlError("glDrawElements"); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + succeeded = true; + } while (false); + return succeeded; +} + +void WarpRenderer::InitializeGLContext() +{ + if(mFrameBuffer != NULL) + { + delete mFrameBuffer; + mFrameBuffer = NULL; + } + + mInputTextureName = -1; + mGlProgram = 0; + mTexHandle = 0; +} + +int WarpRenderer::GetTextureName() +{ + return mInputTextureName; +} + +void WarpRenderer::SetInputTextureName(GLuint textureName) +{ + mInputTextureName = textureName; +} + +void WarpRenderer::SetInputTextureDimensions(int width, int height) +{ + mInputTextureWidth = width; + mInputTextureHeight = height; +} + + +const char* WarpRenderer::VertexShaderSource() const +{ + return gVertexShader; +} + +const char* WarpRenderer::FragmentShaderSource() const +{ + return gFragmentShader; +} diff --git a/jni/feature_mos/src/mosaic_renderer/WarpRenderer.h b/jni/feature_mos/src/mosaic_renderer/WarpRenderer.h new file mode 100755 index 0000000..7986215 --- /dev/null +++ b/jni/feature_mos/src/mosaic_renderer/WarpRenderer.h @@ -0,0 +1,80 @@ +#pragma once + +#include "FrameBuffer.h" + +#include + +#include +#include +#include + +class WarpRenderer { + public: + WarpRenderer(); + virtual ~WarpRenderer(); + + // Initialize OpenGL resources + // @return true if successful + bool InitializeGLProgram(); + + bool SetupGraphics(FrameBuffer* buffer); + bool SetupGraphics(int width, int height); + + bool Clear(float r, float g, float b, float a); + + void SetViewportMatrix(int w, int h, int W, int H); + void SetScalingMatrix(float xscale, float yscale); + bool DrawTexture(GLfloat *affine); + + int GetTextureName(); + void SetInputTextureName(GLuint textureName); + void SetInputTextureDimensions(int width, int height); + + void InitializeGLContext(); + + protected: + + GLuint loadShader(GLenum shaderType, const char* pSource); + GLuint createProgram(const char*, const char* ); + + int SurfaceWidth() const { return mSurfaceWidth; } + int SurfaceHeight() const { return mSurfaceHeight; } + + private: + // Source code for shaders. + virtual const char* VertexShaderSource() const; + virtual const char* FragmentShaderSource() const; + + // Redefine this to use special texture types such as + // GL_TEXTURE_EXTERNAL_OES. + virtual GLenum InputTextureType() const { return GL_TEXTURE_2D; } + + + GLuint mGlProgram; + GLuint mInputTextureName; + int mInputTextureWidth; + int mInputTextureHeight; + + GLuint mTexHandle; // Handle to s_texture. + GLuint mTexCoordHandle; // Handle to a_texCoord. + GLuint mTriangleVerticesHandle; // Handle to vPosition. + + // Attribute locations + GLint mPositionLoc; + GLint mAffinetransLoc; + GLint mViewporttransLoc; + GLint mScalingtransLoc; + GLint mTexCoordLoc; + + GLfloat mViewportMatrix[16]; + GLfloat mScalingMatrix[16]; + + // Sampler location + GLint mSamplerLoc; + + int mSurfaceWidth; // Width of target surface. + int mSurfaceHeight; // Height of target surface. + + FrameBuffer *mFrameBuffer; +}; + diff --git a/jni/feature_mos_jni.cpp b/jni/feature_mos_jni.cpp index 577da24..c858728 100644 --- a/jni/feature_mos_jni.cpp +++ b/jni/feature_mos_jni.cpp @@ -47,8 +47,13 @@ extern "C" { #endif +#include "mosaic_renderer_jni.h" + char buffer[1024]; +double g_dAffinetrans[16]; +double g_dAffinetransInv[16]; + const int MAX_FRAMES_HR = 100; const int MAX_FRAMES_LR = 200; @@ -109,7 +114,8 @@ int Init(int mID, int nmax) t0 = now_ms(); - // When processing higher than 720x480 video, process low-res at quarter resolution + // When processing higher than 720x480 video, process low-res at + // quarter resolution if(tWidth[LR]>180) quarter_res[LR] = true; @@ -117,7 +123,8 @@ int Init(int mID, int nmax) // Check for initialization and if not, initialize if (!mosaic[mID]->isInitialized()) { - mosaic[mID]->initialize(blendingType, tWidth[mID], tHeight[mID], nmax, quarter_res[mID], thresh_still[mID]); + mosaic[mID]->initialize(blendingType, tWidth[mID], tHeight[mID], + nmax, quarter_res[mID], thresh_still[mID]); } t1 = now_ms(); @@ -126,7 +133,8 @@ int Init(int mID, int nmax) return 1; } -void GenerateQuarterResImagePlanar(ImageType im, int input_w, int input_h, ImageType &out) +void GenerateQuarterResImagePlanar(ImageType im, int input_w, int input_h, + ImageType &out) { ImageType imp; ImageType outp; @@ -246,7 +254,8 @@ void YUV420toYVU24(ImageType yvu24, ImageType yuv420sp, int width, int height) } } -void YUV420toYVU24_NEW(ImageType yvu24, ImageType yuv420sp, int width, int height) +void YUV420toYVU24_NEW(ImageType yvu24, ImageType yuv420sp, int width, + int height) { int frameSize = width * height; @@ -283,7 +292,8 @@ void YUV420toYVU24_NEW(ImageType yvu24, ImageType yuv420sp, int width, int heigh } -JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_allocateMosaicMemory(JNIEnv* env, jobject thiz, jint width, jint height) +JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_allocateMosaicMemory( + JNIEnv* env, jobject thiz, jint width, jint height) { tWidth[HR] = width; tHeight[HR] = height; @@ -300,8 +310,9 @@ JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_allocateMosaicMem tImage[HR][i] = ImageUtils::allocateImage(tWidth[HR], tHeight[HR], ImageUtils::IMAGE_TYPE_NUM_CHANNELS); } -} + AllocateTextureMemory(tWidth[LR], tHeight[LR]); +} JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_freeMosaicMemory( JNIEnv* env, jobject thiz) @@ -314,6 +325,45 @@ JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_freeMosaicMemory( { ImageUtils::freeImage(tImage[HR][i]); } + + FreeTextureMemory(); +} + + +void decodeYUV444SP(unsigned char* rgb, unsigned char* yuv420sp, int width, + int height) +{ + int frameSize = width * height; + + for (int j = 0, yp = 0; j < height; j++) + { + int vp = frameSize + j * width, u = 0, v = 0; + int up = vp + frameSize; + + for (int i = 0; i < width; i++, yp++, vp++, up++) + { + int y = (0xff & ((int) yuv420sp[yp])) - 16; + if (y < 0) y = 0; + + v = (0xff & yuv420sp[vp]) - 128; + u = (0xff & yuv420sp[up]) - 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; + + //rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff); + int p = j*width*3+i*3; + rgb[p+0] = (r<<6 & 0xFF0000)>>16; + rgb[p+1] = (g>>2 & 0xFF00)>>8; + rgb[p+2] = b>>10 & 0xFF; + } + } } @@ -349,6 +399,14 @@ JNIEXPORT jfloatArray JNICALL Java_com_android_camera_panorama_Mosaic_setSourceI t0 = now_ms(); GenerateQuarterResImagePlanar(tImage[HR][frame_number_HR], tWidth[HR], tHeight[HR], tImage[LR][frame_number_LR]); + + + sem_wait(&gPreviewImageRGB_semaphore); + decodeYUV444SP(gPreviewImageRGB, tImage[LR][frame_number_LR], + gPreviewImageRGBWidth, gPreviewImageRGBHeight); + sem_post(&gPreviewImageRGB_semaphore); + + t1 = now_ms(); time_c = t1 - t0; LOGV("[%d] HR->LR [%d]: %g ms", frame_number_HR, frame_number_LR, @@ -369,6 +427,8 @@ JNIEXPORT jfloatArray JNICALL Java_com_android_camera_panorama_Mosaic_setSourceI gTRS[0] = gTRS[4] = gTRS[8] = 1.0f; } + UpdateWarpTransformation(gTRS); + gTRS[9] = frame_number_HR; jfloatArray bytes = env->NewFloatArray(10); @@ -379,12 +439,14 @@ JNIEXPORT jfloatArray JNICALL Java_com_android_camera_panorama_Mosaic_setSourceI return bytes; } -JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_setBlendingType(JNIEnv* env, jobject thiz, jint type) +JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_setBlendingType( + JNIEnv* env, jobject thiz, jint type) { blendingType = int(type); } -JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_reset(JNIEnv* env, jobject thiz) +JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_reset( + JNIEnv* env, jobject thiz) { frame_number_HR = 0; frame_number_LR = 0; @@ -392,7 +454,8 @@ JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_reset(JNIEnv* env Init(LR,MAX_FRAMES_LR); } -JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_createMosaic(JNIEnv* env, jobject thiz, jboolean value) +JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_createMosaic( + JNIEnv* env, jobject thiz, jboolean value) { high_res = bool(value); @@ -414,7 +477,8 @@ JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_createMosaic(JNIE } } -JNIEXPORT jintArray JNICALL Java_com_android_camera_panorama_Mosaic_getFinalMosaic(JNIEnv* env, jobject thiz) +JNIEXPORT jintArray JNICALL Java_com_android_camera_panorama_Mosaic_getFinalMosaic( + JNIEnv* env, jobject thiz) { int y,x; int width = mosaicWidth; @@ -422,7 +486,8 @@ JNIEXPORT jintArray JNICALL Java_com_android_camera_panorama_Mosaic_getFinalMosa int imageSize = width * height; // Convert back to RGB24 - resultBGR = ImageUtils::allocateImage(mosaicWidth, mosaicHeight, ImageUtils::IMAGE_TYPE_NUM_CHANNELS); + resultBGR = ImageUtils::allocateImage(mosaicWidth, mosaicHeight, + ImageUtils::IMAGE_TYPE_NUM_CHANNELS); ImageUtils::yvu2bgr(resultBGR, resultYVU, mosaicWidth, mosaicHeight); LOGV("MosBytes: %d, W = %d, H = %d", imageSize, width, height); @@ -434,7 +499,8 @@ JNIEXPORT jintArray JNICALL Java_com_android_camera_panorama_Mosaic_getFinalMosa { for(x=0; x +#include + +#include +#include "mosaic/ImageUtils.h" +#include "mosaic_renderer/FrameBuffer.h" +#include "mosaic_renderer/WarpRenderer.h" +#include +#include +#include + +#include "mosaic_renderer_jni.h" + +#define LOG_TAG "MosaicRenderer" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) + +// Texture handle +GLuint textureId[1]; + +bool warp_image = true; + +// Low-Res input image frame in RGB format for preview rendering +unsigned char* gPreviewImageRGB; +// Low-Res preview image width +int gPreviewImageRGBWidth; +// Low-Res preview image height +int gPreviewImageRGBHeight; + +// Semaphore to protect simultaneous read/writes from gPreviewImageRGB +sem_t gPreviewImageRGB_semaphore; + +// Off-screen preview FBO width (large enough to store the entire +// preview mosaic). +int gPreviewFBOWidth; +// Off-screen preview FBO height (large enough to store the entire +// preview mosaic). +int gPreviewFBOHeight; + +// Shader to add warped current frame to the preview FBO +WarpRenderer gWarper; +// Shader to warp and render the preview FBO to the screen +WarpRenderer gPreview; +// Off-screen FBO to store the result of gWarper +FrameBuffer *gBuffer; + +// Affine transformation in GL 4x4 format (column-major) to warp the +// current frame into the first frame coordinate system. +GLfloat g_dAffinetransGL[16]; + +// Affine transformation in GL 4x4 format (column-major) to warp the +// preview FBO into the current frame coordinate system. +GLfloat g_dAffinetransInvGL[16]; + +// GL 4x4 Identity transformation +GLfloat g_dAffinetransIdent[] = { + 1., 0., 0., 0., + 0., 1., 0., 0., + 0., 0., 1., 0., + 0., 0., 0., 1.}; + + +static void printGLString(const char *name, GLenum s) { + const char *v = (const char *) glGetString(s); + LOGI("GL %s = %s\n", name, v); +} + +// @return false if there was an error +bool checkGlError(const char* op) { + GLint error = glGetError(); + if (error != 0) { + LOGE("after %s() glError (0x%x)\n", op, error); + return false; + } + return true; +} + +void LoadTexture(unsigned char *buffer, int width, int height, GLuint texId) +{ + glBindTexture(GL_TEXTURE_2D, texId); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, + GL_UNSIGNED_BYTE, buffer); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +void ReloadTexture(unsigned char *buffer, int width, int height, GLuint texId) +{ + glBindTexture(GL_TEXTURE_2D, texId); + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, + GL_UNSIGNED_BYTE, buffer); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +void ConvertAffine3x3toGL4x4(double *matGL44, double *mat33) +{ + matGL44[0] = mat33[0]; + matGL44[1] = mat33[3]; + matGL44[2] = 0.0; + matGL44[3] = mat33[6]; + + matGL44[4] = mat33[1]; + matGL44[5] = mat33[4]; + matGL44[6] = 0.0; + matGL44[7] = mat33[7]; + + matGL44[8] = 0; + matGL44[9] = 0; + matGL44[10] = 1.0; + matGL44[11] = 0.0; + + matGL44[12] = mat33[2]; + matGL44[13] = mat33[5]; + matGL44[14] = 0.0; + matGL44[15] = mat33[8]; +} + +// This function computes fills the 4x4 matrices g_dAffinetrans and +// g_dAffinetransInv using the specified 3x3 affine transformation +// between the first captured frame and the current frame. The computed +// g_dAffinetrans is such that it warps the current frame into the +// coordinate system of the first frame. Thus, applying this transformation +// to each successive frame builds up the preview mosaic in the first frame +// coordinate system. Then the computed g_dAffinetransInv is such that it +// warps the computed preview mosaic into the coordinate system of the +// original (as captured) current frame. This has the effect of showing +// the current frame as is (without warping) but warping the rest of the +// mosaic data to still be in alignment with this frame. +void UpdateWarpTransformation(float *trs) +{ + double H[9], Hinv[9], Hp[9], Htemp[9]; + double K[9], Kinv[9]; + + int w = gPreviewImageRGBWidth; + int h = gPreviewImageRGBHeight; + + // K is the transformation to map the canonical [-1,1] vertex coordinate + // system to the [0,w] image coordinate system before applying the given + // affine transformation trs. + K[0] = w / 2.0; + K[1] = 0.0; + K[2] = w / 2.0; + K[3] = 0.0; + K[4] = -h / 2.0; + K[5] = h / 2.0; + K[6] = 0.0; + K[7] = 0.0; + K[8] = 1.0; + + db_Identity3x3(Kinv); + db_InvertCalibrationMatrix(Kinv, K); + + for(int i=0; i<9; i++) + { + H[i] = trs[i]; + } + + // Move the origin such that the frame is centered in the previewFBO + H[2] += (gPreviewFBOWidth / 2 - gPreviewImageRGBWidth / 2); + H[5] -= (gPreviewFBOHeight / 2 - gPreviewImageRGBHeight / 2); + + // Hp = inv(K) * H * K + db_Identity3x3(Htemp); + db_Multiply3x3_3x3(Htemp, H, K); + db_Multiply3x3_3x3(Hp, Kinv, Htemp); + + ConvertAffine3x3toGL4x4(g_dAffinetrans, Hp); + + //////////////////////////////////////////////// + ////// Compute g_dAffinetransInv now... ////// + //////////////////////////////////////////////// + + w = gPreviewFBOWidth; + h = gPreviewFBOHeight; + + K[0] = w / 2.0; + K[1] = 0.0; + K[2] = w / 2.0; + K[3] = 0.0; + K[4] = h / 2.0; + K[5] = h / 2.0; + K[6] = 0.0; + K[7] = 0.0; + K[8] = 1.0; + + db_Identity3x3(Kinv); + db_InvertCalibrationMatrix(Kinv, K); + + db_Identity3x3(Hinv); + db_InvertAffineTransform(Hinv, H); + + Hinv[2] += (gPreviewFBOWidth / 2 - gPreviewImageRGBWidth / 2); + Hinv[5] -= (gPreviewFBOHeight / 2 - gPreviewImageRGBHeight / 2); + + // Hp = inv(K) * Hinv * K + db_Identity3x3(Htemp); + db_Multiply3x3_3x3(Htemp, Hinv, K); + db_Multiply3x3_3x3(Hp, Kinv, Htemp); + + ConvertAffine3x3toGL4x4(g_dAffinetransInv, Hp); +} + +void AllocateTextureMemory(int width, int height) +{ + gPreviewImageRGBWidth = width; + gPreviewImageRGBHeight = height; + + sem_init(&gPreviewImageRGB_semaphore, 0, 1); + + sem_wait(&gPreviewImageRGB_semaphore); + gPreviewImageRGB = ImageUtils::allocateImage(gPreviewImageRGBWidth, + gPreviewImageRGBHeight, ImageUtils::IMAGE_TYPE_NUM_CHANNELS); + memset(gPreviewImageRGB, 0, gPreviewImageRGBWidth * + gPreviewImageRGBHeight * 3 * sizeof(unsigned char)); + sem_post(&gPreviewImageRGB_semaphore); + + gPreviewFBOWidth = PREVIEW_FBO_WIDTH_SCALE * gPreviewImageRGBWidth; + gPreviewFBOHeight = PREVIEW_FBO_HEIGHT_SCALE * gPreviewImageRGBHeight; + + UpdateWarpTransformation(g_dAffinetransIdent); +} + +void FreeTextureMemory() +{ + sem_wait(&gPreviewImageRGB_semaphore); + ImageUtils::freeImage(gPreviewImageRGB); + sem_post(&gPreviewImageRGB_semaphore); + + sem_destroy(&gPreviewImageRGB_semaphore); +} + +extern "C" +{ + JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_init( + JNIEnv * env, jobject obj); + JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_reset( + JNIEnv * env, jobject obj, jint width, jint height); + JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_step( + JNIEnv * env, jobject obj); + JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_ready( + JNIEnv * env, jobject obj); + JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_togglewarping( + JNIEnv * env, jobject obj); +}; + +JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_init( + JNIEnv * env, jobject obj) +{ + gWarper.InitializeGLProgram(); + gPreview.InitializeGLProgram(); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glGenTextures(1, &textureId[0]); +} + +JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_reset( + JNIEnv * env, jobject obj, jint width, jint height) +{ + gBuffer = new FrameBuffer(); + gBuffer->Init(gPreviewFBOWidth, gPreviewFBOHeight, GL_RGBA); + + sem_wait(&gPreviewImageRGB_semaphore); + memset(gPreviewImageRGB, 0, gPreviewImageRGBWidth * + gPreviewImageRGBHeight * 3 * sizeof(unsigned char)); + sem_post(&gPreviewImageRGB_semaphore); + + // Load texture + LoadTexture(gPreviewImageRGB, gPreviewImageRGBWidth, + gPreviewImageRGBHeight, textureId[0]); + + gWarper.SetupGraphics(gBuffer); + gWarper.Clear(0.0, 0.0, 0.0, 1.0); + gWarper.SetViewportMatrix(gPreviewImageRGBWidth, + gPreviewImageRGBHeight, gBuffer->GetWidth(), gBuffer->GetHeight()); + gWarper.SetScalingMatrix(1.0f, 1.0f); + gWarper.SetInputTextureName(textureId[0]); + + gPreview.SetupGraphics(width, height); + gPreview.Clear(0.0, 0.0, 0.0, 1.0); + gPreview.SetViewportMatrix(1, 1, 1, 1); + gPreview.SetScalingMatrix(1.0f, -1.0f); + gPreview.SetInputTextureName(gBuffer->GetTextureName()); +} + +JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_step( + JNIEnv * env, jobject obj) +{ + // Use the gWarper shader to apply the current frame transformation to the + // current frame and then add it to the gBuffer FBO. + gWarper.DrawTexture(g_dAffinetransGL); + + // Clear the screen to black. + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + // Use the gPreview shader to apply the inverse of the current frame + // transformation to the gBuffer FBO and render it to the screen. + gPreview.DrawTexture(g_dAffinetransInvGL); +} + +JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_togglewarping( + JNIEnv * env, jobject obj) +{ + warp_image = !warp_image; +} + + +JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_ready( + JNIEnv * env, jobject obj) +{ + sem_wait(&gPreviewImageRGB_semaphore); + ReloadTexture(gPreviewImageRGB, gPreviewImageRGBWidth, + gPreviewImageRGBHeight, textureId[0]); + sem_post(&gPreviewImageRGB_semaphore); + + if(!warp_image) + { + for(int i=0; i<16; i++) + { + g_dAffinetrans[i] = g_dAffinetransIdent[i]; + } + } + + for(int i=0; i<16; i++) + { + g_dAffinetransGL[i] = g_dAffinetrans[i]; + g_dAffinetransInvGL[i] = g_dAffinetransInv[i]; + } +} diff --git a/jni/mosaic_renderer_jni.h b/jni/mosaic_renderer_jni.h new file mode 100644 index 0000000..ed4180f --- /dev/null +++ b/jni/mosaic_renderer_jni.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include + +// The Preview FBO dimensions are determined from the low-res +// frame dimensions (gPreviewImageRGBWidth, gPreviewImageRGBHeight) +// using the scale factors below. +const int PREVIEW_FBO_WIDTH_SCALE = 4; +const int PREVIEW_FBO_HEIGHT_SCALE = 2; + +extern "C" void AllocateTextureMemory(int width, int height); +extern "C" void FreeTextureMemory(); +extern "C" void UpdateWarpTransformation(float *trs); + +extern unsigned char* gPreviewImageRGB; +extern int gPreviewImageRGBWidth; +extern int gPreviewImageRGBHeight; + +extern sem_t gPreviewImageRGB_semaphore; + +extern double g_dAffinetrans[16]; +extern double g_dAffinetransInv[16]; -- cgit v1.1