天天看點

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 的方法;

android 圖檔壓縮及Bitmap系列文章——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 管理圖檔