android 圖片壓縮及Bitmap系列文章——Bitmap

工具類

獲取Bitmap 並進行採樣率壓縮

 /**
     * 從uri 得到 bitmap
     * Decode image from uri using given "inSampleSize", but if failed due to out-of-memory then raise
     * the inSampleSize until success.
     *
     * @param resolver
     * @param uri
     * @return
     */
    public static Bitmap getBitmapFromUri(ContentResolver resolver, Uri uri) {

        BitmapFactory.Options bmOpts = null;
        try {
            //先對圖片的最大寬高進行限定,防止OOM
            bmOpts = BitmapUtils.decodeImageForOption(resolver, uri);
        } catch (Exception e) {

        }
        int be = 1;//be=1表示不縮放

        if (null != bmOpts) {
            int w = bmOpts.outWidth;
            int h = bmOpts.outHeight;
            float hh = MAX_H_DEFAULT;
            float ww = MAX_W_DEFAULT;
            if (w >= h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
                be = (int) (bmOpts.outWidth / ww) + 1;
            } else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
                be = (int) (bmOpts.outHeight / hh) + 1;
            }
            if (be <= 0) {
                be = 1;
            }
        } else {
            bmOpts = new BitmapFactory.Options();
            bmOpts.inSampleSize = 2;
        }

        //旋轉圖片 動作
        bmOpts.inSampleSize = be;
        //bmOpts.inPreferredConfig = Bitmap.Config.RGB_565;
        InputStream stream = null;
        try {
            stream = resolver.openInputStream(uri);
            return BitmapFactory.decodeStream(stream, new Rect(), bmOpts);
        } catch (Exception e) {
            bmOpts.inSampleSize *= 2;
        } finally {
            closeSafe(stream);
        }
        return null;
    }

    /**
     * Close the given closeable object (Stream) in a safe way: check if it is null and catch-log
     * exception thrown.
     *
     * @param closeable the closable object to close
     */
    private static void closeSafe(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException ignored) {
            }
        }
    }

    /**
     * 從路徑中得到bitmap
     * @param filePath
     * @return
     */
    public static Bitmap getBitmapFromFile(String filePath) {
        Bitmap bm = null;
        try {
            int degree = 0;
            ExifInterface exif = new ExifInterface(filePath);
            int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    degree = 90;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    degree = 180;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    degree = 270;
                    break;
            }
            //先對圖片的最大寬高進行限定,防止OOM
            BitmapFactory.Options bmOpts = new BitmapFactory.Options();
            bmOpts.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(filePath, bmOpts);
            bmOpts.inJustDecodeBounds = false;
            int w = bmOpts.outWidth;
            int h = bmOpts.outHeight;
            float hh = MAX_H_DEFAULT;
            float ww = MAX_W_DEFAULT;
            int be = 1;//be=1表示不縮放
            if (w >= h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
                be = (int) (bmOpts.outWidth / ww) + 1;
            } else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
                be = (int) (bmOpts.outHeight / hh) + 1;
            }
            if (be <= 0) {
                be = 1;
            }
            //旋轉圖片 動作
            Matrix matrix = new Matrix();
            matrix.postRotate(degree);
            bmOpts.inSampleSize = be;
            //bmOpts.inPreferredConfig = Bitmap.Config.RGB_565;
            bm = BitmapFactory.decodeFile(filePath, bmOpts);
            if (degree == 0) {
                return bm;
            }
            // 創建新的圖片
            return Bitmap.createBitmap(bm, 0, 0,
                    bm.getWidth(), bm.getHeight(), matrix, true);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.gc();
        }
        return null;
    }

同理,根據 BitmapFactory 類中的方法,我們可以知道有哪些創建bitmap 的方法;

大致有四類,從 文件、工程內資源、二進制數組、流 可獲取bitmap 。 上面的代碼給出了 從 文件路徑 、Uri (轉化爲流Stream) 獲取bitmap 。其思路都是類似,

  1. 獲取bitmap,得到bitmapConfig 
  2. 根據寬高比獲取採樣比,
  3. 再次獲取bitmap 並帶上options 

1 . 獲取bitmap,得到bitmapConfig  

 BitmapFactory.Options bmOpts = new BitmapFactory.Options();
 bmOpts.inJustDecodeBounds = true;
 // 獲取到bitmap的過程中來得到config
 BitmapFactory.decodeByteArray(bytes, 0, bytes.length, bmOpts);
 // 通過file 
 BitmapFactory.decodeFile(filePath, bmOpts);
  ....
  還可以通過資源、二進制數組

2 根據寬高比獲取採樣比

 法 一:一次性確定採樣率

    bmOpts.inJustDecodeBounds = false;
            int w = bmOpts.outWidth;
            int h = bmOpts.outHeight;
            float hh = max_h;
            float ww = max_w;
            int be = 1;//be=1表示不縮放
            if (w >= h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
                be = (int) (bmOpts.outWidth / ww) + 1;
            } else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
                be = (int) (bmOpts.outHeight / hh) + 1;
            }
            if (be <= 0) {
                be = 1;
            }

法二:循環減少2倍

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int height = options.outHeight;//獲取原始圖片的高度
int width = options.outWidth;//獲取原始圖片的寬度
int inSampleSize = 1;//設置採樣率
if (height > reqHeight || width > reqWidth) {
int halfHeight = height / 2;
int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}

3 再次獲取bitmap 並帶上options 

 bm = BitmapFactory.decodeFile(filePath, bmOpts);

上面的Uri 、 File 都有相應的獲取bitmap 代碼,注意:File 的有些不一樣,最後加了一個 Bitmap.createBitmap

if (degree == 0) {
    return bm;
            }
// 創建新的圖片
return Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);

因爲通過這個Bitmap 類createBitmap 方法可以帶一個矩陣Matrix ,matrix可以進行圖片變換,如拉伸、旋轉、縮放操作,在上面的操作中,就有檢測圖片是否有旋轉,有的話就直接恢復成之前的樣子。

質量Quality壓縮

    /**
     * 只進行質量壓縮
     * @param bitmap
     * @return
     */
    public static byte[] compressMass(Bitmap bitmap) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
        int q = 100;
        while (stream.toByteArray().length / 1024 > 200) {
            stream.reset();
            q -= 10;
            if (q <= 0) break;
            bitmap.compress(Bitmap.CompressFormat.JPEG, q, stream);
        }
        return stream.toByteArray();
    }

一般而言,質量數設置爲80 ,便能夠得到50——60% 的壓縮率。注意,如果使用100,則會使照片大小變大,非常奇怪,由此可見,默認的照片可能不是100。

Matrix 壓縮

一般的,通過採樣率壓縮可以達到縮放的目的,採用Matrix 也可以實現此效果。代碼如下:

    /**
     * 空間尺寸壓縮 給出最大寬高
     * 通過matrix
     * @param bitmap
     * @param w
     * @param h
     * @return
     */
    public static Bitmap zoomBitmap(Bitmap bitmap, int w, int h) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        if (width <= w && height <= h)
            return bitmap;
        LogUtil.debugLog("壓縮前尺寸" + width + " x " + height);
        float scale ;
        Matrix matrix = new Matrix();
        if (width > w) {
            scale = ((float) w) / width;
            matrix.postScale(scale, scale);
        } else {
            scale = ((float) h) / height;
            matrix.postScale(scale, scale);
        }
        LogUtil.debugLog("壓縮率" + scale * 100 + "%");
        return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
    }

有一個疑問,這種方式和採樣率都可以進行尺寸縮放,不知道這二者的區別,這兩個也可以一起用。個人感覺不論是採用率還是Matrix 都是使圖片尺寸減少,從而達到壓縮的目的,二者本質應該一樣的;質量壓縮和上述兩個不一樣,質量壓縮寬高都不會變;

小結論:Bitmap 的寬高就是實際圖片的寬高,這一點可以通過日誌打印來確定。

使用場景

圖片上傳:這種情況只講究圖片大小越小越好,所以無論質量還是尺寸都是會減少圖片磁盤中尺寸,即圖片上傳時,兩種方式都需要進行。

示例代碼:

   /**
     * 先尺寸壓縮
     * 再質量壓縮 (大於200kb 就壓縮) 或者直接設置 80 也可以 (設置爲 80 壓縮率接近50 - 60 %  )
     * 一般而言,用原生相機拍照 用此方法壓縮後,壓縮率可達 95%
     * @param bitmap
     * @return
     */
    public static byte[] compress(Bitmap bitmap) {
        Bitmap zoomBitmap = CompressUtil.zoomBitmap(bitmap, DEFAULT_MAX_W, DEFAULT_MAX_W);
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        zoomBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
        int q = 100;
        while (stream.toByteArray().length / 1024 > 200) {
            stream.reset();
            q -= 10;
            if (q <= 0) break;
            LogUtil.debugLog("當前質量" + q + "%");
            zoomBitmap.compress(Bitmap.CompressFormat.JPEG, q, stream);
        }
        return stream.toByteArray();
    }

    /**
     * 只進行質量壓縮
     * @param bitmap
     * @return
     */
    public static byte[] compressMass(Bitmap bitmap) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
        int q = 100;
        while (stream.toByteArray().length / 1024 > 200) {
            stream.reset();
            q -= 10;
            if (q <= 0) break;
            bitmap.compress(Bitmap.CompressFormat.JPEG, q, stream);
        }
        return stream.toByteArray();
    }


    /**
     * 空間尺寸壓縮 給出最大寬高
     * 通過matrix
     * @param bitmap
     * @param w
     * @param h
     * @return
     */
    public static Bitmap zoomBitmap(Bitmap bitmap, int w, int h) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        if (width <= w && height <= h)
            return bitmap;
        LogUtil.debugLog("壓縮前尺寸" + width + " x " + height);
        float scale ;
        Matrix matrix = new Matrix();
        if (width > w) {
            scale = ((float) w) / width;
            matrix.postScale(scale, scale);
        } else {
            scale = ((float) h) / height;
            matrix.postScale(scale, scale);
        }
        LogUtil.debugLog("壓縮率" + scale * 100 + "%");
        return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
    }

 

Bitmap uploadBitmap = ImageUtil.getBitmapFromUri(getActivity().getContentResolver(), frontImgUri);
byte[] photo = CompressUtil.compress(uploadBitmap);

  getBitmapFromUri 這個方法是前面寫過的,這樣在上傳時,就把能夠壓縮的都做了。

圖片展示:Bitmap 展示會消耗內存,而消耗的內存計算方式 寬 * 高 * 一個像素點佔用的字節數, 可以通過

getByteCount() 、 getAllocationByteCount() 

 獲取內存消耗,一個像素點佔用的字節數和Bitmap的 Config 有關,具體如下

ALPHA_8 表示8位Alpha位圖,即A=8,一個像素點佔用1個字節,它沒有顏色,只有透明度

ARGB_4444 表示16位ARGB位圖,即A=4,R=4,G=4,B=4,一個像素點佔4+4+4+4=16位,2個字節

ARGB_8888 表示32位ARGB位圖,即A=8,R=8,G=8,B=8,一個像素點佔8+8+8+8=32位,4個字節 (系統默認格式)

RGB_565 表示16位RGB位圖,即R=5,G=6,B=5,它沒有透明度,一個像素點佔5+6+5=16位,2個字節

這個模式僅僅是android 內部定義的,它並不是圖片的格式,所以改變這個可以改變內存佔用情況,但不會減少實際尺寸。

 

寫在最後:實際上通過尺寸和質量壓縮兩種方式,可以使一張圖片大小減少90% 以上,有的達到 95% . 一般的質量壓縮設置爲 80 , 圖片的寬高最大可以設置爲不超過 1200 ,尺寸大小約束爲不超過 200 kb ;


參考資料:

https://www.jianshu.com/p/c77158b6e07e

https://www.jianshu.com/p/4b0ba08bfb58/ (有錯誤,壓縮部分有錯誤)

https://www.jianshu.com/p/e907eca48334 

官方資料 (重要)

https://developer.android.com/topic/performance/graphics  

https://developer.android.com/topic/performance/graphics/load-bitmap.html 高效加載大圖片

https://developer.android.com/topic/performance/graphics/cache-bitmap.html 緩存圖片

https://developer.android.com/topic/performance/graphics/manage-memory.html#inBitmap 管理圖片

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章