Bitmap类是安卓中对图像进行处理的最重要的类之一,可以用来获取图片信息,进行颜色变换、剪切、旋转、缩放等操作,并且可以指定保存图片的格式。
1. Bitmap类中的两个内部枚举类:
(1)Config:用于枚举图片的颜色配置信息(RGBA及对应颜色存储的位数等信息);
(2)CompressFormat:用于设置图片的压缩方式(也就是图片的格式);
Config枚举值包括:
枚举值 | 含义 |
Bitmap.Config.ALPHA_8 | 颜色信息只由透明度组成,仅占8bit |
Bitmap.Config.ARGB_4444 | 颜色信息由RGBA组成,每个部分各占4bit,总共是16bit |
Bitmap.Config.ARGB_8888 | 颜色信息由RGBA组成,每个部分各占8bit,总共是32bit |
Bitmap.Config.RGB_565 | 颜色信息由RGB组成,R占5bit,G占6bit,B占5bit,总共是16bit |
开发过程中为了避免图片过大导致内存占用过多产生OOM(Out Of Memory),一般都是使用Bitmap.Config.RGB_565编码图片。
CompressFormat枚举值包括:
枚举值 | 含义 |
Bitmap.CompressFormat.JPEG | 有损压缩,使用JPEG算法对图片进行压缩,生成图片后缀为jpg、jpeg |
Bitmap.CompressFormat.PNG | 无损压缩,使用PNG算法对图片进行压缩,生成图片后缀为png |
Bitmap.CompressFormat.WEBP | 有损压缩,使用WebP算法对图片进行压缩。相比于JPEG,压缩后图片要更小,但是压缩所需时间是JPEG8倍左右 |
2. Bitmap类的实例化及相关操作
Bitmap类可以对图片进行裁剪、旋转、平移、缩放等操作。其具有以下两个静态方法:
Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height);
Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter);
参数1:图片源,bitmap对象实例;
参数2:开始操作图片时,x方向上第一个像素的位置;
参数3:开始操作图片时,y方向上第一个像素的位置;
参数4:操作图片时,x方向像素数;
参数5:操作图片时,y方向像素数;
参数6:Matrix(矩阵)类实例,用于表示对图片进行旋转、平移、缩放等操作的详细信息;
参数7:filter,是否过滤图片源(不太明白,需要实操);
裁剪图片,假设当前有一张图片时50 * 50像素,经过如下裁剪后:
Bitmap imgCrop = Bitmap.createBitmap(sourceImg, 5, 5, 10, 20);
得到的是一张10 * 20像素的图片,裁剪的起点在原图的(5, 5)处。
注:起始位置的像素加上偏移剪切像素值不能超过对应宽高的总像素值(不然就切到外面去了)。
旋转、平移、缩放图片:
在进行如上操作前,我们需要实例化矩阵对象Matrix,在对象中设置我们的具体操作,martix对象实例具有如下方法:
增量修改 | 替换修改 | |
旋转 | postRotate(degrees) | setRotate(degrees) |
平移 | postScale(xScale, yScale) | setScale(xScale, yScale) |
缩放 | postTranslate(x, y) | setTranslate(x, y) |
所谓的增量修改,就是post的对应操作会同时生效。而替换修改则是set操作会覆盖上一次设置的操作。
假设现在我们希望顺时针旋转一张图片60度,并在水平方向缩小至0.8,垂直方向上放大至1.2,同时向水平方向正向平移5个像素,垂直方向负向平移10个像素,我们可以这样设置Matrix对象:
Matrix matrix = new Matrix();
matrix.postRotate(60);
matrix.postScale(0.8f, 1.2f);
matrix.postTranslate(5, -10);
Bitmap newImg = Bitmap.createBitmap(sourceImg, 0, 0, source.getWidth(), source.getHeight(), matrix, false);
此外,postScale和postRotate方法还包含多两个参数的重载,多出的参数表示操作的中心坐标(x, y)。
下面是一个完整的例子,包括从资源文件中获取源bitmap对象,到创建原图的副本并绘制:
ImageView sourceImageView = findViewById(R.id.source);
ImageView copyImageView = findViewById(R.id.copy);
// 设置修改后的原图到sourceImageView上
Bitmap sourceImg = BitmapFactory.decodeResource(getResources(), R.drawable.demoImg);
sourceImg.setPixel(64, 64, Color.BLUE);
sourceImageView.setImageBitmap(sourceImg);
// 创建原图的副本,修改并设置到copyImageView上
Bitmap copy = Bitmap.createBitmap(sourceImg.getWidth(), sourceImg.getHeight(), sourceImg.getConfig()); // 获取原图的拷贝bitmap对象
Paint paint = new Paint();
Canvas canvas = new Canvas(copy);
Matrix matrix = new Matrix();
matrix.postRotate(60);
matrix.postScale(0.8, 1.2);
matrix.postTranslate(5, -10);
canvas.drawBitmap(sourceImg, matrix, paint); // 对原图的修改设置到画布上
copyImageView.setImageBitmap(copy);
3. Bitmap实例对象的常用方法
方法 | 作用 |
public void recycle() | 回收位图占用的内存空间,把位图标记为Dead |
public final boolean isRecycled() | 判断位图的资源是否已经释放 |
public final int getWidth() | 获取位图的宽度 |
public final int getHeight() | 获取位图的高度 |
public final boolean isMutable() | 获取位图是否是可修改状态 |
public int getScaledWidth(Canvas canvas) | 获取指定密度转换后的位图宽度 |
public int getScaledHeight(Canvas canvas) | 获取指定密度转换后的位图高度 |
public boolean compress(CompressFormat format, int quality, OutputStream stream) | 使用指定的算法压缩图片,可以指定图片质量,指定压缩后图片的输出流 |
4. BitmapFactory类
这个类用于从图片源中实例化bitmap对象,图片源可以是文件、流或字节数组。常用方法如下:
方法 | 说明 |
decodeFile(String pathName) | 从文件中读取图片 |
decodeFile(String pathName, Options opts) | 同上 |
decodeFileDescriptor(FileDescriptor fd) | 从文件描述符读取图片(通过jni完成,效率比较高) |
decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) | 同上 |
decodeStream(InputStream is) | 从输入流读取图片 |
decodeStream(InputStream is, Rect outPadding, Options opts) | 同上 |
decodeResource(Resources res, int id) | 从资源文件读取图片 |
decodeResource(Resources res, int id, Options opts) | 同上 |
decodeByteArray(byte[] data, int offset, int length) | 从图片字节数组读取图片 |
decodeByteArray(byte[] data, int offset, int length, Options opts) | 同上 |
(1)从资源文件中读取图片:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
imageView.setImageBitmap(bitmap);
(2)从文件中以流的形式读取图片:
假设在app的内部存储路径中(/data/data/yourPackageName/files/test.png)有一张图片test.png:
File imageFile = new File(mContext.getFilesDir() + File.separator + "test.png");
FileInputStream imageFileStream = new FileInputStream(imageFile);
Bitmap bitmap = BitmapFactory.decodeStream(imageFileStream);
imageView.setImageBitmap(bitmap);
此外BitmapFactory还可以用来保存bitmap图片到本地,使用compress方法指定压缩方式及写入文件输出流对应的文件路径:
try {
File targetWritePath = new File(mContext.getFilesDir() + File.separator + "images");
FileOutputStream stream = new FileOutputStream(targetWritePath);
BufferedOutputStream bufferedStream = new BufferedOutputStream(stream);
int quality = 100; // 0 - 100,100表示质量最高,PNG无损压缩会无视该属性
bitmap.compress(Bitmap.CompressFormat.PNG, quality, bufferedStream);
bufferedStream.flush();
bufferedStream.close();
// 释放bitmap占用的内存空间
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}
} catch (IOException e) {
e.printStackTrace();
}
使用完bitmap后调用recycle还是非常有必要的,但是如果在Activity中正在显示bitmap,不能够回收该对象,可以放到onDestory中进行回收,可以看看这篇博客。
@Override
protected void onDestroy() {
super.onDestroy();
if (sourceImg != null && !sourceImg.isRecycled()) {
sourceImg.recycle();
}
if (copyImg != null && !copyImg.isRecycled()) {
copyImg.recycle();
}
}
5. BitmapFactory.Options
BitmapFactory.Options是Bitmap的内部类,通过实例化这个类,我们可以在decode获取bitmap实例的时候,传入options对象,指定相关属性,支持的部分属性如下:
属性 | 说明 |
public boolean inJustDecodeBounds | true,若在decode时设置为true,不会分配内存获取图片完整数据,但是能够返回图片的宽高等信息,此时返回的bitmap对象是null |
public int inSampleSize | 指定图片的缩放倍数 |
public int outWidth | 图片的宽度值 |
public int outHeight | 图片的高度值 |
public int inDensity | 用于位图的像素压缩比 |
public int inTargetDensity | 用于目标位图的像素压缩比(要生成的位图) |
public byte[] inTempStorage | 创建临时文件,存储图片 |
public boolean inScaled | 设置为true时会对图片压缩,从inDensity到inTargetDensity |
public boolean inDither | 若为true,解码器尝试抖动解码 |
public Bitmap.Config inPreferredConfig | 设置图片色彩模式,默认为ARGB_8888(一个像素占32bit),也可以使用RGB_565(一个像素占用16bit) |
public String outMimeType | 设置解码的Mime类型 |
public boolean inPurgeable | inPurgeable为true情况下才生效,是否可以共享一个InputStream |
public boolean inPreferQualityOverSpeed | 设置为true优先保证解码质量,其次才是解码速度 |
public boolean inMutable | 解码的bitmap对象是否可修改,例如增加像素等 |
public int inScreenDensity | 当前屏幕的像素密度 |
下面是一个例子,当我们只想要获取图片的宽高等信息,而又不想读出整个图片,占用过多内存时,可以这样写:
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, opts);
Log.d(TAG, "image width is " + opts.outWidth + " image height is " + opts.outHeight);
如果读取到图片宽高发现图片过大时,可以对图片进行压缩:
opts.inSampleSize = 2; // 读取的图片宽高分别为原来的1/2,图片总大小是原来的1/4
opts.inJustDecodeBounds = false; // 设置回false
Bitmap bitmap = BitmapFactory.decodeFile(path, opts); // 此时读取的图片是压缩过的
本文参考自:Bitmap详解与Bitmap的内存优化