最近公司需要开发一套美颜相机功能,查阅相关资料得到结论,美颜滤镜归根揭底是opengl es 加GPUImage实时滤镜渲染。
这块经验不足,所以就取开源库寻找项目,最终找到了一个4000+星的MagicCamera项目。这个项目还是非常有参考价值的,找了多个项目对比后发现思路都差不多,有很多项目都能看到MagicCamera的影子,听说有的已经在大规模使用的相机软件都是在这个项目的基础上研发的。自然就要down下来学习一下了。
当然目前MagicCamera项目太老了直接是跑不起来的需要自己配置CMake编译后才能正常跑起来,magicCameraNew这个已经配置好CMake编译了,只要本地配置好编译环境就可以跑起来了。这个项目目前只有相机实时预览加拍照和录制视频。没有提供照片编辑入口,需要自己使用MagicImageView就可以完成编辑功能
实时美颜和滤镜效果没有问题,但是在拍照后生成的效果没有美颜和滤镜的效果,github上也很多人反馈拍照黑屏,这里我解决了
黑屏的问题。
先来看下拍照的流程及代码
CameraEngine.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
CameraEngine.stopPreview();
final Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
//要利用OpenGl 绘图暂时停止摄像头预览,并且渲染操作要放到EGLContenxt环境下
queueEvent(new Runnable() {
@Override
public void run() {
//把摄像头获取的bitmap图片经过Opengl滤镜处理后返回保存
final Bitmap photo = drawPhoto(bitmap,CameraEngine.getCameraInfo().isFront);
GLES20.glViewport(0, 0, surfaceWidth, surfaceHeight);
if (photo != null)
savePictureTask.execute(photo);
}
});
CameraEngine.startPreview();
}
});
这里拍照流程没有什么问题,摄像头获取的bitmap也是正常的数据,这里顺便提一下摄像头预览和拍照的尺寸往往有多个Size需要根据需求选择最优的,一般图片拉伸都是Size设置和保存时的Size不一致导致的。Size设置代码如下
private static void setDefaultParameters(){
Parameters parameters = camera.getParameters();
if (parameters.getSupportedFocusModes().contains(
Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}
Size previewSize = CameraUtils.getLargePreviewSize(camera);
Size pictureSize = CameraUtils.getLargePictureSize(camera);
//设置预览尺寸
parameters.setPreviewSize(previewSize.width, previewSize.height);
//设置拍照尺寸
parameters.setPictureSize(pictureSize.width, pictureSize.height);
parameters.setRotation(90);
camera.setParameters(parameters);
}
这样处理后只要保证了拍照比例和处理图片时的Size比例一样就不会拉伸了。
接下来看看原始drawPhoto里干了些啥
private Bitmap drawPhoto(Bitmap bitmap,boolean isRotated){
//设置最终生成的图片的宽高
int width = bitmap.getWidth();
int height = bitmap.getHeight();
if(beautyFilter == null)
beautyFilter = new MagicBeautyFilter();
beautyFilter.init();
beautyFilter.onDisplaySizeChanged(width, height);
beautyFilter.onInputSizeChanged(width, height);
if(filter != null) {
filter.onInputSizeChanged(width, height);
filter.onDisplaySizeChanged(width, height);
}
//创建并使用FBO
int[] mFrameBuffers = new int[1];
int[] mFrameBufferTextures = new int[1];
GLES20.glGenFramebuffers(1, mFrameBuffers, 0);
GLES20.glGenTextures(1, mFrameBufferTextures, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mFrameBufferTextures[0]);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[0]);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, mFrameBufferTextures[0], 0);
//设置渲染窗口大小
GLES20.glViewport(0, 0, width, height);
//加载原始图片到纹理对象中
int textureId = OpenGlUtils.loadTexture(bitmap, OpenGlUtils.NO_TEXTURE, true);
FloatBuffer gLCubeBuffer = ByteBuffer.allocateDirect(TextureRotationUtil.CUBE.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
FloatBuffer gLTextureBuffer = ByteBuffer.allocateDirect(TextureRotationUtil.TEXTURE_NO_ROTATION.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
gLCubeBuffer.put(TextureRotationUtil.CUBE).position(0);
//这是纹理是否要旋转,因为前置摄像头拍照时反转了的
if(isRotated)
gLTextureBuffer.put(TextureRotationUtil.getRotation(Rotation.NORMAL, false, false)).position(0);
else
gLTextureBuffer.put(TextureRotationUtil.getRotation(Rotation.NORMAL, false, true)).position(0);
if(filter == null){
//只要美颜滤镜直接渲染
beautyFilter.onDrawFrame(textureId, gLCubeBuffer, gLTextureBuffer);
}else{
//双滤镜叠加,首先美颜渲染到原始图片纹理上
beautyFilter.onDrawFrame(textureId);
//然后颜色滤镜渲染到帧缓存FBO上
filter.onDrawFrame(mFrameBufferTextures[0], gLCubeBuffer, gLTextureBuffer);
}
//从EGL环境下获取图片数据
IntBuffer ib = IntBuffer.allocate(width * height);
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, ib);
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
result.copyPixelsFromBuffer(ib);
//释放资源
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
GLES20.glDeleteTextures(1, new int[]{textureId}, 0);
GLES20.glDeleteFramebuffers(mFrameBuffers.length, mFrameBuffers, 0);
GLES20.glDeleteTextures(mFrameBufferTextures.length, mFrameBufferTextures, 0);
beautyFilter.destroy();
beautyFilter = null;
if(filter != null) {
filter.onDisplaySizeChanged(surfaceWidth, surfaceHeight);
filter.onInputSizeChanged(imageWidth, imageHeight);
}
return result;
}
经过这段代码处理后就能获取到渲染后的图片,如果只有美颜效果时没有问题的,但是如果有filter时拍照获取的图片就是黑的
这个时候我看了下FBO的使用滤镜渲染应该要修改为如下代码
if(filter == null){
//只要美颜滤镜直接渲染
beautyFilter.onDrawFrame(textureId, gLCubeBuffer, gLTextureBuffer);
}else{
//双滤镜叠加,首先美颜渲染到原始图片纹理上
beautyFilter.onDrawFrame(textureId);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
//然后颜色滤镜渲染到帧缓存FBO上
filter.onDrawFrame(mFrameBufferTextures[0], gLCubeBuffer, gLTextureBuffer);
}
这个时候组合滤镜生成的图片如下4304x5760,可见区域的图像是
初步怀疑时viewport设置的问题,但是在尝试后发现并没有什么效果,分析图片尺寸后发现原来时图片大小和Opengl es渲染的图像的区域不一致OpenglES 渲染的区域由SurfaceView决定(有问题可以交流)。这里就不让渲染原图大小了,宽高根据预览尺寸来设置就没问题了。代码如下
//设置最终生成的图片的宽高
CameraInfo info = CameraEngine.getCameraInfo();
int width = info.previewHeight;
int height = info.previewWidth;
这样就双滤镜叠加效果就可以正常使用了。其实多滤镜效果也是使用FBO去渲染,流程和原理类似,可以参考
MagicBaseGroupFilter里的代码。
本人是小白,如有不正确的地方,还希望各位兄弟及时提出不吝赐教。
下一篇会介绍不采用离屏渲染bitmap拍照的方式,而是之前在当前渲染的环境下截图。