Android 相机库CameraView源码解析 (六) : 保存滤镜效果 约定不等于承诺〃 2024-04-25 20:24 73阅读 0赞 ### 1. 前言 ### 这段时间,在使用 [natario1/CameraView][natario1_CameraView] 来实现带滤镜的`预览`、`拍照`、`录像`功能。 由于`CameraView`封装的比较到位,在项目前期,的确为我们节省了不少时间。 但随着项目持续深入,对于`CameraView`的使用进入深水区,逐渐出现满足不了我们需求的情况。 `Github`中的`issues`中,有些`BUG`作者一直没有修复。 那要怎么办呢 ? 项目迫切地需要实现相关功能,只能自己硬着头皮去看它的源码,去解决这些问题。 [上篇文章][Link 1],我们对带滤镜拍照的整个流程有了大致的了解,这篇文章,我们重点来看如何保存滤镜的效果。 以下源码解析基于`CameraView 2.7.2` implementation("com.otaliastudios:cameraview:2.7.2") > 为了在博客上更好的展示,本文贴出的代码进行了部分精简 ![在这里插入图片描述][c92f5c122cb6428ba2fa01c2f4421049.png] 绘制并保存是在`SnapshotGlPictureRecorder`类中`takeFrame()`方法的第5部分。 // 5. Draw and save long timestampUs = surfaceTexture.getTimestamp() / 1000L; LOG.i("takeFrame:", "timestampUs:", timestampUs); mTextureDrawer.draw(timestampUs); if (mHasOverlay) mOverlayDrawer.render(timestampUs); mResult.data = eglSurface.toByteArray(Bitmap.CompressFormat.JPEG); 这部分代码做了两件事 : * `mTextureDrawer.draw` : 绘制滤镜 * `eglSurface.toByteArray` : 转化为`JPEG`格式的`Byte`数组 ### 2. 绘制滤镜 ### 首先来看`mTextureDrawer.draw()`,`mTextureDrawer`[上篇文章][Link 2]我们已经介绍过了,通过它,我们最终会调用到`mFilter.draw()` public void draw(final long timestampUs) { if (mPendingFilter != null) { release(); mFilter = mPendingFilter; mPendingFilter = null; } if (mProgramHandle == -1) { mProgramHandle = GlProgram.create( mFilter.getVertexShader(), mFilter.getFragmentShader()); mFilter.onCreate(mProgramHandle); Egloo.checkGlError("program creation"); } GLES20.glUseProgram(mProgramHandle); Egloo.checkGlError("glUseProgram(handle)"); mTexture.bind(); mFilter.draw(timestampUs, mTextureTransform); mTexture.unbind(); GLES20.glUseProgram(0); Egloo.checkGlError("glUseProgram(0)"); } 可以看到,`mTextureDrawer.draw()`里,调用顺序如下 1. 调用`GlProgram.create()`创建一个`OpenGL Program` 2. 调用`Filter`里的`onCreate()` 3. `GLES20.glUseProgram()`,启用这个`Program` 4. 调用`mTexture.bind()`,`mTexture`是`GlTexture`,这个主要是绑定`Texture` 5. 然后调用`Filter`的`onDraw`方法 6. 最后,调用`mTexture.unbind`解除绑定 这里我们重点来看`mFilter.onDraw`,也就是上文说的`Filter`接口中的`onDraw`。 所以拍照的绘制,就是在这一块执行的。 ### 3. 转化为`JPEG`格式的`Byte`数组 ### 当使用`OpenGL`绘制到滤镜后,来看接下来的`eglSurface.toByteArray()` 这里的`eglSurface`是`EglSurface`,可以看到其内部调用了`toOutputStream`,并最终将`ByteArray`返回。 public fun toByteArray(format: Bitmap.CompressFormat = Bitmap.CompressFormat.PNG): ByteArray { val stream = ByteArrayOutputStream() stream.use { toOutputStream(it, format) return it.toByteArray() } } `toOutputStream`中调用了`GLES20.glReadPixels`,作用是从`GPU`帧缓冲区中读取像素数据。 > 具体来说,这个函数可以读取当前帧缓冲区或纹理映射到帧缓冲区上的像素数据,并将这些像素数据写入到内存缓冲区中。这是OpenGL提供的用于从帧缓冲区中读取像素数据的函数。在使用glReadPixels()函数捕获屏幕截图时,一般需要先创建一个大小等同于屏幕分辨率的缓冲区对象,并将其与PBO相关联。然后,通过调用glReadPixels()函数来读取帧缓冲区中的像素数据,并将其存储到PBO中。最后,可以使用标准C/C++语法将PBO中的像素数据保存为图片文件,或者进行其他处理和分析。 public fun toOutputStream(stream: OutputStream, format: Bitmap.CompressFormat = Bitmap.CompressFormat.PNG) { if (!isCurrent()) throw RuntimeException("Expected EGL context/surface is not current") val width = getWidth() val height = getHeight() val buf = ByteBuffer.allocateDirect(width * height * 4) buf.order(ByteOrder.LITTLE_ENDIAN) GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf) Egloo.checkGlError("glReadPixels") buf.rewind() val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) bitmap.copyPixelsFromBuffer(buf) bitmap.compress(format, 90, stream) bitmap.recycle() } 调用了`GLES20.glReadPixels`后,像素数据会被存储在`buf`中,接着通过`buf`创建`Bitmap`,并将`Bitmap`返回。 ### 4. 分发回调 ### 最后,调用`dispatchResult`分发回调 protected void dispatchResult() { if (mListener != null) { mListener.onPictureResult(mResult, mError); mListener = null; mResult = null; } } 实现了`PictureResultListener`接口的是`CameraBaseEngine` public void onPictureResult(PictureResult.Stub result, Exception error) { mPictureRecorder = null; if (result != null) { getCallback().dispatchOnPictureTaken(result); } else { getCallback().dispatchError(new CameraException(error, CameraException.REASON_PICTURE_FAILED)); } } 可以看到这里调用了`getCallback().dispatchOnPictureTaken()`,最终会调用到`CameraView.dispatchOnPictureTaken()` @Override public void dispatchOnPictureTaken(final PictureResult.Stub stub) { LOG.i("dispatchOnPictureTaken", stub); mUiHandler.post(new Runnable() { @Override public void run() { PictureResult result = new PictureResult(stub); for (CameraListener listener : mListeners) { listener.onPictureTaken(result); } } }); } 这里会遍历`mListeners`,然后调用`onPictureTaken`方法。 而`mListeners`什么时候被添加呢 ? `CameraView`中有一个`addCameraListener`方法,专门用来添加回调。 public void addCameraListener(CameraListener cameraListener) { mListeners.add(cameraListener); } ### 5. 设置回调 ### 所以我们只要添加了这个回调,并实现onPictureTaken方法,就可以在onPictureTaken()中获取到拍照后的图像信息了。 binding.cameraView.addCameraListener(object : CameraListener() { override fun onPictureTaken(result: PictureResult) { super.onPictureTaken(result) //拍照回调 val bitmap = BitmapFactory.decodeByteArray(result.data, 0, result.data.size) bitmap?.also { Toast.makeText(this@Test2Activity, "拍照成功", Toast.LENGTH_SHORT).show() //将Bitmap设置到ImageView上 binding.img.setImageBitmap(it) val file = getNewImageFile() //保存图片到指定目录 ImageUtils.save(it, file, Bitmap.CompressFormat.JPEG) } } }) ### 6. 其他 ### #### 6.1 CameraView源码解析系列 #### [Android 相机库CameraView源码解析 (一) : 预览-CSDN博客][Android _CameraView_ _ _ _-CSDN] [Android 相机库CameraView源码解析 (二) : 拍照-CSDN博客][Android _CameraView_ _ _ _-CSDN 1] [Android 相机库CameraView源码解析 (三) : 滤镜相关类说明-CSDN博客][Android _CameraView_ _ _ _-CSDN 2] [Android 相机库CameraView源码解析 (四) : 带滤镜预览-CSDN博客][Android _CameraView_ _ _ _-CSDN 3] [Android 相机库CameraView源码解析 (五) : 带滤镜拍照-CSDN博客][Link 1] [Android 相机库CameraView源码解析 (六) : 保存滤镜效果-CSDN博客][Android _CameraView_ _ _ _-CSDN 4] [natario1_CameraView]: https://github.com/natario1/CameraView [Link 1]: https://blog.csdn.net/EthanCo/article/details/134517154 [c92f5c122cb6428ba2fa01c2f4421049.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/25/efcb97fdcbbe4f638545304eabb4bb89.png [Link 2]: https://blog.csdn.net/EthanCo/article/details/134310244 [Android _CameraView_ _ _ _-CSDN]: https://blog.csdn.net/EthanCo/article/details/134511622 [Android _CameraView_ _ _ _-CSDN 1]: https://blog.csdn.net/EthanCo/article/details/134545086 [Android _CameraView_ _ _ _-CSDN 2]: https://blog.csdn.net/EthanCo/article/details/134517249 [Android _CameraView_ _ _ _-CSDN 3]: https://blog.csdn.net/EthanCo/article/details/135202176 [Android _CameraView_ _ _ _-CSDN 4]: https://blog.csdn.net/EthanCo/article/details/134691849
相关 Android 相机库CameraView源码解析 (四) : 带滤镜预览 Android 相机库CameraView源码解析 (四) : 带滤镜预览 太过爱你忘了你带给我的痛/ 2024年04月25日 20:24/ 0 赞/ 64 阅读
相关 解决相机库CameraView多滤镜拍照错乱的BUG (二) : 解决BUG 解决相机库CameraView多滤镜拍照错乱的BUG (二) : 解决BUG 我不是女神ヾ/ 2024年04月25日 20:24/ 0 赞/ 63 阅读
相关 解决相机库CameraView多滤镜拍照错乱的BUG (一) : 复现BUG 解决相机库CameraView多滤镜拍照错乱的BUG 女爷i/ 2024年04月25日 20:24/ 0 赞/ 73 阅读
相关 为什么相机库CameraView预览和拍照的效果不一致 ? 从源码解析 : 为什么CameraView预览和拍照的效果会不一致呢 ? 我就是我/ 2024年04月25日 20:24/ 0 赞/ 70 阅读
相关 Android 相机库CameraView源码解析 (六) : 保存滤镜效果 Android 相机库CameraView源码解析 : 保存滤镜效果部分 约定不等于承诺〃/ 2024年04月25日 20:24/ 0 赞/ 74 阅读
相关 Android 相机库CameraView源码解析 (二) : 拍照 Android 相机库CameraView源码解析 : 拍照部分 ╰+哭是因爲堅強的太久メ/ 2024年04月25日 20:24/ 0 赞/ 62 阅读
相关 Android 相机库CameraView源码解析 (三) : 滤镜相关类说明 Android 相机库CameraView源码解析 : 滤镜相关类说明 - 日理万妓/ 2024年04月25日 20:24/ 0 赞/ 68 阅读
相关 Android 相机库CameraView源码解析 (一) : 预览 Android 相机库CameraView源码解析 : 预览部分 青旅半醒/ 2024年04月25日 20:23/ 0 赞/ 62 阅读
相关 Android 相机库CameraView源码解析 (五) : 带滤镜拍照 Android 相机库CameraView源码解析 : 带滤镜拍照 我不是女神ヾ/ 2024年04月25日 20:23/ 0 赞/ 90 阅读
相关 android opengl滤镜,Android OpenGL ES滤镜开发之美颜效果 主要是通过对片元着色器的处理来达到美颜的效果 高斯模糊 也叫做高斯平滑,主要是用来降低图像早生以及降低细节层次,是的图像看起来更加的平滑。 在片云着色器中,取当前数据周边 野性酷女/ 2022年10月05日 13:57/ 0 赞/ 360 阅读
还没有评论,来说两句吧...