关于Bitmap,它和Drawable差不多就是一种图片,Bitmap相关的使用主要有两种:
分别对应下面两个方法:
imageView.setImageBitmap(Bitmap bm);
Canvas canvas = new Canvas(Bitmap bm);
我们知道Bitmap
是位图,是由像素点组成的,这就涉及到两个问题:
一:如何存储每个像素点?
二:怎么压缩像素点?
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节省一半的内存开销。
我们不妨来计算一下,如果一张和手机屏幕大小一样的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倍。
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异常
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;
}
Bitmap bm=Bitmap.createBitmap(xxx);
BitmapDrawable bd= new BitmapDrawable(getResource(), bm);
一个像素点一般由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(具体细节与原理自己百度)。
采样的意思就是我们需要采集到的这些YUV分量的值,然后接下来我们需要存储下来这个值。所以根据采样、存储方式的不同,YUV有如下几种格式。
在采样时,我们可以临时使用 三个byte[]数组 来 分别存储 采集到的Y、U、V的值。比如 byte[] byte = [YYYYY UUUUU VVVVV]这样的存储方法。
如上文所描述的,YUV分量全部进行采样,每个YUV都与RGB一一对应;
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)]
对图片中的所有像素点,Y分量全部采样。在每一相同像素水平行上,U 和 V 分量分别 交替 进行采样,即 { YU-YV-YU-YV… },这样YUV数据占用的存储空间明显减少。
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)]
对图片中的所有像素点,Y分量全部采样。在第0像素水平行(偶数行), U 分量 间隔 进行采样,而不采样V分量。在第1行(奇数行), V 分量 间隔 进行采样,而不采样U分量,即 偶数行{ YU-Y-YU-Y… }、奇数行{ YV-Y-YV-Y… },这样YUV数据占用的存储空间减少一半。
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)]
上述描述的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都是连续交叉存储的。
这种方式就是将数组arrayY、arrayU、arrayV中的数据 按顺序 组合起来。那么这种顺序可以是 先存Y,再存U,最后存V
。也可以是先存Y,再存V,再存U
。我们这里把前者称为 YU的存储方式,把后者称为 YV的存储方式,即:YU、YV这两种存储格式,又统称为 YUV420P 格式。
Y数组: [Y, Y, Y, Y, Y, Y, Y, Y]
U数组: [U, U]
V数组: [V, V]
新数组:[YYYYYYYY UU VV]
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 格式:
Y数组: [Y, Y, Y, Y, Y, Y, Y, Y]
U数组: [U, U]
V数组: [V, V]
新数组:[YYYYYYYY UVUV]
Y数组: [Y, Y, Y, Y, Y, Y, Y, Y]
U数组: [U, U]
V数组: [V, V]
新数组:[YYYYYYYY VUVU]
一般使用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
Android有现成的类可以直接处理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;
}
}
这里可以分两种方式将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;
}
}
使用该方法的话显示出来的图像和源图像就没有肉眼上的区别了,而且效率也更高,推荐使用!!!