Bitmap称为位图,内部结构是像素矩阵排列。它由A、R、G、B通道组成,其中A代表Alpha,R代表Red,G代表Green。我们在开发中,通常把图片转为Bitmap来处理。
一、Bitmap结构类型
Bitmap按照内部结构,分为6种类型Config:ALPHA_8、RGB_565、ARGB_4444、ARGB_8888、RGBA_F16、HARDWARE。常用类型是RGB_565和ARGB_8888,在Android中默认使用ARGB_8888来创建Bitmap。
ALPHA_8:只使用一个Alpha单通道,共占1个字节
RGB_565:RGB通道按5:6:5比例排列,共占2个字节
ARGB_4444:ARGB通道各占4bits,共占2个字节(已弃用)
ARGB_8888:ARGB通道各占8bits,共占4个字节
RGBA_F16:RGBA通道各占16bits,共占8个字节
HARDWARE:适用于只保存在图像内存的情况,Bitmap不可修改
二、创建Bitmap
Android的API提供创建的Bitmap方法包括:匿名共享内存Bitmap、缩放Bitmap、硬件Bitmap、通用Bitmap。
1、匿名共享内存:createAshmemBitmap(),共享内存利于进程间传递
2、缩放:createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)
3、硬件:Bitmap createHardwareBitmap(GraphicBuffer graphicBuffer)
4、通用:createBitmap(int width, int height, Config config)
三、Bitmap拷贝
Bitmap可以拷贝成一个新的Bitmap,也可以拷贝到Buffer中,或者从Buffer拷贝中出来。
1、拷贝新Bitmap:copy(Config config, boolean isMutable),与原Bitmap的像素密度和色彩空间一致
2、像素拷贝到Buffer:copyPixelsToBuffer(Buffer dst),Buffer的像素与原Bitmap保持一致
3、从Buffer拷贝像素:copyPixelsFromBuffer(Buffer src),如果要再次从Buffer读取,需要先调用rewind方法
四、Bitmap压缩
Bitmap支持压缩格式包括:PNG、JPEG、WEBP,可设置压缩质量0—100。整个过程是把Bitmap压缩并输出到指定的OutputStream,以指定格式保存,也就是Bitmap保存为图片到内存里。调用的压缩方法:compress(CompressFormat format, int quality, OutputStream stream)。提供的压缩格式:
public enum CompressFormat {
JPEG (0),
PNG (1),
WEBP (2);
CompressFormat(int nativeInt) {
this.nativeInt = nativeInt;
}
final int nativeInt;
}
五、获取Bitmap大小
我们可以获取Bitmap的每行占用字节数、占用的总字节数和分配内存的字节数。
1、每行占用字节数:getRowBytes()
2、占用的总字节数:getByteCount(),通过每行占用字节数*高度得出
3、分配内存字节数:getAllocationByteCount(),如果Bitmap有重配置,可能比getByteCount()的结果更大
六、读写Bitmap的像素数组
如果要修改Bitmap的像素,我们可以把Bitmap的像素数组读出来,然后修改,再重新赋值给Bitmap。前提是Bitmap是mutable可修改的,可以通过isMutable()方法来判断该Bitmap是否支持修改。
1、获取像素点:getPixel(int x, int y)
2、获取像素数组:getPixels(int[] pixels, int offset, int stride, int x, int y, int width, int height)
3、修改像素点:setPixel(int x, int y, @ColorInt int color)
4、修改像素数组:setPixels(@ColorInt int[] pixels, int offset, int stride, int x, int y, int width, int height)
七、获取Bitmap
BitmapFactory提供四种方式来获取Bitmap:文件、资源、字节数组、输入流。
1、从文件获取:decodeFile(String pathName, Options opts)
2、从资源获取:decodeResource(Resources res, int id, Options opts)
3、从字节数组获取:decodeByteArray(byte[] data, int offset, int length, Options opts)
4、从输入流获取:decodeStream(InputStream is, Rect outPadding, Options opts)
------------------------------------------------Bitmap进阶与扩展---------------------------------------------------
八、Bitmap与Mat转换
在openCV的图像处理开发中,操作对象是Mat,而我们Android平台是Bitmap。这时候就需要Bitmap与Mat的互相转换了,需要用到Android平台的openCV SDK,使用Utils类提供的方法来转换。
1、Bitmap转Mat:Utils.bitmapToMat(bitmap, mat);
2、Mat转Bitmap:Utils.matToBitmap(mat, newBitmap);
九:孪生兄弟mipmap
mipmap使用纹理映射技术,压缩率比bitmap高一倍,所占内存比bitmap少一半。在Android开发中,Google官方建议Launcher的icon图标保存在mipmap文件夹下。Bitmap类也提供方法判断是否支持mipmap:boolean hasMipMap()。
十、图片解码为Bitmap
在Android API 28以后,提供ImageDecoder类,可以把编码的图片解码为Bitmap或Drawable,其中支持的图片格式有:PNG、JPEG、WEBP、GIF、HEIF。这个类的功能有点类似BitmapFactory,同样是把图片转为Bitmap。然鹅ImageDecoder功能比较强大。除了解码图片,还支持解码图片头信息监听回调、图片剪裁、设置图片分辨率等等。首先看下图片解码操作:
@TargetApi(28)
private void decodeImage(){
ImageDecoder.Source source = ImageDecoder.createSource(getResources(), R.drawable.ferrari);
try {
Bitmap bitmap = ImageDecoder.decodeBitmap(source);
imgDecode.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}
如果要实现解码时,对图片头信息监听,可以使用OnHeaderDecodedListener来回调,其中info包含图片宽和高。操作如下:
private void decodeImage(){
ImageDecoder.Source source = ImageDecoder.createSource(getResources(), R.drawable.ferrari);
try {
//使用OnHeaderDecodedListener监听解码信息
Bitmap bitmap = ImageDecoder.decodeBitmap(source, new ImageDecoder.OnHeaderDecodedListener() {
@Override
public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info, ImageDecoder.Source source) {
Log.e("onHeaderDecoded", "width&&height="+info.getSize());
}
});
imgDecode.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}
如果要剪裁,可以在OnHeaderDecodedListener回调中,调用setCrop方法对图片剪裁:
@TargetApi(28)
private ImageDecoder.OnHeaderDecodedListener headerDecodedListener = new ImageDecoder.OnHeaderDecodedListener() {
@Override
public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info, ImageDecoder.Source source) {
//剪裁图片
decoder.setCrop(new Rect(0, 0, 300, 200));
}
};
如果要重新设置分辨率,同样地,可以在OnHeaderDecodedListener回调中,调用setTargetSize(int width, int height)或者setTargetSampleSize(int sampleSize)来设置。
十一、加载高清大图
Android SDK提供BitmapRegionDecoder 来加载高清大图,首先调用newInstance(String pathName, boolean isShareable)方法来创建实例对象,然后是调用decodeRegion(Rect rect, Options options)来解码图片的矩形区域,解码出来是一个bitmap对象:
private void decodeLargeImage(){
String rootPath = Environment.getExternalStorageDirectory().getPath();
String filePath = rootPath + File.separator + "large.jpg";
try {
//传参可以是图片路径、输入流、字节数组
BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(filePath, false);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inSampleSize = 1;//设置加载图片分辨率
options.inJustDecodeBounds = false;
Rect rect = new Rect(0, 0, 100, 100);//待解码的矩形区域
Bitmap bitmap = regionDecoder.decodeRegion(rect, options);
imgDecode.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}
左边是原图,右边是加载图片的一部分:
十二、ndk操作Bitmap
Android有提供ndk操作Bitmap的方法,主要用到bitmap.h头文件,另外在Android.mk添加jnigraphics库依赖(如果是cmake编译,在target_link_libraries中添加jnigraphics):
LOCAL_LDLIBS += -ljnigraphics
bitmap.h头文件主要提供三个方法:
//获取bitmap对应图片的宽、高、格式
int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap, AndroidBitmapInfo* info);
//上锁,从bitmap获取像素数据赋值给addrPtr指针
int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);
//解锁
int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);
包含头文件:
#include <jni.h>
#include <android/bitmap.h>
在java层传递jobject类型的bitmap到native层,然后获取到像素数组作进一步处理:
JNIEXPORT jobject JNICALL Java_com_frank_ndk_modifyBitmap
(JNIEnv *env, jobject obj, jobject bitmap) {
AndroidBitmapInfo bitmapInfo;//bitmap信息:宽、高、格式
unsigned char* bitmapPtr;//像素数组
int ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);//获取bitmap信息
if (ret != ANDROID_BITMAP_RESULT_SUCCESS){
return NULL;
}
AndroidBitmap_lockPixels(env, bitmap, &bitmapPtr);//上锁
...//操作像素数组
AndroidBitmap_unlockPixels(env, bitmap);//解锁
return bitmap;
}
十三、使用BitmapShader渲染Bitmap
Android中有提供BitmapShader来把Bitmap渲染成图片,以自定义圆角图片为例,操作步骤包含:创建BitmapShader、计算缩放系数、设置缩放矩阵、渲染圆角图片。在自定义View的onDraw方法调用以上步骤即可,代码如下:
//利用BitmapShader渲染自定义圆角图片
private void drawShader(Canvas canvas){
//使用bitmap创建shader
BitmapShader shader = new BitmapShader(mBitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
//计算bitmap宽与高的较小值
int size = Math.min(mBitmap.getWidth(), mBitmap.getHeight());
//计算缩放系数
float scale = mRadius * 2.0f / size;
mMatrix.setScale(scale, scale);
//shader设置matrix矩阵
shader.setLocalMatrix(mMatrix);
//shader传递给paint
mPaint.setShader(shader);
//渲染圆角图片
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
}
十四、使用RenderNode来绘制Bitmap
在Android Q中,新增RenderNode用于构建硬件加速渲染层次布局。每一个RenderNode包含一个DisplayList和一系列属性,它把复杂的布局切分成更小区域,这样局部更新时所花费代价更小。它需要使用RecordingCanvas来绘制,仅支持硬件加速场景,可以使用Canvas.isHardwareAccelerated()来判断是否支持硬件加速 。
1、创建RenderNode,设置待渲染的矩形区域
RenderNode renderNode = RenderNode.create("myRenderNode");
renderNode.setLeftTopRightBottom(0, 0, 50, 50); // Set the size to 50x50
RecordingCanvas canvas = renderNode.startRecording();
try {
canvas.drawRect(rect);
} finally {
renderNode.endRecording();
}
2、在View中绘制RenderNode
protected void onDraw(Canvas canvas) {
if (canvas instanceof RecordingCanvas) {
RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
if (!myRenderNode.hasDisplayList()) {
updateDisplayList(myRenderNode);
}
recordingCanvas.drawRenderNode(myRenderNode);
}
}
3、遍历Bitmap来分块渲染
private void createDisplayList() {
mRenderNode = RenderNode.create("mRenderNode");
mRenderNode.setLeftTopRightBottom(0, 0, width, height);
RecordingCanvas canvas = mRenderNode.startRecording();
try {
for (Bitmap b : mBitmaps) {
canvas.drawBitmap(b, 0.0f, 0.0f, null);
canvas.translate(0.0f, b.getHeight());
}
} finally {
mRenderNode.endRecording();
}
}
4、释放资源
renderNode.discardDisplayList();