parent
f6e703035b
commit
f3dd152a72
@ -0,0 +1,2 @@ |
|||||||
|
/build |
||||||
|
/.cxx |
@ -0,0 +1,26 @@ |
|||||||
|
plugins { |
||||||
|
id 'com.android.library' |
||||||
|
} |
||||||
|
|
||||||
|
android { |
||||||
|
compileSdkVersion rootProject.ext.compileSdkVersion |
||||||
|
|
||||||
|
defaultConfig { |
||||||
|
minSdkVersion rootProject.ext.minSdkVersion |
||||||
|
targetSdkVersion rootProject.ext.targetSdkVersion |
||||||
|
} |
||||||
|
|
||||||
|
buildTypes { |
||||||
|
release { |
||||||
|
minifyEnabled false |
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
|
||||||
|
implementation "androidx.appcompat:appcompat:$rootProject.appcompatVersion" |
||||||
|
|
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
# Add project specific ProGuard rules here. |
||||||
|
# You can control the set of applied configuration files using the |
||||||
|
# proguardFiles setting in build.gradle. |
||||||
|
# |
||||||
|
# For more details, see |
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html |
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following |
||||||
|
# and specify the fully qualified class name to the JavaScript interface |
||||||
|
# class: |
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
||||||
|
# public *; |
||||||
|
#} |
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for |
||||||
|
# debugging stack traces. |
||||||
|
#-keepattributes SourceFile,LineNumberTable |
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to |
||||||
|
# hide the original source file name. |
||||||
|
#-renamesourcefileattribute SourceFile |
@ -0,0 +1,7 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
package="com.frank.camerafilter"> |
||||||
|
|
||||||
|
<uses-feature android:glEsVersion="0x00030000" android:required="true"/> |
||||||
|
|
||||||
|
</manifest> |
@ -0,0 +1,136 @@ |
|||||||
|
package com.frank.camerafilter.camera; |
||||||
|
|
||||||
|
import android.graphics.SurfaceTexture; |
||||||
|
import android.hardware.Camera; |
||||||
|
import android.hardware.Camera.Parameters; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author xufulong |
||||||
|
* @date 2022/6/17 5:14 下午 |
||||||
|
* @desc |
||||||
|
*/ |
||||||
|
public class CameraManager { |
||||||
|
|
||||||
|
private Camera mCamera; |
||||||
|
private int mCameraId = 0; |
||||||
|
private SurfaceTexture mSurfaceTexture; |
||||||
|
|
||||||
|
public Camera getCamera() { |
||||||
|
return mCamera; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean openCamera() { |
||||||
|
return openCamera(mCameraId); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean openCamera(int cameraId) { |
||||||
|
if (mCamera == null) { |
||||||
|
try { |
||||||
|
mCameraId = cameraId; |
||||||
|
mCamera = Camera.open(cameraId); |
||||||
|
setDefaultParams(); |
||||||
|
return true; |
||||||
|
} catch (RuntimeException e) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public void releaseCamera() { |
||||||
|
if (mCamera == null) |
||||||
|
return; |
||||||
|
stopPreview(); |
||||||
|
mCamera.release(); |
||||||
|
mCamera = null; |
||||||
|
} |
||||||
|
|
||||||
|
public void switchCamera() { |
||||||
|
if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) { |
||||||
|
mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT; |
||||||
|
} else { |
||||||
|
mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK; |
||||||
|
} |
||||||
|
releaseCamera(); |
||||||
|
openCamera(mCameraId); |
||||||
|
startPreview(mSurfaceTexture); |
||||||
|
} |
||||||
|
|
||||||
|
private static Camera.Size getLargePictureSize(Camera camera){ |
||||||
|
if(camera != null){ |
||||||
|
List<Camera.Size> sizes = camera.getParameters().getSupportedPictureSizes(); |
||||||
|
Camera.Size temp = sizes.get(0); |
||||||
|
for(int i = 1;i < sizes.size();i ++){ |
||||||
|
float scale = (float)(sizes.get(i).height) / sizes.get(i).width; |
||||||
|
if(temp.width < sizes.get(i).width && scale < 0.6f && scale > 0.5f) |
||||||
|
temp = sizes.get(i); |
||||||
|
} |
||||||
|
return temp; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private static Camera.Size getLargePreviewSize(Camera camera){ |
||||||
|
if(camera != null){ |
||||||
|
List<Camera.Size> sizes = camera.getParameters().getSupportedPreviewSizes(); |
||||||
|
Camera.Size temp = sizes.get(0); |
||||||
|
for(int i = 1;i < sizes.size();i ++){ |
||||||
|
if(temp.width < sizes.get(i).width) |
||||||
|
temp = sizes.get(i); |
||||||
|
} |
||||||
|
return temp; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public void setDefaultParams() { |
||||||
|
Parameters parameters = mCamera.getParameters(); |
||||||
|
if (parameters.getSupportedFocusModes().contains( |
||||||
|
Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { |
||||||
|
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); |
||||||
|
} |
||||||
|
Camera.Size previewSize = getLargePreviewSize(mCamera); |
||||||
|
parameters.setPreviewSize(previewSize.width, previewSize.height); |
||||||
|
Camera.Size pictureSize = getLargePictureSize(mCamera); |
||||||
|
parameters.setPictureSize(pictureSize.width, pictureSize.height); |
||||||
|
parameters.setRotation(90); |
||||||
|
mCamera.setParameters(parameters); |
||||||
|
} |
||||||
|
|
||||||
|
public void startPreview(SurfaceTexture surfaceTexture) { |
||||||
|
if (mCamera == null) |
||||||
|
return; |
||||||
|
try { |
||||||
|
mCamera.setPreviewTexture(surfaceTexture); |
||||||
|
mSurfaceTexture = surfaceTexture; |
||||||
|
mCamera.startPreview(); |
||||||
|
} catch (IOException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void stopPreview() { |
||||||
|
if (mCamera == null) |
||||||
|
return; |
||||||
|
mCamera.setPreviewCallback(null); |
||||||
|
mCamera.stopPreview(); |
||||||
|
} |
||||||
|
|
||||||
|
public int getOrientation() { |
||||||
|
Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); |
||||||
|
Camera.getCameraInfo(mCameraId, cameraInfo); |
||||||
|
return cameraInfo.orientation; |
||||||
|
} |
||||||
|
|
||||||
|
public Camera.Size getPreviewSize() { |
||||||
|
return mCamera.getParameters().getPreviewSize(); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isFront() { |
||||||
|
return mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
package com.frank.camerafilter.factory; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
|
||||||
|
import com.frank.camerafilter.filter.advance.BeautyCrayonFilter; |
||||||
|
import com.frank.camerafilter.filter.advance.BeautySketchFilter; |
||||||
|
import com.frank.camerafilter.filter.BaseFilter; |
||||||
|
|
||||||
|
public class BeautyFilterFactory { |
||||||
|
|
||||||
|
private static BeautyFilterType filterType = BeautyFilterType.NONE; |
||||||
|
|
||||||
|
public static BaseFilter getFilter(BeautyFilterType type, Context context) { |
||||||
|
filterType = type; |
||||||
|
switch (type) { |
||||||
|
case SKETCH: |
||||||
|
return new BeautySketchFilter(context); |
||||||
|
case CRAYON: |
||||||
|
return new BeautyCrayonFilter(context); |
||||||
|
default: |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static BeautyFilterType getFilterType() { |
||||||
|
return filterType; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
package com.frank.camerafilter.factory; |
||||||
|
|
||||||
|
public enum BeautyFilterType { |
||||||
|
NONE, |
||||||
|
CRAYON, |
||||||
|
SKETCH |
||||||
|
} |
@ -0,0 +1,173 @@ |
|||||||
|
package com.frank.camerafilter.filter; |
||||||
|
|
||||||
|
import android.opengl.GLES30; |
||||||
|
|
||||||
|
import com.frank.camerafilter.util.OpenGLUtil; |
||||||
|
import com.frank.camerafilter.util.Rotation; |
||||||
|
import com.frank.camerafilter.util.TextureRotateUtil; |
||||||
|
|
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.nio.ByteOrder; |
||||||
|
import java.nio.FloatBuffer; |
||||||
|
import java.util.LinkedList; |
||||||
|
|
||||||
|
public class BaseFilter { |
||||||
|
|
||||||
|
public final static String NORMAL_VERTEX_SHADER = |
||||||
|
"attribute vec4 position;\n" + |
||||||
|
"attribute vec4 inputTextureCoordinate;\n" + |
||||||
|
"varying vec2 textureCoordinate;\n" + |
||||||
|
"void main() {\n" + |
||||||
|
" gl_Position = position;\n" + |
||||||
|
" textureCoordinate = inputTextureCoordinate.xy;\n" + |
||||||
|
"}"; |
||||||
|
|
||||||
|
private final String mVertexShader; |
||||||
|
private final String mFragmentShader; |
||||||
|
private final LinkedList<Runnable> mRunnableDraw; |
||||||
|
|
||||||
|
protected int mProgramId; |
||||||
|
protected int mInputWidth; |
||||||
|
protected int mInputHeight; |
||||||
|
protected int mOutputWidth; |
||||||
|
protected int mOutputHeight; |
||||||
|
protected int mUniformTexture; |
||||||
|
protected int mAttributePosition; |
||||||
|
protected int mAttributeTextureCoordinate; |
||||||
|
protected boolean mHasInitialized; |
||||||
|
protected FloatBuffer mVertexBuffer; |
||||||
|
protected FloatBuffer mTextureBuffer; |
||||||
|
|
||||||
|
public BaseFilter(String vertexShader, String fragmentShader) { |
||||||
|
mRunnableDraw = new LinkedList<>(); |
||||||
|
mVertexShader = vertexShader; |
||||||
|
mFragmentShader = fragmentShader; |
||||||
|
|
||||||
|
mVertexBuffer = ByteBuffer.allocateDirect(TextureRotateUtil.VERTEX.length * 4) |
||||||
|
.order(ByteOrder.nativeOrder()) |
||||||
|
.asFloatBuffer(); |
||||||
|
mVertexBuffer.put(TextureRotateUtil.VERTEX).position(0); |
||||||
|
|
||||||
|
mTextureBuffer = ByteBuffer.allocateDirect(TextureRotateUtil.TEXTURE_ROTATE_0.length * 4) |
||||||
|
.order(ByteOrder.nativeOrder()) |
||||||
|
.asFloatBuffer(); |
||||||
|
mTextureBuffer.put(TextureRotateUtil.getRotateTexture(Rotation.NORMAL, false, true)) |
||||||
|
.position(0); |
||||||
|
} |
||||||
|
|
||||||
|
protected void onInit() { |
||||||
|
mProgramId = OpenGLUtil.loadProgram(mVertexShader, mFragmentShader); |
||||||
|
mAttributePosition = GLES30.glGetAttribLocation(mProgramId, "position"); |
||||||
|
mUniformTexture = GLES30.glGetUniformLocation(mProgramId, "inputImageTexture"); |
||||||
|
mAttributeTextureCoordinate = GLES30.glGetAttribLocation(mProgramId, "inputTextureCoordinate"); |
||||||
|
} |
||||||
|
|
||||||
|
protected void onInitialized() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void init() { |
||||||
|
onInit(); |
||||||
|
mHasInitialized = true; |
||||||
|
onInitialized(); |
||||||
|
} |
||||||
|
|
||||||
|
protected void onDestroy() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void destroy() { |
||||||
|
mHasInitialized = false; |
||||||
|
GLES30.glDeleteProgram(mProgramId); |
||||||
|
onDestroy(); |
||||||
|
} |
||||||
|
|
||||||
|
public void onInputSizeChanged(final int width, final int height) { |
||||||
|
mInputWidth = width; |
||||||
|
mInputHeight = height; |
||||||
|
} |
||||||
|
|
||||||
|
protected void runPendingOnDrawTask() { |
||||||
|
while (!mRunnableDraw.isEmpty()) { |
||||||
|
mRunnableDraw.removeFirst().run(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected void onDrawArrayBefore() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
protected void onDrawArrayAfter() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public int onDrawFrame(final int textureId) { |
||||||
|
return onDrawFrame(textureId, mVertexBuffer, mTextureBuffer); |
||||||
|
} |
||||||
|
|
||||||
|
public int onDrawFrame(final int textureId, FloatBuffer vertexBuffer, FloatBuffer textureBuffer) { |
||||||
|
if (!mHasInitialized) |
||||||
|
return OpenGLUtil.NOT_INIT; |
||||||
|
|
||||||
|
GLES30.glUseProgram(mProgramId); |
||||||
|
runPendingOnDrawTask(); |
||||||
|
vertexBuffer.position(0); |
||||||
|
GLES30.glVertexAttribPointer(mAttributePosition, 2, GLES30.GL_FLOAT, false, 0, vertexBuffer); |
||||||
|
GLES30.glEnableVertexAttribArray(mAttributePosition); |
||||||
|
textureBuffer.position(0); |
||||||
|
GLES30.glVertexAttribPointer(mAttributeTextureCoordinate, 2, GLES30.GL_FLOAT, false, 0, textureBuffer); |
||||||
|
GLES30.glEnableVertexAttribArray(mAttributeTextureCoordinate); |
||||||
|
|
||||||
|
if (textureId != OpenGLUtil.NO_TEXTURE) { |
||||||
|
GLES30.glActiveTexture(GLES30.GL_TEXTURE0); |
||||||
|
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId); |
||||||
|
GLES30.glUniform1i(mUniformTexture, 0); |
||||||
|
} |
||||||
|
|
||||||
|
onDrawArrayBefore(); |
||||||
|
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4); |
||||||
|
GLES30.glDisableVertexAttribArray(mAttributePosition); |
||||||
|
GLES30.glDisableVertexAttribArray(mAttributeTextureCoordinate); |
||||||
|
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0); |
||||||
|
onDrawArrayAfter(); |
||||||
|
return OpenGLUtil.ON_DRAWN; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean hasInitialized() { |
||||||
|
return mHasInitialized; |
||||||
|
} |
||||||
|
|
||||||
|
public int getProgramId() { |
||||||
|
return mProgramId; |
||||||
|
} |
||||||
|
|
||||||
|
protected void runOnDraw(final Runnable runnable) { |
||||||
|
synchronized (mRunnableDraw) { |
||||||
|
mRunnableDraw.addLast(runnable); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void setFloat(final int location, final float floatVal) { |
||||||
|
runOnDraw(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
GLES30.glUniform1f(location, floatVal); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public void setFloatVec2(final int location, final float[] floatArray) { |
||||||
|
runOnDraw(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
GLES30.glUniform2fv(location, 1, FloatBuffer.wrap(floatArray)); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public void onOutputSizeChanged(final int width, final int height) { |
||||||
|
mOutputWidth = width; |
||||||
|
mOutputHeight = height; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,155 @@ |
|||||||
|
package com.frank.camerafilter.filter; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.opengl.GLES11Ext; |
||||||
|
import android.opengl.GLES30; |
||||||
|
|
||||||
|
import com.frank.camerafilter.R; |
||||||
|
import com.frank.camerafilter.util.OpenGLUtil; |
||||||
|
|
||||||
|
import java.nio.FloatBuffer; |
||||||
|
|
||||||
|
public class BeautyCameraFilter extends BaseFilter { |
||||||
|
|
||||||
|
private int frameWidth = -1; |
||||||
|
private int frameHeight = -1; |
||||||
|
private int[] frameBuffer = null; |
||||||
|
private int[] frameBufferTexture = null; |
||||||
|
|
||||||
|
private int textureTransformLocation; |
||||||
|
private float[] textureTransformMatrix; |
||||||
|
|
||||||
|
public BeautyCameraFilter(Context context) { |
||||||
|
super(OpenGLUtil.readShaderFromSource(context, R.raw.default_vertex), |
||||||
|
OpenGLUtil.readShaderFromSource(context, R.raw.default_fragment)); |
||||||
|
} |
||||||
|
|
||||||
|
protected void onInit() { |
||||||
|
super.onInit(); |
||||||
|
textureTransformLocation = GLES30.glGetUniformLocation(getProgramId(), "textureTransform"); |
||||||
|
} |
||||||
|
|
||||||
|
public void setTextureTransformMatrix(float[] matrix) { |
||||||
|
textureTransformMatrix = matrix; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int onDrawFrame(int textureId) { |
||||||
|
return onDrawFrame(textureId, mVertexBuffer, mTextureBuffer); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int onDrawFrame(int textureId, FloatBuffer vertexBuffer, FloatBuffer textureBuffer) { |
||||||
|
if (!hasInitialized()) { |
||||||
|
return OpenGLUtil.NOT_INIT; |
||||||
|
} |
||||||
|
GLES30.glUseProgram(getProgramId()); |
||||||
|
runPendingOnDrawTask(); |
||||||
|
vertexBuffer.position(0); |
||||||
|
GLES30.glVertexAttribPointer(mAttributePosition, 2, GLES30.GL_FLOAT, false, 0, vertexBuffer); |
||||||
|
GLES30.glEnableVertexAttribArray(mAttributePosition); |
||||||
|
textureBuffer.position(0); |
||||||
|
GLES30.glVertexAttribPointer(mAttributeTextureCoordinate, 2, GLES30.GL_FLOAT, false, 0, textureBuffer); |
||||||
|
GLES30.glEnableVertexAttribArray(mAttributeTextureCoordinate); |
||||||
|
GLES30.glUniformMatrix4fv(textureTransformLocation, 1, false, textureTransformMatrix, 0); |
||||||
|
|
||||||
|
if (textureId != OpenGLUtil.NO_TEXTURE) { |
||||||
|
GLES30.glActiveTexture(GLES30.GL_TEXTURE0); |
||||||
|
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); |
||||||
|
GLES30.glUniform1i(mUniformTexture, 0); |
||||||
|
} |
||||||
|
|
||||||
|
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4); |
||||||
|
GLES30.glDisableVertexAttribArray(mAttributePosition); |
||||||
|
GLES30.glDisableVertexAttribArray(mAttributeTextureCoordinate); |
||||||
|
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); |
||||||
|
return OpenGLUtil.ON_DRAWN; |
||||||
|
} |
||||||
|
|
||||||
|
public int onDrawToTexture(int textureId) { |
||||||
|
if (!hasInitialized()) { |
||||||
|
return OpenGLUtil.NOT_INIT; |
||||||
|
} |
||||||
|
if (frameBuffer == null) { |
||||||
|
return OpenGLUtil.NO_TEXTURE; |
||||||
|
} |
||||||
|
GLES30.glUseProgram(getProgramId()); |
||||||
|
runPendingOnDrawTask(); |
||||||
|
GLES30.glViewport(0, 0, frameWidth, frameHeight); |
||||||
|
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffer[0]); |
||||||
|
|
||||||
|
mVertexBuffer.position(0); |
||||||
|
GLES30.glVertexAttribPointer(mAttributePosition, 2, GLES30.GL_FLOAT, false, 0, mVertexBuffer); |
||||||
|
GLES30.glEnableVertexAttribArray(mAttributePosition); |
||||||
|
mTextureBuffer.position(0); |
||||||
|
GLES30.glVertexAttribPointer(mAttributeTextureCoordinate, 2, GLES30.GL_FLOAT, false, 0, mTextureBuffer); |
||||||
|
GLES30.glEnableVertexAttribArray(mAttributeTextureCoordinate); |
||||||
|
GLES30.glUniformMatrix4fv(textureTransformLocation, 1, false, textureTransformMatrix, 0); |
||||||
|
|
||||||
|
if (textureId != OpenGLUtil.NO_TEXTURE) { |
||||||
|
GLES30.glActiveTexture(GLES30.GL_TEXTURE0); |
||||||
|
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); |
||||||
|
GLES30.glUniform1i(mUniformTexture, 0); |
||||||
|
} |
||||||
|
|
||||||
|
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4); |
||||||
|
GLES30.glDisableVertexAttribArray(mAttributePosition); |
||||||
|
GLES30.glDisableVertexAttribArray(mAttributeTextureCoordinate); |
||||||
|
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); |
||||||
|
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0); |
||||||
|
GLES30.glViewport(0, 0, mOutputWidth, mOutputHeight); |
||||||
|
return frameBufferTexture[0]; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onInputSizeChanged(int width, int height) { |
||||||
|
super.onInputSizeChanged(width, height); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onDestroy() { |
||||||
|
super.onDestroy(); |
||||||
|
destroyFrameBuffer(); |
||||||
|
} |
||||||
|
|
||||||
|
public void initFrameBuffer(int width, int height) { |
||||||
|
if (frameBuffer != null && (frameWidth != width || frameHeight != height)) |
||||||
|
destroyFrameBuffer(); |
||||||
|
if (frameBuffer == null) { |
||||||
|
frameWidth = width; |
||||||
|
frameHeight = height; |
||||||
|
frameBuffer = new int[1]; |
||||||
|
frameBufferTexture = new int[1]; |
||||||
|
|
||||||
|
GLES30.glGenFramebuffers(1, frameBuffer, 0); |
||||||
|
GLES30.glGenTextures(1, frameBufferTexture, 0); |
||||||
|
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, frameBufferTexture[0]); |
||||||
|
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR); |
||||||
|
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR); |
||||||
|
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE); |
||||||
|
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE); |
||||||
|
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, width, height, |
||||||
|
0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null); |
||||||
|
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffer[0]); |
||||||
|
GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, |
||||||
|
GLES30.GL_TEXTURE_2D, frameBufferTexture[0], 0); |
||||||
|
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0); |
||||||
|
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void destroyFrameBuffer() { |
||||||
|
if (frameBufferTexture != null) { |
||||||
|
GLES30.glDeleteTextures(1, frameBufferTexture, 0); |
||||||
|
frameBufferTexture = null; |
||||||
|
} |
||||||
|
if (frameBuffer != null) { |
||||||
|
GLES30.glDeleteFramebuffers(1, frameBuffer, 0); |
||||||
|
frameBuffer = null; |
||||||
|
} |
||||||
|
frameWidth = -1; |
||||||
|
frameHeight = -1; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
package com.frank.camerafilter.filter.advance; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.opengl.GLES20; |
||||||
|
|
||||||
|
import com.frank.camerafilter.R; |
||||||
|
import com.frank.camerafilter.filter.BaseFilter; |
||||||
|
import com.frank.camerafilter.util.OpenGLUtil; |
||||||
|
|
||||||
|
public class BeautyCrayonFilter extends BaseFilter { |
||||||
|
|
||||||
|
// 1.0--5.0
|
||||||
|
private int mStrengthLocation; |
||||||
|
|
||||||
|
private int mStepOffsetLocation; |
||||||
|
|
||||||
|
public BeautyCrayonFilter(Context context) { |
||||||
|
super(NORMAL_VERTEX_SHADER, OpenGLUtil.readShaderFromSource(context, R.raw.crayon)); |
||||||
|
} |
||||||
|
|
||||||
|
protected void onInit() { |
||||||
|
super.onInit(); |
||||||
|
mStrengthLocation = GLES20.glGetUniformLocation(getProgramId(), "strength"); |
||||||
|
mStepOffsetLocation = GLES20.glGetUniformLocation(getProgramId(), "singleStepOffset"); |
||||||
|
} |
||||||
|
|
||||||
|
protected void onInitialized() { |
||||||
|
super.onInitialized(); |
||||||
|
setFloat(mStrengthLocation, 2.0f); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onInputSizeChanged(int width, int height) { |
||||||
|
super.onInputSizeChanged(width, height); |
||||||
|
setFloatVec2(mStepOffsetLocation, new float[] {1.0f / width, 1.0f / height}); |
||||||
|
} |
||||||
|
|
||||||
|
protected void onDestroy() { |
||||||
|
super.onDestroy(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
package com.frank.camerafilter.filter.advance; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.opengl.GLES20; |
||||||
|
|
||||||
|
import com.frank.camerafilter.R; |
||||||
|
import com.frank.camerafilter.filter.BaseFilter; |
||||||
|
import com.frank.camerafilter.util.OpenGLUtil; |
||||||
|
|
||||||
|
public class BeautySketchFilter extends BaseFilter { |
||||||
|
|
||||||
|
private int strengthLocation; |
||||||
|
private int stepOffsetLocation; |
||||||
|
|
||||||
|
public BeautySketchFilter(Context context) { |
||||||
|
super(NORMAL_VERTEX_SHADER, OpenGLUtil.readShaderFromSource(context, R.raw.sketch)); |
||||||
|
} |
||||||
|
|
||||||
|
protected void onInit() { |
||||||
|
super.onInit(); |
||||||
|
strengthLocation = GLES20.glGetUniformLocation(getProgramId(), "strength"); |
||||||
|
stepOffsetLocation = GLES20.glGetUniformLocation(getProgramId(), "singleStepOffset"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onInitialized() { |
||||||
|
super.onInitialized(); |
||||||
|
setFloat(strengthLocation, 0.5f); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onInputSizeChanged(int width, int height) { |
||||||
|
super.onInputSizeChanged(width, height); |
||||||
|
setFloatVec2(stepOffsetLocation, new float[] {1.0f / width, 1.0f / height}); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,195 @@ |
|||||||
|
package com.frank.camerafilter.recorder.gles; |
||||||
|
|
||||||
|
import android.graphics.SurfaceTexture; |
||||||
|
import android.opengl.EGL14; |
||||||
|
import android.opengl.EGLConfig; |
||||||
|
import android.opengl.EGLContext; |
||||||
|
import android.opengl.EGLDisplay; |
||||||
|
import android.opengl.EGLExt; |
||||||
|
import android.opengl.EGLSurface; |
||||||
|
import android.util.Log; |
||||||
|
import android.view.Surface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Core EGL state (display, context, config). |
||||||
|
* <p> |
||||||
|
* The EGLContext must only be attached to one thread at a time. This class is not thread-safe. |
||||||
|
*/ |
||||||
|
|
||||||
|
public final class EglCore { |
||||||
|
|
||||||
|
private final static String TAG = EglCore.class.getSimpleName(); |
||||||
|
|
||||||
|
public final static int FLAG_RECORDABLE = 0x01; |
||||||
|
|
||||||
|
public final static int FLAG_TRY_GLES3 = 0x02; |
||||||
|
|
||||||
|
private final static int EGL_RECORDABLE_ANDROID = 0x3142; |
||||||
|
|
||||||
|
private int mGlVersion = -1; |
||||||
|
private EGLConfig mEGLConfig = null; |
||||||
|
private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; |
||||||
|
private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; |
||||||
|
|
||||||
|
public EglCore() { |
||||||
|
this(null, 0); |
||||||
|
} |
||||||
|
|
||||||
|
public EglCore(EGLContext sharedContext, int flag) { |
||||||
|
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); |
||||||
|
int[] version = new int[2]; |
||||||
|
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { |
||||||
|
throw new RuntimeException("unable to init EGL14"); |
||||||
|
} |
||||||
|
|
||||||
|
if ((flag & FLAG_TRY_GLES3) != 0) { |
||||||
|
initEGLContext(sharedContext, flag, 3); |
||||||
|
} |
||||||
|
if (mEGLContext == EGL14.EGL_NO_CONTEXT) { |
||||||
|
initEGLContext(sharedContext, flag, 2); |
||||||
|
} |
||||||
|
|
||||||
|
int[] value = new int[1]; |
||||||
|
EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, value, 0); |
||||||
|
Log.i(TAG, "EGLContext client version=" + value[0]); |
||||||
|
} |
||||||
|
|
||||||
|
private void initEGLContext(EGLContext sharedContext, int flag, int version) { |
||||||
|
EGLConfig config = getConfig(flag, version); |
||||||
|
if (config == null) { |
||||||
|
throw new RuntimeException("unable to find suitable EGLConfig"); |
||||||
|
} |
||||||
|
int[] attributeList = {EGL14.EGL_CONTEXT_CLIENT_VERSION, version, EGL14.EGL_NONE}; |
||||||
|
EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, attributeList, 0); |
||||||
|
if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) { |
||||||
|
mEGLConfig = config; |
||||||
|
mEGLContext = context; |
||||||
|
mGlVersion = version; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private EGLConfig getConfig(int flag, int version) { |
||||||
|
int renderType = EGL14.EGL_OPENGL_ES2_BIT; |
||||||
|
if (version >= 3) { |
||||||
|
renderType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR; |
||||||
|
} |
||||||
|
|
||||||
|
int[] attributeList = { |
||||||
|
EGL14.EGL_RED_SIZE, 8, |
||||||
|
EGL14.EGL_GREEN_SIZE, 8, |
||||||
|
EGL14.EGL_BLUE_SIZE, 8, |
||||||
|
EGL14.EGL_ALPHA_SIZE, 8, |
||||||
|
//EGL14.EGL_DEPTH_SIZE, 16,
|
||||||
|
//EGL14.EGL_STENCIL_SIZE, 8,
|
||||||
|
EGL14.EGL_RENDERABLE_TYPE, renderType, |
||||||
|
EGL14.EGL_NONE, 0, |
||||||
|
EGL14.EGL_NONE |
||||||
|
}; |
||||||
|
|
||||||
|
if ((flag & FLAG_RECORDABLE) != 0) { |
||||||
|
attributeList[attributeList.length - 3] = EGL_RECORDABLE_ANDROID; |
||||||
|
attributeList[attributeList.length - 2] = 1; |
||||||
|
} |
||||||
|
int[] numConfigs = new int[1]; |
||||||
|
EGLConfig[] configs = new EGLConfig[1]; |
||||||
|
if (!EGL14.eglChooseConfig(mEGLDisplay, attributeList, 0, configs, |
||||||
|
0, configs.length, numConfigs, 0)) { |
||||||
|
Log.e(TAG, "unable to find RGB8888 / " + version + " EGLConfig"); |
||||||
|
return null; |
||||||
|
} |
||||||
|
return configs[0]; |
||||||
|
} |
||||||
|
|
||||||
|
public void release() { |
||||||
|
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { |
||||||
|
EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); |
||||||
|
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); |
||||||
|
EGL14.eglReleaseThread(); |
||||||
|
EGL14.eglTerminate(mEGLDisplay); |
||||||
|
} |
||||||
|
mEGLConfig = null; |
||||||
|
mEGLDisplay = EGL14.EGL_NO_DISPLAY; |
||||||
|
mEGLContext = EGL14.EGL_NO_CONTEXT; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void finalize() throws Throwable { |
||||||
|
try { |
||||||
|
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { |
||||||
|
release(); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
super.finalize(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void releaseSurface(EGLSurface eglSurface) { |
||||||
|
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { |
||||||
|
EGL14.eglDestroySurface(mEGLDisplay, eglSurface); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public EGLSurface createWindowSurface(Object surface) { |
||||||
|
if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) { |
||||||
|
throw new RuntimeException("invalid surface:" + surface); |
||||||
|
} |
||||||
|
|
||||||
|
int[] surfaceAttr = {EGL14.EGL_NONE}; |
||||||
|
EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface, surfaceAttr, 0); |
||||||
|
if (eglSurface == null) { |
||||||
|
throw new RuntimeException("window surface is null"); |
||||||
|
} |
||||||
|
return eglSurface; |
||||||
|
} |
||||||
|
|
||||||
|
public EGLSurface createOffsetScreenSurface(int width, int height) { |
||||||
|
int[] surfaceAttr = {EGL14.EGL_WIDTH, width, |
||||||
|
EGL14.EGL_HEIGHT, height, |
||||||
|
EGL14.EGL_NONE}; |
||||||
|
EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, surfaceAttr, 0); |
||||||
|
if (eglSurface == null) { |
||||||
|
throw new RuntimeException("offset-screen surface is null"); |
||||||
|
} |
||||||
|
return eglSurface; |
||||||
|
} |
||||||
|
|
||||||
|
public void makeCurrent(EGLSurface eglSurface) { |
||||||
|
if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) { |
||||||
|
throw new RuntimeException("eglMakeCurrent failed!"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) { |
||||||
|
if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) { |
||||||
|
throw new RuntimeException("eglMakeCurrent failed!"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public boolean swapBuffers(EGLSurface eglSurface) { |
||||||
|
return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface); |
||||||
|
} |
||||||
|
|
||||||
|
public void setPresentationTime(EGLSurface eglSurface, long nsec) { |
||||||
|
EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsec); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isCurrent(EGLSurface eglSurface) { |
||||||
|
return mEGLContext.equals(EGL14.eglGetCurrentContext()) |
||||||
|
&& eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW)); |
||||||
|
} |
||||||
|
|
||||||
|
public int querySurface(EGLSurface eglSurface, int what) { |
||||||
|
int[] value = new int[1]; |
||||||
|
EGL14.eglQuerySurface(mEGLDisplay, eglSurface, what, value, 0); |
||||||
|
return value[0]; |
||||||
|
} |
||||||
|
|
||||||
|
public String queryString(int what) { |
||||||
|
return EGL14.eglQueryString(mEGLDisplay, what); |
||||||
|
} |
||||||
|
|
||||||
|
public int getVersion() { |
||||||
|
return mGlVersion; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,106 @@ |
|||||||
|
package com.frank.camerafilter.recorder.gles; |
||||||
|
|
||||||
|
import android.graphics.Bitmap; |
||||||
|
import android.opengl.EGL14; |
||||||
|
import android.opengl.EGLSurface; |
||||||
|
import android.opengl.GLES20; |
||||||
|
|
||||||
|
import java.io.BufferedOutputStream; |
||||||
|
import java.io.File; |
||||||
|
import java.io.FileOutputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.nio.IntBuffer; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author xufulong |
||||||
|
* @date 2022/6/23 8:51 上午 |
||||||
|
* @desc |
||||||
|
*/ |
||||||
|
public class EglSurfaceBase { |
||||||
|
|
||||||
|
protected EglCore mEglCore; |
||||||
|
protected int mWidth = -1; |
||||||
|
protected int mHeight = -1; |
||||||
|
|
||||||
|
private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; |
||||||
|
|
||||||
|
protected EglSurfaceBase(EglCore eglCore) { |
||||||
|
mEglCore = eglCore; |
||||||
|
} |
||||||
|
|
||||||
|
public void createWindowSurface(Object surface) { |
||||||
|
if (mEGLSurface != EGL14.EGL_NO_SURFACE) { |
||||||
|
throw new IllegalStateException("egl surface has already created"); |
||||||
|
} |
||||||
|
mEGLSurface = mEglCore.createWindowSurface(surface); |
||||||
|
} |
||||||
|
|
||||||
|
public void createOffsetScreenSurface(int width, int height) { |
||||||
|
if (mEGLSurface != EGL14.EGL_NO_SURFACE) { |
||||||
|
throw new IllegalStateException("egl surface has already created"); |
||||||
|
} |
||||||
|
mWidth = width; |
||||||
|
mHeight = height; |
||||||
|
mEGLSurface = mEglCore.createOffsetScreenSurface(width, height); |
||||||
|
} |
||||||
|
|
||||||
|
public int getWidth() { |
||||||
|
if (mWidth <= 0) { |
||||||
|
mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); |
||||||
|
} |
||||||
|
return mWidth; |
||||||
|
} |
||||||
|
|
||||||
|
public int getHeight() { |
||||||
|
if (mHeight <= 0) { |
||||||
|
mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); |
||||||
|
} |
||||||
|
return mHeight; |
||||||
|
} |
||||||
|
|
||||||
|
public void releaseEglSurface() { |
||||||
|
mEglCore.releaseSurface(mEGLSurface); |
||||||
|
mEGLSurface = EGL14.EGL_NO_SURFACE; |
||||||
|
mWidth = -1; |
||||||
|
mHeight = -1; |
||||||
|
} |
||||||
|
|
||||||
|
public void makeCurrent() { |
||||||
|
mEglCore.makeCurrent(mEGLSurface); |
||||||
|
} |
||||||
|
|
||||||
|
public void makeCurrentReadFrom(EglSurfaceBase readSurface) { |
||||||
|
mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean swapBuffers() { |
||||||
|
return mEglCore.swapBuffers(mEGLSurface); |
||||||
|
} |
||||||
|
|
||||||
|
public void setPresentationTime(long nsec) { |
||||||
|
mEglCore.setPresentationTime(mEGLSurface, nsec); |
||||||
|
} |
||||||
|
|
||||||
|
public void saveFrame(File file) throws IOException { |
||||||
|
if (!mEglCore.isCurrent(mEGLSurface)) { |
||||||
|
throw new RuntimeException("isn't current surface/context"); |
||||||
|
} |
||||||
|
String fileName = file.toString(); |
||||||
|
int width = getWidth(); |
||||||
|
int height = getHeight(); |
||||||
|
IntBuffer buffer = IntBuffer.allocate(width * height); |
||||||
|
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer); |
||||||
|
BufferedOutputStream outputStream = null; |
||||||
|
try { |
||||||
|
outputStream = new BufferedOutputStream(new FileOutputStream(fileName)); |
||||||
|
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); |
||||||
|
bitmap.copyPixelsFromBuffer(buffer); |
||||||
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); |
||||||
|
bitmap.recycle(); |
||||||
|
} finally { |
||||||
|
if (outputStream != null) |
||||||
|
outputStream.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,325 @@ |
|||||||
|
package com.frank.camerafilter.recorder.video; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.graphics.SurfaceTexture; |
||||||
|
import android.opengl.EGLContext; |
||||||
|
import android.os.Handler; |
||||||
|
import android.os.Looper; |
||||||
|
import android.os.Message; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
|
||||||
|
import com.frank.camerafilter.filter.BeautyCameraFilter; |
||||||
|
import com.frank.camerafilter.filter.BaseFilter; |
||||||
|
import com.frank.camerafilter.factory.BeautyFilterFactory; |
||||||
|
import com.frank.camerafilter.factory.BeautyFilterType; |
||||||
|
import com.frank.camerafilter.recorder.gles.EglCore; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.lang.ref.WeakReference; |
||||||
|
import java.nio.FloatBuffer; |
||||||
|
|
||||||
|
/** |
||||||
|
* Encode a movie from frames rendered from an external texture image. |
||||||
|
* <p> |
||||||
|
* The object wraps an encoder running on a dedicated thread. The various control messages |
||||||
|
* may be sent from arbitrary threads (typically the app UI thread). The encoder thread |
||||||
|
* manages both sides of the encoder (feeding and draining); the only external input is |
||||||
|
* the GL texture. |
||||||
|
* <p> |
||||||
|
* The design is complicated slightly by the need to create an EGL context that shares state |
||||||
|
* with a view that gets restarted if (say) the device orientation changes. When the view |
||||||
|
* in question is a GLSurfaceView, we don't have full control over the EGL context creation |
||||||
|
* on that side, so we have to bend a bit backwards here. |
||||||
|
* <p> |
||||||
|
* To use: |
||||||
|
* <ul> |
||||||
|
* <li>create TextureMovieEncoder object |
||||||
|
* <li>create an EncoderConfig |
||||||
|
* <li>call TextureMovieEncoder#startRecording() with the config |
||||||
|
* <li>call TextureMovieEncoder#setTextureId() with the texture object that receives frames |
||||||
|
* <li>for each frame, after latching it with SurfaceTexture#updateTexImage(), |
||||||
|
* call TextureMovieEncoder#frameAvailable(). |
||||||
|
* </ul> |
||||||
|
*/ |
||||||
|
public class TextureVideoRecorder implements Runnable { |
||||||
|
|
||||||
|
private final static String TAG = TextureVideoRecorder.class.getSimpleName(); |
||||||
|
|
||||||
|
private final static int MSG_START_RECORDING = 0; |
||||||
|
private final static int MSG_STOP_RECORDING = 1; |
||||||
|
private final static int MSG_FRAME_AVAILABLE = 2; |
||||||
|
private final static int MSG_SET_TEXTURE_ID = 3; |
||||||
|
private final static int MSG_UPDATE_SHARED_CONTEXT = 4; |
||||||
|
private final static int MSG_QUIT_RECORDING = 5; |
||||||
|
|
||||||
|
private int mTextureId; |
||||||
|
private EglCore mEglCore; |
||||||
|
private BeautyCameraFilter mInput; |
||||||
|
private WindowEglSurface mWindowSurface; |
||||||
|
private VideoRecorderCore mVideoRecorder; |
||||||
|
|
||||||
|
// access by multiple threads
|
||||||
|
private volatile RecorderHandler mHandler; |
||||||
|
|
||||||
|
private boolean mReady; |
||||||
|
private boolean mRunning; |
||||||
|
private Context mContext; |
||||||
|
private BaseFilter mFilter; |
||||||
|
private FloatBuffer glVertexBuffer; |
||||||
|
private FloatBuffer glTextureBuffer; |
||||||
|
|
||||||
|
// guard ready/running
|
||||||
|
private final Object mReadyFence = new Object(); |
||||||
|
|
||||||
|
private int mPreviewWidth = -1; |
||||||
|
private int mPreviewHeight = -1; |
||||||
|
private int mVideoWidth = -1; |
||||||
|
private int mVideoHeight = -1; |
||||||
|
|
||||||
|
private BeautyFilterType type = BeautyFilterType.NONE; |
||||||
|
|
||||||
|
public TextureVideoRecorder(Context context) { |
||||||
|
mContext = context; |
||||||
|
} |
||||||
|
|
||||||
|
public static class RecorderConfig { |
||||||
|
final int mWidth; |
||||||
|
final int mHeight; |
||||||
|
final int mBitrate; |
||||||
|
final File mOutputFile; |
||||||
|
final EGLContext mEglContext; |
||||||
|
|
||||||
|
public RecorderConfig(int width, int height, int bitrate, File outputFile, EGLContext eglContext) { |
||||||
|
this.mWidth = width; |
||||||
|
this.mHeight = height; |
||||||
|
this.mBitrate = bitrate; |
||||||
|
this.mOutputFile = outputFile; |
||||||
|
this.mEglContext = eglContext; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void startRecording(RecorderConfig config) { |
||||||
|
synchronized (mReadyFence) { |
||||||
|
if (mRunning) { |
||||||
|
return; |
||||||
|
} |
||||||
|
mRunning = true; |
||||||
|
new Thread(this, TAG).start(); |
||||||
|
while (!mReady) { |
||||||
|
try { |
||||||
|
mReadyFence.wait(); |
||||||
|
} catch (InterruptedException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
mHandler.sendMessage(mHandler.obtainMessage(MSG_START_RECORDING, config)); |
||||||
|
} |
||||||
|
|
||||||
|
public void stopRecording() { |
||||||
|
mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_RECORDING)); |
||||||
|
mHandler.sendMessage(mHandler.obtainMessage(MSG_QUIT_RECORDING)); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isRecording() { |
||||||
|
synchronized (mReadyFence) { |
||||||
|
return mRunning; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void updateSharedContext(EGLContext eglContext) { |
||||||
|
mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_SHARED_CONTEXT, eglContext)); |
||||||
|
} |
||||||
|
|
||||||
|
public void frameAvailable(SurfaceTexture surfaceTexture) { |
||||||
|
synchronized (mReadyFence) { |
||||||
|
if (!mReady) |
||||||
|
return; |
||||||
|
} |
||||||
|
float[] transform = new float[16]; |
||||||
|
surfaceTexture.getTransformMatrix(transform); |
||||||
|
long timestamp = surfaceTexture.getTimestamp(); |
||||||
|
if (timestamp == 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
mHandler.sendMessage(mHandler.obtainMessage(MSG_FRAME_AVAILABLE, (int) (timestamp >> 32), (int) timestamp, transform)); |
||||||
|
} |
||||||
|
|
||||||
|
public void setTextureId(int id) { |
||||||
|
synchronized (mReadyFence) { |
||||||
|
if (!mReady) |
||||||
|
return; |
||||||
|
} |
||||||
|
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_TEXTURE_ID, id, 0, null)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
Looper.prepare(); |
||||||
|
synchronized (mReadyFence) { |
||||||
|
mHandler = new RecorderHandler(this); |
||||||
|
mReady = true; |
||||||
|
mReadyFence.notify(); |
||||||
|
} |
||||||
|
Looper.loop(); |
||||||
|
synchronized (mReadyFence) { |
||||||
|
mReady = false; |
||||||
|
mRunning = false; |
||||||
|
mHandler = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class RecorderHandler extends Handler { |
||||||
|
private final WeakReference<TextureVideoRecorder> mWeakRecorder; |
||||||
|
|
||||||
|
public RecorderHandler(TextureVideoRecorder recorder) { |
||||||
|
mWeakRecorder = new WeakReference<>(recorder); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void handleMessage(@NonNull Message msg) { |
||||||
|
Object obj = msg.obj; |
||||||
|
TextureVideoRecorder recorder = mWeakRecorder.get(); |
||||||
|
if (recorder == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
switch (msg.what) { |
||||||
|
case MSG_START_RECORDING: |
||||||
|
recorder.handlerStartRecording((RecorderConfig)obj); |
||||||
|
break; |
||||||
|
case MSG_STOP_RECORDING: |
||||||
|
recorder.handlerStopRecording(); |
||||||
|
break; |
||||||
|
case MSG_FRAME_AVAILABLE: |
||||||
|
long timestamp = (((long) msg.arg1) << 32) | |
||||||
|
(((long) msg.arg2) & 0xffffffffL); |
||||||
|
recorder.handleFrameAvailable((float[]) obj, timestamp); |
||||||
|
break; |
||||||
|
case MSG_SET_TEXTURE_ID: |
||||||
|
recorder.handleSetTexture(msg.arg1); |
||||||
|
break; |
||||||
|
case MSG_UPDATE_SHARED_CONTEXT: |
||||||
|
recorder.handleUpdateSharedContext((EGLContext)obj); |
||||||
|
break; |
||||||
|
case MSG_QUIT_RECORDING: |
||||||
|
Looper.myLooper().quit(); |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void handlerStartRecording(RecorderConfig config) { |
||||||
|
prepareRecorder( |
||||||
|
config.mEglContext, |
||||||
|
config.mWidth, |
||||||
|
config.mHeight, |
||||||
|
config.mBitrate, |
||||||
|
config.mOutputFile); |
||||||
|
} |
||||||
|
|
||||||
|
private void handlerStopRecording() { |
||||||
|
mVideoRecorder.drainEncoder(true); |
||||||
|
releaseRecorder(); |
||||||
|
} |
||||||
|
|
||||||
|
private void handleFrameAvailable(float[] transform, long timestamp) { |
||||||
|
mVideoRecorder.drainEncoder(false); |
||||||
|
mInput.setTextureTransformMatrix(transform); |
||||||
|
if (mFilter == null) { |
||||||
|
mInput.onDrawFrame(mTextureId, glVertexBuffer, glTextureBuffer); |
||||||
|
} else { |
||||||
|
mFilter.onDrawFrame(mTextureId, glVertexBuffer, glTextureBuffer); |
||||||
|
} |
||||||
|
mWindowSurface.setPresentationTime(timestamp); |
||||||
|
mWindowSurface.swapBuffers(); |
||||||
|
} |
||||||
|
|
||||||
|
private void handleSetTexture(int id) { |
||||||
|
mTextureId = id; |
||||||
|
} |
||||||
|
|
||||||
|
private void handleUpdateSharedContext(EGLContext eglContext) { |
||||||
|
mWindowSurface.releaseEglSurface(); |
||||||
|
mInput.destroy(); |
||||||
|
mEglCore.release(); |
||||||
|
|
||||||
|
mEglCore = new EglCore(eglContext, EglCore.FLAG_RECORDABLE); |
||||||
|
mWindowSurface.recreate(mEglCore); |
||||||
|
mWindowSurface.makeCurrent(); |
||||||
|
|
||||||
|
mInput = new BeautyCameraFilter(mContext); |
||||||
|
mInput.init(); |
||||||
|
mFilter = BeautyFilterFactory.getFilter(type, mContext); |
||||||
|
if (mFilter != null) { |
||||||
|
mFilter.init(); |
||||||
|
mFilter.onOutputSizeChanged(mVideoWidth, mVideoHeight); |
||||||
|
mFilter.onInputSizeChanged(mPreviewWidth, mPreviewHeight); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void prepareRecorder(EGLContext eglContext, int width, int height, int bitrate, File file) { |
||||||
|
try { |
||||||
|
mVideoRecorder = new VideoRecorderCore(width, height, bitrate, file); |
||||||
|
} catch (IOException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
mVideoWidth = width; |
||||||
|
mVideoHeight = height; |
||||||
|
mEglCore = new EglCore(eglContext, EglCore.FLAG_RECORDABLE); |
||||||
|
mWindowSurface = new WindowEglSurface(mEglCore, mVideoRecorder.getInputSurface(), true); |
||||||
|
mWindowSurface.makeCurrent(); |
||||||
|
|
||||||
|
mInput = new BeautyCameraFilter(mContext); |
||||||
|
mInput.init(); |
||||||
|
mFilter = BeautyFilterFactory.getFilter(type, mContext); |
||||||
|
if (mFilter != null) { |
||||||
|
mFilter.init(); |
||||||
|
mFilter.onOutputSizeChanged(mVideoWidth, mVideoHeight); |
||||||
|
mFilter.onInputSizeChanged(mPreviewWidth, mPreviewHeight); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void releaseRecorder() { |
||||||
|
mVideoRecorder.release(); |
||||||
|
if (mWindowSurface != null) { |
||||||
|
mWindowSurface.release(); |
||||||
|
mWindowSurface = null; |
||||||
|
} |
||||||
|
if (mInput != null) { |
||||||
|
mInput.destroy(); |
||||||
|
mInput = null; |
||||||
|
} |
||||||
|
if (mFilter != null) { |
||||||
|
mFilter.destroy(); |
||||||
|
mFilter = null; |
||||||
|
type = BeautyFilterType.NONE; |
||||||
|
} |
||||||
|
if (mEglCore != null) { |
||||||
|
mEglCore.release(); |
||||||
|
mEglCore = null; |
||||||
|
} |
||||||
|
} |
||||||
|
public void setFilter(BeautyFilterType type) { |
||||||
|
this.type = type; |
||||||
|
} |
||||||
|
|
||||||
|
public void setPreviewSize(int width, int height){ |
||||||
|
mPreviewWidth = width; |
||||||
|
mPreviewHeight = height; |
||||||
|
} |
||||||
|
|
||||||
|
public void setTextureBuffer(FloatBuffer glTextureBuffer) { |
||||||
|
this.glTextureBuffer = glTextureBuffer; |
||||||
|
} |
||||||
|
|
||||||
|
public void setCubeBuffer(FloatBuffer gLVertexBuffer) { |
||||||
|
this.glVertexBuffer = gLVertexBuffer; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,121 @@ |
|||||||
|
package com.frank.camerafilter.recorder.video; |
||||||
|
|
||||||
|
import android.media.MediaCodec; |
||||||
|
import android.media.MediaCodecInfo; |
||||||
|
import android.media.MediaFormat; |
||||||
|
import android.media.MediaMuxer; |
||||||
|
import android.util.Log; |
||||||
|
import android.view.Surface; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.nio.ByteBuffer; |
||||||
|
|
||||||
|
/** |
||||||
|
* This class wraps up the core components used for surface-input video encoding. |
||||||
|
* <p> |
||||||
|
* Once created, frames are fed to the input surface. Remember to provide the presentation |
||||||
|
* time stamp, and always call drainEncoder() before swapBuffers() to ensure that the |
||||||
|
* producer side doesn't get backed up. |
||||||
|
* <p> |
||||||
|
* This class is not thread-safe, with one exception: it is valid to use the input surface |
||||||
|
* on one thread, and drain the output on a different thread. |
||||||
|
*/ |
||||||
|
public class VideoRecorderCore { |
||||||
|
|
||||||
|
private final static String TAG = VideoRecorderCore.class.getSimpleName(); |
||||||
|
|
||||||
|
private final static int FRAME_RATE = 30; |
||||||
|
private final static int IFRAME_INTERVAL = 5; |
||||||
|
private final static String MIME_TYPE = "video/avc"; |
||||||
|
private final static int TIMEOUT_USEC = 10000; |
||||||
|
|
||||||
|
private int mTrackIndex; |
||||||
|
private boolean mMuxerStarted; |
||||||
|
private final Surface mInputSurface; |
||||||
|
private MediaMuxer mMediaMuxer; |
||||||
|
private MediaCodec mVideoEncoder; |
||||||
|
private final MediaCodec.BufferInfo mBufferInfo; |
||||||
|
|
||||||
|
public VideoRecorderCore(int width, int height, int bitrate, File outputFile) throws IOException { |
||||||
|
mBufferInfo = new MediaCodec.BufferInfo(); |
||||||
|
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, width, height); |
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); |
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); |
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); |
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); |
||||||
|
|
||||||
|
mVideoEncoder = MediaCodec.createEncoderByType(MIME_TYPE); |
||||||
|
mVideoEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); |
||||||
|
mInputSurface = mVideoEncoder.createInputSurface(); |
||||||
|
mVideoEncoder.start(); |
||||||
|
|
||||||
|
mMediaMuxer = new MediaMuxer(outputFile.toString(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); |
||||||
|
mTrackIndex = -1; |
||||||
|
mMuxerStarted = false; |
||||||
|
} |
||||||
|
|
||||||
|
public Surface getInputSurface() { |
||||||
|
return mInputSurface; |
||||||
|
} |
||||||
|
|
||||||
|
public void drainEncoder(boolean endOfStream) { |
||||||
|
if (endOfStream) { |
||||||
|
mVideoEncoder.signalEndOfInputStream(); |
||||||
|
} |
||||||
|
|
||||||
|
ByteBuffer[] outputBuffers = mVideoEncoder.getOutputBuffers(); |
||||||
|
while (true) { |
||||||
|
int encodeStatus = mVideoEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); |
||||||
|
if (encodeStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { |
||||||
|
if (!endOfStream) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} else if (encodeStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { |
||||||
|
outputBuffers = mVideoEncoder.getOutputBuffers(); |
||||||
|
} else if (encodeStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { |
||||||
|
if (mMuxerStarted) { |
||||||
|
throw new RuntimeException("format has changed!"); |
||||||
|
} |
||||||
|
MediaFormat newFormat = mVideoEncoder.getOutputFormat(); |
||||||
|
mTrackIndex = mMediaMuxer.addTrack(newFormat); |
||||||
|
mMediaMuxer.start(); |
||||||
|
mMuxerStarted = true; |
||||||
|
} else if (encodeStatus < 0) { |
||||||
|
Log.e(TAG, "error encodeStatus=" + encodeStatus); |
||||||
|
} else { |
||||||
|
ByteBuffer data = outputBuffers[encodeStatus]; |
||||||
|
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { |
||||||
|
mBufferInfo.size = 0; |
||||||
|
} |
||||||
|
if (mBufferInfo.size != 0) { |
||||||
|
if (!mMuxerStarted) { |
||||||
|
throw new RuntimeException("muxer hasn't started"); |
||||||
|
} |
||||||
|
data.position(mBufferInfo.offset); |
||||||
|
data.limit(mBufferInfo.offset + mBufferInfo.size); |
||||||
|
mMediaMuxer.writeSampleData(mTrackIndex, data, mBufferInfo); |
||||||
|
} |
||||||
|
mVideoEncoder.releaseOutputBuffer(encodeStatus, false); |
||||||
|
// end of stream
|
||||||
|
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void release() { |
||||||
|
if (mVideoEncoder != null) { |
||||||
|
mVideoEncoder.stop(); |
||||||
|
mVideoEncoder.release(); |
||||||
|
mVideoEncoder = null; |
||||||
|
} |
||||||
|
if (mMediaMuxer != null) { |
||||||
|
mMediaMuxer.stop(); |
||||||
|
mMediaMuxer.release(); |
||||||
|
mMediaMuxer = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
package com.frank.camerafilter.recorder.video; |
||||||
|
|
||||||
|
import android.view.Surface; |
||||||
|
|
||||||
|
import com.frank.camerafilter.recorder.gles.EglCore; |
||||||
|
import com.frank.camerafilter.recorder.gles.EglSurfaceBase; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author xufulong |
||||||
|
* @date 2022/6/23 9:15 上午 |
||||||
|
* @desc |
||||||
|
*/ |
||||||
|
public class WindowEglSurface extends EglSurfaceBase { |
||||||
|
|
||||||
|
private Surface mSurface; |
||||||
|
private boolean mReleaseSurface; |
||||||
|
|
||||||
|
public WindowEglSurface(EglCore eglCore, Surface surface) { |
||||||
|
this(eglCore, surface, false); |
||||||
|
} |
||||||
|
|
||||||
|
public WindowEglSurface(EglCore eglCore, Surface surface, boolean releaseSurface) { |
||||||
|
super(eglCore); |
||||||
|
createWindowSurface(surface); |
||||||
|
mSurface = surface; |
||||||
|
mReleaseSurface = releaseSurface; |
||||||
|
} |
||||||
|
|
||||||
|
public void release() { |
||||||
|
releaseEglSurface(); |
||||||
|
if (mSurface != null && mReleaseSurface) { |
||||||
|
mSurface.release(); |
||||||
|
} |
||||||
|
mSurface = null; |
||||||
|
} |
||||||
|
|
||||||
|
public void recreate(EglCore newEglCore) { |
||||||
|
if (mSurface == null) { |
||||||
|
throw new RuntimeException("Surface is null"); |
||||||
|
} |
||||||
|
mEglCore = newEglCore; |
||||||
|
createWindowSurface(mSurface); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,128 @@ |
|||||||
|
package com.frank.camerafilter.util; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.content.res.AssetManager; |
||||||
|
import android.graphics.Bitmap; |
||||||
|
import android.graphics.BitmapFactory; |
||||||
|
import android.opengl.GLES11Ext; |
||||||
|
import android.opengl.GLES30; |
||||||
|
import android.opengl.GLUtils; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
import java.io.BufferedReader; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.io.InputStreamReader; |
||||||
|
|
||||||
|
import javax.microedition.khronos.opengles.GL10; |
||||||
|
|
||||||
|
public class OpenGLUtil { |
||||||
|
|
||||||
|
public final static int ON_DRAWN = 1; |
||||||
|
public static final int NOT_INIT = -1; |
||||||
|
public static final int NO_SHADER = 0; |
||||||
|
public static final int NO_TEXTURE = -1; |
||||||
|
|
||||||
|
private static Bitmap getBitmapFromAssetFile(Context context, String name) { |
||||||
|
try { |
||||||
|
AssetManager assetManager = context.getResources().getAssets(); |
||||||
|
InputStream stream = assetManager.open(name); |
||||||
|
Bitmap bitmap = BitmapFactory.decodeStream(stream); |
||||||
|
stream.close(); |
||||||
|
return bitmap; |
||||||
|
} catch (IOException e) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static int loadTexture(final Context context, final String name) { |
||||||
|
if (context == null || name == null) |
||||||
|
return NO_TEXTURE; |
||||||
|
final int[] textures = new int[1]; |
||||||
|
GLES30.glGenTextures(1, textures, 0); |
||||||
|
if (textures[0] == 0) |
||||||
|
return NO_TEXTURE; |
||||||
|
Bitmap bitmap = getBitmapFromAssetFile(context, name); |
||||||
|
if (bitmap == null) |
||||||
|
return NO_TEXTURE; |
||||||
|
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]); |
||||||
|
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR); |
||||||
|
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR); |
||||||
|
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE); |
||||||
|
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE); |
||||||
|
GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0); |
||||||
|
bitmap.recycle(); |
||||||
|
return textures[0]; |
||||||
|
} |
||||||
|
|
||||||
|
private static int loadShader(final String source, final int type) { |
||||||
|
int shader = GLES30.glCreateShader(type); |
||||||
|
GLES30.glShaderSource(shader, source); |
||||||
|
GLES30.glCompileShader(shader); |
||||||
|
int[] compile = new int[1]; |
||||||
|
GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compile, 0); |
||||||
|
if (compile[0] <= 0) { |
||||||
|
Log.e("OpenGlUtil", "Shader compile error=" + GLES30.glGetShaderInfoLog(shader)); |
||||||
|
return NO_SHADER; |
||||||
|
} |
||||||
|
return shader; |
||||||
|
} |
||||||
|
|
||||||
|
public static int loadProgram(final String vertexSource, final String fragmentSource) { |
||||||
|
int vertexShader = loadShader(vertexSource, GLES30.GL_VERTEX_SHADER); |
||||||
|
int fragmentShader = loadShader(fragmentSource, GLES30.GL_FRAGMENT_SHADER); |
||||||
|
if (vertexShader == NO_SHADER || fragmentShader == NO_SHADER) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
int programId = GLES30.glCreateProgram(); |
||||||
|
GLES30.glAttachShader(programId, vertexShader); |
||||||
|
GLES30.glAttachShader(programId, fragmentShader); |
||||||
|
GLES30.glLinkProgram(programId); |
||||||
|
int[] linked = new int[1]; |
||||||
|
GLES30.glGetProgramiv(programId, GLES30.GL_LINK_STATUS, linked, 0); |
||||||
|
if (linked[0] <= 0) { |
||||||
|
programId = 0; |
||||||
|
Log.e("OpenGlUtil", "program link error=" + GLES30.glGetProgramInfoLog(programId)); |
||||||
|
} |
||||||
|
GLES30.glDeleteShader(vertexShader); |
||||||
|
GLES30.glDeleteShader(fragmentShader); |
||||||
|
return programId; |
||||||
|
} |
||||||
|
|
||||||
|
public static int getExternalOESTextureId() { |
||||||
|
int[] textures = new int[1]; |
||||||
|
GLES30.glGenTextures(1, textures, 0); |
||||||
|
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]); |
||||||
|
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); |
||||||
|
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); |
||||||
|
GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); |
||||||
|
GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); |
||||||
|
return textures[0]; |
||||||
|
} |
||||||
|
|
||||||
|
public static String readShaderFromSource(Context context, final int resourceId) { |
||||||
|
String line; |
||||||
|
StringBuilder builder = new StringBuilder(); |
||||||
|
InputStream inputStream = context.getResources().openRawResource(resourceId); |
||||||
|
InputStreamReader reader = new InputStreamReader(inputStream); |
||||||
|
BufferedReader bufferedReader = new BufferedReader(reader); |
||||||
|
try { |
||||||
|
while ((line = bufferedReader.readLine()) != null) { |
||||||
|
builder.append(line).append("\n"); |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
return null; |
||||||
|
} finally { |
||||||
|
try { |
||||||
|
inputStream.close(); |
||||||
|
reader.close(); |
||||||
|
bufferedReader.close(); |
||||||
|
} catch (IOException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
return builder.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
package com.frank.camerafilter.util; |
||||||
|
|
||||||
|
public enum Rotation { |
||||||
|
|
||||||
|
NORMAL, ROTATION_90, ROTATION_180, ROTATION_270; |
||||||
|
|
||||||
|
public int toInt() { |
||||||
|
switch (this) { |
||||||
|
case NORMAL: |
||||||
|
return 0; |
||||||
|
case ROTATION_90: |
||||||
|
return 90; |
||||||
|
case ROTATION_180: |
||||||
|
return 180; |
||||||
|
case ROTATION_270: |
||||||
|
return 270; |
||||||
|
default: |
||||||
|
throw new IllegalStateException("unknown rotation value..."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static Rotation fromInt(int rotation) { |
||||||
|
switch (rotation) { |
||||||
|
case 0: |
||||||
|
return NORMAL; |
||||||
|
case 90: |
||||||
|
return ROTATION_90; |
||||||
|
case 180: |
||||||
|
return ROTATION_180; |
||||||
|
case 270: |
||||||
|
return ROTATION_270; |
||||||
|
default: |
||||||
|
throw new IllegalStateException("unknown rotation=" +rotation); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
package com.frank.camerafilter.util; |
||||||
|
|
||||||
|
public class TextureRotateUtil { |
||||||
|
|
||||||
|
public final static float[] TEXTURE_ROTATE_0 = { |
||||||
|
0.0f, 1.0f, |
||||||
|
1.0f, 1.0f, |
||||||
|
0.0f, 0.0f, |
||||||
|
1.0f, 0.0f |
||||||
|
}; |
||||||
|
|
||||||
|
public final static float[] TEXTURE_ROTATE_90 = { |
||||||
|
1.0f, 1.0f, |
||||||
|
1.0f, 0.0f, |
||||||
|
0.0f, 1.0f, |
||||||
|
0.0f, 0.0f |
||||||
|
}; |
||||||
|
|
||||||
|
public final static float[] TEXTURE_ROTATE_180 = { |
||||||
|
1.0f, 0.0f, |
||||||
|
0.0f, 0.0f, |
||||||
|
1.0f, 1.0f, |
||||||
|
0.0f, 1.0f |
||||||
|
}; |
||||||
|
|
||||||
|
public final static float[] TEXTURE_ROTATE_270 = { |
||||||
|
0.0f, 0.0f, |
||||||
|
0.0f, 1.0f, |
||||||
|
1.0f, 0.0f, |
||||||
|
1.0f, 1.0f |
||||||
|
}; |
||||||
|
|
||||||
|
public final static float[] VERTEX = { |
||||||
|
-1.0f, -1.0f, |
||||||
|
1.0f, -1.0f, |
||||||
|
-1.0f, 1.0f, |
||||||
|
1.0f, 1.0f |
||||||
|
}; |
||||||
|
|
||||||
|
private TextureRotateUtil() {} |
||||||
|
|
||||||
|
private static float flip(float value) { |
||||||
|
return value == 1.0f ? 0.0f : 1.0f; |
||||||
|
} |
||||||
|
|
||||||
|
public static float[] getRotateTexture(Rotation rotation, boolean horizontalFlip, boolean verticalFlip) { |
||||||
|
float[] rotateTexture; |
||||||
|
switch (rotation) { |
||||||
|
case ROTATION_90: |
||||||
|
rotateTexture = TEXTURE_ROTATE_90; |
||||||
|
break; |
||||||
|
case ROTATION_180: |
||||||
|
rotateTexture = TEXTURE_ROTATE_180; |
||||||
|
break; |
||||||
|
case ROTATION_270: |
||||||
|
rotateTexture = TEXTURE_ROTATE_270; |
||||||
|
break; |
||||||
|
case NORMAL: |
||||||
|
default: |
||||||
|
rotateTexture = TEXTURE_ROTATE_0; |
||||||
|
break; |
||||||
|
} |
||||||
|
if (horizontalFlip) { |
||||||
|
rotateTexture = new float[] { |
||||||
|
flip(rotateTexture[0]), rotateTexture[1], |
||||||
|
flip(rotateTexture[2]), rotateTexture[3], |
||||||
|
flip(rotateTexture[4]), rotateTexture[5], |
||||||
|
flip(rotateTexture[6]), rotateTexture[7] |
||||||
|
}; |
||||||
|
} |
||||||
|
if (verticalFlip) { |
||||||
|
rotateTexture = new float[] { |
||||||
|
rotateTexture[0], flip(rotateTexture[1]), |
||||||
|
rotateTexture[2], flip(rotateTexture[3]), |
||||||
|
rotateTexture[4], flip(rotateTexture[5]), |
||||||
|
rotateTexture[6], flip(rotateTexture[7]) |
||||||
|
}; |
||||||
|
} |
||||||
|
return rotateTexture; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
package com.frank.camerafilter.widget; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.opengl.GLSurfaceView; |
||||||
|
import android.util.AttributeSet; |
||||||
|
import android.view.SurfaceHolder; |
||||||
|
|
||||||
|
import com.frank.camerafilter.factory.BeautyFilterType; |
||||||
|
|
||||||
|
public class BeautyCameraView extends GLSurfaceView { |
||||||
|
|
||||||
|
private final CameraRender mCameraRender; |
||||||
|
|
||||||
|
public BeautyCameraView(Context context) { |
||||||
|
this(context, null); |
||||||
|
} |
||||||
|
|
||||||
|
public BeautyCameraView(Context context, AttributeSet attrs) { |
||||||
|
super(context, attrs); |
||||||
|
getHolder().addCallback(this); |
||||||
|
|
||||||
|
mCameraRender = new CameraRender(this); |
||||||
|
setEGLContextClientVersion(3); |
||||||
|
setRenderer(mCameraRender); |
||||||
|
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void surfaceDestroyed(SurfaceHolder holder) { |
||||||
|
super.surfaceDestroyed(holder); |
||||||
|
if (mCameraRender != null) { |
||||||
|
mCameraRender.releaseCamera(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void switchCamera() { |
||||||
|
if (mCameraRender != null) { |
||||||
|
mCameraRender.switchCamera(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void setFilter(BeautyFilterType type) { |
||||||
|
mCameraRender.setFilter(type); |
||||||
|
} |
||||||
|
|
||||||
|
public void setRecording(boolean isRecording) { |
||||||
|
mCameraRender.setRecording(isRecording); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,246 @@ |
|||||||
|
package com.frank.camerafilter.widget; |
||||||
|
|
||||||
|
import android.graphics.SurfaceTexture; |
||||||
|
import android.hardware.Camera; |
||||||
|
import android.opengl.EGL14; |
||||||
|
import android.opengl.GLES30; |
||||||
|
import android.opengl.GLSurfaceView; |
||||||
|
import android.os.Environment; |
||||||
|
|
||||||
|
import com.frank.camerafilter.camera.CameraManager; |
||||||
|
import com.frank.camerafilter.filter.BeautyCameraFilter; |
||||||
|
import com.frank.camerafilter.filter.BaseFilter; |
||||||
|
import com.frank.camerafilter.factory.BeautyFilterFactory; |
||||||
|
import com.frank.camerafilter.factory.BeautyFilterType; |
||||||
|
import com.frank.camerafilter.recorder.video.TextureVideoRecorder; |
||||||
|
import com.frank.camerafilter.util.OpenGLUtil; |
||||||
|
import com.frank.camerafilter.util.Rotation; |
||||||
|
import com.frank.camerafilter.util.TextureRotateUtil; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.nio.ByteOrder; |
||||||
|
import java.nio.FloatBuffer; |
||||||
|
|
||||||
|
import javax.microedition.khronos.egl.EGLConfig; |
||||||
|
import javax.microedition.khronos.opengles.GL10; |
||||||
|
|
||||||
|
public class CameraRender implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener { |
||||||
|
|
||||||
|
protected BaseFilter mFilter; |
||||||
|
|
||||||
|
private SurfaceTexture surfaceTexture; |
||||||
|
private BeautyCameraFilter cameraFilter; |
||||||
|
|
||||||
|
private final CameraManager cameraManager; |
||||||
|
|
||||||
|
protected int mTextureId = OpenGLUtil.NO_TEXTURE; |
||||||
|
|
||||||
|
protected FloatBuffer mVertexBuffer; |
||||||
|
|
||||||
|
protected FloatBuffer mTextureBuffer; |
||||||
|
|
||||||
|
protected int mImageWidth, mImageHeight; |
||||||
|
|
||||||
|
protected int mSurfaceWidth, mSurfaceHeight; |
||||||
|
private final float[] mMatrix = new float[16]; |
||||||
|
|
||||||
|
private final BeautyCameraView mCameraView; |
||||||
|
|
||||||
|
private final File outputFile; |
||||||
|
private int recordStatus; |
||||||
|
protected boolean recordEnable; |
||||||
|
private final TextureVideoRecorder videoRecorder; |
||||||
|
|
||||||
|
private final static int RECORDING_OFF = 0; |
||||||
|
private final static int RECORDING_ON = 1; |
||||||
|
private final static int RECORDING_RESUME = 2; |
||||||
|
|
||||||
|
private static final int videoBitrate = 6 * 1024 * 1024; |
||||||
|
private static final String videoName = "camera_record.mp4"; |
||||||
|
private static final String videoPath = Environment.getExternalStorageDirectory().getPath(); |
||||||
|
|
||||||
|
public CameraRender(BeautyCameraView cameraView) { |
||||||
|
mCameraView = cameraView; |
||||||
|
|
||||||
|
cameraManager = new CameraManager(); |
||||||
|
mVertexBuffer = ByteBuffer.allocateDirect(TextureRotateUtil.VERTEX.length * 4) |
||||||
|
.order(ByteOrder.nativeOrder()) |
||||||
|
.asFloatBuffer(); |
||||||
|
mVertexBuffer.put(TextureRotateUtil.VERTEX).position(0); |
||||||
|
mTextureBuffer = ByteBuffer.allocateDirect(TextureRotateUtil.TEXTURE_ROTATE_0.length * 4) |
||||||
|
.order(ByteOrder.nativeOrder()) |
||||||
|
.asFloatBuffer(); |
||||||
|
mTextureBuffer.put(TextureRotateUtil.TEXTURE_ROTATE_0).position(0); |
||||||
|
|
||||||
|
recordEnable = false; |
||||||
|
recordStatus = RECORDING_OFF; |
||||||
|
videoRecorder = new TextureVideoRecorder(mCameraView.getContext()); |
||||||
|
outputFile = new File(videoPath, videoName); |
||||||
|
} |
||||||
|
|
||||||
|
private void openCamera() { |
||||||
|
if (cameraManager.getCamera() == null) |
||||||
|
cameraManager.openCamera(); |
||||||
|
Camera.Size size = cameraManager.getPreviewSize(); |
||||||
|
if (cameraManager.getOrientation() == 90 || cameraManager.getOrientation() == 270) { |
||||||
|
mImageWidth = size.height; |
||||||
|
mImageHeight = size.width; |
||||||
|
} else { |
||||||
|
mImageWidth = size.width; |
||||||
|
mImageHeight = size.height; |
||||||
|
} |
||||||
|
cameraFilter.onInputSizeChanged(mImageWidth, mImageHeight); |
||||||
|
adjustSize(cameraManager.getOrientation(), cameraManager.isFront(), true); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) { |
||||||
|
GLES30.glDisable(GL10.GL_DITHER); |
||||||
|
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
||||||
|
GLES30.glEnable(GL10.GL_CULL_FACE); |
||||||
|
GLES30.glEnable(GL10.GL_DEPTH_TEST); |
||||||
|
|
||||||
|
cameraFilter = new BeautyCameraFilter(mCameraView.getContext()); |
||||||
|
cameraFilter.init(); |
||||||
|
mTextureId = OpenGLUtil.getExternalOESTextureId(); |
||||||
|
if (mTextureId != OpenGLUtil.NO_TEXTURE) { |
||||||
|
surfaceTexture = new SurfaceTexture(mTextureId); |
||||||
|
surfaceTexture.setOnFrameAvailableListener(this); |
||||||
|
} |
||||||
|
|
||||||
|
openCamera(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSurfaceChanged(GL10 gl10, int width, int height) { |
||||||
|
GLES30.glViewport(0, 0, width, height); |
||||||
|
mSurfaceWidth = width; |
||||||
|
mSurfaceHeight = height; |
||||||
|
cameraManager.startPreview(surfaceTexture); |
||||||
|
onFilterChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onDrawFrame(GL10 gl10) { |
||||||
|
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT); |
||||||
|
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
||||||
|
|
||||||
|
surfaceTexture.updateTexImage(); |
||||||
|
|
||||||
|
surfaceTexture.getTransformMatrix(mMatrix); |
||||||
|
cameraFilter.setTextureTransformMatrix(mMatrix); |
||||||
|
int id = mTextureId; |
||||||
|
if (mFilter == null) { |
||||||
|
cameraFilter.onDrawFrame(mTextureId, mVertexBuffer, mTextureBuffer); |
||||||
|
} else { |
||||||
|
id = cameraFilter.onDrawToTexture(mTextureId); |
||||||
|
mFilter.onDrawFrame(id, mVertexBuffer, mTextureBuffer); |
||||||
|
} |
||||||
|
|
||||||
|
onRecordVideo(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onFrameAvailable(SurfaceTexture surfaceTexture) { |
||||||
|
mCameraView.requestRender(); |
||||||
|
} |
||||||
|
|
||||||
|
public void adjustSize(int rotation, boolean horizontalFlip, boolean verticalFlip) { |
||||||
|
float[] vertexData = TextureRotateUtil.VERTEX; |
||||||
|
float[] textureData = TextureRotateUtil.getRotateTexture(Rotation.fromInt(rotation), |
||||||
|
horizontalFlip, verticalFlip); |
||||||
|
|
||||||
|
mVertexBuffer.clear(); |
||||||
|
mVertexBuffer.put(vertexData).position(0); |
||||||
|
mTextureBuffer.clear(); |
||||||
|
mTextureBuffer.put(textureData).position(0); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void switchCamera() { |
||||||
|
if (cameraManager != null) { |
||||||
|
cameraManager.switchCamera(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void releaseCamera() { |
||||||
|
if (cameraManager != null) { |
||||||
|
cameraManager.releaseCamera(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void onRecordVideo(int textureId) { |
||||||
|
if (recordEnable) { |
||||||
|
switch (recordStatus) { |
||||||
|
case RECORDING_OFF: |
||||||
|
videoRecorder.setPreviewSize(mImageWidth, mImageHeight); |
||||||
|
videoRecorder.setTextureBuffer(mTextureBuffer); |
||||||
|
videoRecorder.setCubeBuffer(mVertexBuffer); |
||||||
|
videoRecorder.startRecording(new TextureVideoRecorder.RecorderConfig( |
||||||
|
mImageWidth, |
||||||
|
mImageHeight, |
||||||
|
videoBitrate, |
||||||
|
outputFile, |
||||||
|
EGL14.eglGetCurrentContext())); |
||||||
|
recordStatus = RECORDING_ON; |
||||||
|
break; |
||||||
|
case RECORDING_RESUME: |
||||||
|
videoRecorder.updateSharedContext(EGL14.eglGetCurrentContext()); |
||||||
|
recordStatus = RECORDING_ON; |
||||||
|
break; |
||||||
|
case RECORDING_ON: |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new RuntimeException("unknown status " + recordStatus); |
||||||
|
} |
||||||
|
} else { |
||||||
|
switch (recordStatus) { |
||||||
|
case RECORDING_ON: |
||||||
|
case RECORDING_RESUME: |
||||||
|
videoRecorder.stopRecording(); |
||||||
|
recordStatus = RECORDING_OFF; |
||||||
|
break; |
||||||
|
case RECORDING_OFF: |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new RuntimeException("unknown status " + recordStatus); |
||||||
|
} |
||||||
|
} |
||||||
|
videoRecorder.setTextureId(textureId); |
||||||
|
videoRecorder.frameAvailable(surfaceTexture); |
||||||
|
} |
||||||
|
|
||||||
|
public void setRecording(boolean isRecording) { |
||||||
|
recordEnable = isRecording; |
||||||
|
} |
||||||
|
|
||||||
|
public void setFilter(final BeautyFilterType type) { |
||||||
|
mCameraView.queueEvent(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
if (mFilter != null) |
||||||
|
mFilter.destroy(); |
||||||
|
mFilter = null; |
||||||
|
mFilter = BeautyFilterFactory.getFilter(type, mCameraView.getContext()); |
||||||
|
if (mFilter != null) |
||||||
|
mFilter.init(); |
||||||
|
onFilterChanged(); |
||||||
|
} |
||||||
|
}); |
||||||
|
mCameraView.requestRender(); |
||||||
|
} |
||||||
|
|
||||||
|
public void onFilterChanged() { |
||||||
|
if (mFilter != null) { |
||||||
|
mFilter.onInputSizeChanged(mImageWidth, mImageHeight); |
||||||
|
mFilter.onOutputSizeChanged(mSurfaceWidth, mSurfaceHeight); |
||||||
|
} |
||||||
|
cameraFilter.onOutputSizeChanged(mSurfaceWidth, mSurfaceHeight); |
||||||
|
if (mFilter != null) |
||||||
|
cameraFilter.initFrameBuffer(mImageWidth, mImageHeight); |
||||||
|
else |
||||||
|
cameraFilter.destroyFrameBuffer(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
varying highp vec2 textureCoordinate; |
||||||
|
precision mediump float; |
||||||
|
|
||||||
|
uniform sampler2D inputImageTexture; |
||||||
|
uniform vec2 singleStepOffset; |
||||||
|
uniform float strength; |
||||||
|
|
||||||
|
const highp vec3 W = vec3(0.299,0.587,0.114); |
||||||
|
|
||||||
|
const mat3 rgb2yiqMatrix = mat3( |
||||||
|
0.299, 0.587, 0.114, |
||||||
|
0.596,-0.275,-0.321, |
||||||
|
0.212,-0.523, 0.311); |
||||||
|
|
||||||
|
const mat3 yiq2rgbMatrix = mat3( |
||||||
|
1.0, 0.956, 0.621, |
||||||
|
1.0,-0.272,-1.703, |
||||||
|
1.0,-1.106, 0.0); |
||||||
|
|
||||||
|
|
||||||
|
void main() |
||||||
|
{ |
||||||
|
vec4 oralColor = texture2D(inputImageTexture, textureCoordinate); |
||||||
|
|
||||||
|
vec3 maxValue = vec3(0.,0.,0.); |
||||||
|
|
||||||
|
for(int i = -2; i<=2; i++) |
||||||
|
{ |
||||||
|
for(int j = -2; j<=2; j++) |
||||||
|
{ |
||||||
|
vec4 tempColor = texture2D(inputImageTexture, textureCoordinate+singleStepOffset*vec2(i,j)); |
||||||
|
maxValue.r = max(maxValue.r,tempColor.r); |
||||||
|
maxValue.g = max(maxValue.g,tempColor.g); |
||||||
|
maxValue.b = max(maxValue.b,tempColor.b); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
vec3 textureColor = oralColor.rgb / maxValue; |
||||||
|
|
||||||
|
float gray = dot(textureColor, W); |
||||||
|
float k = 0.223529; |
||||||
|
float alpha = min(gray,k)/k; |
||||||
|
|
||||||
|
textureColor = textureColor * alpha + (1.-alpha)*oralColor.rgb; |
||||||
|
|
||||||
|
vec3 yiqColor = textureColor * rgb2yiqMatrix; |
||||||
|
|
||||||
|
yiqColor.r = max(0.0,min(1.0,pow(gray,strength))); |
||||||
|
|
||||||
|
textureColor = yiqColor * yiq2rgbMatrix; |
||||||
|
|
||||||
|
gl_FragColor = vec4(textureColor, oralColor.w); |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
#extension GL_OES_EGL_image_external : require |
||||||
|
|
||||||
|
precision mediump float; |
||||||
|
|
||||||
|
varying mediump vec2 textureCoordinate; |
||||||
|
|
||||||
|
uniform samplerExternalOES inputImageTexture; |
||||||
|
|
||||||
|
void main(){ |
||||||
|
|
||||||
|
vec3 centralColor = texture2D(inputImageTexture, textureCoordinate).rgb; |
||||||
|
gl_FragColor = vec4(centralColor.rgb,1.0);; |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
attribute vec4 position; |
||||||
|
attribute vec4 inputTextureCoordinate; |
||||||
|
|
||||||
|
uniform mat4 textureTransform; |
||||||
|
varying vec2 textureCoordinate; |
||||||
|
|
||||||
|
void main() |
||||||
|
{ |
||||||
|
textureCoordinate = (textureTransform * inputTextureCoordinate).xy; |
||||||
|
gl_Position = position; |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
varying highp vec2 textureCoordinate; |
||||||
|
precision mediump float; |
||||||
|
|
||||||
|
uniform sampler2D inputImageTexture; |
||||||
|
uniform vec2 singleStepOffset; |
||||||
|
uniform float strength; |
||||||
|
|
||||||
|
const highp vec3 W = vec3(0.299,0.587,0.114); |
||||||
|
|
||||||
|
|
||||||
|
void main() |
||||||
|
{ |
||||||
|
float threshold = 0.0; |
||||||
|
//pic1 |
||||||
|
vec4 oralColor = texture2D(inputImageTexture, textureCoordinate); |
||||||
|
|
||||||
|
//pic2 |
||||||
|
vec3 maxValue = vec3(0.,0.,0.); |
||||||
|
|
||||||
|
for(int i = -2; i<=2; i++) |
||||||
|
{ |
||||||
|
for(int j = -2; j<=2; j++) |
||||||
|
{ |
||||||
|
vec4 tempColor = texture2D(inputImageTexture, textureCoordinate+singleStepOffset*vec2(i,j)); |
||||||
|
maxValue.r = max(maxValue.r,tempColor.r); |
||||||
|
maxValue.g = max(maxValue.g,tempColor.g); |
||||||
|
maxValue.b = max(maxValue.b,tempColor.b); |
||||||
|
threshold += dot(tempColor.rgb, W); |
||||||
|
} |
||||||
|
} |
||||||
|
//pic3 |
||||||
|
float gray1 = dot(oralColor.rgb, W); |
||||||
|
|
||||||
|
//pic4 |
||||||
|
float gray2 = dot(maxValue, W); |
||||||
|
|
||||||
|
//pic5 |
||||||
|
float contour = gray1 / gray2; |
||||||
|
|
||||||
|
threshold = threshold / 25.; |
||||||
|
float alpha = max(1.0,gray1>threshold?1.0:(gray1/threshold)); |
||||||
|
|
||||||
|
float result = contour * alpha + (1.0-alpha)*gray1; |
||||||
|
|
||||||
|
gl_FragColor = vec4(vec3(result,result,result), oralColor.w); |
||||||
|
} |
Loading…
Reference in new issue