Android Bitmap知識梳理學(xué)習(xí)

學(xué)習(xí)資料:

  • android 開發(fā)藝術(shù)探索
  • Bitmap api

1.關(guān)于 Bitmap

AndroidBitamp指的就是一張圖片,一般是pngjpeg格式。

Bitmap類中有一個(gè)enum類型的Config,其中有4個(gè)值

  • ALPHA_8
    8位位圖;1 個(gè)字節(jié),只有透明度,沒有顏色值

  • RGB_565
    16位位圖;2 個(gè)字節(jié),r = 5,g = 6,b = 5,一個(gè)像素點(diǎn) 5+6+5 = 16

  • ARGB_4444
    16位位圖;2 個(gè)字節(jié),a = 4,r = 4,g = 4,b = 4,一個(gè)像素點(diǎn) 4+4+4+4 = 16

  • ARGB_8888
    32 位位圖; 4個(gè)字節(jié),a = 8,r = 8,g = 8, b = 8,一個(gè)像素點(diǎn) 8 + 8 + 8 + 8 = 32

每8位一個(gè)字節(jié)

以上主要摘自:關(guān)于ARGB_8888、ALPHA_8、ARGB_4444、RGB_565的理解

并不理解a,r,g,b對像素的影響,主要了解一下,不同的類型格式,占用內(nèi)存情況


一張 1024 * 1024 像素,采用ARGB8888格式,一個(gè)像素32位,每個(gè)像素就是4字節(jié),占有內(nèi)存就是4M

若采用RGB565,一個(gè)像素16位,每個(gè)像素就是2字節(jié),占有內(nèi)存就是2M

Glide加載圖片默認(rèn)格式RGB565PicassoARGB8888,默認(rèn)情況下,Glide占用內(nèi)存會比Picasso低,色彩不如Picasso鮮艷,自然清晰度就低


  • BitmaFactory

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

通過BitmapFactory從文件系統(tǒng),資源,輸入流,字節(jié)數(shù)組中加載得到一個(gè)Bitmap對象。

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

BitmapFactory所有public method都是靜態(tài)方法。一共也就6個(gè)方法,后兩個(gè)用的幾率不如前4個(gè)高 :)


2.Bitmap 的高效加載

核心思想: 利用BitmapFactory.Options來加載實(shí)際所需的尺寸

2.1 BitmapFactory.Options

這個(gè)類中只有一個(gè)方法requestCancelDecode(),剩下全是一些常量值

BitmapFactory.Options縮放圖片主要用到inSample采樣率

inSample = 1,采樣后圖片的寬高為原始寬高
inSample > 1,例如2,寬高均為原圖的寬高的1/2

一個(gè)采用ARGB88881024 * 1024 的圖片
inSample = 1,占用內(nèi)存就 1024 * 1024 * 4 = 4M
inSample = 2,占用內(nèi)存就 512 * 512 * 4 = 1M

縮小規(guī)律就是:1 /(inSample ^ 2)


inSample的值最小為1,低于1時(shí)無效的。inSample的值最好為2,4,8,16,2的指數(shù)。在某些時(shí)候,系統(tǒng)會向下取整,例如為3時(shí),系統(tǒng)會用2來代替。2 的指數(shù),可以一定程度上避免圖片拉伸變形。


2.2 獲取采樣率的流程

以讀取資源文件為例:

  1. 創(chuàng)建BitmapFactory.Options對象options
  2. optionsinJustDecodeBounds參數(shù)設(shè)為true,然后使用BitmapFactory.decodeResource(res,resId,options)加載圖片
  3. 利用options取出圖片的原始寬高信息,outWidth,outHeight
  4. 根據(jù)采樣率的規(guī)則并結(jié)合實(shí)際需要顯示的寬高計(jì)算出inSample
  5. optionsinJustDecodeBounds參數(shù)設(shè)為false,并再次使用BitmapFactory.decodeResource(res,resId,options)返回采樣后的Bitmap

inJustDecodeBounds設(shè)為trueBitmapFactory只會解析圖片的原始信息,并不會真正的加載圖片

BitmapFactory讀取圖片的寬高的信息受圖片所在drawable文件夾和設(shè)備屏幕本身的像素密度影響。


2.3 壓縮圖片簡單實(shí)踐

直接百度了一張imac的5k分辨率5120 * 2880大小為5.97M的壁紙,直接加載我的手機(jī)百分百會出現(xiàn)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));
            }
        });
    }

    /**
     * 對圖片進(jìn)行壓縮
     *
     * @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設(shè)為RGB565
        BitmapFactory.decodeResource(res,resId,options);
        options.inSampleSize = calculateInSample(options,targetWidth,targetWidth);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res,resId,options);
    }

    /**
     * 計(jì)算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的格式設(shè)為RGB565。設(shè)置成RGB565后,占用內(nèi)存會少一半,也會減少OOM。個(gè)人感覺,除非是專門的圖像處理app,大部分時(shí)候都可以用RGB565代替ARGB8888,犧牲圖像的清晰度,換來一半的占用內(nèi)存,個(gè)人感覺還是比較劃算的。并且,清晰度的差別,不同時(shí)放在一起比較是不會有很大直觀差別的。

Bitmap中有一個(gè)setConfig()方法,源碼中調(diào)用了reconfigure(int width, int height, Config config),這個(gè)方法是創(chuàng)建一個(gè)新的bitmap用的。看了看不是很理解。這個(gè)方法不能用于目前已經(jīng)存在的Bitmap。修改config還是利用Options

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


calculateInSample()方法中,final int halfWidth = rawWidth / 2這行代碼的目的在于防止過度壓縮。因?yàn)?4行已經(jīng)做了判斷,到了57行條件必然滿足,當(dāng)要顯示的目標(biāo)大小和圖像的實(shí)際大小比較接近時(shí),會產(chǎn)生過度沒必要的壓縮。

例如,ImageView的大小為200 * 200,而圖像的大小為為250 * 250,如果不進(jìn)行除以2,到了57行,條件成立,此時(shí)inSample的值會再次乘以2,根據(jù)縮小規(guī)律縮小 = inSample ^ 2,就會又次縮小4倍。

這只是我的分析,代碼是從Android開發(fā)藝術(shù)探索學(xué)到的,若分析的不對,請指出

這時(shí)已經(jīng)可以大大減少oom的發(fā)生,若還發(fā)生,可以把57行的&&改為|| ,這樣改后,final int halfWidth = rawWidth / 2的作用就會受到影響。可能會出現(xiàn)過度壓縮

其他從文件,流中讀取也差不太多,流程沒啥變化,細(xì)節(jié)不同


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數(shù)據(jù)質(zhì)量壓縮并轉(zhuǎn)換成流,若format參數(shù)設(shè)置為了png格式,quality設(shè)置無效

  • format 圖片的格式,支持3種JPEG,PNG,WEBP
  • quality 壓縮質(zhì)量壓縮率,0-100,0表示壓縮程度最大,100為原質(zhì)量,但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變?yōu)榱?code>RGB565,這里遇到個(gè)情況,mipmap文件夾下的圖片,這種方法并不能改變其Config,一直都是默認(rèn)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.

拷貝一個(gè)Bitmap的像素到一個(gè)新的指定信息配置的Bitmap

  • config 配置信息
  • isMutable 是否支持可改變可寫入
  • 返回值,bitmap,成功返回一個(gè)新的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這個(gè)參數(shù)暫時(shí)不了解具體作用和使用場景


3.3 createBitmap方法

這個(gè)方法一共有9個(gè)重載方法

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

  • source 資源bitmap
  • x 資源bitmap的第一個(gè)像素的x坐標(biāo)
  • y 資源bitmap的第一個(gè)像素的y坐標(biāo)
  • m 矩陣
  • filter 是否過濾資源bitmap
  • 返回值 一個(gè)不可變的btimap

由資源bitmap根據(jù)坐標(biāo)系截取創(chuàng)建一個(gè)新的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());
}

這兩個(gè)方法最終都調(diào)用了一個(gè)沒有對外公開的private方法。返回值是一個(gè)可變的bitmap


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

  • display DisplayMetrics對象,指定初始密度
  • colors 初始化顏色的數(shù)組,數(shù)組的長度至少大于width*height
  • offset 偏移量
  • stride Number of colors in the array between rows (must be >=width or <= -width) 并不理解作用
  • width bitmap的寬
  • height bitmap的高
  • config 格式
  • 返回值 一個(gè)不可變的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調(diào)用了本方法

本方法簡單使用:

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());
}

這個(gè)方法并沒有使用過。對參數(shù)要求比較多,使用時(shí)在源碼中看一下參數(shù)要求。這個(gè)方法目前不清楚使用場景,只能遇到時(shí),再次學(xué)習(xí)


3.4其他方法

方法 作用
recycle() 釋放bitmap所占的內(nèi)存
isRecycled() 判斷是否回收內(nèi)存
getWidth() 得到寬
getHeight 得到高
isMutable() 是否可以改變
sameAs(Bitmap other) 判斷兩個(gè)bitmap大小,格式,像素信息是否相同

其他的用到再學(xué)習(xí)了。


4.BitmapFactory.Options類

屬性 作用
boolean inJustDecodeBounds 是否只掃描輪廓
int inSample 采樣率
Bitmap.Config inPreferredConfig 格式,色彩模式
int outWidth bitmap的寬
int outHeight bitmap的高
boolean inDither 防抖動,默認(rèn)false
int inDensity 像素密度
boolean inScaled 是否可以縮放,默認(rèn)true
boolean inMutable 是否可變,設(shè)為turedecode轉(zhuǎn)換方法返回的結(jié)果全部可改變

其他更不常見的用到再學(xué)習(xí)


5 最后

bitmap基礎(chǔ)知識大概就了解這些,深入的知識就隨著工作再學(xué)習(xí)。

明天星期天,不學(xué)習(xí)了。打算去圖書館看妹紙去 :)
周末愉快。共勉 :)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容