1. 程式人生 > >Android 壓縮大圖到容量超小的圖片

Android 壓縮大圖到容量超小的圖片

2016過去了,今天是2017年的第一天。雖然2016年發生了很多事,But anyway,過去的事就不再去躊躇了,人總要向前看,向樂觀的方向前進。新的一年,新的希望,這篇是新年第一篇文章了,也祝大家新年快樂了。

今天主要記錄下壓縮大圖的方法和封裝。首先先分析下壓縮圖片的重點,最後上封裝後的程式碼。

壓縮圖片的寬高

現在的手機解析度一般都是1080P,很多相片拍照出來都是4K左右的解析度,一張圖片的容量就高達4M以上,要是把原圖直接上傳,那耗費的時間和流量是無比巨大的。原圖的解析度是4K,那麼我們就可以把他壓縮到1080P級別了,也就是把寬和高都按比例縮小。壓縮寬高主要用到了BitmapFactory.Options的inSampleSize欄位,壓縮比例為 1/inSampleSize,也就是當inSampleSize為2時,壓縮為原圖寬高的1/2,當inSampleSize為8時,壓縮為原圖的1/8,以此類推。
首先需要計算當前螢幕的寬高和圖片的寬高之間的壓縮比,當然也可以按照自己專案的需求去設定壓縮比。
先抽象一個比較壓縮比的方法:

public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){//計算圖片的壓縮比
        final int height=options.outHeight;//圖片的高度 單位1px 即畫素點
        final int width=options.outWidth;//圖片的寬度 

        int inSampleSize=1;//壓縮比

        if(height>reqHeight||width>reqWidth){
            final
int halfHeight=height/2; final int halfWidth=width/2; while ((halfHeight/inSampleSize)>=reqHeight &&(halfWidth/inSampleSize)>=reqWidth){ inSampleSize*=2; } } return inSampleSize; }

呼叫calculateInSampleSize計算壓縮比,並解碼原圖為Bitmap:

BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds=true;//只測量image 不載入到記憶體
BitmapFactory.decodeFile(imagePath,options);//測量image
options.inPreferredConfig= Bitmap.Config.RGB_565;//設定565編碼格式 省記憶體
options.inSampleSize=calculateInSampleSize(options,displayWidth,displayHeight);//獲取壓縮比 根據當前螢幕寬高去壓縮圖片
options.inJustDecodeBounds=false;
Bitmap bitmap=BitmapFactory.decodeFile(imagePath,options);//按照Options配置去載入圖片到記憶體

這裡比較重要的inJustDecodeBounds欄位,當inJustDecodeBounds為true時,呼叫BitmapFactory.decode 時並沒有把圖片載入到記憶體中去,只是去測量圖片的寬高,並不佔用記憶體。當inJustDecodeBounds為false時,呼叫BitmapFactory.decode時就把圖片載入到記憶體中去了,所以獲取bitmap應在在inJustDecodeBounds為false後的BitmapFactory.decode去獲取。

設定解碼格式

Android中預設的解碼格式是ARGB8888,我們解碼圖片一般可以使用ARGB565來節省圖片載入到記憶體中的大小。

在Android.graphics.Bitmap類裡有一個內部類Bitmap.Config類,裡面定義了列舉變數
public static final Bitmap.Config ALPHA_8
public static final Bitmap.Config ARGB_4444
public static final Bitmap.Config ARGB_8888
public static final Bitmap.Config RGB_565

這些都是色彩的儲存方法,ARGB指的是一種色彩模式,裡面A代表Alpha,R表示red,G表示green,B表示blue,其實所有的可見色都是右紅綠藍組成的,所以紅綠藍又稱為三原色,每個原色都儲存著所表示顏色的資訊值。
ALPHA_8就是Alpha由8位組成;
ARGB_4444就是由4個4位組成即16位;
ARGB_8888就是由4個8位組成即32位;
RGB_565就是R為5位,G為6位,B為5位共16位。
由此可見:
ALPHA_8 代表8位Alpha點陣圖
ARGB_4444 代表16位ARGB點陣圖
ARGB_8888 代表32位ARGB點陣圖
RGB_565 代表8位RGB點陣圖
點陣圖位數越高代表其可以儲存的顏色資訊越多,當然影象也就越逼真

所以使用RGB565 16位比ARGB8888 32位少了一半的位數,減少了儲存器的容量的同時,降低了資料量。
使用RGB565解碼時直接設定BitmapFactory.Options就可以了。

options.inPreferredConfig= Bitmap.Config.RGB_565;//設定565編碼格式 省記憶體

圖片質量壓縮

上面的壓縮方法只是壓縮圖片載入到記憶體中的佔用大小,我們還必須將圖片進行一次質量壓縮,輸出位元組流陣列,才能有效的將圖片壓縮。

 ByteArrayOutputStream out=new ByteArrayOutputStream();//位元組流輸出
 bitmap.compress(Bitmap.CompressFormat.JPEG,50,out);//壓縮成JPEG格式 壓縮畫素質量為50%

compress中第一個引數輸出檔案的格式,在Bitmap列舉類CompressFormat中定義,有JPEG(0),PNG (1), WEBP (2), 一般應當選擇JPEG,壓縮出來的容量小,經過測試WEBP壓縮很耗時,耗時時間比較:WEBP>PNG>JPEG,壓縮大小:PNG>WEBP>JPEG。第二個引數是壓縮的質量比例,也就是壓縮畫素的顯示色彩,當100時表示不壓縮,當為50時表示壓縮50%的質量,設定這個引數可以有效的極大的縮小圖片的大小,可以按照自己的需求進行設定,但建議一般不要大於60。第三個引數就是想要寫入圖片資料的位元組流陣列了。

位元組流寫出檔案

但我們經過上述步驟後,就拿到了位元組流資料了,此時我們可以根據專案需求直接上傳位元組流或者儲存為本地圖片再上傳。

        ByteArrayOutputStream out=new ByteArrayOutputStream();//位元組流輸出
        bitmap.compress(Bitmap.CompressFormat.JPEG,50,out);//壓縮成JPEG格式 壓縮畫素質量為50%

        String fileName=imagePath.substring(imagePath.lastIndexOf("/")+1,imagePath.lastIndexOf("."));//獲取檔名稱
        File outFile=new File("/storage/emulated/0/PhotoPickTemp",fileName+"_temp.jpeg");//建立壓縮後的image檔案
        try {
            if(!outFile.exists()){//判斷新檔案是否存在
                if(outFile.createNewFile()){//判斷建立新檔案是否成功
                    FileOutputStream fos=new FileOutputStream(outFile);//建立一個檔案輸出流
                    byte[] bytes=out.toByteArray();//位元組陣列
                    int count=bytes.length;//位元組陣列的長度
                    fos.write(bytes,0,count);//寫到檔案中
                    fos.close();//關閉流
                    out.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

獲取當前螢幕的寬高

首先需要獲取當前螢幕的寬高,我們抽取一個工具類出來。

public class ScreenUtil {
    private int displayWidth;
    private int displayHeight;

    private ScreenUtil() {
        DisplayMetrics metric = new DisplayMetrics();//獲取系統螢幕資訊
        WindowManager windowManager = (WindowManager) MyApplication.getInstance().getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getMetrics(metric);
        this.displayWidth = metric.widthPixels;     // 螢幕寬度(畫素)
        this.displayHeight = metric.heightPixels;   // 螢幕高度(畫素)
    }

    private static class ScreenHolder {
        private final static ScreenUtil screenUtil = new ScreenUtil();
    }

    public static ScreenUtil getInstance() {
        return ScreenHolder.screenUtil;
    }

    public int getDisplayWidth() {
        return displayWidth;
    }

    public void setDisplayWidth(int displayWidth) {
        this.displayWidth = displayWidth;
    }

    public int getDisplayHeight() {
        return displayHeight;
    }

    public void setDisplayHeight(int displayHeight) {
        this.displayHeight = displayHeight;
    }
}

獲取Application物件:

public class MyApplication extends Application {
    private static MyApplication myApplication;

    @Override
    public void onCreate(){
        super.onCreate();
        myApplication=this;
    }

    public static MyApplication getInstance(){
        return myApplication;
    }
}

自定義Application需要在AndroidManifest中Application節點進行name欄位宣告:

<application
        android:name="application.MyApplication"//宣告自定義Application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

封裝ImageUtil

public class ImageUtil {

    public ImageUtil(){

    }

    public static File compressImage(String imagePath,int displayWidth,int displayHeight){
        BitmapFactory.Options options=new BitmapFactory.Options();
        options.inJustDecodeBounds=true;//只測量image 不載入到記憶體
        BitmapFactory.decodeFile(imagePath,options);//測量image

        options.inPreferredConfig= Bitmap.Config.RGB_565;//設定565編碼格式 省記憶體
        options.inSampleSize=calculateInSampleSize(options,displayWidth,displayHeight);//獲取壓縮比 根據當前螢幕寬高去壓縮圖片

        options.inJustDecodeBounds=false;
        Bitmap bitmap=BitmapFactory.decodeFile(imagePath,options);//按照Options配置去載入圖片到記憶體

        ByteArrayOutputStream out=new ByteArrayOutputStream();//位元組流輸出
        bitmap.compress(Bitmap.CompressFormat.JPEG,50,out);//壓縮成JPEG格式 壓縮畫素質量為50%

        String fileName=imagePath.substring(imagePath.lastIndexOf("/")+1,imagePath.lastIndexOf("."));//獲取檔名稱
        File outFile=new File("/storage/emulated/0/PhotoPickTemp",fileName+"_temp.jpeg");//建立壓縮後的image檔案
        try {
            if(!outFile.exists()){//判斷新檔案是否存在
                if(outFile.createNewFile()){//判斷建立新檔案是否成功
                    FileOutputStream fos=new FileOutputStream(outFile);//建立一個檔案輸出流
                    byte[] bytes=out.toByteArray();//位元組陣列
                    int count=bytes.length;//位元組陣列的長度
                    fos.write(bytes,0,count);//寫到檔案中
                    fos.close();//關閉流
                    out.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return outFile;
    }

    public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){//計算圖片的壓縮比
        final int height=options.outHeight;//圖片的高度
        final int width=options.outWidth;//圖片的寬度 單位1px 即畫素點

        int inSampleSize=1;//壓縮比

        if(height>reqHeight||width>reqWidth){
            final int halfHeight=height/2;
            final int halfWidth=width/2;
            while ((halfHeight/inSampleSize)>=reqHeight
                    &&(halfWidth/inSampleSize)>=reqWidth){
                inSampleSize*=2;
            }
        }
        return inSampleSize;
    }
}

在子執行緒中使用 千萬記得在子執行緒中呼叫,因為這是耗時操作,主執行緒呼叫很可能會導致ANR發生,如果圖片比較多,建議開啟多執行緒去操作,可以參考我上一篇部落格 多檔案上傳 ,在UploadFile中run()中呼叫:

File outFile=ImageUtil.compressImage(filePath,
                    ScreenUtil.getInstance().getDisplayWidth(),
                    ScreenUtil.getInstance().getDisplayHeight());

經過上面的壓縮後,一張4.41M的圖片可以壓縮到255KB,一張2.8M的圖片可以壓縮到111KB,壓縮比例高達2000%左右。