Android Bitmap压缩的坑

  • 微信小程序分享时,微信要求的分享小程序大小有限制,印象中是图片的大小不能超过128KB。
  • 所以在过程中针对性地将bitmap进行了压缩。没想到遇到了PNG压缩的一个小坑。
  • 特此记录下。

Bitmap压缩

  • Bitmap最终是要压缩成byte[]进行分享,而我在开发中,面对到的图片大小不一。
  • 大的时候,能到好几MB,小的怎么也几百KB。这年头128KB大小的图片限制,其实挺不科学的。5G普及以后感觉图片会越来越大的。
  • 所以,在分享小程序之前,我需要先把Bitmap的大小进行压缩。

压缩方案:

  • 我当时的想法是,每一次将Bitmap的大小,压缩为本身质量的90%,这样一步一步地往下缩放,缩放到合适的大小为止。
  • 使用的API,就是Bitmap本身的compress()函数:
    public boolean compress(CompressFormat format, int quality, OutputStream stream) ;
  • 这个函数也不复杂
    • CompressFormat :压缩格式,传入个PNG、JPEG之类的(坑就在这里)
    • quality:quality就更简单了,0-100,传入一个值,就是压缩的百分比
    • OutputStream:这个是最终压缩返回的流,压缩后的Bitmap数据放到传入的流中。

  • 坑就坑在传入的CompressFormat上。
  • 大家都知道,其实常用的图片格式,在Android或者Web端,并不是JPEG,而是PNG。
  • 至少我经手的所有项目里,resources用png的概率真是太大了,毕竟小一些。
  • 当然google还搞了一个webp的格式,这玩儿比较搞笑,IOS好像不支持,因为我用这个格式尝试过分享给IOS端,在IOS端的微信上看不到这个图片。
  • 言归正传,我在使用compress()的时候,传入的CompressFormat格式,一开始是PNG。
        Bitmap tmp = 来一个Bitmap对象;
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        tmp.compress(Bitmap.CompressFormat.PNG, 90, output);
  • 然后Rock'N'Roll吧,压缩压缩压缩…………
  • 搞了半天,特么PNG走compress和没走compress效果一样的。传入的quality 0-100,随便你怎么填,只要是PNG格式,来的时候是多大,走的时候是多大。
  • 真真的我挥一挥衣袖,不带走一片云彩啊!!!
  • 当时我也是一脸懵逼,不是说好了compress就compress呢,怎么你PNG就静悄悄啥也不做???

##PNG是lossless

  • 后来,看了下compress的源码:
   /**
     * Write a compressed version of the bitmap to the specified outputstream.
     * If this returns true, the bitmap can be reconstructed by passing a
     * corresponding inputstream to BitmapFactory.decodeStream(). Note: not
     * all Formats support all bitmap configs directly, so it is possible that
     * the returned bitmap from BitmapFactory could be in a different bitdepth,
     * and/or may have lost per-pixel alpha (e.g. JPEG only supports opaque
     * pixels).
     *
     * @param format   The format of the compressed image
     * @param quality  Hint to the compressor, 0-100. 0 meaning compress for
     *                 small size, 100 meaning compress for max quality. Some
     *                 formats, like PNG which is lossless, will ignore the
     *                 quality setting
     * @param stream   The outputstream to write the compressed data.
     * @return true if successfully compressed to the specified stream.
     */
    @WorkerThread
    public boolean compress(CompressFormat format, int quality, OutputStream stream) ;
  • 注意看quality的注释:PNG是lossless的格式,我们会ignore掉quality的设置!!!
  • 妈耶,急得我真是国贸腔都给跑出来了。
  • 从注释上来看,也就是说只要用的是PNG格式的压缩,quality是不会生效的,因为没法压缩了。

##解决

  • 其实解决方案也很简单:换成JPEG格式就好啦
    /**
     *
     *
     * @param bmp 待压缩bitmap
     * @param expectSizeKb  希望压缩的大小
     * @param needRecycle bitmap需要回收吗
     * @return : {@link Bitmap}
     * @author : MuXi
     * @date : 2020/5/8 19:02
     */
    private static Bitmap scaleToBitmapMinSize(Bitmap bmp, int expectSizeKb, boolean needRecycle) {

        ByteArrayOutputStream output = new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.JPEG, 100, output);
        int options = 91;
        while (output.toByteArray().length > expectSizeKb * 1024) {
            output.reset(); // 重置output
            bmp.compress(Bitmap.CompressFormat.JPEG, options, output);// 这里压缩options%,把压缩后的数据存放到output
            options -= 5;// 每次都减少5大小的质量(其实可以自行调整,每次减少1也行)
            if (options <= 0) {
                break;
            }
        }
        if (needRecycle) {
            bmp.recycle();
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(output.toByteArray());// 新的bitmap流,用于后续生成新的bitmap返回
        return BitmapFactory.decodeStream(isBm, null, null);
    }}
  • 上述函数可以方便地对Bitmap进行大小质量压缩。

 评论