仿寫一個android圖片壓縮工具

夜幕降流星雨發表於2018-01-02

安卓開發圖片壓縮一直是一個頭痛的問題,一不小心就會oom。 我對一個github上的庫就行了簡單的改寫,把程式碼記錄下來,自己也梳理了下圖片壓縮的過程。

本文的程式碼參考自github專案 Compressor,我只是進行了我認為需要的改動

本文的程式碼

尺寸壓縮

尺寸壓縮也就是按比例壓縮尺寸,當我們需要顯示圖片時,控制元件載入的是bitmap,而bitmap的大小主要和尺寸和圖片格式有關,這時候我們不需要進行質量壓縮

  • 計算取樣比(圖片壓縮比例)
    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // 獲得原始寬高
        final int height = options.outHeight;
        final int width = options.outWidth;
        //預設為1(不壓縮)
        int inSampleSize = 1;
    
        if (height > reqHeight || width > reqWidth) {
            //壓縮到寬高都小於我們要求的大小
            while ((height / inSampleSize) >= reqHeight || (width / inSampleSize) >= reqWidth) {
                //android內部只會取2的倍數,也就是一半一半的壓縮
                inSampleSize *= 2;
            }
        }
    
        return inSampleSize;
    }
    複製程式碼
  • 壓縮尺寸
    static Bitmap decodeSampledBitmapFromFile(File imageFile, int reqWidth, int reqHeight) throws IOException {
        BitmapFactory.Options options = new BitmapFactory.Options();
        //設定inJustDecodeBounds=true,時BitmapFactory載入圖片不返回bitmap,只返回資訊,減少記憶體開銷
        options.inJustDecodeBounds = true;
        //相當於載入空圖片獲取尺寸資訊
        BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
    
        //用我們上面的方法計算壓縮比例
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    
        //設定inJustDecodeBounds=false,返回bitmap
        options.inJustDecodeBounds = false;
        Bitmap scaledBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
    
        //把圖片旋轉成正確的方向
        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
        int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
        Matrix matrix = new Matrix();
        if (orientation == 6) {
            matrix.postRotate(90);
        } else if (orientation == 3) {
            matrix.postRotate(180);
        } else if (orientation == 8) {
            matrix.postRotate(270);
        }
        //複製一份旋轉後的bitmap後返回
        scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(),
                scaledBitmap.getHeight(), matrix, true);
    
        return scaledBitmap;
    }
    複製程式碼

質量壓縮

質量壓縮程式碼比較簡單但要注意非常耗時

 private static ByteArrayOutputStream compressBitmapSize(Bitmap bitmap, Bitmap.CompressFormat compressFormat,
                                                            int defaultQuality, long maxSize) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int quality = defaultQuality;
        bitmap.compress(compressFormat, quality, baos);
        //當大於我們指定的大小時,我就繼續壓縮
        while (baos.toByteArray().length / 1024 > maxSize) {
            if (quality <= 10) { //壓縮比例要大於0
                break;
            } else {
                baos.reset();
                quality -= 10;
                //quality 表示壓縮多少 100 表示不壓縮
                bitmap.compress(compressFormat, quality, baos);
            }
        }

        return baos;
    }
複製程式碼

完整的壓縮工具類

public class ImageUtil {
    //compress main way
    static File compressImage(File imageFile, long size, int reqWith, int reqHeight, Bitmap.CompressFormat compressFormat,
                              int quantity, String destinationPath) throws IOException {
        FileOutputStream fileOutputStream = null;
        File file = new File(destinationPath).getParentFile();
        if (!file.exists()) {
            file.mkdirs();
        }
        try {
            fileOutputStream = new FileOutputStream(destinationPath);
            //先壓縮尺寸,防止記憶體溢位
            Bitmap bitmap = decodeSampledBitmapFromFile(imageFile, reqWith, reqHeight);
            //ByteArrayOutputStream 不需要關閉
            //壓縮尺寸
            ByteArrayOutputStream baos = compressBitmapSize(bitmap, compressFormat, quantity, size);
        fileOutputStream.write(baos.toByteArray());
        } finally {
            if (fileOutputStream != null) {
                fileOutputStream.flush();
                fileOutputStream.close();
            }
        }
        return new File(destinationPath);
    }

    //按照比例壓縮圖片
    static Bitmap decodeSampledBitmapFromFile(File imageFile, int reqWidth, int reqHeight) throws IOException {
        BitmapFactory.Options options = new BitmapFactory.Options();
        //just need size
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
        //calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        //real decode bitmap
        options.inJustDecodeBounds = false;
        Bitmap scaledBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
        //rotation of the image
        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
        int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
        Matrix matrix = new Matrix();
        if (orientation == 6) {
            matrix.postRotate(90);
        } else if (orientation == 3) {
            matrix.postRotate(180);
        } else if (orientation == 8) {
            matrix.postRotate(270);
        }
        scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(),
                scaledBitmap.getHeight(), matrix, true);

        return scaledBitmap;
    }

    //獲取取樣(壓縮比)
    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            while ((height / inSampleSize) >= reqHeight || (width / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }
    //壓縮尺寸
    private static ByteArrayOutputStream compressBitmapSize(Bitmap bitmap, Bitmap.CompressFormat compressFormat,
                                                            int defaultQuality, long maxSize) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int quality = defaultQuality;
        bitmap.compress(compressFormat, quality, baos);
        while (baos.toByteArray().length / 1024 > maxSize) {
            if (quality <= 10) { // quality must >0
                break;
            } else {
                baos.reset();
                quality -= 10;
                bitmap.compress(compressFormat, quality, baos);
            }
        }
        return baos;
    }
}
複製程式碼

封裝成一個工廠模式工具類

public class Compressor {

    private long maxSize = 1024; //1024kb
    private int maxWidth = 800;
    private int maxHeight = 800;
    private Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG;
    private int quality = 80;
    private String destinationDirectoryPath;


    public Compressor(Context context) {
        destinationDirectoryPath = context.getCacheDir().getPath() + File.separator + "images";
    }

    public Compressor setMaxSize(long size) {
        this.maxSize = size;
        return this;
    }


    public Compressor setMaxWidth(int maxWidth) {
        this.maxWidth = maxWidth;
        return this;
    }

    public Compressor setMaxHeight(int maxHeight) {
        this.maxHeight = maxHeight;
        return this;
    }

    public Compressor setCompressFormat(Bitmap.CompressFormat compressFormat) {
        this.compressFormat = compressFormat;
        return this;
    }

    public Compressor setQuality(int quality) {
        this.quality = quality;
        return this;
    }

    public Compressor setDestinationDirectoryPath(String destinationDirectoryPath) {
        this.destinationDirectoryPath = destinationDirectoryPath;
        return this;
    }
    
    //壓縮到檔案
    public File compressToFile(File imageFile, String compressedFileName) throws IOException {
        return ImageUtil.compressImage(imageFile, maxSize, maxWidth, maxHeight, compressFormat, quality,
                destinationDirectoryPath + File.separator + compressedFileName);
    }

    //只壓縮尺寸
    public Bitmap compressToBitmap(File imageFile) throws IOException {
        return ImageUtil.decodeSampledBitmapFromFile(imageFile, maxWidth, maxHeight);
    }
}
複製程式碼

使用

推薦大家使用WEBP格式的圖片,記憶體更小(png無損格式很大)而且不會損失透明度(jpg沒有透明)

  • 簡單用法
  File file = new Compressor(CompressorActivity.this)
                        .compressToFile(getExternalCacheDir().getAbsoluteFile(),
                                UUID.randomUUID().toString() + ".jpg");
複製程式碼
  • 指定壓縮的引數
   File file = new Compressor(CompressorActivity.this)
                        .setMaxWidth(1080)
                        .setMaxHeight(1080)
                        .setQuality(75)
                        .setMaxSize(100)
                        .setCompressFormat(Bitmap.CompressFormat.WEBP)
                        .setDestinationDirectoryPath(getExternalCacheDir().getAbsolutePath())
                        .compressToFile(new File(getPath(CompressorActivity.this, uri)), UUID.randomUUID().toString() + ".webp");
複製程式碼

相關文章