Android Bitmap

1、前言

在前面的两讲当中,我们研究了Drawable是什么东西,我们今天一块来探讨一下什么是Bitmap,首先我们要明白,Drawable是Android当中所有可以绘制事物的一个抽象的概念,而Bitmap是可以绘制的,所以Bitmap属于Drawable的一种,所以Drawable是大的范围,而Bitmap是小的范围,如果是这样的话,就会衍生出,一个Bitmap是Drawable,而Drawable不一定是Bitmap。这也是两者的区别所在,我们在前两讲当中提过对Bitmap进行的一个封装就是BitmapDrawable而BitmapDrawable是Drawable的一个子类,想想我们当时定义的Xml文件,我相信大家会理解这两者的区别。


2、正文

今天的内容我先给大家看一幅图片,大家应该就明白了


今天的重点是关于Bitmap,至于关于如何绘制,以后会讲解的,下面我们就上述三点来开始今天的博客,不过在开始之前我们一定到明白,Drawable和Bitmap的区别。

Bitmap是一张位图,扩展名为.bmp.dip,为什么说它是位图呢,因为它将图像定义为由点(像素)组成,每个点可以由多种色彩表示,包括2、4、8、16、24和32位色彩,例如一幅1024×768分辨率的32位真彩图片,其所占存储字节数为:1024×768×32/8=3072KB。位图文件图像效果好,但是非压缩格式的,需要占用较大存储空间,所以我们遇到的OOM大部分都是由Bitmap的造成的

如果还是不太清楚Bitmap和Drawable的区别的话,那我们现在就理解为,Drawable实际上就是一个空的架子,里面可以以装载各式各样的图像,而bitmap就是其中的一幅图像,这种图像图像质量比较好。而前者重行为,而后者重数据(图像数据)像一般的移动架子之类的我们就会对其封装到Drawable当中,而如果我们要去改动画本身的属性,就去修改Bitmap,或者其他的画。

3、Bitmap怎么来的(怎么创建一个Bitmap,或者通过资源生成一个Bitmap)

Bitmap是不能New出来的,因为它的构造器私有,并且它的实例化是JNI做的,所以只有去实例化了JNI我们才能去创建Bitmap,既然是JNI做的,那么必然Google会给我提供使用的方法,不错那就Bitmap.createBitmap();通过这个方法我们就能得到一个Bitmap。

在我们创建Bitmap的时候,涉及到一个名为Bitmap.Config的这个类,这个类的具体的作用就是在我们构建自己新的Bitmap的时候来告诉系统,构建一个什么质量的Bitmap。好的   还是坏的,点进去我们看

    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];
        }
    }
大家也看到了在这其中主要维护了4个变量,来表示图片质量的高低,分别是:

ALPHA_8 Alpha:由8位组成
ARGB_4444:由4个4位组成即16位,
ARGB_8888:由4个8位组成即32位,
RGB_565:R为5位,G为6位,B为5位共16位,

当然位数越高代表的图片的质量就越高

  • createBitmap(Bitmap source):根据原有的Bitmap去创建一个新的bitmap
  • createBitmap(Bitmap source, int x, int y, int width, int height):根据原有的bitmap去创建一个从原有bitmap的(x, y)这个点起,宽为width,高为height的bitmap一般使用这个方法去裁剪原有的bitmap
  • createBitmap(Bitmap source, int x, int y, int width, int height, Matrix matrix, boolean) :根据原有的bitmap创建一个从原有bitmap的(x, y)这个点起,宽为width,高为height的bitmap,并且对该bitmap进行matrix的矩阵变换,最后一个参数是:当进行的不仅仅是平移变换时,filter为true的情况下会进行滤波处理,意思就是说让新生成的bitmap的图像质量好点,为false,就没有滤波处理
  • createBitmap(int width, int height, Config config):创建一个宽为width,高为height,图片质量为config的bitmap
  • createBitmap(DisplayMetrics displayMetrics, int width, int height, Config config):根据屏幕信息displayMetrics创建一个宽为width,高为height,图片质量为config的bitmap
  • createBitmap(DisplayMetrics displayMetrics, int[] color, int width, int height, Config onfig):根据屏幕信息displayMetrice创建一个宽为width,高为heighe,图片质量为config,并且使用颜色数组color从上而下,从左到右进行填充
  • createBitmap(DisplayMetrics, int[], int, int, int, int, Config): 这个方法和上述方法功能相近,但是有两个参数博主不知道什么意思,Android官方文档上的解释居然和上述方法相近
  • createBitmap(int[], int, int, int, int, Config):创建一个宽为width,高为heighe,图片质量为config,并且使用颜色数组color从上而下,从左到右进行填充
  • createBitmap(int[], int, int, Config):创建一个宽为width,高为heighe,图片质量为config,并且使用颜色数组color从上而下,从左到右进行填充
  • createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter):对原始的Bitmap进行缩放,最后一个参数的意思就是bitmap边缘抗锯齿。

上述就是我们自己去创建一个一个新的Bitmap,在这里我们得说说 在我们使用裁剪Bitmap的时候createBitmap(Bitmap source, int x, int y, int width, int height)我们得记住我们开始的点x+width<=source.getWidth(),要不然会报异常。

下面我们去看看如何在资源文件当中去解析出我们的Bitmap,在我们开始解析之前,我们要在这里再次为大家介绍一个帮助类,那么这个帮助类是BitmapFactory,这个类有一个decodeXXXXX();的静态方法,通过这个方法我们就可以得到一个Bitmap对象,decodeXXXXX();主要分为四种,分别是读取文件,资源,流,byte[]数组,当中的Bitmap,而在读取的时候会有一些配置需要我们去选择,Google也给我们封装在BitmapFactory的静态内部类Options类当中,我们点进去看一下

public static class Options {

    public Options() {
        inDither = false;
        inScaled = true;
        inPremultiplied = true;
    }

    /** 维护了一个Bitmap位图对象,如果更改解码方式则,重用这个位图加载内容。  */
    public Bitmap inBitmap;

    /**
     * 配置Bitmap是否可以更改,比如:在Bitmap上隔几个像素加一条线段
     */
    @SuppressWarnings({"UnusedDeclaration"}) // used in native code
    public boolean inMutable;

    /**
     * 如果设置为true,不获取图片,不分配内存,但会返回图片的高度宽度信息。
     */
    public boolean inJustDecodeBounds;

    /**
     * 图片缩放的倍数
     */
    public int inSampleSize;

    /**
     * 设置解码图片质量
     */
    public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;

    /**
     * 为true,产生的位图将它的颜色通道pre-multipled alpha通道。
     */
    public boolean inPremultiplied;

    /**
     * 如果为true,解码器尝试抖动解码
     */
    public boolean inDither;

    /**
     * 用于位图的像素压缩比
     */
    public int inDensity;

    /**
     * 用于目标位图的像素压缩比(要生成的位图)
     */
    public int inTargetDensity;

    /**
     * 当前屏幕的像素密度
     */
    public int inScreenDensity;

    /**
     * 设置为true时进行图片压缩,从inDensity到inTargetDensity
     */
    public boolean inScaled;

    /**
     * 当存储Pixel的内存空间在系统内存不足时是否可以被回收
     */
    @Deprecated
    public boolean inPurgeable;

    /**
     * inPurgeable为true情况下才生效,是否可以共享一个InputStream
     */
    @Deprecated
    public boolean inInputShareable;

    /**
     * 为true则优先保证Bitmap质量其次是解码速度
     */
    public boolean inPreferQualityOverSpeed;

    /**
     * 获取图片的宽度值
     */
    public int outWidth;

    /**
     * 获取图片的高度值
     */
    public int outHeight;

    /**
     * 设置解码图像
     */
    public String outMimeType;

    /**
     * 创建临时文件,将图片存储
     */
    public byte[] inTempStorage;

    /**
     * mCancel为true取消当前Decode
     */
    public boolean mCancel;
    
}

读取文件当中的bitmap文件

  • public static Bitmap decodeFile(String pathName, Options opts) //从文件读取图片 

  • public static Bitmap decodeFile(String pathName)

  • public static Bitmap decodeFileDescriptor(FileDescriptor fd)//从文件读取文件 与decodeFile不同的是这个直接调用JNI函数进行读取 效率比较高

  • public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)

读取bitmap资源文件

  • public static Bitmap decodeResource(Resources res, int id) //从资源文件读取图片

  • public static Bitmap decodeResource(Resources res, int id, Options opts) 

读取流中bitmap

  • public static Bitmap decodeStream(InputStream is) //从输入流读取图片

  • public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)

读取byte[]数组中的Bitmap

  • public static Bitmap decodeByteArray(byte[] data, int offset, int length) //从数组读取图片

  • public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)

以上就是如何创建一个Bitmap文件

4、如何显示到屏幕上面,

如果我们要把我们的Bitmap显示到屏幕上我们必须要依赖View通过View进行显示,其实每一个View都是可以显示图片的,用作背景的情况下都是可以的,但是显示的是一个Drawable对象,所以有时候我们要去研究Drawable和Bitmap之间的转换问题,

注意:(这里考虑View,ViewGroup,ImagView,ImageButton等个别可以直接显示的等会再说)

1、Bitmap转换成为Drawable,

我们说过Bitmap是Drawable的一个子类,所以我们只需要通过中间BitmapDrawable去封装一下Bitmap即可

    /** bitmap ---> Drawable */
    public Drawable bitmap2Drawable(Bitmap bitmap){
        BitmapDrawable bitmapDrawable = new BitmapDrawable(bitmap);
        return bitmapDrawable;
    }

    /** bitmap ---> Drawable */
    public Drawable bitmap2Drawable(Resources res, Bitmap bitmap){
        BitmapDrawable bitmapDrawable = new BitmapDrawable(res, bitmap);
        return bitmapDrawable;
    }
2、Drawable 转换成为Bitmap

我们说过Bitmap保存的是图像的数据,通过这个我们可以使用Canvas把Drawable的图像数据画到bitmap

  /** Drawable --- > Bitmap */
    public Bitmap bitmap2Drawable(Drawable drawable){
        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
                drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        drawable.draw(canvas);
        return bitmap;
    }
转换成为Drawable以后我们就可以轻松设置到随便一个View的背景显示出来了,至于ImageView,ImageButton,它则提供了直接设置Bitmap的方法,直接调用即可。

5、怎么保存我们的Bitmap文件呢!

我们先来看一下这个方法

  • public boolean compress(CompressFormat format, int quality, OutputStream stream)//按指定的图片格式以及画质,将图片转换为输出流。
  • format:Bitmap.CompressFormat.PNG或Bitmap.CompressFormat.JPEG

  • quality:画质,0-100.0表示最低画质压缩,100以最高画质压缩。对于PNG等无损格式的图片,会忽略此项设置。


    /** 把Bitmap图片保存在本地 */
    public void writeBitmapToFile(String filePath, Bitmap b, int quality) {
        try {
            File desFile = new File(filePath);
            FileOutputStream fos = new FileOutputStream(desFile);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            b.compress(Bitmap.CompressFormat.JPEG, quality, bos);
            bos.flush();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


通过上述方法不仅仅是可以保存,它是可以压缩的,我们来看看这个方法

    /** 以quality画质对Bitmap进行压缩 */
    private Bitmap compressImage(Bitmap image, int quality) {
        if (image == null || quality < 0 || quality > 100) { return null; }
        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream();
            image.compress(Bitmap.CompressFormat.JPEG, quality, baos);
            byte[] bytes = baos.toByteArray();
            ByteArrayInputStream isBm = new ByteArrayInputStream(bytes);
            Bitmap bitmap = BitmapFactory.decodeStream(isBm);
            return bitmap;
        } catch (OutOfMemoryError e) {
        } finally {
            try {
                if (baos != null) {
                    baos.close();
                }
            } catch (IOException e) {
            }
        }
        return null;
    }

关于优化的问题,后面博客说吧,毕竟这篇是介绍篇,好了我们接下来看看我们的今天的测试Demo,其实我们学习关于Bitmap的各种操作方法,大多数的情况不是说是为了某种目的去操作它。而是由于OOM异常,被迫的去压缩,去裁剪,去降低图片质量,已达到最优的情况,最优的情况包括,首先程序不能崩,速度最快,用户体验不能卡之类的。所以我打算我的这个Demo就主要去对这一方面去写点东西。

首先我们来看一下效果吧


生成一种毛玻璃的效果,在这里我要说的是每一种毛玻璃的效果都有一种模糊算法在支持,有的模糊算法是C写的,有的是用Java写的,这里面有很大的学问,这里就不要深究啦总之通过原图的Bitmap生成模糊的Bitmap很吃内存,没办法,我们首先对Bitmap缩小  去生成模糊bitmap 然后再去放大,有可能还会用到压缩Bitmap,最后以ImageView为载体显示出来,所作的一切都是被迫的,这种情况会在以后发生很多,好了看代码吧!

/**
 * 通过Java去实现的模糊算法 不是博主写的,也不要问为什么,大家都懂的!
 * Created by jay on 11/7/15.
 */
public class FastBlurUtil {

    public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) {

        Bitmap bitmap;
        if (canReuseInBitmap) {
            bitmap = sentBitmap;
        } else {
            bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
        }

        if (radius < 1) {
            return (null);
        }

        int w = bitmap.getWidth();
        int h = bitmap.getHeight();

        int[] pix = new int[w * h];
        bitmap.getPixels(pix, 0, w, 0, 0, w, h);

        int wm = w - 1;
        int hm = h - 1;
        int wh = w * h;
        int div = radius + radius + 1;

        int r[] = new int[wh];
        int g[] = new int[wh];
        int b[] = new int[wh];
        int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
        int vmin[] = new int[Math.max(w, h)];

        int divsum = (div + 1) >> 1;
        divsum *= divsum;
        int dv[] = new int[256 * divsum];
        for (i = 0; i < 256 * divsum; i++) {
            dv[i] = (i / divsum);
        }

        yw = yi = 0;

        int[][] stack = new int[div][3];
        int stackpointer;
        int stackstart;
        int[] sir;
        int rbs;
        int r1 = radius + 1;
        int routsum, goutsum, boutsum;
        int rinsum, ginsum, binsum;

        for (y = 0; y < h; y++) {
            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
            for (i = -radius; i <= radius; i++) {
                p = pix[yi + Math.min(wm, Math.max(i, 0))];
                sir = stack[i + radius];
                sir[0] = (p & 0xff0000) >> 16;
                sir[1] = (p & 0x00ff00) >> 8;
                sir[2] = (p & 0x0000ff);
                rbs = r1 - Math.abs(i);
                rsum += sir[0] * rbs;
                gsum += sir[1] * rbs;
                bsum += sir[2] * rbs;
                if (i > 0) {
                    rinsum += sir[0];
                    ginsum += sir[1];
                    binsum += sir[2];
                } else {
                    routsum += sir[0];
                    goutsum += sir[1];
                    boutsum += sir[2];
                }
            }
            stackpointer = radius;

            for (x = 0; x < w; x++) {

                r[yi] = dv[rsum];
                g[yi] = dv[gsum];
                b[yi] = dv[bsum];

                rsum -= routsum;
                gsum -= goutsum;
                bsum -= boutsum;

                stackstart = stackpointer - radius + div;
                sir = stack[stackstart % div];

                routsum -= sir[0];
                goutsum -= sir[1];
                boutsum -= sir[2];

                if (y == 0) {
                    vmin[x] = Math.min(x + radius + 1, wm);
                }
                p = pix[yw + vmin[x]];

                sir[0] = (p & 0xff0000) >> 16;
                sir[1] = (p & 0x00ff00) >> 8;
                sir[2] = (p & 0x0000ff);

                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];

                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;

                stackpointer = (stackpointer + 1) % div;
                sir = stack[(stackpointer) % div];

                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];

                rinsum -= sir[0];
                ginsum -= sir[1];
                binsum -= sir[2];

                yi++;
            }
            yw += w;
        }
        for (x = 0; x < w; x++) {
            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
            yp = -radius * w;
            for (i = -radius; i <= radius; i++) {
                yi = Math.max(0, yp) + x;

                sir = stack[i + radius];

                sir[0] = r[yi];
                sir[1] = g[yi];
                sir[2] = b[yi];

                rbs = r1 - Math.abs(i);

                rsum += r[yi] * rbs;
                gsum += g[yi] * rbs;
                bsum += b[yi] * rbs;

                if (i > 0) {
                    rinsum += sir[0];
                    ginsum += sir[1];
                    binsum += sir[2];
                } else {
                    routsum += sir[0];
                    goutsum += sir[1];
                    boutsum += sir[2];
                }

                if (i < hm) {
                    yp += w;
                }
            }
            yi = x;
            stackpointer = radius;
            for (y = 0; y < h; y++) {
                // Preserve alpha channel: ( 0xff000000 & pix[yi] )
                pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];

                rsum -= routsum;
                gsum -= goutsum;
                bsum -= boutsum;

                stackstart = stackpointer - radius + div;
                sir = stack[stackstart % div];

                routsum -= sir[0];
                goutsum -= sir[1];
                boutsum -= sir[2];

                if (x == 0) {
                    vmin[y] = Math.min(y + r1, hm) * w;
                }
                p = x + vmin[y];

                sir[0] = r[p];
                sir[1] = g[p];
                sir[2] = b[p];

                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];

                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;

                stackpointer = (stackpointer + 1) % div;
                sir = stack[stackpointer];

                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];

                rinsum -= sir[0];
                ginsum -= sir[1];
                binsum -= sir[2];

                yi += w;
            }
        }

        bitmap.setPixels(pix, 0, w, 0, 0, w, h);

        return (bitmap);
    }

}
来看看我们的Layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent"
    >

    <ImageView
        android:layout_width="match_parent"
        android:layout_weight="1"
        android:src="@mipmap/index"
        android:layout_margin="20dip"
        android:layout_height="0dip"
        />

    <ImageView
        android:id="@+id/iv_content"
        android:layout_margin="20dip"
        android:layout_width="match_parent"
        android:layout_weight="1"
        android:layout_height="0dip" />

</LinearLayout>
和以前一样,上面的为原始图,下面的为模糊图

activity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    /** 初始化View */
    private void initView() {
        ImageView mImage = (ImageView) findViewById(R.id.iv_content);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.index).
                                                       copy(Bitmap.Config.ARGB_4444, true);
        mImage.setImageBitmap(getBitmapBlur(bitmap, 10));
    }

    /** 对Bitmap进行处理 */
    private Bitmap initBitmap(Bitmap bitmap, boolean isup) {
        int width;
        int height;
        if(isup){
            width = bitmap.getWidth() * 6;
            height = bitmap.getHeight() * 6;
        }else{
            width = bitmap.getWidth() / 6;
            height = bitmap.getHeight() / 6;
        }
        return  Bitmap.createScaledBitmap(bitmap, width, height, false);
    }

    /** 初始化Bitmap */
    private Bitmap getBitmapBlur(Bitmap bitmap, int level) {
        if(level > 0){
            Bitmap bitmap1 = FastBlurUtil.doBlur(initBitmap(bitmap, false), level, true);
            return initBitmap(bitmap1, true) ;
        }
       return initBitmap(bitmap, true);
   
效果就是上述,关于内存的东西,后面会提到,

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值