简介Bitmap、YUV,NV21与Bitmap互转
1. Bitmap
1.1Bitmap简介
关于Bitmap,它和Drawable差不多就是一种图片,Bitmap相关的使用主要有两种:
- 给ImageView设置背景
- 当做画布来使用
分别对应下面两个方法:
imageView.setImageBitmap(Bitmap bm);
Canvas canvas = new Canvas(Bitmap bm);
1.2 Bitmap的格式
我们知道Bitmap
是位图,是由像素点组成的,这就涉及到两个问题:
一:如何存储每个像素点?
二:怎么压缩像素点?
1.2.1 存储格式
Bitmap有四种存储方式,对应Bitmap.Config中的四个常量
ALPHA_8: 只存储透明度,不存储色值,1个像素点占1个字节
ARGB_4444: ARGB各用4位存储,1个像素点16位占2个字节
ARGB_8888: ARGB各用8位存储,1个像素点32位占4个字节
RGB_565: 只存储色值,不存储透明度,默认不透明,RGB分别占5,6 ,5位,一个像素点占用16位2个字节。
通常不会使用ALPHA_8,它只存储透明度,没啥用处。ARGB_4444的画质又太差,ARGB_8888的画质高但占内存较大,RGB_565还行,就是不可以设置透明度。
注意以下三点即可:
一般情况下用ARGB_8888格式存储Bitmap;
ARGB_4444画面惨不忍睹,被弃用;
假如对图片没有透明度要求,可以使用RGB_565,比ARGB_8888节省一半的内存开销。
1.2.2 压缩格式
我们不妨来计算一下,如果一张和手机屏幕大小一样的Bitmap图片,采用ARGB_8888格式存储需要多大的内存!
//按照1024*768的屏幕大小来计算,每个像素需要32位也就是4个字节,
result = 1024*768*32B=25165824B=24MB
必须要对图片进行压缩呀,压缩格式使用枚举类Bitmap.CompressFormat中,有以下三种:
Bitmap.CompressFormat.JPEG: 采用JPEG
压缩算法,是一种有损压缩格式,会在压缩过程中改变图像原本质量,画质越差,对原来的图片质量损伤越大,但是得到的文件比较小,而且JPEG
不支持透明度,当遇到透明度像素时,会以黑色背景填充。
Bitmap.CompressFormat.JPEG:采用JPEG
压缩算法,是一种有损压缩格式,会在压缩过程中改变图像原本质量,画质越差,对原来的图片质量损伤越大,但是得到的文件比较小,而且JPEG
不支持透明度,当遇到透明度像素时,会以黑色背景填充。
Bitmap.CompressFormat.WEBP:WEBP是一种同时提供了有损压缩和无损压缩的图片文件格式,在14<=api<=17时,WEBP是一种有损压缩格式,而且不支持透明度,在api18以后WEBP是一种无损压缩格式,而且支持透明度,有损压缩时,在质量相同的情况下,WEBP格式的图片体积比JPEG小40%,但是编码时间比JPEG长8倍。在无损压缩时,无损的WEBP图片比PNG压缩小26%,但是WEBP的压缩时间是PNG格式压缩时间的5倍。
1.3 Bitmap创建方法
BitmapFactory提供了多种创建bitmap的静态方法:
//从资源文件中通过id加载bitmap
//Resources res:资源文件,可以context.getResources()获得
//id:资源文件的id,如R.drawable.xxx
public static Bitmap decodeResources(Resources res,int id)
//第二种只是第一种的重载方法,多了个Options参数
public static Bitmap decodeResources(Resources res,int id,Options opt)
//传入文件路径加载,比如加载sd卡中的文件
//pathName:文件的全路径名
public static Bitmap decodeFile(String pathName);
public static Bitmap decodeFile(String pathName,Options opt);
//从byte数组中加载
//offset:对应data数组的起始下标
//length:截取的data数组的长度
public static Bitmap decodeByteArray(byte[] data,int offset , int length);
public static Bitmap decodeByteArray(byte[] data,int offset , int length,Options opt);
//从输入流中加载图片
//InputStream is:输入流
//Rect outPadding:用于返回矩形的内边距
public static Bitmap decodeStream(InputStream is);
public static Bitmap decodeStream(InputStream is,Rect outPadding,Options opt);
//FileDescriptor :包含解码位图的数据文件的路径
//通过该方式从路径加载bitmap比decodeFile更节省内存,原因不解释了。
public static Bitmap decodeFileDescriptor(FileDescriptor fd);
public static Bitmap decodeFileDescriptor(FileDescriptor fd,Rect outPadding,Options opt);
Bitmap.create静态方法
//width和height是长和宽单位px,config是存储格式
static Bitmap createBitmap(int width , int height Bitmap.Config config)
// 根据一幅图像创建一份一模一样的实例
static Bitmap createBitmap(Bitmap bm)
//截取一幅bitmap,起点是(x,y),width和height分别对应宽高
static Bitmap createBitmap(Bitmap bm,int x,int y,int width,int height)
//比上面的裁剪函数多了两个参数,Matrix:给裁剪后的图像添加矩阵 boolean filter:是否给图像添加滤波效果
static Bitmap createBitmap(Bitmap bm,int x,int y,int width,int height,Matrix m,boolean filter);
//用于缩放bitmap,dstWidth和dstHeight分别是目标宽高
createScaledBitmap(Bitmap bm,int dstWidth,int dstHeight,boolean filter)
总结:
加载图像可以使用BitmapFactory和Bitmap.create系列方法
可以通过Options实现缩放图片,获取图片信息,配置缩放比例等功能
如果需要裁剪或者缩放图片,只能使用create系列函数
注意加载和创建bitmap事通过try catch捕捉OOM异常
1.4 Bitmap与Drawable的转换
1.4.1 Drawable转换成Bitmap
public static Bitmap drawableToBitmap(Drawable drawable) { // 取 drawable 的长宽 int w = drawable.getIntrinsicWidth(); int h = drawable.getIntrinsicHeight(); // 取 drawable 的颜色格式 Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; // 建立对应 bitmap Bitmap bitmap = Bitmap.createBitmap(w, h, config); // 建立对应 bitmap 的画布 Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, w, h); // 把 drawable 内容画到画布中 drawable.draw(canvas); return bitmap; }
5.4.2 Bitmap转换成Drawable
Bitmap bm=Bitmap.createBitmap(xxx);
BitmapDrawable bd= new BitmapDrawable(getResource(), bm);
2.1 YUV简介
一个像素点一般由RGB三个颜色分量组成,其实YUV与RGB一样都是描述像素点的分量。 与RGB格式(红 - 绿 - 蓝)不同,YUV是用一个称为Y(相当于灰度)的“亮度”分量和两个“色度”分量表示,分别称为U(蓝色投影)和V(红色投影),由此得名。
YUV 与 RGB对应关系如下:知道了RGB就可以轻松的得到YUV了。
y = (( 66 * r + 129 * g + 25 * b + 128) >> 8) + 16 ;
u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128 ;
v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128 ;
其实上面描述的只是一种 YUV,每个YUV都与RGB一一对应,称其为全采样的YUV即YUV 4:4:4。但是在实际存储过程中为了减少存储占用空间,YUV可以通过不同的采样方式来减少一些U、V分量,当然恢复为RGB的时候可以几个Y分量共用U、V分量来恢复为RGB(具体细节与原理自己百度)。
2.2 YUV的采样方式
采样的意思就是我们需要采集到的这些YUV分量的值,然后接下来我们需要存储下来这个值。所以根据采样、存储方式的不同,YUV有如下几种格式。
在采样时,我们可以临时使用 三个byte[]数组 来 分别存储 采集到的Y、U、V的值。比如 byte[] byte = [YYYYY UUUUU VVVVV]这样的存储方法。
- YUV 4:4:4
如上文所描述的,YUV分量全部进行采样,每个YUV都与RGB一一对应;
- YUV 4:4:4 采集到的数组长度(每个分量是byte类型数据)
8bit + 8bit + 8bit 长度为 24bit
RGB 第一行 [(236,102,93) (39,186,106) (88,158,254)] 第二行 [(88,158,254) (39,186,106) (225,50, 43)]YUV 第一行 [(136,104,187) (130,115,69) (143,180, 91)] 第二行 [(143,180, 91) (130,115,69) (136,104,187)]
- YUV 4:2:2
对图片中的所有像素点,Y分量全部采样。在每一相同像素水平行上,U 和 V 分量分别 交替 进行采样,即 { YU-YV-YU-YV… },这样YUV数据占用的存储空间明显减少。
- YUV 4:2:2 采集到的数组长度
8bit + 4bit + 4bit 长度为 16bit,只有原来 YUV4:4:4 的2/3。
RGB 第一行 [(236,102,93) (39,186,106) (88,158,254)] 第二行 [(88,158,254) (39,186,106) (225,50, 43)]YUV 第一行 [(136,104,null) (130,null,69) (143,180,null)] 第二行 [(143,180,null) (130,null,69) (136,104,null)]
- YUV 4:2:0(重点)
对图片中的所有像素点,Y分量全部采样。在第0像素水平行(偶数行), U 分量 间隔 进行采样,而不采样V分量。在第1行(奇数行), V 分量 间隔 进行采样,而不采样U分量,即 偶数行{ YU-Y-YU-Y… }、奇数行{ YV-Y-YV-Y… },这样YUV数据占用的存储空间减少一半。
- YUV 4:2:2 采集到的数组长度
8bit + 2bit + 2bit 长度为 12bit,只有原来 YUV4:4:4 的1/2。
RGB 第一行 [(236,102,93) (39,186,106) (88,158,254)] 第二行 [(88,158,254) (39,186,106) (225,50, 43)] YUV 第一行 [(136,104,null) (130,null,null) (143,180,null)] 第二行 [(143,null, 91) (130,null,null) (136,null,187)]
3. YUV的存储方式
上述描述的YUV分量分别存储在一个数组中 [YUV] ,然而实际上的存储方式是将上述三个数组合并为一个数组,即类似 [YUVYUVYUV] 这样的格式。
一般情况下我们采用 YUV420 方式来实现采样,通常把 Y、U、V 分量数组分别命名为 arrayY,arrayU,arrayV 。如上述所述,一个像素一个Y值,即Y的最大采样数就是8个bit,其他数组长度就是它的 1/4,2个bit。
byte[] arrayY = new byte[8];
byte[] arrayU = new byte[8/4];
byte[] arrayV = new byte[8/4];
那么采样完成的的数组分别为:
Y数组: [-119, -126, -113, -69, -69, -113, -126, -119]
U数组: [104, -76]
V数组: [-93, 69]
这里大家会产生疑惑,为什么上面展示的都是正值,而这里的数组中却是负值呢?
首先请注意下,我们开始的时候使用int类型来存储所有的RGB,YUV的值的,这是由于从图片中获取到RGB的值是 int 类型的,然而在存储时采用的是 byte 数组,因为RGB的取值范围是[0, 255] ,但是一个byte就可以存储从 [-128,127] 的值,也就是可以存储256个值,这样只需要映射即可表示。例如:第一个像素 Y 的int值是136,存储中的 byte 值是 -119(136-255=-119)。
为了方便演示清晰,将数组的值都抹去,用YUV来作示例表示:
Y数组: [Y, Y, Y, Y, Y, Y, Y, Y]
U数组: [U, U]
V数组: [V, V]
YUV的格式有两大类:planar和packed。
对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。
对于packed的YUV格式,每个像素点的Y、U、V都是连续交叉存储的。
3.1planar(平面方式)
这种方式就是将数组arrayY、arrayU、arrayV中的数据 按顺序 组合起来。那么这种顺序可以是 先存Y,再存U,最后存V
。也可以是先存Y,再存V,再存U
。我们这里把前者称为 YU的存储方式,把后者称为 YV的存储方式,即:YU、YV这两种存储格式,又统称为 YUV420P 格式。
- 420采样方式 + YU存储方式 = YU12(又叫 I420 )
Y数组: [Y, Y, Y, Y, Y, Y, Y, Y]
U数组: [U, U]
V数组: [V, V]新数组:[YYYYYYYY UU VV]
- 420采样方式 + YV存储方式 = YV12
Y数组: [Y, Y, Y, Y, Y, Y, Y, Y]
U数组: [U, U]
V数组: [V, V]新数组:[YYYYYYYY VV UU]
同理可得:类似YU、YV这两种存储格式,还存在 UV交替存储
的,还有VU交替存储
的,那么我们就把前者称为UV存储,把后者称为VU存储。这两种特殊的平面存储方式又叫 Semi-Splanar ,又称为 YUV420SP 格式:
- 420采样方式 +UV存储方式 =NV12
Y数组: [Y, Y, Y, Y, Y, Y, Y, Y]
U数组: [U, U]
V数组: [V, V]新数组:[YYYYYYYY UVUV]
- 420采样方式 + VU存储方式 = NV21(常用的存储方式)****
Y数组: [Y, Y, Y, Y, Y, Y, Y, Y]
U数组: [U, U]
V数组: [V, V]新数组:[YYYYYYYY VUVU]
3.2 packed(打包方式)
一般使用422采样方式的时候会采用这种存储方式,这种方式就不像上面那种那么直白了,先用数组表示吧,注意是422采样模式,所以U、V数组长度也变化了,如下:
Y数组: [Y, Y, Y, Y, Y, Y, Y, Y]
U数组: [U, U, U, U]
V数组: [V, V, V, V]新数组:[YUYV YUYV YUYV YUYV]
如上所示,因为YUV的比例是2:1:1 ,所以取两个Y元素就需要分别取一个U和V元素,后面同理。所以根据上面这种格式:
422采样方式 + YUYV打包存储方式 = YUYV
422采样方式 + UYVY打包存储方式 = UYVY
4. NV21与Bitmap互转
Android有现成的类可以直接处理NV21的数据。
4.1 Bitmap转NV21
按照如下步骤:
1、BitmapFactory读取到这张图片;
2、bitmap.getPixels()获取图片所有像素点数据,可以得到RGB数据的数组;
3、根据RGB数组采样分别获取Y,U,V数组,并存储为NV21格式的数组;
/***1、获取bitmap图片***/
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.demo);
//或者读入SD卡中的图片
public static byte[] getNV21FromBitmap(Bitmap bitmap) {int width = bitmap.getWidth();int height = bitmap.getHeight();if (width > WIDTH_1280 || height > HEIGHT_720) {return null;//举例图像尺寸(1280,720)}/***2.bitmap.getPixels()获取图片所有像素点数据,可以得到RGB数据的数组***/int[] argb = new int[width * height];bitmap.getPixels(argb, 0, width, 0, 0, width, height);/***3.根据RGB数组采样分别获取Y,U,V数组,并存储为NV21格式的数组***/byte[] yuv = new byte[WIDTH_720 * HEIGHT_720 * 3 / 2];encodeARGBTO720PNV21(yuv, argb, width, height);//调用方法如下return yuv;
}
/***3.根据RGB数组采样分别获取Y,U,V数组,并存储为NV21格式的数组***/
private static void encodeARGBTO720PNV21(byte[] yuv420sp, int[] argb, int width, int height) {int yIndex = 0;int uvIndex = WIDTH_1280 * HEIGHT_720;int R, G, B, Y, U, V;int index = 0;for (int i = 0; i < height; i++) {for (int j = 0; j < width; j++) {R = (argb[index] & 0xff0000) >> 16;G = (argb[index] & 0xff00) >> 8;B = argb[index] & 0xff;Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));//偶数行取U,基数行取V,并存储if (i % 2 == 0 && index % 2 == 0) {yuv420sp[uvIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V));yuv420sp[uvIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U));}index++;}yIndex += (WIDTH_720 - width);uvIndex += (WIDTH_720 - width) / 2;}
}
4.2 NV21转Bitmap
这里可以分两种方式将NV21数据转换为Bitmap:使用 YuvImage 或者 ScriptIntrinsicYuvToRGB。
1.使用YuvImage+BitmapFactory来得到Bitmap图片
YuvImage image = new YuvImage(arrayNV21, ImageFormat.NV21, width, height, null);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
image.compressToJpeg(new Rect(0, 0, width, height), 80, stream);
Bitmap newBitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
stream.close();
//ImageView显示图片
ImageView imageView = findViewById(R.id.img);
imageView.setImageBitmap(newBitmap);
而使用上述方法:图片亮度会降低,而且画质也不清楚。一般不建议采用这种方法。
2.使用ScriptIntrinsicYuvToRGB来得到Bitmap图片
mNV21ToBitmap = new NV21ToBitmap(this);
//调用即可
Bitmap bmp = mNV21ToBitmap.nv21ToBitmap(byte[] nv21, int width, int height);
public class NV21ToBitmap {private RenderScript rs;private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;private Type.Builder yuvType, rgbaType;public NV21ToBitmap(Context context) {rs = RenderScript.create(context);yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));yuvType = new Type.Builder(rs, Element.U8(rs));rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs));}public Bitmap nv21ToBitmap(byte[] nv21, int width, int height) {yuvType.setX(nv21.length);Allocation in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);in.copyFrom(nv21);rgbaType.setX(width).setY(height);Allocation out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);yuvToRgbIntrinsic.setInput(in);yuvToRgbIntrinsic.forEach(out);Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);out.copyTo(bmp);return bmp;}
}
使用该方法的话显示出来的图像和源图像就没有肉眼上的区别了,而且效率也更高,推荐使用!!!
简介Bitmap、YUV,NV21与Bitmap互转相关推荐
- Android NV21与Bitmap相互转换 可实时添加水印
获取相机回调数据 @Overridepublic void onPreviewFrame(byte[] data, Camera camera) {//这里的data便是原始视频数据} 将视频数据转化 ...
- Android开发之虹软人脸识别活体检测SDK包Bitmap转NV21方法
/** * Bitmap 转化为 ARGB 数据,再转化为 NV21 数据 * * @param src 传入的 Bitmap,格式为 Bitmap.Config.ARGB_8888 * @param ...
- android bitmap nv21,Nv21转Bitmap(高效率转化)
https://blog.csdn.net/qq1137830424/article/details/81980673 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附 ...
- android bitmap nv21,Android开发之虹软人脸识别活体检测SDK包Bitmap转NV21方法
/** * Bitmap 转化为 ARGB 数据,再转化为 NV21 数据 * * @param src 传入的 Bitmap,格式为 Bitmap.Config.ARGB_8888 * @param ...
- Bitmap转圆形图片Bitmap
private Bitmap toRoundBitmap(Bitmap bitmap) { //--Bitmap转圆形图片Bitmap int width = bitmap.getWidth(); i ...
- Android view转bitmap,byte[]转Bitmap
1.自定义marker布局文件即自定义view文件 <?xml version="1.0" encoding="utf-8"?> <Linea ...
- Android 将图片网址url转化为bitmap,drawable转bitmap,file转bitmap,bitmap转file
file转bitmap File param = new File(); Bitmap bitmap= BitmapFactory.decodeFile(param.getPath()); drawa ...
- Android 将PDF文件转Bitmap,并将Bitmap以图片的方式保存到相册
转自:https://blog.csdn.net/chaogelilun/article/details/85095821 背景: 公司项目要求用户可以在手机端查看发票并将发票已图片的形式保存到相册, ...
- android将pdf文件转换成Bitmap,并将bitmap以图片的 方式保存到相册。
背景: 公司项目要求用户可以在手机端查看发票并将发票已图片的形式保存到相册,以便打印,问题来了,因为发票是.pdf,android不支持直接查看pdf,所以要下载下来然后在手机上看,当时的需求是要下载 ...
最新文章
- LeetCode简单题之丢失的数字
- 独家|手把手教你赋能Jupyter Notebooks!(附代码)
- AutoML取得最新进展 深兰科技助推复杂场景下智能化应用
- 怎样在Razor中使用HtmlHelper(MvcHtmlString)
- CSS修改tr边框属性
- Linux系统的日志管理
- final cut pro编辑的时候
- 『中级篇』Docker Cloud自动构建 Docker image(55)
- 第十七篇 --ANDROID DisplayManager 服务解析一
- 组合体计算机绘图的实验原理,机械制图及计算机绘图
- html中url路径是什么意思,url是什么意思?
- [js]js设计模式-构造函数模式
- 杭州市建筑物矢量数据(Shp格式+带高度)
- 离散数学——逻辑推理系统
- cassandra_在Chaordic上从MySQL过渡到Cassandra
- 不同系统mysql安装教程
- vue+vuecli+webpack中使用mockjs模拟后端数据
- 整理 node-sass 安装失败的原因及解决办法
- 在阿里云5年后被裁员,历经100场面试,我终于拿到了4个offer
- vscode的下载速度会特别慢问题处理
热门文章
- SEO之网站快速被收录
- mysql外网访问phpmyadmin_MYSQL如何用phpMyAdmin设置外部IP可以访问
- spring boot实战
- batocera整合包_OGA 官方固件 + Retroarch + 睡眠模式 ES整合包
- java静态网页_【屌炸天源码分享】《企业网站html静态网页模板》
- 【汇编 C】循环语句goto、while、dowhile、for
- 安徽省池州市谷歌卫星地图下载
- MATLAB矩阵画法,MATLAB矩阵与数组
- 云计算运维工程师前景怎么样?
- 那么厉害的个人所得税计算器代码你竟然不点开看看,你膨胀了!