Android中的Bitmap的詳細(xì)介紹
Bitmap簡(jiǎn)介(摘抄于網(wǎng)絡(luò))
位圖文件(Bitmap),擴(kuò)展名可以是.bmp或者.dib。位圖是Windows標(biāo)準(zhǔn)格式圖形文件,它將圖像定義為由點(diǎn)(像素)組成,每個(gè)點(diǎn)可以由多種色彩表示,包括2、4、8、16、24和32位色彩。
例如,一幅1024×768分辨率的32位真彩圖片,其所占存儲(chǔ)字節(jié)數(shù)為:1024×768×32/(8*1024)=3072KB
位圖文件圖像效果好,但是非壓縮格式的,需要占用較大存儲(chǔ)空間,不利于在網(wǎng)絡(luò)上傳送。jpg/png格式則恰好彌補(bǔ)了位圖文件的缺點(diǎn)。
在Android中計(jì)算bitmap的大小:bitmap.getByteCount()(返回byte)
掃盲:1M=1024KB=1024*1024byte
一般1920X1080尺寸的圖片在內(nèi)存中的大小,1920x1080x4=2025kb=1.977539M
乘以4位的原因是在安卓系統(tǒng)中bitmap圖片一般是以ARGB_8888(ARGB分別代表的是透明度,紅色,綠色,藍(lán)色,每個(gè)值分別用8bit來(lái)記錄,也就是一個(gè)像素會(huì)占用4byte,共32bit。)來(lái)進(jìn)行存儲(chǔ)的。
Android中圖片有四種顏色格式
顏色格式 | 每個(gè)像素占用內(nèi)存(單位byte) | 每個(gè)像素占用內(nèi)存(單位bit) |
---|---|---|
ALPHA_8 | 1 | 8 |
ARGB_8888(默認(rèn)) | 4 | 32 |
ARGB_4444 | 2 | 16 |
RGB_565 | 2 | 16 |
ARGB_8888占位算法: 8+8+8+8 =32
1 bit 0/1 ;最小單位
1 byte = 8bit 10101010 (0-255)00000000 --- 11111111
說(shuō)明:
ARGB_8888:ARGB分別代表的是透明度,紅色,綠色,藍(lán)色,每個(gè)值分別用8bit來(lái)記錄,也就是一個(gè)像素會(huì)占用4byte,共32bit.
ARGB_4444:ARGB的是每個(gè)值分別用4bit來(lái)記錄,一個(gè)像素會(huì)占用2byte,共16bit.
RGB_565:R=5bit,G=6bit,B=5bit,不存在透明度,每個(gè)像素會(huì)占用2byte,共16bit.
ALPHA_8:該像素只保存透明度,會(huì)占用1byte,共8bit.
在實(shí)際應(yīng)用中而言,建議使用ARGB_8888以及RGB_565。
如果你不需要透明度,那么就選擇RGB_565,可以減少一半的內(nèi)存占用.
Bitmap的回收
在安卓3.0以前Bitmap是存放在堆中的,我們只要回收堆內(nèi)存即可
在安卓3.0以后Bitmap是存放在內(nèi)存中的,我們需要回收native層和Java層的內(nèi)存
官方建議我們3.0以后使用recycle方法進(jìn)行回收,該方法也可以不主動(dòng)調(diào)用,因?yàn)槔厥掌鲿?huì)自動(dòng)收集不可用的Bitmap對(duì)象進(jìn)行回收
recycle方法會(huì)判斷Bitmap在不可用的情況下,將發(fā)送指令到垃圾回收器,讓其回收native層和Java層的內(nèi)存,則Bitmap進(jìn)入dead狀態(tài)
recycle方法是不可逆的,如果再次調(diào)用getPixels()等方法,則獲取不到想要的結(jié)果
LruCache原理
LruCache是個(gè)泛型類,內(nèi)部采用LinkedHashMap來(lái)實(shí)現(xiàn)緩存機(jī)制,它提供get方法和put方法來(lái)獲取緩存和添加緩存,其最重要的方法trimToSize是用來(lái)移除最少使用的緩存和使用最久的緩存,并添加最新的緩存到隊(duì)列中
計(jì)算inSampleSize
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int width = options.outWidth; final int height = options.outHeight; int inSampleSize = 1; if (width > reqWidth || height > reqHeight) { if (width > height) { inSampleSize = Math.round((float) height / (float) reqHeight); } else { inSampleSize = Math.round((float) width / (float) reqWidth); } } return inSampleSize; }
縮略圖
public static Bitmap thumbnail(String path, int maxWidth, int maxHeight,boolean autoRotate) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Bitmap bitmap = BitmapFactory.decodeFile(path); options.inJustDecodeBounds = false; int sampleSize = calculateInSampleSize(options,maxWidth,maxHeight); options.inSampleSize =sampleSize; options.inPreferredConfig = Bitmap.Config.RGB_565; options.inPurgeable =true; options.inInputShareable = true; if(bitmap !=null&&!bitmap.isRecycled()){ bitmap.recycle(); } bitmap = BitmapFactory.decodeFile(path,options); return bitmap; }
保存Bitmap
public static String save(Bitmap bitmap, Bitmap.CompressFormat format, int quality,File desFile) { try{ FileOutputStream out = new FileOutputStream(desFile); if(bitmap.compress(format,quality,out)){ out.flush(); out.close(); } if(bitmap!=null&&!bitmap.isRecycled()){ bitmap.recycle(); } return desFile.getAbsolutePath(); }catch (FileNotFoundException e){ e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; }
保存到SD卡
public static String save(Bitmap bitmap, Bitmap.CompressFormat format, int quality,Context context) { if(!Environment.getExternalStorageState() .equals(Environment.MEDIA_MOUNTED)){ return null; } File dir = new File(Environment.getExternalStorageDirectory()+ "/"+context.getPackageName()); if(!dir.exists()){ dir.mkdir(); } File desFile = new File(dir, UUID.randomUUID().toString()); return save(bitmap,format,quality,desFile); }
壓縮
一、質(zhì)量壓縮
質(zhì)量壓縮方法:在保持像素的前提下改變圖片的位深及透明度等,來(lái)達(dá)到壓縮圖片的目的:
1、bitmap圖片的大小不會(huì)改變
2、bytes.length是隨著quality變小而變小的。
這樣適合去傳遞二進(jìn)制的圖片數(shù)據(jù),比如分享圖片,要傳入二進(jìn)制數(shù)據(jù)過(guò)去,限制500kb之內(nèi)。
public static Bitmap compressImage(Bitmap image) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 把ByteArrayInputStream數(shù)據(jù)生成圖片 Bitmap bitmap = null; image.compress(Bitmap.CompressFormat.JPEG, 5, baos); byte[] bytes = baos.toByteArray(); // 把壓縮后的數(shù)據(jù)baos存放到bytes中 bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); if (bitmap != null) { loga(bitmap, baos.toByteArray()); } return bitmap; } // 質(zhì)量壓縮方法,options的值是0-100,這里100表示原來(lái)圖片的質(zhì)量,不壓縮,把壓縮后的數(shù)據(jù)存放到baos中 image.compress(Bitmap.CompressFormat.JPEG, 100, baos); int options = 90; // 循環(huán)判斷如果壓縮后圖片是否大于500kb,大于繼續(xù)壓縮 while (baos.toByteArray().length / 1024 > 100) { // 重置baos即清空baos baos.reset(); // 這里壓縮options%,把壓縮后的數(shù)據(jù)存放到baos中 image.compress(Bitmap.CompressFormat.JPEG, options, baos); // 每次都減少10 if(options == 1){ break; }else if (options <= 10) { options -= 1; } else { options -= 10; } }
二、采樣率壓縮
設(shè)置inSampleSize的值(int類型)后,假如設(shè)為n,則寬和高都為原來(lái)的1/n,寬高都減少,內(nèi)存降低。上面的代碼沒(méi)用過(guò)options.inJustDecodeBounds = true; 因?yàn)槲沂枪潭▉?lái)取樣的數(shù)據(jù),為什么這個(gè)壓縮方法叫采樣率壓縮?
是因?yàn)榕浜蟟nJustDecodeBounds,先獲取圖片的寬、高(這個(gè)過(guò)程就是取樣)。
然后通過(guò)獲取的寬高,動(dòng)態(tài)的設(shè)置inSampleSize的值。 當(dāng)inJustDecodeBounds設(shè)置為true的時(shí)候, BitmapFactory通過(guò)decodeResource或者decodeFile解碼圖片時(shí), 將會(huì)返回空(null)的Bitmap對(duì)象,這樣可以避免Bitmap的內(nèi)存分配, 但是它可以返回Bitmap的寬度、高度以及MimeType。
用法
int inSampleSize = getScaling(bitmap); bitmap = samplingRateCompression(path,inSampleSize); private Bitmap samplingRateCompression(String path, int scaling) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = scaling; Bitmap bitmap = BitmapFactory.decodeFile(path, options); int size = (bitmap.getByteCount() / 1024 / 1024); Log.i("wechat", "壓縮后圖片的大小" + (bitmap.getByteCount() / 1024 / 1024) + "M寬度為" + bitmap.getWidth() + "高度為" + bitmap.getHeight()); return bitmap; } /** * 獲取縮放比例 * @param bitmap * @return */ private int getScaling(Bitmap bitmap) { //設(shè)置目標(biāo)尺寸(以像素的寬度為標(biāo)準(zhǔn)) int Targetsize = 1500; int width = bitmap.getWidth(); int height = bitmap.getHeight(); //選擇最大值作為比較值(保證圖片的壓縮大小) int handleValue = width > height ? width : height; //循環(huán)計(jì)算壓縮比 int i = 1; while (handleValue / i > Targetsize) { i++; } }
三、縮放法壓縮, 效果和方法2一樣
Android中使用Matrix對(duì)圖像進(jìn)行縮放、旋轉(zhuǎn)、平移、斜切等變換的。 Matrix是一個(gè)3*3的矩陣,其值對(duì)應(yīng)如下:
|scaleX, skewX, translateX|
|skewY, scaleY, translateY|
|0 , 0 , scale |
Matrix提供了一些方法來(lái)控制圖片變換:
- setTranslate(float dx,float dy):控制Matrix進(jìn)行位移。
- setSkew(float kx,float ky):控制Matrix進(jìn)行傾斜,kx、ky為X、Y方向上的比例。
- setSkew(float kx,float ky,float px,float py):控制Matrix以px、py為軸心進(jìn)行傾斜,kx、ky為X、Y方向上的傾斜比例。
- setRotate(float degrees):控制Matrix進(jìn)行depress角度的旋轉(zhuǎn),軸心為(0,0)。
- setRotate(float degrees,float px,float py):控制Matrix進(jìn)行depress角度的旋轉(zhuǎn),軸心為(px,py)。
- setScale(float sx,float sy):設(shè)置Matrix進(jìn)行縮放,sx、sy為X、Y方向上的縮放比例。
- setScale(float sx,float sy,float px,float py):設(shè)置Matrix以(px,py)為軸心進(jìn)行縮放,sx、sy為X、Y方向上的縮放比例。
注意:以上的set方法,均有對(duì)應(yīng)的post和pre方法,Matrix調(diào)用一系列set,pre,post方法時(shí),可視為將這些方法插入到一個(gè)隊(duì)列. 當(dāng)然,按照隊(duì)列中從頭至尾的順序調(diào)用執(zhí)行. 其中pre表示在隊(duì)頭插入一個(gè)方法,post表示在隊(duì)尾插入一個(gè)方法. 而set表示把當(dāng)前隊(duì)列清空,并且總是位于隊(duì)列的最中間位置. 當(dāng)執(zhí)行了一次set后: pre方法總是插入到set前部的隊(duì)列的最前面,post方法總是插入到set后部的隊(duì)列的最后面
private Bitmap ScalingCompression(Bitmap bitmap) { Matrix matrix = new Matrix(); matrix.setScale(0.25f, 0.25f);//縮放效果類似于方法2 Bitmap bm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); Log.i("wechat", "壓縮后圖片的大小" + (bm.getByteCount() / 1024 / 1024) + "M寬度為" + bm.getWidth() + "高度為" + bm.getHeight()); return bm; }
四、Bitmap.Config
原圖尺寸:4M----轉(zhuǎn)化為File---Bitmap大小
- ALPHA_8----------6.77M -------45M
- ARGB_4444-------9.37M -------22M
- ARGB_8888-------6.77M -------45M
- RGB_565-----------8.13M -------22M
一般情況下默認(rèn)使用的是ARGB8888,由此可知它是最占內(nèi)存的,因?yàn)橐粋€(gè)像素占32位,8位=1字節(jié),所以一個(gè)像素占4字節(jié)的內(nèi)存。假設(shè)有一張480x800的圖片,如果格式為ARGB8888,那么將會(huì)占用1500KB的內(nèi)存。
private Bitmap bitmapConfig(String path) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap bm = BitmapFactory.decodeFile(path, options); Log.i("wechat", "壓縮后圖片的大小" + (bm.getByteCount() / 1024f / 1024f) + "M寬度為" + bm.getWidth() + "高度為" + bm.getHeight()); return bm; }
五、Bitmap提供的:createScaledBitmap
方法
Bitmap.createScaledBitmap(src, dstWidth, dstHeight, filter);
參數(shù)說(shuō)明:
- src 用來(lái)構(gòu)建子集的源位圖
- dstWidth 新位圖期望的寬度
- dstHeight 新位圖期望的高度
- filter 為true則選擇抗鋸齒
補(bǔ)充抗鋸齒的知識(shí)點(diǎn)
在Android中,目前,我知道有兩種出現(xiàn)鋸齒的情況。
1、當(dāng)我們用Canvas繪制位圖的時(shí)候,如果對(duì)位圖進(jìn)行了選擇,則位圖會(huì)出現(xiàn)鋸齒。
2、在用View的RotateAnimation做動(dòng)畫(huà)時(shí)候, 如果View當(dāng)中包含有大量的圖形,也會(huì)出現(xiàn)鋸齒。
我們分別以這兩種情況加以考慮。 用Canvas繪制位圖的的情況。 在用Canvas繪制位圖時(shí),一般地,我們使用drawBitmap函數(shù)家族, 在這些函數(shù)中,都有一個(gè)Paint參數(shù), 要做到防止鋸齒,我們就要使用到這個(gè)參數(shù)。如下:
首先在你的構(gòu)造函數(shù)中,需要?jiǎng)?chuàng)建一個(gè)Paint。 Paint mPaint = new Paint();
然后,您需要設(shè)置兩個(gè)參數(shù):
1)mPaint.setAntiAlias(Boolean aa);
2)mPaint.setBitmapFilter(true)。
第一個(gè)函數(shù)是用來(lái)防止邊緣的鋸齒, (true時(shí)圖像邊緣相對(duì)清晰一點(diǎn),鋸齒痕跡不那么明顯, false時(shí),寫(xiě)上去的字不飽滿,不美觀,看地不太清楚)。
第二個(gè)函數(shù)是用來(lái)對(duì)位圖進(jìn)行濾波處理。
最后,在畫(huà)圖的時(shí)候,調(diào)用drawBitmap函數(shù),只需要將整個(gè)Paint傳入即可。
有時(shí)候,當(dāng)你做RotateAnimation時(shí), 你會(huì)發(fā)現(xiàn),討厭的鋸齒又出現(xiàn)了。 這個(gè)時(shí)候,由于你不能控制位圖的繪制, 只能用其他方法來(lái)實(shí)現(xiàn)防止鋸齒。 另外,如果你畫(huà)的位圖很多。 不想每個(gè)位圖的繪制都傳入一個(gè)Paint。 還有的時(shí)候,你不可能控制每個(gè)窗口的繪制的時(shí)候, 您就需要用下面的方法來(lái)處理——對(duì)整個(gè)Canvas進(jìn)行處理。
1)在您的構(gòu)造函數(shù)中,創(chuàng)建一個(gè)Paint濾波器。 PaintFlagsDrawFilter mSetfil = new PaintFlagsDrawFilter(0, Paint.FILTERBITMAPFLAG); 第一個(gè)參數(shù)是你要清除的標(biāo)志位, 第二個(gè)參數(shù)是你要設(shè)置的標(biāo)志位。此處設(shè)置為對(duì)位圖進(jìn)行濾波。
2)當(dāng)你在畫(huà)圖的時(shí)候, 如果是View則在onDraw當(dāng)中,如果是ViewGroup則在dispatchDraw中調(diào)用如下函數(shù)。 canvas.setDrawFilter( mSetfil );
另外,在Drawable類及其子類中, 也有函數(shù)setFilterBitmap可以用來(lái)對(duì)Bitmap進(jìn)行濾波處理, 這樣,當(dāng)你選擇Drawable時(shí),會(huì)有抗鋸齒的效果。
private Bitmap createScaledBitmap(Bitmap bitmap) { Bitmap bm = Bitmap.createScaledBitmap(bitmap, 200, 200, true); Log.i("wechat", "壓縮后圖片的大小" + (bm.getByteCount() / 1024) + "KB寬度為" + bm.getWidth() + "高度為" + bm.getHeight()); return bm; }
六、輔助方法(上述方法的):
通過(guò)路徑獲取bitmap的方法
1、利用BitmapFactory解析文件,轉(zhuǎn)換為Bitmap
bitmap = BitmapFactory.decodeFile(path);
2、自己寫(xiě)解碼,轉(zhuǎn)換為Bitmap過(guò)程, 同樣需使用BitmapFactory.decodeByteArray(buf, 0, len);代碼如下:
private Bitmap getBitmapByPath(String path) { if (!new File(path).exists()) { System.err.println("getBitmapByPath: 文件不存在"); return null; } byte[] buf = new byte[1024 * 1024];// 1M Bitmap bitmap = null; try { FileInputStream fis = new FileInputStream(path); int len = fis.read(buf, 0, buf.length); bitmap = BitmapFactory.decodeByteArray(buf, 0, len); //當(dāng)bitmap為空的時(shí)候,說(shuō)明解析失敗 if (bitmap == null) { System.out.println("文件長(zhǎng)度:" + len); System.err.println("path: " + path + " 無(wú)法解析!!!"); } } catch (Exception e) { e.printStackTrace(); } return bitmap; }
保存圖片
private void savaPictrue(Bitmap bitmap) { File file = new File("storage/emulated/0/DCIM/Camera/test.jpg"); FileOutputStream stream = null; try { stream = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream); Log.e("圖片大?。?, file.length() / 1024 / 1024 + "M"); } catch (Exception e) { e.printStackTrace(); } }
剪切圖片(這里只是裁剪圖片,但是對(duì)圖片的大小并不影響)
private void crop(Uri uri) { // 裁剪圖片意圖 Intent intent = new Intent("com.android.camera.action.CROP"); intent.setDataAndType(uri, "image/*"); intent.putExtra("crop", "true"); // 裁剪框的比例,1:1 intent.putExtra("aspectX", 1); intent.putExtra("aspectY", 1); // 裁剪后輸出圖片的尺寸大小 intent.putExtra("outputX", 300); intent.putExtra("outputY", 300); intent.putExtra("outputFormat", "JPEG");// 圖片格式 intent.putExtra("noFaceDetection", true);// 取消人臉識(shí)別 intent.putExtra("return-data", true); // 開(kāi)啟一個(gè)帶有返回值的Activity,請(qǐng)求碼為PHOTO_REQUEST_CUT startActivityForResult(intent, 200); } private void logp(Bitmap bitmap) { Log.i("wechat", "壓縮前圖片的大小" + (bitmap.getByteCount() / 1024 / 1024) + "M寬度為" + bitmap.getWidth() + "高度為" + bitmap.getHeight()); } private static void loga(Bitmap bitmap, byte[] bytes) { Log.i("wechat", "壓縮后圖片的大小" + (bitmap.getByteCount() / 1024 / 1024) + "M寬度為" + bitmap.getWidth() + "高度為" + bitmap.getHeight() + "bytes.length= " + (bytes.length / 1024) + "KB" ); }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
利用Android模仿微信攝像圓環(huán)進(jìn)度效果實(shí)例
圓環(huán)進(jìn)度條,大家應(yīng)該都見(jiàn)過(guò),而這篇文章主要給大家介紹了關(guān)于利用Android模仿微信攝像圓環(huán)進(jìn)度效果的相關(guān)資料,實(shí)現(xiàn)后的效果非常不錯(cuò),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11Android 使用手機(jī)NFC的讀取NFC標(biāo)簽數(shù)據(jù)的方法
這篇文章主要介紹了Android 使用手機(jī)NFC的讀取NFC標(biāo)簽數(shù)據(jù)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07Android編程實(shí)現(xiàn)手繪及保存為圖片的方法(附demo源碼下載)
這篇文章主要介紹了Android編程實(shí)現(xiàn)手繪及保存為圖片的方法,涉及Android畫(huà)布的使用及圖片的操作技巧,并附帶了demo源碼供讀者下載,需要的朋友可以參考下2015-12-12Android Surfaceview的繪制與應(yīng)用
這篇文章主要介紹了Android Surfaceview的繪制與應(yīng)用的相關(guān)資料,需要的朋友可以參考下2017-07-07利用Jetpack Compose實(shí)現(xiàn)主題切換功能
這篇文章主要介紹了如何利用Android中的Jetpack Compose實(shí)現(xiàn)主題切換功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)有一定幫助,需要的可以參考一下2022-01-01利用DrawerLayout和觸摸事件分發(fā)實(shí)現(xiàn)抽屜側(cè)滑效果
這篇文章主要為大家詳細(xì)介紹了利用DrawerLayout和觸摸事件分發(fā)實(shí)現(xiàn)抽屜側(cè)滑效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10android為L(zhǎng)istView每個(gè)Item上面的按鈕添加事件
本篇文章主要介紹了android為L(zhǎng)istView每個(gè)Item上面的按鈕添加事件,有興趣的同學(xué)可以了解一下。2016-11-11Android動(dòng)畫(huà)之逐幀動(dòng)畫(huà)(Frame Animation)實(shí)例詳解
這篇文章主要介紹了Android動(dòng)畫(huà)之逐幀動(dòng)畫(huà)(Frame Animation),結(jié)合實(shí)例形式較為詳細(xì)的分析了逐幀動(dòng)畫(huà)的原理,注意事項(xiàng)與相關(guān)使用技巧,需要的朋友可以參考下2016-01-01