Bitmap的加载简单优化

Bitmap的加载简单优化

Bitmap如何加载

Bitmap可以认为是Android系统将图片加载GPU的一个映射,Android可以读取png格式的,也可以读取jpg格式的。那么Android是如何加载一张图片的呢?有个类叫做BitmapFactory,它提供了四个方法:decodeFile(从文件系统中加载),decodeResource(从资源中加载),decodeStream(从输入流中加载),decodeByteArray(从字节数组中加载);其中decodeFile和decodeResource又间接调用了decodeStream方法;

下面是decodeFile的代码:

public static Bitmap decodeFile(String pathName, Options opts) {
        Bitmap bm = null;
        InputStream stream = null;
        try {
            stream = new FileInputStream(pathName);
            bm = decodeStream(stream, null, opts);
        } catch (Exception e) {
            /*  do nothing.
                If the exception happened on open, bm will be null.
            */
            Log.e("BitmapFactory", "Unable to decode stream: " + e);
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    // do nothing here
                }
            }
        }
        return bm;
    }

下面是decodeResource的代码:

    public static Bitmap decodeResource(Resources res, int id, Options opts) {
        Bitmap bm = null;
        InputStream is = null; 

        try {
            final TypedValue value = new TypedValue();
            is = res.openRawResource(id, value);

            bm = decodeResourceStream(res, value, is, null, opts);
        } catch (Exception e) {
            /*  do nothing.
                If the exception happened on open, bm will be null.
                If it happened on close, bm is still valid.
            */
        } finally {
            try {
                if (is != null) is.close();
            } catch (IOException e) {
                // Ignore
            }
        }

        if (bm == null && opts != null && opts.inBitmap != null) {
            throw new IllegalArgumentException("Problem decoding into existing bitmap");
        }

        return bm;
    }

其中的decodeResourceStream方法是这样的:

    public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, Options opts) {

        if (opts == null) {
            opts = new Options();
        }

        if (opts.inDensity == 0 && value != null) {
            final int density = value.density;
            if (density == TypedValue.DENSITY_DEFAULT) {
                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
            } else if (density != TypedValue.DENSITY_NONE) {
                opts.inDensity = density;
            }
        }

        if (opts.inTargetDensity == 0 && res != null) {
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        }

        return decodeStream(is, pad, opts);
    }

最后依然调用的是decodeStream方法。

这几个方法是在底层实现的,对应着几个native方法,这里不说了。

BitmapFactory.Option的几个参数简介

我们都知道在Android里内存溢出的最大元凶就是图片,如何避免这种情况呢?其实也挺简单,就是采用BitmapFactory.Option这个对象设置图片显示的尺寸。

我们一般显示图片会使用ImageView等控件,但是它们需要的尺寸一般都会比我们提供的图片的尺寸下,所以我们要对图片压缩,下面我们就看一下BitmapFactory.Option提供的方法。

属性说明
int inSampleSize字面意思为取样的尺寸,为int 型;虽然它只设定一个值,但是它会影响到解析图片为Bitmap时长宽两个方向上的像素大小。它设定值会使Bitmap按倍数缩小。默认为1,最小值也为1,表示不缩小;当大于1时,它才会按照比例缩小;比如我们设置为2,Bitmap的长宽会变为原大小的一半,那么相应的它的像素所占有的内存就会缩小为原来的1/4。需要注意的是它的值必须为2的n次幂,例如:1,2,4,8,16
boolean inJustDecodeBounds字面意思为只解析Bitmap边界,也就是长宽;为Boolean型,当我们设置为true时,我们使用BitmapFactory的decode方式解析图片时就不会返回Bitmap,而只会读取该图片的尺寸和类型信息。我们一般通过这个方法来获取尺寸,然后再按照其尺寸缩小。当然用完记得重新置为false。
Bitmap.Config inPreferredConfig字面为Bitmap优先的设置,它是一个enum,它设置的是Bitmap的像素类型;默认为ARGB_8888,这个后面再具体介绍。
int outHeightBitmap 的高
int outWidthBitmap 的宽
String outMimeType图片的MIME类型,比如“image/jpeg”

上面的只是其中的一部分,但是已经够用了。下面我们通过实例来看一下它的使用。

如何利用inSampleSize压缩图片

我们上面说过,我们不想加载图片的原尺寸,我们需要对它进行尺寸的压缩。那么我们一般的步骤是什么样的呢?

  1. 获取图片尺寸
  2. 计算Bitmap合适的尺寸,即计算inSampleSize的值
  3. 设置inSampleSize,压缩Bitmap,并显示在ImageView上

下面我们根据这个步骤来实现一下,这里我随便在网上找了了一张图片,放到了Android的外部存储设备中,我们直接读取这个图片,它的像素为1024 × 683,格式为”jpg“。

首先我们不压缩,看看什么情况:

        Bitmap b = BitmapFactory.decodeFile(getBeautyPath());
        ivBeauty.setImageBitmap(b);

上面的ImageView宽高都为wrap_content,然后我们开始压缩之旅。

1. 获取尺寸

BitmapFactory.Options options = new BitmapFactory.Options();
//这里我获取图片的尺寸和类型信息
options.inJustDecodeBounds = true;
//我们这里获取到的Bitmap是空的,图片没有真正的加载到内存,只有尺寸和类型信息
Bitmap bitmap = BitmapFactory.decodeFile(getBeautyPath(), options);
//为了确定为空,我们做一下判断
if (bitmap == null) {
    loge("图片的MIME类型 = " + options.outMimeType);
    loge("图片的高 = " + options.outHeight);
    loge("图片的宽 = " + options.outWidth);
}

上面的loge方法为我自己封装的,调用的是Log.e()。运行之后我们得到了下面的log,证明我们的是没有问题的。

09-03 11:16:41.426 13194-13194/com.liteng.mytest E/BitmapFactoryTest: 图片的MIME类型 = image/jpeg
09-03 11:16:41.426 13194-13194/com.liteng.mytest E/BitmapFactoryTest: 图片的高 = 683
09-03 11:16:41.426 13194-13194/com.liteng.mytest E/BitmapFactoryTest: 图片的宽 = 1024

2.计算Bitmap合适的尺寸

我们获取了原图的尺寸,我们想把它显示到一个宽高分别为200,100的ImageView上,那我们怎么计算呢?

这里我们封装一个方法:

private int computeInSampleSize(BitmapFactory.Options options, int targetW, int targetH) {
    int width = options.outWidth;
    int height = options.outHeight;

    int inSampleSize = 1;
    //判断一下原图的宽高与我们的目标宽高大小,如果原图的长或者宽大于目标宽高才计算
    if (width > targetW || height > targetH) {
        int halfH = height / 2;
        int halfW = width / 2;
        //inSimpleSize的必须是2的指数次幂,所以我们取最可能的inSampleSize的最大值
        while ((halfH / inSampleSize) > targetH && (halfW / inSampleSize) > targetW) {
            inSampleSize *= 2;
        }
    }
    return  inSampleSize;
}

3.设置inSampleSize,压缩Bitmap,并显示在ImageView上

BitmapFactory.Options options = new BitmapFactory.Options();
//这里我获取图片的尺寸和类型信息
options.inJustDecodeBounds = true;
//我们这里获取到的Bitmap是空的,图片没有真正的加载到内存,只有尺寸和类型信息
Bitmap bitmap = BitmapFactory.decodeFile(getBeautyPath(), options);
//为了确定为空,我们做一下判断
if (bitmap == null) {
    loge("图片的MIME类型 = " + options.outMimeType);
    loge("图片的高 = " + options.outHeight);
    loge("图片的宽 = " + options.outWidth);
}

//获取计算到的inSampleSize
options.inSampleSize = computeInSampleSize(options,200,100);
//我们不再只获取图片的尺寸和类型了,下一步我们需要加载Bitmap了
options.inJustDecodeBounds = false;
Bitmap targetBitmap = BitmapFactory.decodeFile(getBeautyPath(),options);
ivBeauty.setImageBitmap(targetBitmap);

那么经过我们压缩过的图片是什么样的呢?看图

那么我们在看一下log呢?

09-03 12:03:19.957 8642-8642/com.liteng.mytest E/BitmapFactoryTest: 图片的MIME类型 = image/jpeg
09-03 12:03:19.957 8642-8642/com.liteng.mytest E/BitmapFactoryTest: 图片的高 = 683
09-03 12:03:19.957 8642-8642/com.liteng.mytest E/BitmapFactoryTest: 图片的宽 = 1024
09-03 12:03:19.957 8642-8642/com.liteng.mytest E/BitmapFactoryTest: inSampleSize = 4

我们计算出来的inSampleSize为4,之前我们说过,in SampleSize只能是2的指数次幂,我们这里只能尽可能接近我们需要值,而不能完全精确。

上面我们说到Bitmap.Config 这个枚举,它的代码如下(去掉了注释和空行):

public enum Config {
    ALPHA_8     (1),
    RGB_565     (3),
    @Deprecated
    ARGB_4444   (4),
    ARGB_8888   (5);
    final int nativeInt;
    private static Config sConfigs[] = {
        null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888
    };
    Config(int ni) {
        this.nativeInt = ni;
    }
    static Config nativeToConfig(int ni) {
        return sConfigs[ni];
    }
}

我们都知道Bitmap占有内存为:

Bitmap×××

我们在上面代码中看到的 ALPHA_8,RGB_565,ARGB_4444 ,ARGB_8888就是Bitmap的四种像素形式,它们每个像素占用的字节数分别为4、2、2、1;即
ALPHA8:RGB565:ARGB4444:ARGB88884:2:2:1

其中ARGB4444在API13被废弃了。

下面是整个Activity的代码,感兴趣的可以再封装一下,做一个工具类:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "BitmapFactoryTest";

    private ImageView ivBeauty;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ivBeauty = (ImageView) this.findViewById(R.id.ivBeauty);

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.ARGB_4444;
        //这里我获取图片的尺寸和类型信息
        options.inJustDecodeBounds = true;
        //我们这里获取到的Bitmap是空的,图片没有真正的加载到内存,只有尺寸和类型信息
        Bitmap bitmap = BitmapFactory.decodeFile(getBeautyPath(), options);
        //为了确定为空,我们做一下判断
        if (bitmap == null) {
            loge("图片的MIME类型 = " + options.outMimeType);
            loge("图片的高 = " + options.outHeight);
            loge("图片的宽 = " + options.outWidth);
        }

        //获取计算到的inSampleSize
        options.inSampleSize = computeInSampleSize(options,200,100);
        loge("inSampleSize = " + options.inSampleSize);
        //我们不再只获取图片的尺寸和类型了,下一步我们需要加载Bitmap了
        options.inJustDecodeBounds = false;

        Bitmap targetBitmap = BitmapFactory.decodeFile(getBeautyPath(),options);
        ivBeauty.setImageBitmap(targetBitmap);
    }

    private int computeInSampleSize(BitmapFactory.Options options, int targetW, int targetH) {
        int width = options.outWidth;
        int height = options.outHeight;

        int inSampleSize = 1;
        //判断一下原图的宽高与我们的目标宽高大小,如果原图的长或者宽大于目标宽高才计算
        if (width > targetW || height > targetH) {
            int halfH = height / 2;
            int halfW = width / 2;
            //inSimpleSize的必须是2的指数次幂,所以我们取最可能的inSampleSize的最大值
            while ((halfH / inSampleSize) > targetH && (halfW / inSampleSize) > targetW) {
                inSampleSize *= 2;
            }
        }
        return  inSampleSize;
    }


    private String getBeautyPath() {
        String path = Environment.getExternalStorageDirectory() + "/Download/beauty.jpg";
        return path;
    }


    private void loge(String msg) {
        Log.e(TAG, msg);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要在 Android 上合成多个 Bitmap 图片,可以使用 Canvas 类和 Bitmap 类。下面是一个简单的示例: 1. 创建一个空的 Bitmap 对象,作为最终合成的图片: ```java Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(result); ``` 2. 将需要合成的 Bitmap 依次绘制到空的 Bitmap 上: ```java canvas.drawBitmap(bitmap1, x1, y1, null); canvas.drawBitmap(bitmap2, x2, y2, null); canvas.drawBitmap(bitmap3, x3, y3, null); ``` 3. 最后,可以保存合成后的 Bitmap图片文件或者显示在 ImageView 中: ```java imageView.setImageBitmap(result); ``` 完整的代码示例: ```java public Bitmap mergeBitmaps(Bitmap bitmap1, Bitmap bitmap2, Bitmap bitmap3) { int width = bitmap1.getWidth(); int height = bitmap1.getHeight() + bitmap2.getHeight() + bitmap3.getHeight(); Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(result); canvas.drawBitmap(bitmap1, 0, 0, null); canvas.drawBitmap(bitmap2, 0, bitmap1.getHeight(), null); canvas.drawBitmap(bitmap3, 0, bitmap1.getHeight() + bitmap2.getHeight(), null); return result; } ``` 以上代码将三个 Bitmap 垂直合成为一个 Bitmap,第一个 Bitmap 在最上面,第二个在中间,第三个在最下面。可以根据实际需求修改代码。 ### 回答2: Android中合成bitmap图片可以使用Canvas和Paint类来实现。步骤如下: 1. 创建一个新的Bitmap对象,用于存储合成后的图片。 2. 创建一个Canvas对象,并将新的Bitmap对象与Canvas关联起来。 3. 创建一个Paint对象,并设置相关的合成属性,如颜色、透明度、画笔风格等。 4. 使用Canvas的drawBitmap()方法将多个Bitmap对象绘制到新的Bitmap上,实现图片的合成效果。 5. 最后,可以将合成后的Bitmap保存到本地文件或者显示在界面上。 以下是一个简单的示例代码: ```java // 创建合成后的Bitmap对象 Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); // 创建Canvas对象,并与新的Bitmap关联 Canvas canvas = new Canvas(resultBitmap); // 创建Paint对象 Paint paint = new Paint(); // 设置合成属性 paint.setColor(Color.RED); paint.setAlpha(128); paint.setStyle(Paint.Style.FILL); // 绘制bitmap1 canvas.drawBitmap(bitmap1, matrix1, paint); // 绘制bitmap2 canvas.drawBitmap(bitmap2, matrix2, paint); // 绘制bitmap3 canvas.drawBitmap(bitmap3, matrix3, paint); // 可以依次绘制更多的bitmap ... // 保存合成后的Bitmap到本地文件 resultBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream); ``` 以上代码中,width和height表示合成后的Bitmap的宽度和高度,bitmap1、bitmap2、bitmap3等表示待合成的原始Bitmap对象,matrix1、matrix2、matrix3等表示对相应的Bitmap进行变换的Matrix对象。 通过以上步骤,就可以实现Android中的Bitmap图片合成。 ### 回答3: Android中的Bitmap是一个表示图像的类,可以用来显示图片、进行图像处理等操作。要实现Bitmap图片合成,可以通过以下步骤: 1. 创建一个新的Bitmap对象,用于存储合成后的图片。可以使用Bitmap的createBitmap()方法,指定宽度、高度和颜色格式创建一个空白的Bitmap对象。 2. 获取要合成的原始图片。可以使用BitmapFactory的decodeResource()方法,从资源文件中加载图片,并通过BitmapFactory.Options对象设置图片的缩放比例、色彩模式等参数。 3. 将原始图片绘制到新的Bitmap对象上。可以使用Canvas的drawBitmap()方法,在新的Bitmap上绘制原始图片。可以设置合成图片的位置、大小等属性。 4. 如果需要合成多张图片,重复步骤2和步骤3,将其他图片依次绘制到新的Bitmap对象上。 5. 最后,可以将合成后的Bitmap对象进行保存或显示。可以使用Bitmap的compress()方法将Bitmap对象保存到指定的输出流中,或使用ImageView等控件的setImageBitmap()方法显示合成后的图片。 需要注意的是,图片合成可能会消耗较大的内存和处理时间,特别是在合成大尺寸图片或大量图片时。为了避免内存溢出和性能问题,可以适当进行图片的压缩、缩放或分块处理,或使用异步处理方式进行合成。此外,可以通过Bitmap的回收和复用来优化内存使用。 总之,通过创建新的Bitmap对象,获取原始图片,绘制到新的Bitmap上,就可以实现Android中的图片合成功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值