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。好的 还是坏的,点进去我们看
大家也看到了在这其中主要维护了4个变量,来表示图片质量的高低,分别是: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]; } }
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资源文件
读取流中bitmap
public static Bitmap decodeResource(Resources res, int id) //从资源文件读取图片
public static Bitmap decodeResource(Resources res, int id, Options opts)
读取byte[]数组中的Bitmap
public static Bitmap decodeStream(InputStream is) //从输入流读取图片
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
以上就是如何创建一个Bitmap文件
public static Bitmap decodeByteArray(byte[] data, int offset, int length) //从数组读取图片
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)
4、如何显示到屏幕上面,
如果我们要把我们的Bitmap显示到屏幕上我们必须要依赖View通过View进行显示,其实每一个View都是可以显示图片的,用作背景的情况下都是可以的,但是显示的是一个Drawable对象,所以有时候我们要去研究Drawable和Bitmap之间的转换问题,
注意:(这里考虑View,ViewGroup,ImagView,ImageButton等个别可以直接显示的等会再说)
1、Bitmap转换成为Drawable,
我们说过Bitmap是Drawable的一个子类,所以我们只需要通过中间BitmapDrawable去封装一下Bitmap即可
2、Drawable 转换成为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; }
我们说过Bitmap保存的是图像的数据,通过这个我们可以使用Canvas把Drawable的图像数据画到bitmap
转换成为Drawable以后我们就可以轻松设置到随便一个View的背景显示出来了,至于ImageView,ImageButton,它则提供了直接设置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; }
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为载体显示出来,所作的一切都是被迫的,这种情况会在以后发生很多,好了看代码吧!
来看看我们的Layout:/** * 通过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); } }
和以前一样,上面的为原始图,下面的为模糊图<?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);