OpenGLES:glReadPixels()获取相机GLSurfaceView预览数据并保存

news2024/11/27 18:30:22

Android现行的Camera API2机制可以通过onImageAvailable(ImageReader reader)回调从底层获取到Jpeg、YuvRaw三种格式的Image,然后通过保存Image实现拍照功能,但是却并没有Api能直接在上层直接拿到实时预览的数据。

Android Camera预览的实现是上层下发SurfaceCameraHAL,由CameraHAL也就是android.hardware.camera.provider@2.4-service进程往Surface对应的Buffer中填充预览数据,然后再copySurfaceFling中由OpenGL进行渲染显示。

实际相机开发中,不仅仅只是要实现预览,还经常需要拿到预览数据做一些特效处理,那么问题来了,怎么在相机App获取到实时预览数据呢?

这跟上层Camera App用于显示SurfaceView控件有关:

  • 如果上层使用的是GLSurfaceView,可以直接通过OpenGLESglReadPixels()获取到copy到显存中的预览数据
  • 如果上层使用的不是GLSurfaceView,可以通过自己搭建EGL环境,然后在EGL环境中调用OpenGLESglReadPixels()获取到预览数据。

GLSurfaceView其实就是Android封装好的EGL+SufaceView控件,Android的所有渲染最终都是通过OpenGL来实现的,所以万变不离其宗,本质上上层Camera App都只能通过OpenGLESglReadPixels()实现预览数据的获取。

一个SurfaceAndroid EGL中对应一个FrameBuffer,学习过OpenGL的应该都知道,一个FrameBuffer会有多个附着(attachment),其中必须且只能有一个ColorBuffer附着,有一个或多个StencilBufferDepthBuffer附着

glReadPixels()仅限于读取ColorBuffer,无法读取DepthBufferStencilBuffer,它可以将图像内容从显存读取到内存中,将ColorBuffer中的像素值保存到预分配的内存缓冲区。

前面关于OpenGLES的博文中,有两篇是使用OpenGLES实现相机的相关功能,一篇是《OpenGLES:GLSurfaceView实现Android Camera预览》,一篇是《OpenGLES:相机实时滤镜四宫格、九宫格》,今天就在这两篇博文基础上实现相机预览数据的获取和保存。

相机实现部分在此不做过多讲解,有兴趣的可以参看前面两篇博文,有详细的讲解
本文主要展示glReadPixels()对相机预览数据获取的实现

代码实现其实很简单
GLSurfaceView.Renderer实现类的onDrawFrame(GL10 gl)函数中新增如下代码段:

if (shouldTakePic) {
    //预览尺寸
	int w = 1080;int h = 1440;
    //预览数据保存成照片的目录
	String savePath = Environment.getExternalStorageDirectory().getPath() + "/DCIM/MyCamera/";

	int[] iat = new int[w * h];
	IntBuffer ib = IntBuffer.allocate(w * h);
    //(0,580)距离屏幕左下角的距离,与glViewport(0, 580,...)保持一致
	glReadPixels(0, 580, w, h, GL_RGBA, GL_UNSIGNED_BYTE, ib);

	int[] ia = ib.array();
	//glReadPixels 读取的内容是上下翻转的,要处理一下
	for (int i = 0; i < h; i++) {
		for (int j = 0; j < w; j++) {
			iat[(h - i - 1) * w + j] = ia[i * w + j];
		}
	}

	Bitmap inBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
	inBitmap.copyPixelsFromBuffer(IntBuffer.wrap(iat));

	ByteArrayOutputStream bos = new ByteArrayOutputStream();
	inBitmap.compress(Bitmap.CompressFormat.JPEG, 90, bos);
	byte[] bitmapData = bos.toByteArray();

	File tempDir = new File(savePath);
	tempDir.mkdirs();
	String fileName = "temp_" + System.currentTimeMillis() + ".jpeg";
	File imgFile = new File(savePath, fileName);

	try {
		FileOutputStream output = new FileOutputStream(imgFile);
		output.write(bitmapData);
		output.flush();
		output.close();
		Log.v(TAG, "ImageReader X");
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		inBitmap.recycle();
	}
}

glReadPixels读取的内容上下翻转处理还有另外一种实现,
原理都是一样的,实现起来大同小异

if (shouldTakePic) {
	//预览尺寸
	int w = 1080;
	int h = 1440;
	//预览数据保存成照片的目录
	String savePath = Environment.getExternalStorageDirectory().getPath() + "/DCIM/MyCamera/";
	
	int b[] = new int[(int) (w * h)];
	int bt[] = new int[(int) (w * h)];
	IntBuffer buffer = IntBuffer.wrap(b);
	buffer.position(0);
    //(0,580)距离屏幕左下角的距离,与glViewport(0, 580,...)保持一致
	glReadPixels(0, 580, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
	for (int i = 0; i < h; i++) {
		for (int j = 0; j < w; j++) {
			int pix = b[i * w + j];
			int pb = (pix >> 16) & 0xff;
			int pr = (pix << 16) & 0x00ff0000;
			int pix1 = (pix & 0xff00ff00) | pr | pb;
			bt[(h - i - 1) * w + j] = pix1;
		}
	}
	Bitmap inBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
	inBitmap.copyPixelsFromBuffer(buffer);
	inBitmap = Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);

	ByteArrayOutputStream bos = new ByteArrayOutputStream();
	inBitmap.compress(Bitmap.CompressFormat.JPEG, 90, bos);
	byte[] bitmapData = bos.toByteArray();
	ByteArrayInputStream fis = new ByteArrayInputStream(bitmapData);

	String tempPicFile = "temp_" + System.currentTimeMillis() + ".jpeg";
	File tempDir = new File(savePath);
	tempDir.mkdirs();
	try {
		File tmpFile = new File(tempDir, tempPicFile);
		FileOutputStream fos = new FileOutputStream(tmpFile);
		byte[] buf = new byte[1024];
		int len;
		while ((len = fis.read(buf)) > 0) {
			fos.write(buf, 0, len);
		}
		fis.close();
		fos.close();
		inBitmap.recycle();
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

验证下效果,抓两张预览照试试:

抓一张普通预览:

抓一张四宫格滤镜预览:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1300615.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

飞天使-linux操作的一些技巧与知识点

命令行光标移动到行首行尾 ctrl a 跳到首 ctrl e 跳到尾/etc/passwd rpm 包格式 RPM&#xff08;Red Hat Package Manager&#xff09;是一种常用的Linux软件包管理系统&#xff0c;它使用特定的命名规则来标识和命名软件包。RPM包的名称格式通常遵循以下规则&#xff1a;…

angular route guards

它的作用 有5种guard 1.先创建一个services的typescript class 这个sivices 实现了 canActive 这个interface &#xff08;接口&#xff09; 返回true可以继续执行&#xff0c; 返回false中断执行 2. 在app.module的providers中使用这个services 3.最后在路由文件中使用 路…

一张图解释maxHistory、fileNamePattern、totalSizeCap、maxFileSize之间的关系

文中结合配置和坐标轴的关系进行案例讲解&#xff0c;文中知识针对每个案例情况进行解释&#xff0c;当然也存在其他案例&#xff0c;对比理解即可。 配置案例 <rollingPolicy class"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><fileNam…

配电房无人值守管理系统

配电房无人值守管理系统是一种基于物联网、云计算、大数据等技术的智能化管理系统&#xff0c;依托电易云-智慧电力物联网&#xff0c;旨在实现对配电房的远程实时监控、自动化运行、故障预警和处理等功能&#xff0c;从而实现配电房的无人值守。 实时监测&#xff1a;通过安装…

MATLAB | 官方举办的动图绘制大赛 | 第四周(收官周)赛情回顾

MATHWORKS官方举办的迷你黑客大赛第三期(MATLAB Flipbook Mini Hack)圆满结束&#xff0c;虽然我的水平和很多大佬还有比较大的差距&#xff0c;但所有奖也算是拿满了&#xff1a; 专家评选前三名&#xff0c;以及投票榜前十&#xff1a;~ 每周的阶段性获奖者&#xff1a; 下面…

深入了解Python pydash库

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在数据处理和分析领域&#xff0c;Python一直是一种强大的编程语言。然而&#xff0c;在处理大规模数据集和执行复杂操作时&#xff0c;有时候需要更高效的工具。在本文中&#xff0c;我们将深入探讨pydash库&am…

Docker | 使用Dockerfile制作镜像

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏:Docker系列 ✨特色专栏: MySQL学习 🥭本文内容: DDocker | 使用Dockerfile制作镜像 📚个人知识库: 知识库,欢迎大家访问 1.前言 大家…

Python字典去重竟然比集合去重快速40多倍

这里写目录标题 对比代码结果图代码解析 对比代码 from glob import glob from tqdm import tqdm import time path_listglob("E:/sky_150b/任务组_20231207_2023/*.jsonl") # for two in tqdm(path_list): onepath_list[0]with open(one,"r",encoding&q…

第 9 部分 — 内存增强 Transformer 网络:数学见解

一、说明 在顺序数据处理领域&#xff0c;传统的 Transformer 架构擅长处理短期依赖性&#xff0c;但在需要大量内存和长序列上下文保留的任务中表现不佳。在这篇综合博客中&#xff0c;我打算探索一种新颖的混合方法&#xff0c;将 Transformer 与显式长期记忆模块集成在一起。…

viple模拟器使用(五):Web 2D模拟器中实现两距离局部最优迷宫算法

关于两距离局部最优迷宫算法的原理本文不再赘述&#xff0c;详情请参考&#xff1a;viple模拟器使用&#xff08;四&#xff09;&#xff0c;归纳总结为&#xff1a; 前方有路&#xff0c;则直行&#xff1b; 前方无路&#xff0c;则右转90度&#xff0c;标记右转完成&#xff…

【js】js实现多个视频连续播放:

文章目录 一、效果&#xff1a;二、实现&#xff1a;三、案例&#xff1a; 一、效果&#xff1a; 二、实现&#xff1a; <!DOCTYPE html> <html> <head><title>Video Player</title><style>#progressBar { width: 800px;height: 20px;b…

Botton进一步了解(点击事件)

点击事件和长按事件 监听器&#xff1a;专门监听控件的动作行为。只有控件发生了指定的动作&#xff0c;监听器才会触发开关区执行对应的代码逻辑。按钮控件有两种常用的监听器&#xff1a; 点击监听器&#xff1a;通过setOnClickListener方法设置。按钮被按住少于500ms时会触…

集合的基本内容以及迭代器的介绍

在我们java中我们存储数据的方式&#xff0c;在我们之前学习中数组是可以存储我们的数据的&#xff0c;但是数组存储数据有一些弊端&#xff0c;灵活性不强&#xff0c;存储数据类型有限&#xff0c;灵活性不强这一点主要是体现在两个方面&#xff1a;一方面数组的长度是自定义…

【数据结构】——排序篇(下)

前言&#xff1a;前面我们的排序已经详细的讲解了一系列的方法&#xff0c;那么我们现在久之后一个归并排序了&#xff0c;所以我们现在就来讲解一下归并排序。 归并排序&#xff1a; 归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种有效的排序算法,该算法…

springboot集成knife4j详细教程

使用原生的swagger作为接口文档&#xff0c;功能不够强大&#xff0c;并且默认的ui比较简陋&#xff0c;不符合大众审美。所以实际开发中推荐使用knife4j对swagger进行增强。knife4j的地址&#xff1a;https://gitee.com/xiaoym/knife4j 基本使用 想要使用knife4j非常简单&…

MyBatis 四大核心组件之 ResultSetHandler 源码解析

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

【Linux】 通过宝塔面板重置SSH密码,无需原密码

目录 应用场景&#xff1a; 步骤一&#xff1a;登录面板 步骤二&#xff1a;安装linux工具箱 步骤三&#xff1a;点击设置 步骤总结&#xff1a; 应用场景&#xff1a; 忘记ssh密码&#xff08;前提要有宝塔的账号密码&#xff09;修改重置ssh密码 步骤一&#xff1a;登录…

LVGL | Demo实例使用说明

LVGL | Demo实例使用说明 时间&#xff1a;2023年12月10日21:51:17 文章目录 LVGL | Demo实例使用说明Demos for LVGLAdd the examples to your projectsDemosWidgetsMusic playerKeypad and encoderBenchmarkStress Contributing Demos for LVGL Add the examples to your p…

【工具】JS脚本|浏览器脚本6分钟极速入门 · 开发一个限制自己刷b站的脚本

这张图花里胡哨的是让AI生成的&#xff0c;我觉得怪可爱的&#xff0c;就直接作为封面了。 这篇文章中会开发一个JS脚本&#xff0c;这是一个用来限制b站网页版功能的脚本&#xff0c;避免刷b站的时间过长。功能如下&#xff1a; 除了搜索、视频页、私信页之外的任何页都会被重…