通用的Bitmap压缩算法,进一步节约内存

  前几天我写了一篇通过压缩Bitmap,减少OOM的文章,那篇文章的目的是按照imageview的大小来压缩bitmap,让bitmap的大小正好是imageview。但是那种算法的通用性比较差,仅仅能适合fit_xy的情况。对此我进一步分析了下这个问题,并且参考了Volley的源码,最终得出了结论:如果你要让这个压缩后的bitmap完全适合多种imageview拉伸模式,你就必须重写拉伸模式的算法,但这过于小题大做了。讨巧一点的办法就是让这个imageview不完全按照imageview的长宽进行压缩,而仅仅按照imageview的长或宽按比例缩小,得到的是一张和原图比率一样的小图,让imageview加载这个小图就行了。世上没有十全十美的事情,你这个虽然讨巧了,但问题也就来了,在某些模式下可能会有一部分图片没有显示在屏幕上,浪费了一点点内存,在cent模式下,原图的显示效果和小图的显示效果完全不一样。

总结:考虑到多种因素,我还是决定使用比较讨巧的做法,因为它通用性比较高,浪费内存的情况有,但浪费的内存很少(几kb),一般情况下我们不用center模式进行图片的显示,所以我们完全可以考虑这个方式。

工具类:

我参考了volley的代码,重新构建了工具类,下面直接贴出工具类的代码:

package com.kale.bitmaptest;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

public class BitUtils {

    private static int mDesiredWidth;
    private static int mDesiredHeight;

    /**
     * @description 从Resources中加载图片
     *
     * @param res
     * @param resId
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        // 设置成了true,不占用内存,只获取bitmap宽高
        options.inJustDecodeBounds = true;
        // 初始化options对象
        BitmapFactory.decodeResource(res, resId, options);
        // 得到计算好的options,目标宽、目标高
        options = getBestOptions(options, reqWidth, reqHeight);
        Bitmap src = BitmapFactory.decodeResource(res, resId, options); // 载入一个稍大的缩略图
        return createScaleBitmap(src, mDesiredWidth, mDesiredHeight); // 进一步得到目标大小的缩略图
    }

    /**
     * @description 从SD卡上加载图片
     *
     * @param pathName
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static Bitmap decodeSampledBitmapFromFile(String pathName, int reqWidth, int reqHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(pathName, options);
        options = getBestOptions(options, reqWidth, reqHeight);
        Bitmap src = BitmapFactory.decodeFile(pathName, options);
        return createScaleBitmap(src, mDesiredWidth, mDesiredHeight);
    }

    /**
     * @description 计算目标宽度,目标高度,inSampleSize
     *
     * @param options
     * @param reqWidth
     * @param reqHeight
     * @return BitmapFactory.Options对象
     */
    private static BitmapFactory.Options getBestOptions(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // 读取图片长宽
        int actualWidth = options.outWidth;
        int actualHeight = options.outHeight;
        // Then compute the dimensions we would ideally like to decode to.
        mDesiredWidth = getResizedDimension(reqWidth, reqHeight, actualWidth, actualHeight);
        mDesiredHeight = getResizedDimension(reqHeight, reqWidth, actualHeight, actualWidth);
        // 根据现在得到计算inSampleSize
        options.inSampleSize = calculateBestInSampleSize(actualWidth, actualHeight, mDesiredWidth, mDesiredHeight);
        // 使用获取到的inSampleSize值再次解析图片
        options.inJustDecodeBounds = false;
        return options;
    }

    /**
     * Scales one side of a rectangle to fit aspect ratio. 最终得到重新测量的尺寸
     *
     * @param maxPrimary
     *            Maximum size of the primary dimension (i.e. width for max
     *            width), or zero to maintain aspect ratio with secondary
     *            dimension
     * @param maxSecondary
     *            Maximum size of the secondary dimension, or zero to maintain
     *            aspect ratio with primary dimension
     * @param actualPrimary
     *            Actual size of the primary dimension
     * @param actualSecondary
     *            Actual size of the secondary dimension
     */
    private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary, int actualSecondary) {
        double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;
        if (resized * ratio > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }
        return resized;
    }

    /**
     * Returns the largest power-of-two divisor for use in downscaling a bitmap
     * that will not result in the scaling past the desired dimensions.
     *
     * @param actualWidth
     *            Actual width of the bitmap
     * @param actualHeight
     *            Actual height of the bitmap
     * @param desiredWidth
     *            Desired width of the bitmap
     * @param desiredHeight
     *            Desired height of the bitmap
     */
    // Visible for testing.
    private static int calculateBestInSampleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
        double wr = (double) actualWidth / desiredWidth;
        double hr = (double) actualHeight / desiredHeight;
        double ratio = Math.min(wr, hr);
        float inSampleSize = 1.0f;
        while ((inSampleSize * 2) <= ratio) {
            inSampleSize *= 2;
        }

        return (int) inSampleSize;
    }

    /**
     * @description 通过传入的bitmap,进行压缩,得到符合标准的bitmap
     *
     * @param src
     * @param dstWidth
     * @param dstHeight
     * @return
     */
    private static Bitmap createScaleBitmap(Bitmap tempBitmap, int desiredWidth, int desiredHeight) {
        // If necessary, scale down to the maximal acceptable size.
        if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth || tempBitmap.getHeight() > desiredHeight)) {
            // 如果是放大图片,filter决定是否平滑,如果是缩小图片,filter无影响
            Bitmap bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true);
            tempBitmap.recycle(); // 释放Bitmap的native像素数组
            return bitmap;
        } else {
            return tempBitmap; // 如果没有缩放,那么不回收
        }
    }

}

这个工具类构造的思想和原本的构造思想完全一致,差别之处在于这里的图片是等比缩放的。

测试代码:

    public void loadBitmap(boolean exactable) {
        int bmSize = 0;
        Bitmap bm = null;
        if (exactable) {
            // 通过工具类来产生一个符合ImageView的缩略图
            bm = BitUtils.decodeSampledBitmapFromResource(getResources(), R.drawable.saber, iv.getWidth(), iv.getHeight());
        } else {
            // 直接加载原图
            bm = BitmapFactory.decodeResource(getResources(), R.drawable.saber);
        }
        iv.setImageBitmap(bm);
        bmSize += bm.getByteCount(); // 得到bitmap的大小
        int kb = bmSize / 1024;
        int mb = kb / 1024;
        kb = kb % 1024;
        Log.d("Bitmap", "bitmap w = " + bm.getWidth() + " h = " + bm.getHeight());
        Log.d("Bitmap", "bitmap size = " + mb + "MB " + kb + "KB");
        Toast.makeText(this, "bitmap size = " + mb + "MB " + kb + "KB", Toast.LENGTH_LONG).show();
    }

通过加载原图和加载缩略图进行比较,最终在log打印出图片的宽高和图片内存占用。

测试结果:

布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context="${relativePackage}.${activityClass}" >

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:src="@drawable/ic_launcher" />

    <Button
        android:id="@+id/original_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:onClick="butonListener"
        android:text="加载原图" />

    <Button
        android:id="@+id/clip_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/original_button"
        android:layout_alignBottom="@+id/original_button"
        android:layout_alignParentRight="true"
        android:onClick="butonListener"
        android:text="加载缩略图" />

</RelativeLayout>

前题:我的手机定义的imageview是100dp,实际是200pix。加载图片的实际大小:850 x 1200

① 加载原图

bitmap宽 = 567,高 = 800;

内存占用:1M 747KB

解释:最终得到的图片大小和原始图片不同,这里应该是BitmapFactory在解码时就已经做了压缩,算是自带的一个智能压缩方案吧。

② 用工具类加载缩略图

bitmap宽 = 141,高 = 200;

内存占用:110KB

解释:目标的imageview宽、高均为100dp,在我手机上换算为200pix,这里做了等比缩放处理,所以高为200.最后我们也明显的看出,用这种方式得到的图片比较小,不会轻易出现OOM

完整的activity代码:

package com.kale.bitmaptest;

import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.Toast;

public class MainActivity extends Activity {
    ImageView iv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iv = (ImageView) findViewById(R.id.imageView);
        iv.setScaleType(ScaleType.CENTER_CROP);
        // center 变了
        getMemoryCacheSize();
    }

    public void butonListener(View v) {
        switch (v.getId()) {
        case R.id.original_button:
            loadBitmap(false); // 加载原图
            break;

        case R.id.clip_button:
            loadBitmap(true); // 加载缩略图
            break;
        }
    }

    public void loadBitmap(boolean exactable) {
        int bmSize = 0;
        Bitmap bm = null;
        if (exactable) {
            // 通过工具类来产生一个符合ImageView的缩略图
            bm = BitUtils.decodeSampledBitmapFromResource(getResources(), R.drawable.saber, iv.getWidth(), iv.getHeight());
        } else {
            // 直接加载原图
            bm = BitmapFactory.decodeResource(getResources(), R.drawable.saber);
        }
        iv.setImageBitmap(bm);
        bmSize += bm.getByteCount(); // 得到bitmap的大小
        int kb = bmSize / 1024;
        int mb = kb / 1024;
        kb = kb % 1024;
        Log.d("Bitmap", "bitmap w = " + bm.getWidth() + " h = " + bm.getHeight());
        Log.d("Bitmap", "bitmap size = " + mb + "MB " + kb + "KB");
        Toast.makeText(this, "bitmap size = " + mb + "MB " + kb + "KB", Toast.LENGTH_LONG).show();
    }

    public int getMemoryCacheSize() {
        // Get memory class of this device, exceeding this amount will throw an
        // OutOfMemory exception.
        final int memClass = ((ActivityManager) getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
        System.out.println("memory = " + memClass + "M");
        return memClass;
    }

    public int dip2px(float dpValue) {
        final float scale = getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}

利弊:

利:

节约内存,降低出现OOM的几率

弊:

降低了图片的清晰度,不适用于center模式的imageview。

  

左边的是加载的缩略图,右边的是加载的原图。右边的图片明显比坐标的清晰,但锐化过于严重了,左边的虽然小,但是较为模糊。

源码下载:CSDN抽风了,稍后上传

扩展阅读:

http://www.open-open.com/lib/view/open1329994992015.html

时间: 2024-10-10 13:57:17

通用的Bitmap压缩算法,进一步节约内存的相关文章

android图片的缓存--节约内存提高程序效率

现在android应用占内存一个比一个大,android程序的质量亟待提高. 这里简单说说网络图片的缓存,我这边就简单的说说思路 1:网络图片,无疑需要去下载图片,我们不需要每次都去下载. 维护一张表,表里面放url 对应 存储的文件名 实现方法就是先拿到要下载的url,拿这个url去数据库匹配,如果有这张图片,那就不需要重新去下载了,直接通过数据库,去拿到图片的位置,然后从本地把图片加载出来就OK了.没有这张图片的时候去下载一下,并且存一下数据库 好处:这么做的好处很明显,下载过的图片不需要重

节约内存:Instagram的Redis实践(转)

add by zhj:本文只翻译了一部分,更多分析要参考英文原文 译文:节约内存:Instagram的Redis实践 英文原文:Storing hundreds of millions of simple key-value pairs in Redis Instagram可以说是网拍App的始祖级应用,也是当前最火热的拍照App之一,Instagram的照片数量已经达到3亿,而在Instagram里,我们需要知道每一张照片的作者是谁,下面就是Instagram团队如何使用Redis来解决这个问

Bitmap那些事之内存占用计算和加载注意事项

前言:本来我是做电视应用的,但是因为公司要出手机,人员紧张,所以就抽调我去支援一下,谁叫俺是雷锋呢!我做的一个功能就是处理手机中的应用ICON,处理无非就是美化一下,重新与底板进行合成和裁剪,用到了很多Bitmap的知识.本来之前一直想写一些关于Bitmap的博客,正好这是个机会,因此Bitmap那些事系列博客诞生了.这个系列我会把学习Bitmap的一些知识发布出来供大家参考和交流. 在手机中图片一般都是指Bitmap图片,为什么要说Bitmap呢?因为大家在开发应用的时候,都会使用一些图片来表

[C#绘图]Bitmap锁定到系统内存中

具体方法Bitmap.LockBits方法的实现功能是讲Bitmap锁定到系统内存中. 使用LockBits方法,可以在系统内存中锁定现有的位图,以便通过编程方式进行修改.尽管用LockBits方式进行大规模更改可以获得更好的性能,但是仍然可以用SetPixel方法来更改图像的颜色. 函数的返回值的类型是BitmapData,包含选定Bitmap区域的特性. private void LockUnlockBitsExample(PaintEventArgs e) { // Create a ne

节约内存:Instagram的Redis实践(转)

一.问题: 数据库表数据量极大(千万条),要求让服务器更加快速地响应用户的需求. 二.解决方案: 1.通过高速服务器Cache缓存数据库数据 2.内存数据库 三.主流解Cache和数据库对比: 从以上各数据可知,对于我们产品最可行的技术方案有两种: 1.Memcached         内存Key-Value Cache 2.Redis                     内存数据库 四,节约内存:Instagram的Redis实践 Instagram可以说是网拍App的始祖级应用,也是当

libCURL动态分配buffer——节约内存

libCURL是一个免费的.开源的强大客户端url传输库.支持的平台.协议甚广.平台上有Windows.Linux.FreeBSD:协议上有FTP.HTTP(S).Telnet.DICT.File等.它是线程安全的,而且支持ipv6.同时,它还是线程安全的. 本文所讲述的内容,要基于Libcurl的基础.具体的了解.官方具体描述地址在:http://curl.haxx.se/libcurl/c/上面.需要多看例子,才能灵活运用. 通常libCURL的数据下载都是通过回调函数下载到buffer中的

关于bitmap储存图片报内存不足的问题

这几天在js中将canvas作为流进行bitmap保存原始尺寸大小的图片,发生了内存不足,每次报这个错误的时候就要进行重新启动程序,真心恶心到爆了,close().Dispose()方法全部都用了个便,垃圾回收机制也强制加进去了,依旧没办法,bitmap这个太吃内存了. 后面突然发现用FileStream写流的方式将图片写入进去后,问题居然解决了,而且生成的图片质量比bitmap生成的质量要好.废话不多,直接上代码 dataURL = dataURL.Replace(" ", &quo

MATLAB中选择合适的数据类型以节约内存

在做大矩阵的运算,发现占用太多内存,于是在好友jcyao的提醒下,将double类型改为uint8类型内存是原来的1/8.(我的问题可以使用整形,其他问题不一定使用).在网上搜到一篇如何选择变量类型的文章,写的很清楚,先摘下来.其网站matlabtips.com 上面有很多好的建议和优化方法. Choose your variables wisely- Posted on March 21, 2012 by Jerome I am stuck home with a big fever so I

python的__slots__节约内存的魔法

在Python中,每个类都有实例属性.默认情况下Python用一个字典来保存一个对象的实例属性.这非常有用,因为它允许我们在运行时去设置任意的新属性. 然而,对于有着已知属性的小类来说,它可能是个瓶颈.这个字典浪费了很多内存.Python不能在对象创建时直接分配一个固定量的内存来保存所有的属性.因此如果你创建许多对象(我指的是成千上万个),它会消耗掉很多内存. 不过还是有一个方法来规避这个问题.这个方法需要使用__slots__来告诉Python不要使用字典,而且只给一个固定集合的属性分配空间.