Android系統(tǒng)對(duì)加載圖片做了一些限制蛉艾,其中一個(gè)就是對(duì)Bitmap有最大寬高限制照棋,某些系統(tǒng)的機(jī)子的限制不一樣科贬,但起碼是4096x4096。當(dāng)加載一張長(zhǎng)或者寬鳖悠,或者兩者都超出限制的圖片的時(shí)候榜掌,通常會(huì)報(bào)出:Bitmap too large to be uploaded into a texture exception。圖片并不會(huì)顯示乘综。
那么憎账,怎么解決這個(gè)問(wèn)題呢?
首先思路是:既然這個(gè)限制是對(duì)寬高進(jìn)行限制的卡辰,而且限制值也知道了胞皱,那么只要控制在加載圖片前,將圖片的寬高都限制在4096以?xún)?nèi)九妈。
解決方案
1.壓縮圖片像素
將一張圖片的寬高都?jí)嚎s在4096以?xún)?nèi)反砌,Android提供了挺多可以這樣操作的API方法,譬如萌朱,指定尺寸進(jìn)行壓縮:
// 利用矩陣并指定寬高
public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m)
// 用法示例
public static Bitmap resizeImage(Bitmap bitmap, int w, int h) {
// 原圖的bitmap
Bitmap BitmapOrg = bitmap;
// 原圖的寬高
int width = BitmapOrg.getWidth();
int height = BitmapOrg.getHeight();
// 指定的新的寬高
int newWidth = w;
int newHeight = h;
// 計(jì)算的縮放比例
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 用于縮放的矩陣
Matrix matrix = new Matrix();
// 矩陣縮放
matrix.postScale(scaleWidth, scaleHeight);
// 如果要旋轉(zhuǎn)圖片
// matrix.postRotate(45);
// 生成新的bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(BitmapOrg, 0, 0, width,
height, matrix, true);
return resizedBitmap;
}
當(dāng)然宴树,createBitmap(...)還有多種方法,不使用矩陣的而直接縮放的也有createScaledBitmap(...)晶疼,但用法基本一樣酒贬,最后也是生成一個(gè)新的Bitmap對(duì)象,尺寸可以直接指定翠霍,或者通過(guò)某些計(jì)算規(guī)則(如屏幕寬高與原圖分辨率的比值等)獲得尺寸锭吨,只要保證這個(gè)尺寸是長(zhǎng)寬小于等于4096的就可以成功加載。
同樣的寒匙,不加載超規(guī)格的原圖零如,而是加載調(diào)整過(guò)尺寸的縮略圖,也是同樣的道理,不過(guò)使用的API方法不一樣埠况,下面也給一個(gè)示例:
//使用BitmapFactory.Options的inSampleSize參數(shù)來(lái)縮放
public static Bitmap resizeImage2(String path, int width, int height) {
// Options 代表圖片在解析的時(shí)候耸携,BitmapFactory內(nèi)部應(yīng)該如何處理圖片內(nèi)容
BitmapFactory.Options options = new BitmapFactory.Options();
// 不加載bitmap到內(nèi)存中
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path,options);
// 原圖寬高
int outWidth = options.outWidth;
int outHeight = options.outHeight;
options.inDither = false;
// 圖片格式,當(dāng)然為了省內(nèi)存也可以用RGB_565
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inSampleSize = 1;
if (outWidth != 0 && outHeight != 0 && width != 0 && height != 0)
{
// 圖片解碼采樣2的冪辕翰,采樣效率是最快的
int sampleSize = (outWidth/width+outHeight/height)/2;
options.inSampleSize = sampleSize;
}
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, options);
}
這兩種方法可以解決超規(guī)格的大圖夺衍、長(zhǎng)圖的加載問(wèn)題,不過(guò)圖片經(jīng)過(guò)壓縮喜命,分辨率的降低沟沙,圖片的清晰度肯定是有損失的。
按照解決問(wèn)題的思路壁榕,同時(shí)又不想圖片的質(zhì)量下降矛紫,那么還有什么方法呢?
2.圖片分割
實(shí)際上牌里,這只是為了方便理解颊咬,準(zhǔn)確地說(shuō)應(yīng)該是圖片區(qū)域解碼,就是解碼圖片的一塊不大于4096X4096的區(qū)域?yàn)锽itmap再顯示牡辽。
圖片不壓縮喳篇,而是顯示圖片局部區(qū)域,相當(dāng)于將圖片“分割”成一塊塊地去加載态辛、顯示麸澜。
Android里面提供了這樣的一個(gè)類(lèi),BitmapRegionDecoder奏黑。
/**
* BitmapRegionDecoder can be used to decode a rectangle region from an image.
* BitmapRegionDecoder is particularly useful when an original image is large and
* you only need parts of the image.
*
* <p>To create a BitmapRegionDecoder, call newInstance(...).
* Given a BitmapRegionDecoder, users can call decodeRegion() repeatedly
* to get a decoded Bitmap of the specified region.
*
*/
看源碼里面對(duì)這個(gè)類(lèi)的描述就可以知道:BitmapRegionDecoder類(lèi)用來(lái)編譯(解碼)在圖片內(nèi)不同的方形區(qū)域炊邦,BitmapRegionDecoder類(lèi)在使用較大圖片只需要取得圖片中的一小部分的內(nèi)容是特別有效益的。
那么就看一下熟史,怎么用這個(gè)類(lèi)去加載超規(guī)格的圖片馁害。
主要有四步:
① 使用BitmapRegionDecoder的newInstance(...)獲得一個(gè)解碼對(duì)象decoder;
② 生成一個(gè)Rect類(lèi)的對(duì)象rect蹂匹,這個(gè)矩形對(duì)象就是設(shè)置好要解碼的區(qū)域(當(dāng)然長(zhǎng)寬不能大于4096蜗细,實(shí)際上就算設(shè)置了大于4096的數(shù)值,最后也是會(huì)不能加載的)怒详;
③ decoder調(diào)用decodeRegion(...)將rect對(duì)象轉(zhuǎn)化為bitmap對(duì)象炉媒;
④ 控件加載bitmap。
同樣來(lái)一個(gè)示例:
dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
screenHeight = dm.heightPixels;
screenWidth = dm.widthPixels;
...
try {
// 獲取本地圖片
InputStream is = getResources().openRawResource(R.drawable.bg);
// 生成decoder對(duì)象
mDecoder = BitmapRegionDecoder.newInstance(is, true);
final int imgWidth = mDecoder.getWidth();
final int imgHeight = mDecoder.getHeight();
BitmapFactory.Options opts = new BitmapFactory.Options();
// 為了內(nèi)存考慮昆烁,將圖片格式轉(zhuǎn)化為RGB_565
opts .inPreferredConfig = Bitmap.Config.RGB_565;
// 設(shè)置圖片解碼矩形區(qū)域
mRect.set(0, 0, 2000, 3000);
// 將矩形區(qū)域解碼生成要加載的Bitmap對(duì)象
Bitmap bm = mDecoder.decodeRegion(mRect, opts);
// 控件加載Bitmap對(duì)象
img.setImageBitmap(bm);
...
} catch (IOException e) {
e.printStackTrace();
}
使用起來(lái)并不困難吊骤,而且效果不錯(cuò),當(dāng)然也要考慮內(nèi)存而做一些處理静尼,而且白粉,這里只是用一個(gè)控件加載了圖片的一部分區(qū)域传泊,可以看到Rect對(duì)象調(diào)用set(...)可以設(shè)置區(qū)域大小,那么計(jì)算圖片的寬高鸭巴,“分割”成幾個(gè)部分給多個(gè)控件去分別加載眷细,這些控件無(wú)間距布局在一起,就相當(dāng)于整個(gè)圖片完整顯示出來(lái)了鹃祖。至于怎么計(jì)算要多少個(gè)控件溪椎,“分割”的區(qū)域大小多少合適?這里就不展開(kāi)說(shuō)了恬口。同樣的基于這個(gè)原理校读,也可以自定義View做成在一個(gè)控件里面清晰顯示整張圖片,具體的可以看鴻洋大神的這篇博客:Android 高清加載巨圖方案 拒絕壓縮圖片
注意:如果讀取的長(zhǎng)圖是本地圖片祖能,那么放在drawable或者mipmap歉秫,是無(wú)法生成資源ID的,也就是無(wú)法使用的养铸,可以放在raw或者assets文件夾中雁芙。但怎么獲取這些文件夾里面的圖片這里就不展開(kāi)說(shuō)了。
如果還是嫌上面的兩個(gè)方案做起來(lái)麻煩钞螟,或者還想要加上手勢(shì)縮放却特,平移等,GitHub上面也有一些開(kāi)源的庫(kù)可以使用筛圆,比較流行的是subsampling-scale-image-view,可以看一看椿浓,試一試太援。
Android加載長(zhǎng)圖暫時(shí)就這么多,歡迎多多交流扳碍。