关于bitmap一些常见问题大小压缩等

虽说从事android开发多年,但是一直处于不求甚解的过程,只是积累了工作年限,但实际的工作经验并未见长,这不在日常开发中会经常遇到的bitmap的相关问题,每次都得百度一番惭愧惭愧呀!

首先在实际应用中,会遇到各种概念,有时总是傻傻的分不清楚,索性又百度了一番结合源码来个小小的总结吧。

与大小相关的概念
  • 图片实际大小
  • 图片占用内存空间大小
原始图片

在tinyPNG网站上压缩之后的图片
占用内存空间显示

一张图片占用内存空间大小是多少?

我们可以看到2个图片的分辩率都是1500*2560,一个是2.73 MB,一个是583 KB,在经过BitmapFactory.decodeResource (禁止缩放)之后得到bitmap,再通过Bitmap.getAllocationByteCount获取占用内存空间大小竟然是一样的,让人疑惑?

Bitmap类中有2个方法getAllocationByteCountgetByteCount:

getAllocationByteCount 根据文档的意思即占用内存空间大小,在没有其他操作的情况下默认与getByteCount取值一样,那么也就是说默认这个值一样,大小为getRowBytes() * getHeight()再深入一些,会发现getRowBytes涉及到了C++代码,暂且不说,可以参考其他文档

image-20200910173344850

getRowBytes代码没找到,C++不懂,不过这个取值实际就是= 宽 * 4 * 高,这个是在没有任何缩放的情况下计算的 。

  • 作一个说明如果图片来源是File、URL 或者 Assets目录,通过BitmapFactory得到的图片则是没有缩放的,其占用内存空间大小就是等于 宽 * 4 * 高;

  • 如果图片来源资源目录xxhdpi(480dpi) 、xxxhdpi(640dpi)等其他目录 ,则在经过·BitmapFactory解析得到bitmap的时候 会经过缩放,缩放比为设备的DPI与资源目录对应的DPI进行对比,也就是我们经常遇过的现象 如手机设备的density为480,

    • 这个时候若把图片放在xxhdpi里面,解析得到的bitmap然后取其宽高,这个取值跟实际的一样,

    • 这个时候若将图片放到xhdpi里面,则得到的bitmap的宽高都会变大,图片放大了

    • 这个时候若将图片放到xxhdpi里面,刚得到bitmap的宽高都会变小,图片缩小了

    在种场景之下,获取的的bitmap所占用的内存空间则为 宽 x scale x 4 x 高 x scale

与图片压缩相关
  • 质量压缩 compress
  • 邻近采样压缩 options.inSampleSize=2
  • 双线性采样 matrix.setScale(0.5f, 0.5f);
  • 还有一些其他策略但涉及到底层算法之类的,暂时还搞不懂

具体可以参考上篇文章里面推荐的链接

Android中图片压缩分析(下)

LuBan 仿微信朋友圈压缩策略

鲁班压缩,这个工具类库代码不多,1.1.8版本压缩核心方法其实也就是先采样再质量压缩 ,这样可以适用于一般的场景之下, 由于这个类库长期未进行更新,遗留许多问题需要改进,但我们可以借鉴其原理,根据实际情况加一改进。

 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()));
        }
        //是否保留透明通道 保留PNG无损压缩 JPEG质量60%
     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;
 }
  private int computeSize() {
        //%2==1的话 加1  是为了方便计算取整吧
        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);
        //先计算一下宽高比   拍照宽高比 16:9=1:0.5625
        float scale = ((float) shortSide / longSide);
      //这些取值不知道作者是根据什么推断出来的,但现在从微信聊天记录中保存的图片大小似乎是1080*1440
        if (scale <= 1 && scale > 0.5625) {
            if (longSide < 1664) {
                return 1;
            } else if (longSide < 4990) { //1024*4 = 4096
                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));
        }
    }
保存图片大小的问题

在对bitmap压缩之后,有时我们会将图片保存到本地,这个时候有时会发现实际保存的图片大小 与压缩之后计算的预计保存的图片大小不一致的问题?

InputStream inputStream1 = getResources().openRawResource(R.mipmap.china_map_xxhdpi);
//这个大小 基本与硬盘中的显示大小一样,如果略有差别应该是kb按照1000或者1024计算的原因
KLog.d(TAG, "==china map 原始大小" + inputStream1.available() / default_size + "kb");

Drawable drawable = getResources().getDrawable(R.mipmap.china_map_xxhdpi);
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
KLog.d(TAG, "占用内存大小 getAllocationByteCount===" + bitmap.getAllocationByteCount() / default_size + " kb");

ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
KLog.d(TAG, "==before=baos大小===最高质量压缩的大小" + baos.toByteArray().length / default_size + " kb");

int quality = 95;
boolean flag = true;
while (baos.toByteArray().length / default_size > 100 && flag) {
    baos.reset();   
    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
    quality -= 5;
    if (quality <= 0) {
        flag = false;
    }

}
KLog.d(TAG, "==after=baos大小===控制100以内-----" + baos.toByteArray().length / default_size + " kb");
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(baos.toByteArray());
KLog.d(TAG, "byteArrayInputStream==1024===" + byteArrayInputStream.available() / default_size + " kb");
KLog.d(TAG, "byteArrayInputStream==1000===" + byteArrayInputStream.available() / 1000 + " kb");
    ...省略
//图片大小 与计算大小致
String filepath = dirpath + File.separator + name + ".jpg";
KLog.d(filepath);
FileOutputStream fos = new FileOutputStream(new File(filepath));
fos.write(baos.toByteArray());
fos.flush();
fos.close();

ByteArrayOutputStream baosResult = new ByteArrayOutputStream();
resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baosResult);
KLog.d(TAG, "===baosResult==resultBitmap==" + baosResult.toByteArray().length / default_size + " kb");

//经测试保存的图片确实 变大 了
String filepath2 = dirpath + File.separator + name + "_result_" + baosResult.toByteArray().length / default_size + ".jpg";
KLog.d(filepath2);
FileOutputStream fos2 = new FileOutputStream(new File(filepath2));
fos2.write(baosResult.toByteArray());
fos2.flush();
fos2.close();

image-20200910190503664

image-20200910191137723

所以说当压缩图片到目标大小后,不能再经过其他操作,直接使用流保存到本地这个时候大小就与计算大小一样,如果再经过转换大小就可能出现变化。

你可能感兴趣的:(关于bitmap一些常见问题大小压缩等)