阅读 661

Android 图片压缩

前言

上次面试被问到图片压缩方法,没有回答上来,只依稀记得有质量压缩、尺寸压缩,却说不出个所以然来。所以今天研究了一下,Android的图片压缩方式,经过百度和查看谷歌文档,暂时知道了一共有质量压缩、尺寸压缩(或者说裁剪)、采样率压缩、设置图片格式、通过JNI调用libjpeg库压缩

1.质量压缩

保持像素的前提下改变图片的位深及透明度(即:通过算法抹掉(同化)图片中的一些某点附近 相近的像素)达到降低质量压缩文件的目的。 使用场景:将图片压缩后将图片上传到服务器,或者保存到本地,根据实际需求

    /**
     * 质量压缩
     * 设置bitmap options属性,降低图片的质量,像素不会减少
     * 第一个参数为需要压缩的bitmap图片对象,第二个参数为压缩后图片保存的位置
     * 设置options 属性0-100,来实现压缩(因为png是无损压缩,所以该属性对png是无效的)
     *
     * @param bmp
     * @param file
     */
    public static void qualityCompress(Bitmap bmp, File file) {
        // 0-100 100为不压缩
        int quality = 20;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 把压缩后的数据存放到baos中
        bmp.compress(Bitmap.CompressFormat.JPEG, quality, baos);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(baos.toByteArray());
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

复制代码

2.尺寸压缩

通过减少单位尺寸的像素值,真正意义上的降低像素(通过缩放图片像素来减少图片占用内存大小) 使用场景:缓存缩略图的时候(头像处理)

    /**
     * 尺寸压缩(通过缩放图片像素来减少图片占用内存大小)
     *
     * @param bmp
     * @param file
     */

    public static void sizeCompress(Bitmap bmp, File file) {
        // 尺寸压缩倍数,值越大,图片尺寸越小
        int ratio = 8;
        // 压缩Bitmap到对应尺寸
        Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Config.ARGB_8888);
        Canvas canvas = new Canvas(result);
        Rect rect = new Rect(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio);
        canvas.drawBitmap(bmp, null, rect, null);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 把压缩后的数据存放到baos中
        result.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(baos.toByteArray());
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
复制代码

3.采样率压缩

设置图片的采样率,降低图片像素 好处:是不会先将大图片读入内存,大大减少了内存的使用,也不必考虑将大图片读入内存后的释放事宜。 问题:因为采样率是整数,所以不能很好的保证图片的质量。如我们需要的是在2和3采样率之间,用2的话图片就大了一点,但是用3的话图片质量就会有很明显的下降,这样也无法完全满足我的需要。 GitHub上有名的图片压缩算法采用的就是采样率压缩,他的核心算法就是计算这个采样值

 private int computeSize() {
    srcWidth = srcWidth % 2 == 1 ? srcWidth + 1 : srcWidth;
    srcHeight = srcHeight % 2 == 1 ? srcHeight + 1 : srcHeight;

    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 < 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));
    }
  }
复制代码

鲁班框架的流程大致是这样的:1.通过建造者模式传入图片的地址,或者图片的uri,或者图片流 2.把图片转为流,添加到一个list 3.判断图片格式,是否需要旋转等 4.计算采样率也就是 ** options.inSampleSize = computeSize();** 5.压缩

 File compress() throws IOException {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = computeSize();

    Bitmap tagBitmap = BitmapFactory.decodeStream(srcImg.open(), null, options);
    ByteArrayOutputStream stream = new ByteArrayOutputStream();

    if (Checker.SINGLE.isJPG(srcImg.open())) {
      tagBitmap = rotatingImage(tagBitmap, Checker.SINGLE.getOrientation(srcImg.open()));
    }
    tagBitmap.compress(focusAlpha ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG, 60, stream);
    tagBitmap.recycle();

    FileOutputStream fos = new FileOutputStream(tagImg);
    fos.write(stream.toByteArray());
    fos.flush();
    fos.close();
    stream.close();

    return tagImg;
  }
复制代码

4.通过JNI调用libjpeg库压缩

Android图像处理引擎的漏洞 通常IOS拍的照片1M左右还比Android拍出来的照片5M的还要清晰,都是在同一个环境下,保存的都是JPEG

  • (1)历程

1995年 JPEG处理引擎,用于最初的在PC上面处理图片的引擎。 2005年 skia开源的引擎, 开发了一套基于JPEG处理引擎的第二次开发。便于浏览器的使用。 2007年安卓用的skia引擎(阉割版),谷歌拿了skia,去掉一个编码算法—哈夫曼算法。采用定长编码算法。但是解码还是保留了哈夫曼算法,导致了图片处理后文件变大了。

  • (2)原因

当时由于CPU和内存在手机上都非常吃紧 性能差,由于哈夫曼算法非常吃CPU,被迫用了其他的算法。

  • (3)优化方案

绕过安卓Bitmap API层,来自己编码实现—-修复使用哈夫曼算法。

JNI开发步骤

  • 准备工作
(1)http://www.ijg.org/下载JPEG引擎使用的库---libjpeg库,
基于该引擎来做一定的开发----自己实现编码,JNI开发。
(2)导入库文件libjpegbither.so
(3)导入头文件
(4)写mk文件——Android.mk、Applicatoin.mk
(5)写代码——C++:XX.cpp、C:XX.c
复制代码
  • 开发过程
(1)将android的bitmap解码,并转换成RGB数据
   一个图片信息---像素点(argb),alpha去掉
(2)JPEG对象分配空间以及初始化
(3)指定压缩数据源
(4)获取文件信息
(5)为压缩设置参数,比如图像大小、类型、颜色空间
   boolean arith_code;       
  /* TRUE=arithmetic coding, FALSE=Huffman */
(6)开始压缩——jpeg_start_compress()
(7)压缩结束——jpeg_finish_compress()
(8)释放资源
复制代码
  • 代码
    /**
     * 1.JNI终极压缩(通过JNI图片压缩把Bitmap保存到指定目录)
     *
     * @param image    bitmap对象
     * @param filePath 要保存的指定目录
     * @Description: 通过JNI图片压缩把Bitmap保存到指定目录
     */
    public static void jniUltimateCompress(Bitmap image, String filePath) {
        // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
        int quality = 20;
        // JNI调用保存图片到SD卡 这个关键
        NativeUtil.saveBitmap(image, quality, filePath, true);
    }

    /**
     * 1.JNI基本压缩(不保存Bitmap)
     *
     * @param bit      bitmap对象
     * @param fileName 指定保存目录名
     * @param optimize 是否采用哈弗曼表数据计算 品质相差5-10倍
     * @Description: JNI基本压缩
     */
    public static void jniBasicCompress(Bitmap bit, String fileName, boolean optimize) {
        saveBitmap(bit, DEFAULT_QUALITY, fileName, optimize);
    }

/**
     * 调用native方法
     *
     * @param bit
     * @param quality
     * @param fileName
     * @param optimize
     * @Description:函数描述
     */
    private static void saveBitmap(Bitmap bit, int quality, String fileName, boolean optimize) {
        compressBitmap(bit, bit.getWidth(), bit.getHeight(), quality, fileName.getBytes(), optimize);
    }

    /**
     * 调用底层 bitherlibjni.c中的方法
     *
     * @param bit
     * @param w
     * @param h
     * @param quality
     * @param fileNameBytes
     * @param optimize
     * @return
     * @Description:函数描述
     */
    private static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
                                                boolean optimize);

    /**
     * 加载lib下两个so文件
     */
    static {
        System.loadLibrary("jpegbither");
        System.loadLibrary("bitherjni");
    }

复制代码

5.图片格式

Android目前常用的图片格式有png,jpeg和webp,

png:无损压缩图片格式,支持Alpha通道,Android切图素材多采用此格式

jpeg:有损压缩图片格式,不支持背景透明,适用于照片等色彩丰富的大图压缩,不适合logo

webp:是一种同时提供了有损压缩和无损压缩的图片格式,派生自视频编码格式VP8,从 谷歌官网 来看,无损webp平均比png小26%,有损的webp平均比jpeg小25%~34%,无损webp支持Alpha通道,有损webp在一定的条件下同样支持,有损webp在Android4.0(API 14)之后支持,无损和透明在Android4.3(API18)之后支持

采用webp能够在保持图片清晰度的情况下,可以有效减小图片所占有的磁盘空间大小

结语

感谢各位大佬开源和 写博客,附上链接