Bitmap学习(大图片、大长图显示及加载)

前几天公司项目出现一个需求,加载网络大长图,搜索了一些方法,最终是将图片下载到本地,然后通过BitmapRegionDecode.newInstance(...)获取一个对象,然后通过这个对象去调用decodeRegion(mRect, options)得到bitmap,用手势控制图片显示的区域。解决办法的原理就是这样,可是实现起来确实遇到了很多问题,而且晚上也没有很完整的方法,基本都是参照张鸿洋大神的本地加载大图片方法,网络加载有些不太适用,而且我的场景是在recyclerview的item中的imageview。所以走了很多弯路。我这里就顺便复习一下bitmap

1.Bitmap的概念:

Android中的Bitmap对象是对位图的抽象,它可以从文件系统、资源文件夹、网络等各种不同的来源获取。位图可以看做是像素点的集合,本质上就是通过一系列二进制位来描述一张图片,具有不同色彩格式的位图使用不同数量的二进制位来描述一个像素点,因而图片质量和图片大小也就不同。

获取Bitmap对象并计算它所占用的内存大小:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.size);
int size = bitmap.getByteCount();

getByteCount()源码:

public final int getByteCount() {
    return getRowBytes() * getHeight();
}

getHeight():图片的高度(单位为px), getRowBytes方法返回的是图片的像素宽度与色彩深度的乘积。
所以getByteCount()是这样计算的:像素宽 * 像素高 * 色彩深度,其中色彩深度与Bitmap的色彩格式有关,默认为ARGB_8888.

这里补充一个小知识点吧!

ARGB_4444: 2bytes 每个像素占据2 个字节:A(Alpha)占4位的精度,R(Red)占4位的精度,G(Green)占4位的精度,B(Blue)占4位的精度,加起来一共是16位的精度,折合是2个字节,也就是一个像素占两个字节的内存,同时存储位图的透明度和颜色信息。

ARGB_8888 : 4bytes 每个像素占据4 个字节:这个类型的跟ARGB_4444的原理是一样的,只是A,R,G,B各占8个位的精度,所以一个像素占4个字节的内存。由于该类型的位图质量较好,官方特别推荐使用。但是,如果一个480800的位图设置了此类型,那个它占用的内存空间是:4808004/(10241024)=1.5M

RGB_565 : 2bytes 每个像素占据2 个字节:R占5位精度,G占6位精度,B占5位精度,一共是16位精度,折合两个字节。这里注意的时,这个类型存储的只是颜色信息,没有透明度信息

2.构造Bitmap对象

构造一个类,通常都是通过构造器方法,然而Bitmap是采用了工厂的设计模式,所以一般不会直接调用构造方法。

1.createBitmap(不建议)

图一

2.BitmapFactory工厂类的static Bitmap decodeXxx()系

图二

注意,图片的基本加载既不是本文的重点,也不是什么难点,所以这里就不贴详细代码,提示一句,有些方法需要在子线程中

3.高效的加载Bitmap

在使用bitmap时,经常会遇到内存溢出等情况,这是因为图片太大或者android系统对单个应用施加的内存限制等原因造成的
常见的错误:

OpenGLRenderer: Bitmap too large to be uploaded into a texture

java.lang.OutOfMemoryError:

所以,高效的使用bitmap就显得尤为重要,对他效率的优化也是如此。

系统为我们提供了Options类,通过对它的options进行合理的配置,我们就能够将Bitmap对象调整到令我们满意的大小。

3.1.Options类介绍

public static class Options {
    public Options() {
        inDither = false;
        inScaled = true;
        inPremultiplied = true;
    }
...      
    public Bitmap inBitmap; //用于实现Bitmap的复用,下面会具体介绍
    public int inSampleSize;  //采样率 
    public boolean inPremultiplied;   
    public boolean inDither;  //是否开启抖动
    public int inDensity; //即上文我们提到的inDensity
    public int inTargetDensity;  //目标屏幕密度,同上文提到的含义相同            
    public boolean inScaled;    //是否支持缩放
    public int outWidth;   //图片的原始宽度
    public int outHeight;  //图片的原始高度
...
}

上面代码是Options类的主要成员变量,我通过对大图片的处理方式去做具体讲解吧!

1>尺寸压缩(采样率压缩)bitmap.inSampleSize

尺寸压缩是压缩图片的像素,一张图片所占内存的大小 图片类型*宽*高,通过改变三个值减小图片所占的内存,防止OOM,当然这种方式可能会使图片失真 。

第一步,获取图片的原始宽高,通过将Options的inJustDecodeBounds属性设为true后调用decodeXXX方法,BitmapFactory只会解析图片的原始宽高信息,并不会真正的加载图片,请看以下代码:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), resId, options);
//现在原始宽高以存储在了options对象的outWidth和outHeight实例域中

第二步,根据原始宽高计算出inSampleSize,代码如下:

//dstWidth和dstHeight分别为目标ImageView的宽高
public static int calSampleSize(BitmapFactory.Options options, int dstWidth, int    dstHeight) {
    int rawWidth = options.outWidth;
    int rawHeight = options.outHeight;
    int inSampleSize = 1;
    if (rawWidth > dstWidth || rawHeight > dstHeight) {
    float ratioHeight = (float) rawHeight / dstHeight;
    float ratioWidth = (float) rawWidth / dstHeight;
    inSampleSize = (int) Math.min(ratioWidth, ratioHeight);
    }
    return inSampleSize;
}

第三步,将BitmapFacpry.Options的inSampleSize参数设为false并重新加载图片

options.inJustDecodeBounds =false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId, options);

这样就可以实现图片尺寸的压缩

2>质量压缩bitmap.compress

质量压缩是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的,经过它压缩的图片文件大小(kb)会有改变,但是导入成bitmap后占得内存是不变的,宽高也不会改变。

ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
int options = 90;
int bytes = baos.toByteArray().length;
while ((bytes / 1024 > 500) && (options >= 20)) {  //循环判断如果压缩后图片是否大于500kb,大于继续压缩
   baos.reset();//重置baos即清空baos
   options -= 10;//每次都减少10
   //第一个参数 :图片格式 ,第二个参数: 图片质量,100为最高,0为最差  ,第三个参数:保存压缩后的数据的流
   image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中
   bytes = baos.toByteArray().length;
}
image.recycle();
图三

可以看到,图片的大小是没有变的,因为质量压缩不会减少图片的像素,它是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的,这也是为什么该方法叫质量压缩方法。那么,图片的长,宽,像素都不变,那么bitmap所占内存大小是不会变的。

Bitmap.compress方法确实可以压缩图片,但压缩的是存储大小,即你放到disk上的大小

3>缩放法压缩 martix

Matrix matrix = new Matrix();
        matrix.setScale(0.5f, 0.5f);
        bm = Bitmap.createBitmap(bit, 0, 0, bit.getWidth(),
                bit.getHeight(), matrix, true);
        Log.i("wechat", "压缩后图片的大小" + (bm.getByteCount() / 1024 / 1024)
                + "M宽度为" + bm.getWidth() + "高度为" + bm.getHeight());

以上三种方法适用场景:大图片上传,一般是1,2方法结合使用,网上有很多最优算法,但是实现思路不变,下面再说说大图片显示问题

3.2.BitmapRegionDecoder类介绍

BitmapRegionDecoder类用来编译(解码)在图片内不同的方形区域,BitmapRegionDecoder类在使用较大图片只需要取得图片中的一小部分的内容是特别有效益的。

我们创建一个BitmapRegionDecoder类,并调用newInstace()方法,就可以得到BitmapRegionDecoder的对象之后,我们就能调用decodeRegion()方法去多次获得位图的特定地区的小图片

1>BitmapRegionDecoder的创建

public static BitmapRegionDecoder newInstance (String pathName, boolean isShareable)

用于创建BitmapRegionDecoder,pathName表示路径,只有jpeg和png图片才支持这种方式,isShareable如果为true,那BitmapRegionDecoder会对输入流保持一个表面的引用,如果为false,那么它将会创建一个输入流的复制,并且一直使用它。即使为true,程序也有可能会创建一个输入流的深度复制。如果图片是逐步解码的,那么为true会降低图片的解码速度。如果路径下的图片不是支持的格式,那就会抛出异常

public static BitmapRegionDecoder newInstance (InputStream is, boolean isShareable)

同上,不过参数is变成了输入流

public static BitmapRegionDecoder newInstance (FileDescriptor fd, boolean isShareable)

同上,不过参数变成了文件描述符,在函数运行完之前,文件描述符不可以改变

public static BitmapRegionDecoder newInstance (byte[] data, int offset, int length, boolean isShareable)

同上,不过参数变成了字节数组,offset为起始位置,length为长度

2>BitmapRegionDecoder的解码区域

public Bitmap decodeRegion (Rect rect, BitmapFactory.Options options)

参数一很明显是一个rect,参数二是BitmapFactory.Options,可以控制图片的inSampleSize,inPreferredConfig等。

本地加载大图片,我之前分享过一篇文章:

https://www.jianshu.com/p/5a250b68c331

网络加载大图片,思路差不多,但是需要先下载到本地再进行一系列操作,具体代码,就不展示了,可以提供个github库,大家看一看就应该懂了:

compile 'com.shizhefei:LargeImageView:1.0.9'

因为是公司项目,所以暂时不方便展示具体代码,后期抽时间提炼出来个demo贴出来。。。

到这里其实就差不多了。。。记得点赞
谢谢阅读,我这里也是学习的态度在这里分享,有什么问题希望大家能提出来能提出来,随时可以和我交流探讨:QQ:707086125 微信:loveme_dp

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容