Android Bitmap知识梳理学习

学习资料:


1.关于 Bitmap

AndroidBitamp指的就是一张图片,一般是pngjpeg格式。

Bitmap类中有一个enum类型的Config,其中有4个值

  • ALPHA_8
    8位位图;1 个字节,只有透明度,没有颜色值

  • RGB_565
    16位位图;2 个字节,r = 5,g = 6,b = 5,一个像素点 5+6+5 = 16

  • ARGB_4444
    16位位图;2 个字节,a = 4,r = 4,g = 4,b = 4,一个像素点 4+4+4+4 = 16

  • ARGB_8888
    32 位位图; 4个字节,a = 8,r = 8,g = 8, b = 8,一个像素点 8 + 8 + 8 + 8 = 32

每8位一个字节

以上主要摘自:关于ARGB_8888、ALPHA_8、ARGB_4444、RGB_565的理解

并不理解a,r,g,b对像素的影响,主要了解一下,不同的类型格式,占用内存情况


一张 1024 * 1024 像素,采用ARGB8888格式,一个像素32位,每个像素就是4字节,占有内存就是4M

若采用RGB565,一个像素16位,每个像素就是2字节,占有内存就是2M

Glide加载图片默认格式RGB565PicassoARGB8888,默认情况下,Glide占用内存会比Picasso低,色彩不如Picasso鲜艳,自然清晰度就低


  • BitmaFactory

Creates Bitmap objects from various sources, including files, streams, and byte-arrays.

通过BitmapFactory从文件系统,资源,输入流,字节数组中加载得到一个Bitmap对象。

  • decodeByteArray()
  • decodeFile()
  • decodeResource()
  • decodeStream()
  • decodeFileDescriptor()
  • decodeResourceStream()

BitmapFactory所有public method都是静态方法。一共也就6个方法,后两个用的几率不如前4个高 :)


2.Bitmap 的高效加载

核心思想: 利用BitmapFactory.Options来加载实际所需的尺寸

2.1 BitmapFactory.Options

这个类中只有一个方法requestCancelDecode(),剩下全是一些常量值

BitmapFactory.Options缩放图片主要用到inSample采样率

inSample = 1,采样后图片的宽高为原始宽高
inSample > 1,例如2,宽高均为原图的宽高的1/2

一个采用ARGB88881024 * 1024 的图片
inSample = 1,占用内存就 1024 * 1024 * 4 = 4M
inSample = 2,占用内存就 512 * 512 * 4 = 1M

缩小规律就是:1 /(inSample ^ 2)


inSample的值最小为1,低于1时无效的。inSample的值最好为2,4,8,16,2的指数。在某些时候,系统会向下取整,例如为3时,系统会用2来代替。2 的指数,可以一定程度上避免图片拉伸变形。


2.2 获取采样率的流程

以读取资源文件为例:

  1. 创建BitmapFactory.Options对象options
  2. optionsinJustDecodeBounds参数设为true,然后使用BitmapFactory.decodeResource(res,resId,options)加载图片
  3. 利用options取出图片的原始宽高信息,outWidth,outHeight
  4. 根据采样率的规则并结合实际需要显示的宽高计算出inSample
  5. optionsinJustDecodeBounds参数设为false,并再次使用BitmapFactory.decodeResource(res,resId,options)返回采样后的Bitmap

inJustDecodeBounds设为trueBitmapFactory只会解析图片的原始信息,并不会真正的加载图片

BitmapFactory读取图片的宽高的信息受图片所在drawable文件夹和设备屏幕本身的像素密度影响。


2.3 压缩图片简单实践

直接百度了一张imac的5k分辨率5120 * 2880大小为5.97M的壁纸,直接加载我的手机百分百会出现oom

效果

java代码:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        final ImageView iv = (ImageView) findViewById(R.id.iv_main);
        iv.post(new Runnable() {
            @Override
            public void run() {
                int width  = iv.getWidth();
                int height = iv.getHeight();
                iv.setImageBitmap(decodeBitmap(getResources(),R.drawable.test,width,height));
            }
        });
    }

    /**
     * 对图片进行压缩
     *
     * @param res
     * @param resId
     * @param targetWidth
     * @param targetHeight
     * @return
     */
    private Bitmap decodeBitmap(Resources res , int resId, int targetWidth, int targetHeight){
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
34行    options.inPreferredConfig = Bitmap.Config.RGB_565;//将Config设为RGB565
        BitmapFactory.decodeResource(res,resId,options);
        options.inSampleSize = calculateInSample(options,targetWidth,targetWidth);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res,resId,options);
    }

    /**
     * 计算inSample
     *
     * @param options
     * @param targetWidth
     * @param targetHeight
     * @return
     */

    private int calculateInSample(BitmapFactory.Options options, int targetWidth, int targetHeight) {
        final int rawWidth  = options.outWidth;
        final int rawHeight = options.outHeight;
        int inSample = 1;
54行    if (rawWidth > targetWidth || rawHeight > targetHeight){
            final int halfWidth  = rawWidth / 2;//为了避免过分压缩 例如 图片大小为 250 * 250 view 200 * 200
            final int halfHeight = rawHeight / 2;
57行        while((halfWidth / intSample) >= targetWidth && (halfHeight / intSample) >= targetHeight){
                inSample *= 2;
            }
        }
        return inSample;
    }
}

代码就是按照流程走的。 只是加入了34行Bitmap色彩格式的修改


34行,通过optionsBitmap的格式设为RGB565。设置成RGB565后,占用内存会少一半,也会减少OOM。个人感觉,除非是专门的图像处理app,大部分时候都可以用RGB565代替ARGB8888,牺牲图像的清晰度,换来一半的占用内存,个人感觉还是比较划算的。并且,清晰度的差别,不同时放在一起比较是不会有很大直观差别的。

Bitmap中有一个setConfig()方法,源码中调用了reconfigure(int width, int height, Config config),这个方法是创建一个新的bitmap用的。看了看不是很理解。这个方法不能用于目前已经存在的Bitmap。修改config还是利用Options

如果已经得到了Bitmap,想要修改BitmapConfig值,可以使用3.1Bitmap.cropress()和3.2Bitmap.copy()方法


calculateInSample()方法中,final int halfWidth = rawWidth / 2这行代码的目的在于防止过度压缩。因为54行已经做了判断,到了57行条件必然满足,当要显示的目标大小和图像的实际大小比较接近时,会产生过度没必要的压缩。

例如,ImageView的大小为200 * 200,而图像的大小为为250 * 250,如果不进行除以2,到了57行,条件成立,此时inSample的值会再次乘以2,根据缩小规律缩小 = inSample ^ 2,就会又次缩小4倍。

这只是我的分析,代码是从Android开发艺术探索学到的,若分析的不对,请指出

这时已经可以大大减少oom的发生,若还发生,可以把57行的&&改为|| ,这样改后,final int halfWidth = rawWidth / 2的作用就会受到影响。可能会出现过度压缩

其他从文件,流中读取也差不太多,流程没啥变化,细节不同


3.Bitmap中的方法

主要是查看api文档,想了解下都有哪些方法


3.1compress方法

  • compress(Bitmap.CompressFormat format, int quality, OutputStream stream)

Write a compressed version of the bitmap to the specified outputstream.<p>
If this returns true, the bitmap can be reconstructed by passing a corresponding inputstream to BitmapFactory.decodeStream(). Note: not all Formats support all bitmap configs directly, so it is possible that the returned bitmap from BitmapFactory could be in a different bitdepth, and/or may have lost per-pixel alpha (e.g. JPEG only supports opaque pixels).<p>
@param format The format of the compressed image
@param quality Hint to the compressor, 0-100. 0 meaning compress for small size, 100 meaning compress for max quality. Some formats, like PNG which is lossless, will ignore the quality setting
@param stream The outputstream to write the compressed data.
@return true if successfully compressed to the specified stream.

bitmap数据质量压缩并转换成流,若format参数设置为了png格式,quality设置无效

  • format 图片的格式,支持3种JPEG,PNG,WEBP
  • quality 压缩质量压缩率,0-100,0表示压缩程度最大,100为原质量,但png无效
  • stream 输出流
  • 返回值,boolean

简单使用:

private void initView() {
    Bitmap bitmap =  BitmapFactory.decodeResource(getResources(),R.drawable.cc);
    ByteArrayOutputStream outputStream =  new ByteArrayOutputStream(1024 * 8);
    bitmap.compress(Bitmap.CompressFormat.PNG,100,outputStream);
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.RGB_565;
    bitmap =BitmapFactory.decodeByteArray(outputStream.toByteArray(),0,outputStream.size(),options);
    Log.e(TAG,"++"+outputStream.size()+","+bitmap.getConfig().name()+","+bitmap.getByteCount()+","+bitmap.getHeight()+","+bitmap.getWidth());
    }

在变换成输出流的过程中,把BitmapConfig变为了RGB565,这里遇到个情况,mipmap文件夹下的图片,这种方法并不能改变其Config,一直都是默认ARGB8888


3.2 copy方法

  • copy(Bitmap.Config config, boolean isMutable)

Tries to make a new bitmap based on the dimensions of this bitmap,setting the new bitmap's config to the one specified, and then copying this bitmap's pixels into the new bitmap. If the conversion is not supported, or the allocator fails, then this returns NULL. The returned bitmap initially has the same density as the original.<p>
@param config The desired config for the resulting bitmap
@param isMutable True if the resulting bitmap should be mutable (i.e.its pixels can be modified)
@return the new bitmap, or null if the copy could not be made.

拷贝一个Bitmap的像素到一个新的指定信息配置的Bitmap

  • config 配置信息
  • isMutable 是否支持可改变可写入
  • 返回值,bitmap,成功返回一个新的bitmap,失败就null

简单使用:

private void initView() {
    Bitmap bitmap =  BitmapFactory.decodeResource(getResources(),R.drawable.m);
    bitmap = bitmap.copy(Bitmap.Config.RGB_565,true);
    Log.e(TAG,"++"+bitmap.getConfig().name()+","+bitmap.getByteCount()+","+bitmap.getHeight()+","+bitmap.getWidth());
}

方法中isMutable这个参数暂时不了解具体作用和使用场景


3.3 createBitmap方法

这个方法一共有9个重载方法

createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter)

  • source 资源bitmap
  • x 资源bitmap的第一个像素的x坐标
  • y 资源bitmap的第一个像素的y坐标
  • m 矩阵
  • filter 是否过滤资源bitmap
  • 返回值 一个不可变的btimap

由资源bitmap根据坐标系截取创建一个新的bitmap

  1. createBitmap(Bitmap src)
  2. createBitmap(Bitmap source, int x, int y, int width, int height)

1->2,2->本方法

本方法简单使用:

private void initView() {
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.m);
    Matrix matrix = new Matrix();
    Bitmap b = bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
    Log.e(TAG,"==bitmap-->"+bitmap.getConfig().name()+","+bitmap.getByteCount()+","+bitmap.getHeight()+","+bitmap.getWidth());
    Log.e(TAG,"==b--->"+b.getConfig().name()+","+b.getByteCount()+","+b.getHeight()+","+b.getWidth());
}

需要注意:
x + width <= 资源bitmap.getWidth()
y + height <= 资源bitmap.getHeight()


  1. createBitmap(int width, int height, Config config)
  2. createBitmap(DisplayMetrics display, int width,int height, Config config)
private void initView() {
    Bitmap bitmap =  BitmapFactory.decodeResource(getResources(),R.drawable.m);
    Bitmap b = bitmap.createBitmap(new DisplayMetrics(),10,10, Bitmap.Config.RGB_565);
    Bitmap b2 = bitmap.createBitmap(10,10, Bitmap.Config.RGB_565);
    Log.e(TAG,"==bitmap-->"+bitmap.getConfig().name()+","+bitmap.getByteCount()+","+bitmap.getHeight()+","+bitmap.getWidth());
    Log.e(TAG,"==b--->"+b.getConfig().name()+","+b.getByteCount()+","+b.getHeight()+","+b.getWidth());
    Log.e(TAG,"==b2--->"+b2.getConfig().name()+","+b2.getByteCount()+","+b2.getHeight()+","+b2.getWidth());
}

这两个方法最终都调用了一个没有对外公开的private方法。返回值是一个可变的bitmap


createBitmap(DisplayMetrics display, int colors[],int offset, int stride, int width, int height, Config config)

  • display DisplayMetrics对象,指定初始密度
  • colors 初始化颜色的数组,数组的长度至少大于width*height
  • offset 偏移量
  • stride Number of colors in the array between rows (must be >=width or <= -width) 并不理解作用
  • width bitmap的宽
  • height bitmap的高
  • config 格式
  • 返回值 一个不可变的bitmap

  1. createBitmap(int colors[], int width, int height, Config config)
  2. createBitmap(DisplayMetrics display, int colors[], int width, int height, Config config)
  3. createBitmap(int colors[], int offset, int stride, int width, int height, Config config)

1,2,3调用了本方法

本方法简单使用:

private void initView() {
     Bitmap bitmap =     BitmapFactory.decodeResource(getResources(),R.drawable.m);
     Bitmap b = bitmap.createBitmap(new DisplayMetrics(),new int[]{10,20,30,40,50},0,1,1,1, Bitmap.Config.RGB_565);
     Log.e(TAG,"==bitmap-->"+bitmap.getConfig().name()+","+bitmap.getByteCount()+","+bitmap.getHeight()+","+bitmap.getWidth());
    Log.e(TAG,"==b--->"+b.getConfig().name()+","+b.getByteCount()+","+b.getHeight()+","+b.getWidth());
}

这个方法并没有使用过。对参数要求比较多,使用时在源码中看一下参数要求。这个方法目前不清楚使用场景,只能遇到时,再次学习


3.4其他方法

方法 作用
recycle() 释放bitmap所占的内存
isRecycled() 判断是否回收内存
getWidth() 得到宽
getHeight 得到高
isMutable() 是否可以改变
sameAs(Bitmap other) 判断两个bitmap大小,格式,像素信息是否相同

其他的用到再学习了。


4.BitmapFactory.Options类

属性 作用
boolean inJustDecodeBounds 是否只扫描轮廓
int inSample 采样率
Bitmap.Config inPreferredConfig 格式,色彩模式
int outWidth bitmap的宽
int outHeight bitmap的高
boolean inDither 防抖动,默认false
int inDensity 像素密度
boolean inScaled 是否可以缩放,默认true
boolean inMutable 是否可变,设为turedecode转换方法返回的结果全部可改变

其他更不常见的用到再学习


5 最后

bitmap基础知识大概就了解这些,深入的知识就随着工作再学习。

明天星期天,不学习了。打算去图书馆看妹纸去 :)
周末愉快。共勉 :)

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

推荐阅读更多精彩内容