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。好的 还是坏的,点进去我们看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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]; } } 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类当中,我们点进去看一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 public static class Options { public Options () { inDither = false ; inScaled = true ; inPremultiplied = true ; } public Bitmap inBitmap; @SuppressWarnings ({"UnusedDeclaration" }) public boolean inMutable; public boolean inJustDecodeBounds; public int inSampleSize; public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888; public boolean inPremultiplied; public boolean inDither; public int inDensity; public int inTargetDensity; public int inScreenDensity; public boolean inScaled; @Deprecated public boolean inPurgeable; @Deprecated public boolean inInputShareable; public boolean inPreferQualityOverSpeed; public int outWidth; public int outHeight; public String outMimeType; public byte [] inTempStorage; public boolean mCancel; }
读取文件当中的bitmap文件
1 2 3 4 5 6 7 public static Bitmap decodeFile (String pathName, Options opts) public static Bitmap decodeFile (String pathName) public static Bitmap decodeFileDescriptor (FileDescriptor fd) public static Bitmap decodeFileDescriptor (FileDescriptor fd, Rect outPadding, Options opts)
读取bitmap资源文件
1 2 3 public static Bitmap decodeResource (Resources res, int id) public static Bitmap decodeResource (Resources res, int id, Options opts)
读取流中bitmap
1 2 3 public static Bitmap decodeStream(InputStream is) //从输入流读取图片 public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
读取byte[]数组中的Bitmap
1 2 3 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即可
1 2 3 4 5 6 7 8 9 10 11 public Drawable bitmap2Drawable (Bitmap bitmap) { BitmapDrawable bitmapDrawable = new BitmapDrawable(bitmap); return bitmapDrawable; } public Drawable bitmap2Drawable (Resources res, Bitmap bitmap) { BitmapDrawable bitmapDrawable = new BitmapDrawable(res, bitmap); return bitmapDrawable; }
2、Drawable转换成为Bitmap 我们说过Bitmap保存的是图像的数据,通过这个我们可以使用Canvas把Drawable的图像数据画到bitmap
1 2 3 4 5 6 7 8 9 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文件呢! 我们先来看一下这个方法
1 2 3 public boolean compress(CompressFormat format, int quality, OutputStream stream)//按指定的图片格式以及画质,将图片转换为输出流。 format:Bitmap.CompressFormat.PNG或Bitmap.CompressFormat.JPEG quality:画质,0-100.0表示最低画质压缩,100以最高画质压缩。对于PNG等无损格式的图片,会忽略此项设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 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(); } }
通过上述方法不仅仅是可以保存,它是可以压缩的,我们来看看这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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为载体显示出来,所作的一切都是被迫的,这种情况会在以后发生很多,好了看代码吧!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 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++) { 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class MainActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } 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 )); } 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 ); } 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 );
效果就是上述,关于内存的东西,后面会提到,