441 lines
16 KiB
Java
441 lines
16 KiB
Java
package org.opencv.android;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.nio.FloatBuffer;
|
|
|
|
import javax.microedition.khronos.egl.EGLConfig;
|
|
import javax.microedition.khronos.opengles.GL10;
|
|
|
|
import org.opencv.android.CameraGLSurfaceView.CameraTextureListener;
|
|
|
|
import android.annotation.TargetApi;
|
|
import android.graphics.SurfaceTexture;
|
|
import android.opengl.GLES11Ext;
|
|
import android.opengl.GLES20;
|
|
import android.opengl.GLSurfaceView;
|
|
import android.util.Log;
|
|
import android.view.View;
|
|
|
|
@TargetApi(15)
|
|
public abstract class CameraGLRendererBase implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
|
|
|
|
protected final String LOGTAG = "CameraGLRendererBase";
|
|
|
|
// shaders
|
|
private final String vss = ""
|
|
+ "attribute vec2 vPosition;\n"
|
|
+ "attribute vec2 vTexCoord;\n" + "varying vec2 texCoord;\n"
|
|
+ "void main() {\n" + " texCoord = vTexCoord;\n"
|
|
+ " gl_Position = vec4 ( vPosition.x, vPosition.y, 0.0, 1.0 );\n"
|
|
+ "}";
|
|
|
|
private final String fssOES = ""
|
|
+ "#extension GL_OES_EGL_image_external : require\n"
|
|
+ "precision mediump float;\n"
|
|
+ "uniform samplerExternalOES sTexture;\n"
|
|
+ "varying vec2 texCoord;\n"
|
|
+ "void main() {\n"
|
|
+ " gl_FragColor = texture2D(sTexture,texCoord);\n" + "}";
|
|
|
|
private final String fss2D = ""
|
|
+ "precision mediump float;\n"
|
|
+ "uniform sampler2D sTexture;\n"
|
|
+ "varying vec2 texCoord;\n"
|
|
+ "void main() {\n"
|
|
+ " gl_FragColor = texture2D(sTexture,texCoord);\n" + "}";
|
|
|
|
// coord-s
|
|
private final float vertices[] = {
|
|
-1, -1,
|
|
-1, 1,
|
|
1, -1,
|
|
1, 1 };
|
|
private final float texCoordOES[] = {
|
|
0, 1,
|
|
0, 0,
|
|
1, 1,
|
|
1, 0 };
|
|
private final float texCoord2D[] = {
|
|
0, 0,
|
|
0, 1,
|
|
1, 0,
|
|
1, 1 };
|
|
|
|
private int[] texCamera = {0}, texFBO = {0}, texDraw = {0};
|
|
private int[] FBO = {0};
|
|
private int progOES = -1, prog2D = -1;
|
|
private int vPosOES, vTCOES, vPos2D, vTC2D;
|
|
|
|
private FloatBuffer vert, texOES, tex2D;
|
|
|
|
protected int mCameraWidth = -1, mCameraHeight = -1;
|
|
protected int mFBOWidth = -1, mFBOHeight = -1;
|
|
protected int mMaxCameraWidth = -1, mMaxCameraHeight = -1;
|
|
protected int mCameraIndex = CameraBridgeViewBase.CAMERA_ID_ANY;
|
|
|
|
protected SurfaceTexture mSTexture;
|
|
|
|
protected boolean mHaveSurface = false;
|
|
protected boolean mHaveFBO = false;
|
|
protected boolean mUpdateST = false;
|
|
protected boolean mEnabled = true;
|
|
protected boolean mIsStarted = false;
|
|
|
|
protected CameraGLSurfaceView mView;
|
|
|
|
protected abstract void openCamera(int id);
|
|
protected abstract void closeCamera();
|
|
protected abstract void setCameraPreviewSize(int width, int height); // updates mCameraWidth & mCameraHeight
|
|
|
|
public CameraGLRendererBase(CameraGLSurfaceView view) {
|
|
mView = view;
|
|
int bytes = vertices.length * Float.SIZE / Byte.SIZE;
|
|
vert = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder()).asFloatBuffer();
|
|
texOES = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder()).asFloatBuffer();
|
|
tex2D = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder()).asFloatBuffer();
|
|
vert.put(vertices).position(0);
|
|
texOES.put(texCoordOES).position(0);
|
|
tex2D.put(texCoord2D).position(0);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void onFrameAvailable(SurfaceTexture surfaceTexture) {
|
|
//Log.i(LOGTAG, "onFrameAvailable");
|
|
mUpdateST = true;
|
|
mView.requestRender();
|
|
}
|
|
|
|
@Override
|
|
public void onDrawFrame(GL10 gl) {
|
|
//Log.i(LOGTAG, "onDrawFrame start");
|
|
|
|
if (!mHaveFBO)
|
|
return;
|
|
|
|
synchronized(this) {
|
|
if (mUpdateST) {
|
|
mSTexture.updateTexImage();
|
|
mUpdateST = false;
|
|
}
|
|
|
|
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
|
|
|
CameraTextureListener texListener = mView.getCameraTextureListener();
|
|
if(texListener != null) {
|
|
//Log.d(LOGTAG, "haveUserCallback");
|
|
// texCamera(OES) -> texFBO
|
|
drawTex(texCamera[0], true, FBO[0]);
|
|
|
|
// call user code (texFBO -> texDraw)
|
|
boolean modified = texListener.onCameraTexture(texFBO[0], texDraw[0], mCameraWidth, mCameraHeight);
|
|
|
|
if(modified) {
|
|
// texDraw -> screen
|
|
drawTex(texDraw[0], false, 0);
|
|
} else {
|
|
// texFBO -> screen
|
|
drawTex(texFBO[0], false, 0);
|
|
}
|
|
} else {
|
|
Log.d(LOGTAG, "texCamera(OES) -> screen");
|
|
// texCamera(OES) -> screen
|
|
drawTex(texCamera[0], true, 0);
|
|
}
|
|
//Log.i(LOGTAG, "onDrawFrame end");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onSurfaceChanged(GL10 gl, int surfaceWidth, int surfaceHeight) {
|
|
Log.i(LOGTAG, "onSurfaceChanged("+surfaceWidth+"x"+surfaceHeight+")");
|
|
mHaveSurface = true;
|
|
updateState();
|
|
setPreviewSize(surfaceWidth, surfaceHeight);
|
|
}
|
|
|
|
@Override
|
|
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
|
|
Log.i(LOGTAG, "onSurfaceCreated");
|
|
initShaders();
|
|
}
|
|
|
|
private void initShaders() {
|
|
String strGLVersion = GLES20.glGetString(GLES20.GL_VERSION);
|
|
if (strGLVersion != null)
|
|
Log.i(LOGTAG, "OpenGL ES version: " + strGLVersion);
|
|
|
|
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
|
|
progOES = loadShader(vss, fssOES);
|
|
vPosOES = GLES20.glGetAttribLocation(progOES, "vPosition");
|
|
vTCOES = GLES20.glGetAttribLocation(progOES, "vTexCoord");
|
|
GLES20.glEnableVertexAttribArray(vPosOES);
|
|
GLES20.glEnableVertexAttribArray(vTCOES);
|
|
|
|
prog2D = loadShader(vss, fss2D);
|
|
vPos2D = GLES20.glGetAttribLocation(prog2D, "vPosition");
|
|
vTC2D = GLES20.glGetAttribLocation(prog2D, "vTexCoord");
|
|
GLES20.glEnableVertexAttribArray(vPos2D);
|
|
GLES20.glEnableVertexAttribArray(vTC2D);
|
|
}
|
|
|
|
private void initSurfaceTexture() {
|
|
Log.d(LOGTAG, "initSurfaceTexture");
|
|
deleteSurfaceTexture();
|
|
initTexOES(texCamera);
|
|
mSTexture = new SurfaceTexture(texCamera[0]);
|
|
mSTexture.setOnFrameAvailableListener(this);
|
|
}
|
|
|
|
private void deleteSurfaceTexture() {
|
|
Log.d(LOGTAG, "deleteSurfaceTexture");
|
|
if(mSTexture != null) {
|
|
mSTexture.release();
|
|
mSTexture = null;
|
|
deleteTex(texCamera);
|
|
}
|
|
}
|
|
|
|
private void initTexOES(int[] tex) {
|
|
if(tex.length == 1) {
|
|
GLES20.glGenTextures(1, tex, 0);
|
|
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0]);
|
|
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
|
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
|
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
|
|
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
|
|
}
|
|
}
|
|
|
|
private static void deleteTex(int[] tex) {
|
|
if(tex.length == 1) {
|
|
GLES20.glDeleteTextures(1, tex, 0);
|
|
}
|
|
}
|
|
|
|
private static int loadShader(String vss, String fss) {
|
|
Log.d("CameraGLRendererBase", "loadShader");
|
|
int vshader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
|
|
GLES20.glShaderSource(vshader, vss);
|
|
GLES20.glCompileShader(vshader);
|
|
int[] status = new int[1];
|
|
GLES20.glGetShaderiv(vshader, GLES20.GL_COMPILE_STATUS, status, 0);
|
|
if (status[0] == 0) {
|
|
Log.e("CameraGLRendererBase", "Could not compile vertex shader: "+GLES20.glGetShaderInfoLog(vshader));
|
|
GLES20.glDeleteShader(vshader);
|
|
vshader = 0;
|
|
return 0;
|
|
}
|
|
|
|
int fshader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
|
|
GLES20.glShaderSource(fshader, fss);
|
|
GLES20.glCompileShader(fshader);
|
|
GLES20.glGetShaderiv(fshader, GLES20.GL_COMPILE_STATUS, status, 0);
|
|
if (status[0] == 0) {
|
|
Log.e("CameraGLRendererBase", "Could not compile fragment shader:"+GLES20.glGetShaderInfoLog(fshader));
|
|
GLES20.glDeleteShader(vshader);
|
|
GLES20.glDeleteShader(fshader);
|
|
fshader = 0;
|
|
return 0;
|
|
}
|
|
|
|
int program = GLES20.glCreateProgram();
|
|
GLES20.glAttachShader(program, vshader);
|
|
GLES20.glAttachShader(program, fshader);
|
|
GLES20.glLinkProgram(program);
|
|
GLES20.glDeleteShader(vshader);
|
|
GLES20.glDeleteShader(fshader);
|
|
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, status, 0);
|
|
if (status[0] == 0) {
|
|
Log.e("CameraGLRendererBase", "Could not link shader program: "+GLES20.glGetProgramInfoLog(program));
|
|
program = 0;
|
|
return 0;
|
|
}
|
|
GLES20.glValidateProgram(program);
|
|
GLES20.glGetProgramiv(program, GLES20.GL_VALIDATE_STATUS, status, 0);
|
|
if (status[0] == 0)
|
|
{
|
|
Log.e("CameraGLRendererBase", "Shader program validation error: "+GLES20.glGetProgramInfoLog(program));
|
|
GLES20.glDeleteProgram(program);
|
|
program = 0;
|
|
return 0;
|
|
}
|
|
|
|
Log.d("CameraGLRendererBase", "Shader program is built OK");
|
|
|
|
return program;
|
|
}
|
|
|
|
private void deleteFBO()
|
|
{
|
|
Log.d(LOGTAG, "deleteFBO("+mFBOWidth+"x"+mFBOHeight+")");
|
|
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
|
|
GLES20.glDeleteFramebuffers(1, FBO, 0);
|
|
|
|
deleteTex(texFBO);
|
|
deleteTex(texDraw);
|
|
mFBOWidth = mFBOHeight = 0;
|
|
}
|
|
|
|
private void initFBO(int width, int height)
|
|
{
|
|
Log.d(LOGTAG, "initFBO("+width+"x"+height+")");
|
|
|
|
deleteFBO();
|
|
|
|
GLES20.glGenTextures(1, texDraw, 0);
|
|
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texDraw[0]);
|
|
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
|
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
|
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
|
|
|
|
GLES20.glGenTextures(1, texFBO, 0);
|
|
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texFBO[0]);
|
|
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
|
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
|
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
|
|
|
|
//int hFBO;
|
|
GLES20.glGenFramebuffers(1, FBO, 0);
|
|
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, FBO[0]);
|
|
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, texFBO[0], 0);
|
|
Log.d(LOGTAG, "initFBO error status: " + GLES20.glGetError());
|
|
|
|
int FBOstatus = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
|
|
if (FBOstatus != GLES20.GL_FRAMEBUFFER_COMPLETE)
|
|
Log.e(LOGTAG, "initFBO failed, status: " + FBOstatus);
|
|
|
|
mFBOWidth = width;
|
|
mFBOHeight = height;
|
|
}
|
|
|
|
// draw texture to FBO or to screen if fbo == 0
|
|
private void drawTex(int tex, boolean isOES, int fbo)
|
|
{
|
|
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo);
|
|
|
|
if(fbo == 0)
|
|
GLES20.glViewport(0, 0, mView.getWidth(), mView.getHeight());
|
|
else
|
|
GLES20.glViewport(0, 0, mFBOWidth, mFBOHeight);
|
|
|
|
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
|
|
|
if(isOES) {
|
|
GLES20.glUseProgram(progOES);
|
|
GLES20.glVertexAttribPointer(vPosOES, 2, GLES20.GL_FLOAT, false, 4*2, vert);
|
|
GLES20.glVertexAttribPointer(vTCOES, 2, GLES20.GL_FLOAT, false, 4*2, texOES);
|
|
} else {
|
|
GLES20.glUseProgram(prog2D);
|
|
GLES20.glVertexAttribPointer(vPos2D, 2, GLES20.GL_FLOAT, false, 4*2, vert);
|
|
GLES20.glVertexAttribPointer(vTC2D, 2, GLES20.GL_FLOAT, false, 4*2, tex2D);
|
|
}
|
|
|
|
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
|
|
|
|
if(isOES) {
|
|
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex);
|
|
GLES20.glUniform1i(GLES20.glGetUniformLocation(progOES, "sTexture"), 0);
|
|
} else {
|
|
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tex);
|
|
GLES20.glUniform1i(GLES20.glGetUniformLocation(prog2D, "sTexture"), 0);
|
|
}
|
|
|
|
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
|
|
GLES20.glFlush();
|
|
}
|
|
|
|
public synchronized void enableView() {
|
|
Log.d(LOGTAG, "enableView");
|
|
mEnabled = true;
|
|
updateState();
|
|
}
|
|
|
|
public synchronized void disableView() {
|
|
Log.d(LOGTAG, "disableView");
|
|
mEnabled = false;
|
|
updateState();
|
|
}
|
|
|
|
protected void updateState() {
|
|
Log.d(LOGTAG, "updateState");
|
|
Log.d(LOGTAG, "mEnabled="+mEnabled+", mHaveSurface="+mHaveSurface);
|
|
boolean willStart = mEnabled && mHaveSurface && mView.getVisibility() == View.VISIBLE;
|
|
if (willStart != mIsStarted) {
|
|
if(willStart) doStart();
|
|
else doStop();
|
|
} else {
|
|
Log.d(LOGTAG, "keeping State unchanged");
|
|
}
|
|
Log.d(LOGTAG, "updateState end");
|
|
}
|
|
|
|
protected synchronized void doStart() {
|
|
Log.d(LOGTAG, "doStart");
|
|
initSurfaceTexture();
|
|
openCamera(mCameraIndex);
|
|
mIsStarted = true;
|
|
if(mCameraWidth>0 && mCameraHeight>0)
|
|
setPreviewSize(mCameraWidth, mCameraHeight); // start preview and call listener.onCameraViewStarted()
|
|
}
|
|
|
|
|
|
protected void doStop() {
|
|
Log.d(LOGTAG, "doStop");
|
|
synchronized(this) {
|
|
mUpdateST = false;
|
|
mIsStarted = false;
|
|
mHaveFBO = false;
|
|
closeCamera();
|
|
deleteSurfaceTexture();
|
|
}
|
|
CameraTextureListener listener = mView.getCameraTextureListener();
|
|
if(listener != null) listener.onCameraViewStopped();
|
|
|
|
}
|
|
|
|
protected void setPreviewSize(int width, int height) {
|
|
synchronized(this) {
|
|
mHaveFBO = false;
|
|
mCameraWidth = width;
|
|
mCameraHeight = height;
|
|
setCameraPreviewSize(width, height); // can change mCameraWidth & mCameraHeight
|
|
initFBO(mCameraWidth, mCameraHeight);
|
|
mHaveFBO = true;
|
|
}
|
|
|
|
CameraTextureListener listener = mView.getCameraTextureListener();
|
|
if(listener != null) listener.onCameraViewStarted(mCameraWidth, mCameraHeight);
|
|
}
|
|
|
|
public void setCameraIndex(int cameraIndex) {
|
|
disableView();
|
|
mCameraIndex = cameraIndex;
|
|
enableView();
|
|
}
|
|
|
|
public void setMaxCameraPreviewSize(int maxWidth, int maxHeight) {
|
|
disableView();
|
|
mMaxCameraWidth = maxWidth;
|
|
mMaxCameraHeight = maxHeight;
|
|
enableView();
|
|
}
|
|
|
|
public void onResume() {
|
|
Log.i(LOGTAG, "onResume");
|
|
}
|
|
|
|
public void onPause() {
|
|
Log.i(LOGTAG, "onPause");
|
|
mHaveSurface = false;
|
|
updateState();
|
|
mCameraWidth = mCameraHeight = -1;
|
|
}
|
|
|
|
}
|