天天看點

Android 圖像壓縮

Android中圖檔是以Bitmap形式存在的,Bitmap所占記憶體直接影響應用所占記憶體大小,Bitmap所占記憶體大小計算公式:

圖檔長度 * 圖檔寬度 * 一個像素點占用的位元組數

Bitmap壓縮顔色格式:

Android 圖像壓縮
圖1.png

  1. 品質壓縮
Bitmap bitmap = BitmapFactory.decodeFile(path);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int quality = 80;
        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
        byte[] bytes = baos.toByteArray();
        bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
           

圖檔的大小沒有變化,因為品質有壓縮不會減少圖檔的像素,它是在保持像素的前提下改變圖圖檔的位深及透明度,來達到壓縮圖檔的目的。

注: 當compress方法中第一個參數為Bitmap.CompressFormat.PNG時,quality沒有作用,因為png圖檔時無損的,不能進行壓縮。

  1. 采樣率壓縮
BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 2;
        Bitmap bitmap = BitmapFactory.decodeFile(path, options);
           

設定inSampleSize的值為2時,寬和高都變為原來的1/2。

注:當設定options.inJustDecodeBounds = true時,BitmapFactory解碼圖檔時會傳回空的Bitmap對象,但是也可以傳回Bitmap的寬、高以及MimeType。

  1. 縮放法壓縮(Martix)
Matrix matrix = new Matrix();
        matrix.setScale(0.5f, 0.5f);
        Bitmap bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
            bitmap.getHeight(), matrix, true);
           

其中,bitmap的寬高分别縮小了一半,圖檔大小壓縮成1/4。

  1. RGB_565
BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        Bitmap bitmap = BitmapFactory.decodeFile(path, options);
           

圖檔大小直接縮小了一半,寬高沒有變。

注意:由于ARGB_4444的畫質慘不忍睹,一般假如對圖檔沒有透明度要求的話,可以改成RGB_565,相比ARGB_8888将節省一半的記憶體開銷。

  1. createScaledBitmap
Bitmap bitmap = BitmapFactory.decodeFile(path);
        bitmap = Bitmap.createScaledBitmap(bitmap, 150, 150, true);
           

這裡将圖檔壓縮成使用者所期望的寬高。

系統壓縮工具類

從Android 2.2開始系統新增了一個縮略圖ThumbnailUtils類,位于framework包下的android.media.ThumbnailUtils位置,可以幫助我們從mediaprovider中擷取系統中的視訊或圖檔檔案的縮略圖,該類提供了三種靜态方法可以直接調用擷取。
 1、extractThumbnail (source, width, height):
 /**
  * 建立一個指定大小的縮略圖
  * @param source 源檔案(Bitmap類型)
  * @param width  壓縮成的寬度
  * @param height 壓縮成的高度
  */
  ThumbnailUtils.extractThumbnail(source, width, height);  

2、extractThumbnail(source, width, height, options):
/**
  * 建立一個指定大小居中的縮略圖
  * @param source 源檔案(Bitmap類型)
  * @param width  輸出縮略圖的寬度
  * @param height 輸出縮略圖的高度
  * @param options 如果options定義為OPTIONS_RECYCLE_INPUT,則回收@param source這個資源檔案
  * (除非縮略圖等于@param source)
  *
  */
ThumbnailUtils.extractThumbnail(source, width, height, options);  

3、createVideoThumbnail(filePath, kind): 
/**
  * 建立一張視訊的縮略圖
  * 如果視訊已損壞或者格式不支援可能傳回null
  * @param filePath 視訊檔案路徑  如:/sdcard/android.3gp
  * @param kind kind可以為MINI_KIND或MICRO_KIND
  *
  */
 ThumbnailUtils.createVideoThumbnail(filePath, kind);
           

壓縮工具類

public class CompressUtils {  
    /** 
     * 按品質壓縮 
     * @param bitmap 
     * @return 
     */  
        public static Bitmap compressImage(Bitmap bitmap){  
            ByteArrayOutputStream baos = new ByteArrayOutputStream();  
            //品質壓縮方法,這裡100表示不壓縮,把壓縮後的資料存放到baos中  
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);  
            int options = 100;  
            //循環判斷如果壓縮後圖檔是否大于100kb,大于繼續壓縮  
            while ( baos.toByteArray().length / 1024>100) {  
                //清空baos  
                baos.reset();  
                bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);  
                options -= 10;//每次都減少10  
            }  
            //把壓縮後的資料baos存放到ByteArrayInputStream中  
            ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());  
            //把ByteArrayInputStream資料生成圖檔  
            Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null);  
            return newBitmap;  
        }  
  
    /** 
     * 按圖檔尺寸壓縮 參數為路徑 
     * @param imgPath 圖檔路徑 
     * @param pixelW 目标圖檔寬度 
     * @param pixelH 目标圖檔高度 
     * @return 
     */  
    public static Bitmap compressImageFromPath(String imgPath, int pixelW, int pixelH) {  
        BitmapFactory.Options options = new BitmapFactory.Options();  
        // 開始讀入圖檔,此時把options.inJustDecodeBounds 設回true,即隻讀邊不讀内容  
        options.inJustDecodeBounds = true;  
        options.inPreferredConfig = Bitmap.Config.RGB_565;  
        BitmapFactory.decodeFile(imgPath,options);  
        options.inJustDecodeBounds = false;  
        options.inSampleSize = computeSampleSize(options , pixelH > pixelW ? pixelH : pixelW ,pixelW * pixelH );  
        Bitmap bitmap = BitmapFactory.decodeFile(imgPath, options);  
        return bitmap;  
    }  
  
    /** 
     * 按圖檔尺寸壓縮 參數是bitmap 
     * @param bitmap 
     * @param pixelW 
     * @param pixelH 
     * @return 
     */  
    public static Bitmap compressImageFromBitmap(Bitmap bitmap, int pixelW, int pixelH) {  
        ByteArrayOutputStream os = new ByteArrayOutputStream();  
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);  
        if( os.toByteArray().length / 1024>1024) {//判斷如果圖檔大于1M,進行壓縮避免在生成圖檔(BitmapFactory.decodeStream)時溢出  
            os.reset();  
            bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);//這裡壓縮50%,把壓縮後的資料存放到baos中  
        }  
        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());  
        BitmapFactory.Options options = new BitmapFactory.Options();  
        options.inJustDecodeBounds = true;  
        options.inPreferredConfig = Bitmap.Config.RGB_565;  
        BitmapFactory.decodeStream(is, null, options);  
        options.inJustDecodeBounds = false;  
        options.inSampleSize = computeSampleSize(options , pixelH > pixelW ? pixelH : pixelW ,pixelW * pixelH );  
        is = new ByteArrayInputStream(os.toByteArray());  
        Bitmap newBitmap = BitmapFactory.decodeStream(is, null, options);  
        return newBitmap;  
    }  
  
    /** 
     * 對圖檔進行縮放指定大小 
     * @param bitmap 
     * @param width 
     * @param height 
     * @return 
     */  
    public static Bitmap scaleTo(Bitmap bitmap, int width, int height){  
        int originalWidth = bitmap.getWidth();  
        int originalHeight = bitmap.getHeight();  
        float xScale = (float)width / originalWidth;  
        float yScale = (float)height / originalHeight;  
        Matrix matrix = new Matrix();  
        matrix.setScale(xScale,yScale);  
        return Bitmap.createBitmap(bitmap, 0, 0, originalWidth, originalHeight, matrix, true);  
    }  
  
    /** 
     * 以最省記憶體的方式讀取本地資源的圖檔 
     * @param context 
     * @param resId 
     * @return 
     */  
    public static Bitmap readBitmap(Context context, int resId) {  
        BitmapFactory.Options opt = new BitmapFactory.Options();  
        opt.inPreferredConfig = Bitmap.Config.RGB_565;  
        opt.inPurgeable = true;  
        opt.inInputShareable = true;  
        // 擷取資源圖檔  
        InputStream is = context.getResources().openRawResource(resId);  
        return BitmapFactory.decodeStream(is, null, opt);  
    }  
  
    /** 
     * android源碼提供給我們的動态計算出圖檔的inSampleSize方法 
     * @param options 
     * @param minSideLength 
     * @param maxNumOfPixels 
     * @return 
     */  
    public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {  
        int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);  
        int roundedSize;  
        if (initialSize <= 8) {  
            roundedSize = 1;  
            while (roundedSize < initialSize) {  
                roundedSize <<= 1;  
            }  
        } else {  
            roundedSize = (initialSize + 7) / 8 * 8;  
        }  
        return roundedSize;  
    }  
  
    private static int computeInitialSampleSize(BitmapFactory.Options options,int minSideLength, int maxNumOfPixels) {  
        double w = options.outWidth;  
        double h = options.outHeight;  
        int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));  
        int upperBound = (minSideLength == -1) ? 128 :(int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));  
        if (upperBound < lowerBound) {  
            return lowerBound;  
        }  
        if ((maxNumOfPixels == -1) && (minSideLength == -1)) {  
            return 1;  
        } else if (minSideLength == -1) {  
            return lowerBound;  
        } else {  
            return upperBound;  
        }  
    }  
}  
           

有效的處理Bitmap OOM方法

public class BitmapActivity extends Activity {  
    public ImageView imageView = null;  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.bitmap_activity);  
        imageView = (ImageView) findViewById(R.id.imageView1);  
        ImageAsyncTask task = new ImageAsyncTask(imageView,50,50);  
        task.execute("http://image.baidu.com/search/down?tn=download&word=download&ie=utf8&fr=detail&url=http%3A%2F%2Fimg1.pconline.com.cn%2Fpiclib%2F200904%2F28%2Fbatch%2F1%2F32910%2F12408824046039mk21hbi75.jpg&thumburl=http%3A%2F%2Fimg2.imgtn.bdimg.com%2Fit%2Fu%3D3414739956%2C4196877666%26fm%3D21%26gp%3D0.jpg");  
    }  
    /** 
     * 計算出壓縮比 
     * @param options 
     * @param reqWith 
     * @param reqHeight 
     * @return 
     */  
    public int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight)  
    {  
        //通過參數options來擷取真實圖檔的寬、高  
        int width = options.outWidth;  
        int height = options.outHeight;  
        int inSampleSize = 1;//初始值是沒有壓縮的  
        if(width > reqWidth || height > reqHeight)  
        {  
            //計算出原始寬與現有寬,原始高與現有高的比率  
            int widthRatio = Math.round((float)width/(float)reqWidth);  
            int heightRatio = Math.round((float)height/(float)reqHeight);  
            //選出兩個比率中的較小值,這樣的話能夠保證圖檔顯示完全   
            inSampleSize = widthRatio < heightRatio ? widthRatio:heightRatio;  
        }  
        System.out.println("壓縮比:  "+inSampleSize);  
        return inSampleSize;  
    }  
    /** 
     * 将InputStream轉換為Byte數組 
     * @param in 
     * @return 
     */  
    public static byte[] inputStreamToByteArray(InputStream in)  
    {  
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();  
        byte[] buffer = new byte[1024];  
        int len;  
        try {  
            while((len = in.read(buffer)) != -1)  
            {  
                outputStream.write(buffer, 0, len);  
            }  
        } catch (IOException e) {  
            e.printStackTrace();  
        }finally{  
            try {  
                in.close();  
                outputStream.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
        return outputStream.toByteArray();  
    }  
      
    class ImageAsyncTask extends AsyncTask<String, Void, Bitmap>  
    {  
        public ImageView iv;  
        public int reqWidth;  
        public int reqHeight;  
        public ImageAsyncTask(ImageView imageView,int reqWidth,int reqHeight)  
        {  
            this.iv = imageView;  
            this.reqWidth = reqWidth;  
            this.reqHeight = reqHeight;  
        }  
        @Override  
        protected Bitmap doInBackground(String... params) {  
            URL url;  
            HttpURLConnection connection = null;  
            InputStream in = null;  
            Bitmap beforeBitmap = null;  
            Bitmap afterBitmap = null;  
            try {  
                url = new URL(params[0]);  
                connection = (HttpURLConnection) url.openConnection();  
                in = connection.getInputStream();  
                BitmapFactory.Options options = new BitmapFactory.Options();  
                //設定BitmapFactory.Options的inJustDecodeBounds屬性為true表示禁止為bitmap配置設定記憶體  
                options.inJustDecodeBounds = true;  
                byte[] data = inputStreamToByteArray(in);  
                beforeBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);//這次調用的目的是擷取到原始圖檔的寬、高,但是這次操作是沒有寫記憶體操作的  
                options.inSampleSize = calculateInSampleSize(options,reqWidth, reqHeight);   
                //設定這次加載圖檔需要加載到記憶體中  
                options.inJustDecodeBounds = false;  
                afterBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);  
                float afterSize = (float)(afterBitmap.getRowBytes()*afterBitmap.getHeight());  
                System.out.println("壓縮之後的圖檔大小:  "+(float)afterSize/1024+"KB");  
            } catch (Exception e) {  
                System.out.println(e.toString());  
            }  
            return afterBitmap;  
        }  
          
        @Override  
        protected void onPostExecute(Bitmap result) {  
            if(result != null)  
                imageView.setImageBitmap(result);  
        }  
    }  
}
           

Github圖檔壓

繼續閱讀