Android图片压缩

在我们的业务场景中,一般需要使用客户端采集图片上传至服务器,为了提升性能,我们一般会对图片进行压缩。

在Android平台上,默认提供的压缩有三种方式:质量压缩和采样压缩。

一、质量压缩

质量压缩不改变图片的尺寸,只改变图片的存储体积,即原来是1080*1920的图片压缩后还是分辨率不变,并且压缩前后由File格式转换成Bitmap格式进入内存中,占用的内存并没有改变,因为内存是根据图片的像素来给图片分配内存大小的。

质量压缩主要借助Bitmap中的compress方法实现:

public boolean compress (Bitmap.CompressFormat format, int quality, OutputStream stream)

该方法接收三个参数,其含义分别如下:

  1. format:枚举类型,有三个选项 JPEG, PNG 和 WEBP,表示图片的格式;
  2. quality:图片的质量,取值在 [0,100] 之间,表示图片质量,越大,图片的质量越高;(PNG格式会忽略该值设定 )

  3. stream:一个输出流,通常是我们压缩结果输出的文件的流。

注意:当调用bitmap.compress(CompressFormat.JPEG, 100, fos);保存为图片时发现图片背景为黑色时,将格式改为png格式就好了。

二、采样压缩

通过设置采样率,减少图片的像素,达到对内存中Bitmap进行压缩。

采样压缩主要通过BitmapFactorry中的decodeFile方法实现:

public static Bitmap decodeFile (String pathName, BitmapFactory.Options opts)

该方法接收2个参数:

  1. pathName是图片文件路径
  2. opts是采样率,通过设置采样率属性,来达到根据需要压缩图片的目的。

标准使用如下:

// 获取原始图片的尺寸
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inSampleSize = 1;
BitmapFactory.decodeStream(srcImg.open(), null, options);
this.srcWidth = options.outWidth;
this.srcHeight = options.outHeight;

// 进行图片加载,此时会将图片加载到内存中
options.inJustDecodeBounds = false;
options.inSampleSize = calInSampleSize();
Bitmap bitmap = BitmapFactory.decodeStream(srcImg.open(), null, options);

这里分成了2步实现:

1.设置inJustDecodeBounds为true来得到图片的宽高,此时图片不会被加载到内存中,也就不会造成OOM。

2.根据第一步的尺寸,计算inSampleSize,然后将inJustDecodeBounds设置为true,加载采样后的图片至内存中。

  inSampleSize代表压缩后的一个像素点代表原图像素点的几个像素点,例如inSampleSize为2,则压缩后的图片宽高是原图的1/2,像素点是原来的1/4,呈指数型增长。

图片压缩算法总结:

实际使用中,我们一般通过先进行采样压缩,再进行质量压缩得到最终图片。

使用示例:

  private void compressBitmapToFile() {
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_image2);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG,100,baos);
        //图片质量小于100K不压缩(该值计算不太准)
       if( baos.toByteArray().length /1024 > 100){

    //采样压缩
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        options.inSampleSize = 1;
        BitmapFactory.decodeResource(getResources(),R.drawable.ic_image3,options);
        //计算缩放值
        options.inSampleSize =  computeSize(options, 720, 1028);
        LogUtil.e("inSampleSize-"+options.inSampleSize);

        options.inJustDecodeBounds = false;
        bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_image2,options);

        //压缩图片质量 像素值不变
        baos = new ByteArrayOutputStream();
        int options1 = 75;
        bitmap.compress(Bitmap.CompressFormat.JPEG,options1,baos);
        bitmap.recycle();
/*
        while(baos.toByteArray().length /1024 > 100){
            baos.reset();
            options1 -= 10;
            bitmap.compress(Bitmap.CompressFormat.JPEG,options1,baos);
        }*/
       }
//将压缩后图片写入文件
        String path = getExternalCacheDir().getAbsolutePath() + "/ic_image.jpg";
        File file = new File(path);
        if(!file.exists()){
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            fos.write(baos.toByteArray());
            fos.flush();
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
computeSize代码如下。
 private int computeSize(BitmapFactory.Options options,int reqWidth,int reqHeight) {
        int srcHeight = options.outHeight;
        int srcWidth = options.outWidth;
        srcWidth = srcWidth % 2 == 1 ? srcWidth + 1 : srcWidth;
        srcHeight = srcHeight % 2 == 1 ? srcHeight + 1 : srcHeight;

        int inSampleSize = 1;
        if (srcHeight > reqHeight || srcWidth > reqWidth) {
            final int heightRatio = Math.round((float) srcHeight / (float) reqHeight);
            final int widthRatio = Math.round((float) srcWidth / (float) reqWidth);
            inSampleSize = heightRatio <= widthRatio ? heightRatio : widthRatio;//
        }
        if (inSampleSize <= 0) {
            return 1;
        }
        return inSampleSize;
        
        //鲁班压缩代码
       /* int longSide = Math.max(srcWidth, srcHeight);
        int shortSide = Math.min(srcWidth, srcHeight);

        float scale = ((float) shortSide / longSide);
        if (scale <= 1 && scale > 0.5625) {
            if (longSide < 1664) {
                return 1;
            } else if (longSide >= 1664 && longSide < 4990) {
                return 2;
            } else if (longSide > 4990 && longSide < 10240) {
                return 4;
            } else {
                return longSide / 1280 == 0 ? 1 : longSide / 1280;
            }
        } else if (scale <= 0.5625 && scale > 0.5) {
            return longSide / 1280 == 0 ? 1 : longSide / 1280;
        } else {
            return (int) Math.ceil(longSide / (1280.0 / scale));
        }*/

        
    }

旋转图片:

    private Bitmap rotatingImage(Bitmap bitmap) {
        if (srcExif == null) return bitmap;

        Matrix matrix = new Matrix();
        int angle = 0;
        int orientation = srcExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
        switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                angle = 90;
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                angle = 180;
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
                angle = 270;
                break;
        }

        matrix.postRotate(angle);

        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    }

压缩亲测可用,图片压缩对比大致效果如下。

鲁班算法逻辑见:https://github.com/Curzibn/Luban/blob/master/DESCRIPTION.md

参考地址:

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

https://blog.csdn.net/qq_27634797/article/details/79424507

原文地址:https://www.cnblogs.com/fangg/p/11233566.html