当前位置:   article > 正文

Android开发中Bitmap的全面总结_android bitmap 拷贝

android bitmap 拷贝

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)。提供的压缩格式:

  1. public enum CompressFormat {
  2. JPEG (0),
  3. PNG (1),
  4. WEBP (2);
  5. CompressFormat(int nativeInt) {
  6. this.nativeInt = nativeInt;
  7. }
  8. final int nativeInt;
  9. }

五、获取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功能比较强大。除了解码图片,还支持解码图片头信息监听回调、图片剪裁、设置图片分辨率等等。首先看下图片解码操作:

  1. @TargetApi(28)
  2. private void decodeImage(){
  3. ImageDecoder.Source source = ImageDecoder.createSource(getResources(), R.drawable.ferrari);
  4. try {
  5. Bitmap bitmap = ImageDecoder.decodeBitmap(source);
  6. imgDecode.setImageBitmap(bitmap);
  7. } catch (IOException e) {
  8. e.printStackTrace();
  9. }
  10. }

如果要实现解码时,对图片头信息监听,可以使用OnHeaderDecodedListener来回调,其中info包含图片宽和高。操作如下:

  1. private void decodeImage(){
  2. ImageDecoder.Source source = ImageDecoder.createSource(getResources(), R.drawable.ferrari);
  3. try {
  4. //使用OnHeaderDecodedListener监听解码信息
  5. Bitmap bitmap = ImageDecoder.decodeBitmap(source, new ImageDecoder.OnHeaderDecodedListener() {
  6. @Override
  7. public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info, ImageDecoder.Source source) {
  8. Log.e("onHeaderDecoded", "width&&height="+info.getSize());
  9. }
  10. });
  11. imgDecode.setImageBitmap(bitmap);
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. }
  15. }

如果要剪裁,可以在OnHeaderDecodedListener回调中,调用setCrop方法对图片剪裁:

  1. @TargetApi(28)
  2. private ImageDecoder.OnHeaderDecodedListener headerDecodedListener = new ImageDecoder.OnHeaderDecodedListener() {
  3. @Override
  4. public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info, ImageDecoder.Source source) {
  5. //剪裁图片
  6. decoder.setCrop(new Rect(0, 0, 300, 200));
  7. }
  8. };

如果要重新设置分辨率,同样地,可以在OnHeaderDecodedListener回调中,调用setTargetSize(int width, int height)或者setTargetSampleSize(int sampleSize)来设置。

十一、加载高清大图

Android SDK提供BitmapRegionDecoder 来加载高清大图,首先调用newInstance(String pathName, boolean isShareable)方法来创建实例对象,然后是调用decodeRegion(Rect rect, Options options)来解码图片的矩形区域,解码出来是一个bitmap对象:

  1. private void decodeLargeImage(){
  2. String rootPath = Environment.getExternalStorageDirectory().getPath();
  3. String filePath = rootPath + File.separator + "large.jpg";
  4. try {
  5. //传参可以是图片路径、输入流、字节数组
  6. BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(filePath, false);
  7. BitmapFactory.Options options = new BitmapFactory.Options();
  8. options.inJustDecodeBounds = true;
  9. options.inSampleSize = 1;//设置加载图片分辨率
  10. options.inJustDecodeBounds = false;
  11. Rect rect = new Rect(0, 0, 100, 100);//待解码的矩形区域
  12. Bitmap bitmap = regionDecoder.decodeRegion(rect, options);
  13. imgDecode.setImageBitmap(bitmap);
  14. } catch (IOException e) {
  15. e.printStackTrace();
  16. }
  17. }

左边是原图,右边是加载图片的一部分:

      

十二、ndk操作Bitmap

Android有提供ndk操作Bitmap的方法,主要用到bitmap.h头文件,另外在Android.mk添加jnigraphics库依赖(如果是cmake编译,在target_link_libraries中添加jnigraphics):

LOCAL_LDLIBS += -ljnigraphics

bitmap.h头文件主要提供三个方法:

  1. //获取bitmap对应图片的宽、高、格式
  2. int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap, AndroidBitmapInfo* info);
  3. //上锁,从bitmap获取像素数据赋值给addrPtr指针
  4. int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);
  5. //解锁
  6. int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);

包含头文件:

  1. #include <jni.h>
  2. #include <android/bitmap.h>

在java层传递jobject类型的bitmap到native层,然后获取到像素数组作进一步处理:

  1. JNIEXPORT jobject JNICALL Java_com_frank_ndk_modifyBitmap
  2. (JNIEnv *env, jobject obj, jobject bitmap) {
  3. AndroidBitmapInfo bitmapInfo;//bitmap信息:宽、高、格式
  4. unsigned char* bitmapPtr;//像素数组
  5. int ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);//获取bitmap信息
  6. if (ret != ANDROID_BITMAP_RESULT_SUCCESS){
  7. return NULL;
  8. }
  9. AndroidBitmap_lockPixels(env, bitmap, &bitmapPtr);//上锁
  10. ...//操作像素数组
  11. AndroidBitmap_unlockPixels(env, bitmap);//解锁
  12. return bitmap;
  13. }

十三、使用BitmapShader渲染Bitmap

Android中有提供BitmapShader来把Bitmap渲染成图片,以自定义圆角图片为例,操作步骤包含:创建BitmapShader、计算缩放系数、设置缩放矩阵、渲染圆角图片。在自定义View的onDraw方法调用以上步骤即可,代码如下:

  1. //利用BitmapShader渲染自定义圆角图片
  2. private void drawShader(Canvas canvas){
  3. //使用bitmap创建shader
  4. BitmapShader shader = new BitmapShader(mBitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
  5. //计算bitmap宽与高的较小值
  6. int size = Math.min(mBitmap.getWidth(), mBitmap.getHeight());
  7. //计算缩放系数
  8. float scale = mRadius * 2.0f / size;
  9. mMatrix.setScale(scale, scale);
  10. //shader设置matrix矩阵
  11. shader.setLocalMatrix(mMatrix);
  12. //shader传递给paint
  13. mPaint.setShader(shader);
  14. //渲染圆角图片
  15. canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
  16. }

十四、使用RenderNode来绘制Bitmap

在Android Q中,新增RenderNode用于构建硬件加速渲染层次布局。每一个RenderNode包含一个DisplayList和一系列属性,它把复杂的布局切分成更小区域,这样局部更新时所花费代价更小。它需要使用RecordingCanvas来绘制,仅支持硬件加速场景,可以使用Canvas.isHardwareAccelerated()来判断是否支持硬件加速 。

1、创建RenderNode,设置待渲染的矩形区域

  1. RenderNode renderNode = RenderNode.create("myRenderNode");
  2. renderNode.setLeftTopRightBottom(0, 0, 50, 50); // Set the size to 50x50
  3. RecordingCanvas canvas = renderNode.startRecording();
  4. try {
  5. canvas.drawRect(rect);
  6. } finally {
  7. renderNode.endRecording();
  8. }

2、在View中绘制RenderNode

  1. protected void onDraw(Canvas canvas) {
  2. if (canvas instanceof RecordingCanvas) {
  3. RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
  4. if (!myRenderNode.hasDisplayList()) {
  5. updateDisplayList(myRenderNode);
  6. }
  7. recordingCanvas.drawRenderNode(myRenderNode);
  8. }
  9. }

3、遍历Bitmap来分块渲染

  1. private void createDisplayList() {
  2. mRenderNode = RenderNode.create("mRenderNode");
  3. mRenderNode.setLeftTopRightBottom(0, 0, width, height);
  4. RecordingCanvas canvas = mRenderNode.startRecording();
  5. try {
  6. for (Bitmap b : mBitmaps) {
  7. canvas.drawBitmap(b, 0.0f, 0.0f, null);
  8. canvas.translate(0.0f, b.getHeight());
  9. }
  10. } finally {
  11. mRenderNode.endRecording();
  12. }
  13. }

4、释放资源

renderNode.discardDisplayList();

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/46127?site
推荐阅读
相关标签
  

闽ICP备14008679号