android bitmap FileInputStream File轉化

Android中圖片的讀取,修改,顯示和保存涉及到的類大致如圖所示。

這裏寫圖片描述

在讀取圖片文件時,先將圖片文件轉換爲InputStream對象,然後通過BitmapFactory將其轉換爲Bitmap對象。 
在圖片保存時,先將Bitmap對象轉換爲OutputStream對象,然後再將OutputStream輸出到文件中。 
如果要對圖片進行修改,可以通過將Bitmap對象轉換爲顏色數組(int[])來修改,也可以通過Canvas來修改。此外Bitmap類提供了一個createBitmap的靜態方法,可以對Bitmap對象做一些轉換。 
顯示圖片時,可以將Bitmap對象轉換爲Drawable對象,然後設置給ImageView。 
本文介紹圖片文件的讀取。

Android支持的圖片格式

目前在Android中支持的圖片文件格式如下。其中WebP格式是從Android4.0開始支持,但在Android4.0到Android4.2.1之間的Android系統不支持無損壓縮和有透明度的WebP格式圖片,Android4.2.1之後纔開始支持所有的WebP格式圖片。 
下圖截取自http://developer.android.com/guide/appendix/media-formats.html 
這裏寫圖片描述

讀取圖片文件到InputStream

1、讀取SD卡中的圖片到InputStream 
對SD卡中的文件,獲取到文件的路徑之後便可以通過FileInputStream類來生成InputStream。FileInputStream類是InputStream類的派生類。代碼示例如下。

FileInputStream stream = new FileInputStream(fileName);
  • 1
  • 1

2、讀取resource中的圖片到InputStream 
對resource/drawable目錄下的圖片,每個文件都會在R文件中生成一個對應的整數id,通過“R.drawable.文件名”來得到id值,然後通過Resource類的openRawResource()來得到該文件對應的InputStream對象。代碼示例如下。

InputStream strem = getResources().openRawResource(R.drawable.name);
  • 1
  • 1

3、讀取assets中的圖片到InputStream 
對assets目錄中的圖片,先通過getAssets()得到AssetManager對象,然後通過AssetManager對象的open方法來打開文件,得到該文件對應的InputStream對象。代碼示例如下。

InputStream strem = getAssets().open("apple.png");
  • 1
  • 1

注意:如果圖片在assets根目錄,讀取時只需要加圖片文件名,不需要加/assets/,圖片文件名需要包含擴展名。如果圖片在assets的子目錄中,需要加上子目錄的路徑,例如apple.png在/assert/fruit/spring/目錄中,則open參數爲”/fruit/spring/apple.png”。

4、 通過Uri讀取圖片到InputStream 
Uri全寫是Universal Resource Identifier(通用資源標誌符),Android系統中所有資源都可以用Uri來表示。在獲取到資源的Uri後可以通過ContentResolver將其解析成InputStream對象。代碼示例如下。

InputStream strem = getContentResolver().openInputStream(uri);
  • 1
  • 1

openInputStream()核心代碼如下。

String scheme = uri.getScheme();
if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
    OpenResourceIdResult r = getResourceId(uri);
    InputStream stream = r.r.openRawResource(r.id);
    return stream;
} else if (SCHEME_FILE.equals(scheme)) {
    return new FileInputStream(uri.getPath());
} else {
    AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r", null);
    return fd.createInputStream();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

這裏openInputStream()內先判斷Uri的scheme類型,如果是resource類型,則先調用getResourceId()得到該Uri對應的資源id,然後調用openRawResource()讀取文件到InputStream對象。如果是文件類型,則調用uri.getPath()得到文件路徑,然後調用FileInputStream()讀取文件到InputStream對象。對其他類型(即assets類型)則調用openAssetFileDescriptor()先得到一個AssetFileDescriptor對象,然後再得到一個InputStream對象。 
可以看到,通過Uri讀取圖片的流程和上述根據類型直接讀取大體是一樣的(assets類型稍有不同)。

將InputStream轉換爲Bitmap

通過BitmapFactory的decodeStream()方法可以將InputStream轉換爲Bitmap對象。代碼示例如下。

Bitmap bitmap = BitmapFactory.decodeStream(stream);
  • 1
  • 1

直接讀取圖片文件到Bitmap

上述流程是先將圖片文件讀取到InputStream中,然後將InputStream轉換爲Bitmap對象,需要兩步。BitmapFactory提供了一些靜態類可以直接將圖片文件轉換爲Bitmap對象。 
直接讀取SD卡中的圖片到Bitmap示例代碼如下。

Bitmap bitmap = BitmapFactory.decodeFile(fileName);
  • 1
  • 1

直接讀取resource中的圖片到Bitmap示例代碼如下。

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), id);
  • 1
  • 1

BitmapFactory並沒有提供直接讀取assets中圖片的api。 
查看BitmapFactory源碼可以看到,BitmapFactory.decodeFile()和BitmapFactory.decodeResource()兩個api都是先將SD卡或resource中的圖片讀取到InputStream,然後再調用BitmapFactory.decodeStream()來解析。由此可以看出,直接讀取圖片到Bitmap和先讀取圖片到InputStream,然後將InputStream轉換爲Bitmap本質上是一樣的,只是BitmapFactory將這兩步封裝在一起。因此,如果要將非assets中的圖片文件轉換爲Bitmap,一般就直接使用BitmapFactory即可。

BitmapFactory.Options

BitmapFactory在decode時可以傳遞一個Options參數,用來設置轉換到Bitmap時的一些參數。 
BitmapFactory.Options中包含了一系列的public成員變量,每個成員變量代表一個轉換參數。這裏列出了BitmapFactory.Options類中定義的全部public成員變量。在BitmapFactory.Options類的註釋中詳細的描述了每個成員變量的含義。這裏只介紹幾個常用的成員變量的用法。

    public static class Options {
        public Bitmap inBitmap;
        public boolean inMutable;
        public boolean inJustDecodeBounds;
        public int inSampleSize;
        public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
        public boolean inPremultiplied;
        public boolean inDither;
        public int inDensity;
        public int inTargetDensity;
        public int inScreenDensity;
        public boolean inScaled;
        public boolean inPurgeable;
        public boolean inInputShareable;
        public boolean inPreferQualityOverSpeed;
        public int outWidth;
        public int outHeight;
        public String outMimeType;
        public byte[] inTempStorage;
        public boolean mCancel;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

inJustDecodeBounds:此變量默認爲false。當此變量被設置爲true時,表示只對圖片的Bound(圖片的範圍,也就是長寬包含的像素大小)進行解析,不生成Bitmap對象。在BitmapFactory.Options定義中可以看到有三個out開頭的成員變量,outWidth,outHeight和outMimeType。當inJustDecodeBounds設置爲true時,會分別將圖片的寬度,高度和Mime類型保存到這三個成員變量中。 
inSampleSize:inSampleSize是一個整數,表示對圖片像素的取樣比例,即圖片中每inSampleSize個像素取樣後對應Bitmap中的一個像素。例如inSampleSize=2,表示圖片中每兩個像素對應Bitmap中的一個像素,轉換後Bitmap的寬和高包含的像素個數是原圖片的1/2,總像素個數是原圖的1/4。inSampleSize=4,表示圖片中每四個像素對應Bitmap中的一個像素,轉換後Bitmap的寬和高包含的像素個數是原圖片的1/4,總像素個數是原圖的1/16。如果它小於1等於1,則表示完全取樣,即圖片中每個像素都會被提取保存到Bitmap中,轉換後Bitmap的寬和高包含的像素個數和原圖片相等。圖片取樣時只能按照2的整數次冪來取樣,比如1,2,4,8,16……,如果inSampleSize不是2的整數次冪,則實際取樣時使用和inSampleSize最接近的2的整數次冪來取樣,比如inSampleSize= 10,和10最接近的2的整數次冪是8,因此會每8個像素取樣一次。 
inPreferredConfig:表示圖片中每個像素的保存方式。默認爲Bitmap.Config.ARGB_8888,表示每個像素需要包含Alpha,R,G,B四個通道的信息,每個通道用8位來表示,也就是需要4個字節來保存一個像素。

圖片文件讀取注意事項

1、WebP圖片格式問題 
雖然Android官方描述是在4.0之後開始支持WebP格式,但在4.0到4.2.1不支持有透明度設置和無損壓縮的WebP格式圖片。所以如果簡單的判斷,圖片如果是WebP格式,且是4.0以上的系統,就通過BitmapFactory來解析,很可能會出現有些圖片能解析,有些又不能解析的情況。此外,還有一些特殊機型,例如NokiaXL雖然是Android4.1的系統,但並不支持WebP編解碼。因此,最好是從4.2系統開始用BitmapFactory來解析WebP格式的圖片。 
2、解析大圖時的OOM問題 
Android爲每個應用分配的堆內存空間是有限的,如果應用請求的內存超過了限制,就會導致OOM。根據手機內存大小的不同,一般應用能夠使用的堆內存在幾十兆到兩三百兆之間。然後應用在讀取圖片文件時非常消耗內存的,一張手機相機拍出的照片都有3M左右,如果每張圖都直接讀取,即使不造成OOM,也會導致頻繁GC。還有些超大圖一張就有幾個G,如果直接讀取肯定會導致OOM。爲了避免讀取大圖時造成的OOM問題,並減少內存消耗,一般在讀取圖片時都需要根據圖片尺寸來取樣。也就是設置BitmapFactory.Options的inSampleSize屬性。示例代碼如下。

    Options options = new BitmapFactory.Options();
    //設置inJustDecodeBounds爲true表示只獲取大小,不生成Btimap
    options.inJustDecodeBounds = true;
    //解析圖片大小
    InputStream stream = getContentResolver().openInputStream(uri);
    BitmapFactory.decodeStream(stream, null, options);
    stream.close();
    int width = options.outWidth;
    int height = options.outHeight;
    int ratio = 0;
    //如果寬度大於高度,交換寬度和高度
    if (width > height) {
        int temp = width;
        width = height;
        height = temp;
    }
    //計算取樣比例
    it sampleRatio = Math.max(width/900, height/1600);
    //定義圖片解碼選項
    Options options = new Options();
    options.inSampleSize = sampleRatio;

    //讀取圖片,並將圖片縮放到指定的目標大小
    InputStream stream = getContentResolver().openInputStream(uri);
    Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
    stream.close();
發佈了90 篇原創文章 · 獲贊 80 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章