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