仿写一个android图片压缩工具

1,103 阅读3分钟

安卓开发图片压缩一直是一个头痛的问题,一不小心就会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");