Android中的Bitmap

Bitmap位圖簡介

位圖文件(Bitmap),擴展名能夠是.bmp或者.dib。位圖是Windows標準格式圖形文件,它將圖像定義爲由點(像素)組成,每一個點能夠由多種色彩表示,包括二、四、八、1六、24和32位色彩。位圖文件是非壓縮格式的,須要佔用較大存儲空間。java

例如,一幅1920X1080分辨率的32位圖片,其所佔存儲字節數爲:1920×1080×32/(8*1024)=8100KB=7.91Mandroid

在安卓系統中bitmap圖片通常是以ARGB_8888(ARGB分別表明的是透明度,紅色,綠色,藍色,每一個值分別用8bit來記錄,也就是一個像素會佔用4byte,共32bit。)來進行存儲的。canvas

Android中圖片有四種顏色格式

顏色格式 每一個像素佔用內存(單位byte) 每一個像素佔用內存(單位bit)
ALPHA_8 1 8
ARGB_8888(默認) 4 32
ARGB_4444 2 16
RGB_565 2 16

ARGB_8888佔位計算: 8+8+8+8 =32緩存

1 bit 0/1 ;最小單位bash

1 byte = 8bit 10101010 (0-255)00000000 --- 11111111dom

說明:函數

ARGB_8888:ARGB分別表明的是透明度,紅色,綠色,藍色,每一個值分別用8bit來記錄,也就是一個像素會佔用4byte,共32bit.post

ARGB_4444:ARGB的是每一個值分別用4bit來記錄,一個像素會佔用2byte,共16bit.動畫

RGB_565:R=5bit,G=6bit,B=5bit,不存在透明度,每一個像素會佔用2byte,共16bit.ui

ALPHA_8:該像素只保存透明度,會佔用1byte,共8bit.

在實際應用中而言,建議使用ARGB_8888以及RGB_565。 若是你不須要透明度,選擇RGB_565,能夠減小一半的內存佔用.

Bitmap的回收

在Android3.0之前Bitmap是存放在內存中的,咱們須要回收native層和Java層的內存

在Android3.0之後Bitmap是存放在堆中的,咱們只要回收堆內存便可

官方建議咱們3.0之後使用recycle()方法進行回收,該方法能夠不主動調用,由於垃圾回收器會自動收集不可用的Bitmap對象進行回收

recycle()官方說明:

釋放與此位圖關聯的本地對象,並清除對像素數據的引用。這不會同步釋放像素數據;它只是容許它被垃圾收集,若是沒有其餘的參考。位圖被標記爲「dead」,意味着若是getPixels()或setPixels()被調用,它將拋出異常,而且不會畫任何東西。這個操做是不能逆轉的,因此只有在你肯定沒有進一步的位圖使用時才能調用它。這是一個高級調用,一般不須要調用,由於當沒有更多的引用到這個位圖時,正常的GC進程將釋放這個內存。

LruCache介紹

LruCache是個泛型類,內部採用LinkedHashMap來實現緩存機制,它提供get方法和put方法來獲取緩存和添加緩存,其最重要的方法trimToSize是用來移除最少使用的緩存和使用最久的緩存,並添加最新的緩存到隊列中

計算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;
    }
複製代碼

縮略圖

獲取指定的寬高的bitmap的縮略圖

public static Bitmap thumbnail(String  path, int maxWidth, int maxHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeFile(path,options);
        int sampleSize = calculateInSampleSize(options,maxWidth,maxHeight);
        options.inJustDecodeBounds = false;
        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

將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);
    }
複製代碼

壓縮

1、質量壓縮

用到的方法是Bitmap 的compress();

質量壓縮方法:在保持像素的前提下改變圖片的位深及透明度等,來達到壓縮圖片的目的:

一、bitmap圖片的大小不會改變

二、bytes.length是隨着quality變小而變小的。

這樣適合去傳遞二進制的圖片數據,好比分享圖片,要傳入二進制數據過去,限制500kb以內。

public static Bitmap compressImage(Bitmap image) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 把ByteArrayInputStream數據生成圖片
    Bitmap bitmap = null;
    image.compress(Bitmap.CompressFormat.JPEG, 5, baos);
    byte[] bytes = baos.toByteArray();
    // 把壓縮後的數據baos存放到bytes中
    bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    if (bitmap != null) {
        loga(bitmap, baos.toByteArray());
    }
    return bitmap;
}

/**
 * @param image    目標原圖
 * @param maxSize 最大的圖片大小,
 * @return
 */
public static Bitmap compressImage(Bitmap image,long maxSize) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 把ByteArrayInputStream數據生成圖片
    Bitmap bitmap = null;
    // 質量壓縮方法,options的值是0-100,這裏100表示原來圖片的質量,不壓縮,把壓縮後的數據存放到baos中
    image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
    int options = 90;
    // 循環判斷若是壓縮後圖片是否大於maxSize,大於繼續壓縮
    while (baos.toByteArray().length  > maxSize) {
        // 重置baos即清空baos
        baos.reset();
        // 這裏壓縮options%,把壓縮後的數據存放到baos中
        image.compress(Bitmap.CompressFormat.JPEG, options, baos);
        // 每次都減小10,當爲1的時候中止,options<10的時候,遞減1
        if(options == 1){
            break;
        }else if (options <= 10) {
            options -= 1;
        } else {
            options -= 10;
        }
    }
    byte[] bytes = baos.toByteArray();
    // 把壓縮後的數據baos存放到bytes中
    bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    if (bitmap != null) {
        loga(bitmap, baos.toByteArray());
    }
    return bitmap;
}
複製代碼

2、採樣率壓縮

設置inSampleSize的值(int類型)後,假如設爲n,則寬和高都爲原來的1/n,寬高都減小,內存下降。上面的代碼沒用過options.inJustDecodeBounds = true; 由於我是固定來取樣的數據,爲何這個壓縮方法叫採樣率壓縮?

是由於配合inJustDecodeBounds,先獲取圖片的寬、高(這個過程就是取樣)。

而後經過獲取的寬高,動態的設置inSampleSize的值。 當inJustDecodeBounds設置爲true的時候, BitmapFactory經過decodeResource或者decodeFile解碼圖片時, 將會返回空(null)的Bitmap對象,這樣能夠避免Bitmap的內存分配, 可是它能夠返回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) {
    //設置目標尺寸(以像素的寬度爲標準)
    int Targetsize = 1500;
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    //選擇最大值做爲比較值(保證圖片的壓縮大小)
    int handleValue = width > height ? width : height;
    //循環計算壓縮比
    int i = 1;
    while (handleValue / i > Targetsize) {
        i++;
    }
}
複製代碼

3、縮放法壓縮, 效果和方法2同樣

Android中使用Matrix對圖像進行縮放、旋轉、平移、斜切等變換的。 Matrix是一個3*3的矩陣,其值對應以下:

|scaleX, skewX, translateX|

|skewY, scaleY, translateY|

|0 , 0 , scale |

Matrix提供了一些方法來控制圖片變換:

  • setTranslate(float dx,float dy):控制Matrix進行位移。
  • setSkew(float kx,float ky):控制Matrix進行傾斜,kx、ky爲X、Y方向上的比例。
  • setSkew(float kx,float ky,float px,float py):控制Matrix以px、py爲軸心進行傾斜,kx、ky爲X、Y方向上的傾斜比例。
  • setRotate(float degrees):控制Matrix進行depress角度的旋轉,軸心爲(0,0)。
  • setRotate(float degrees,float px,float py):控制Matrix進行depress角度的旋轉,軸心爲(px,py)。
  • setScale(float sx,float sy):設置Matrix進行縮放,sx、sy爲X、Y方向上的縮放比例。
  • setScale(float sx,float sy,float px,float py):設置Matrix以(px,py)爲軸心進行縮放,sx、sy爲X、Y方向上的縮放比例。

注意:以上的set方法,均有對應的post和pre方法, Matrix調用一系列set,pre,post方法時,可視爲將這些方法插入到一個隊列. 固然,按照隊列中從頭到尾的順序調用執行. 其中pre表示在隊頭插入一個方法,post表示在隊尾插入一個方法. 而set表示把當前隊列清空,而且老是位於隊列的最中間位置. 當執行了一次set後: pre方法老是插入到set前部的隊列的最前面,post方法老是插入到set後部的隊列的最後面

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;
}
複製代碼

4、Bitmap.Config

原圖大小:4M----轉化爲File---Bitmap大小

  • ALPHA_8----------6.77M -------45M
  • ARGB_4444-------9.37M -------22M
  • ARGB_8888-------6.77M -------45M
  • RGB_565-----------8.13M -------22M

通常狀況下默認使用的是ARGB8888,由此可知它是最佔內存的,由於一個像素佔32位,8位=1字節,因此一個像素佔4字節的內存。假設有一張480x800的圖片,若是格式爲ARGB8888,那麼將會佔用1500KB的內存。

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;
} 
複製代碼

5、Bitmap提供的:createScaledBitmap

方法

Bitmap.createScaledBitmap(src, dstWidth, dstHeight, filter);

參數說明:

  • src 用來構建子集的源位圖
  • dstWidth 新位圖指望的寬度
  • dstHeight 新位圖指望的高度
  • filter 爲true則選擇抗鋸齒

補充抗鋸齒的知識點

在Android中,目前,我知道有兩種出現鋸齒的狀況。

  • 一、當咱們用Canvas繪製位圖的時候,若是對位圖進行了選擇,則位圖會出現鋸齒。
  • 二、在用View的RotateAnimation作動畫時候, 若是View當中包含有大量的圖形,也會出現鋸齒。

咱們分別以這兩種狀況加以考慮。 用Canvas繪製位圖的的狀況。 在用Canvas繪製位圖時,通常地,咱們使用drawBitmap函數家族, 在這些函數中,都有一個Paint參數, 要作到防止鋸齒,咱們就要使用到這個參數。以下:

首先在你的構造函數中,須要建立一個Paint。 Paint mPaint = new Paint(); 而後,您須要設置兩個參數:

  • 1)mPaint.setAntiAlias(Boolean aa);
  • 2)mPaint.setBitmapFilter(true)。

第一個函數是用來防止邊緣的鋸齒, (true時圖像邊緣相對清晰一點,鋸齒痕跡不那麼明顯, false時,寫上去的字不飽滿,不美觀,看地不太清楚)。

第二個函數是用來對位圖進行濾波處理。

最後,在畫圖的時候,調用drawBitmap函數,只須要將整個Paint傳入便可。

有時候,當你作RotateAnimation時, 你會發現,鋸齒又出現了。 這個時候,因爲你不能控制位圖的繪製, 只能用其餘方法來實現防止鋸齒。 另外,若是你畫的位圖不少。 不想每一個位圖的繪製都傳入一個Paint。 還有的時候,你不可能控制每一個窗口的繪製的時候, 您就須要用下面的方法來處理——對整個Canvas進行處理。

  • 1)在您的構造函數中,建立一個Paint濾波器。 PaintFlagsDrawFilter mSetfil = new PaintFlagsDrawFilter(0, Paint.FILTERBITMAPFLAG); 第一個參數是你要清除的標誌位, 第二個參數是你要設置的標誌位。此處設置爲對位圖進行濾波。
  • 2)當你在畫圖的時候, 若是是View則在onDraw當中,若是是ViewGroup則在dispatchDraw中調用以下函數。 canvas.setDrawFilter( mSetfil );

另外,在Drawable類及其子類中, 也有函數setFilterBitmap能夠用來對Bitmap進行濾波處理, 這樣,當你選擇Drawable時,會有抗鋸齒的效果。

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;
}
複製代碼

6、輔助方法(上述方法的):

經過路徑獲取bitmap的方法

  • 一、利用BitmapFactory解析文件,轉換爲Bitmap bitmap = BitmapFactory.decodeFile(path);
  • 二、本身寫解碼,轉換爲Bitmap過程, 一樣需使用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);
    //當bitmap爲空的時候,說明解析失敗
    if (bitmap == null) {
        System.out.println("文件長度:" + len);
        System.err.println("path: " + path + " 沒法解析!!!");
    }
} 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();
    }
}
複製代碼

剪切圖片(這裏只是裁剪圖片,可是對圖片的大小並不影響)

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);// 取消人臉識別
    intent.putExtra("return-data", true);
    // 開啓一個帶有返回值的Activity,請求碼爲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"
    );
}
複製代碼

相關文章
相關標籤/搜索