点读笔写字App(1)——从Drawable中获取图片画图

【如果你想了解这个点读笔写字App的背景,请移步这里
http://www.jianshu.com/p/ee2a1bb99280

直到这篇文章的时候,我并不知道在android App运行的过程中需要使用到的图片文件应该放到何处比较合适,一点可以肯定的是不能在app安装时直接写到安装路径包的文件夹下面,因为这样会发生误删,从而引起app运行时获取不到图片的错误。如果不应该这么做,麻烦告诉我应该放到哪吧。

正如题目所说的,我把他放到了Drawable中,运行中就可以方便的取得图片了,不过放到drawable文件中方便的同时也给我带来一些困扰,下面听我慢慢道来吧。

在app中,我去取得图片来作为笔记本的页背景(就是普通的笔记本上一行一行的行文线的样子),在上面书写,实际也就是用特定的像素点去取代之前背景图片的像素点了。当然最终笔记本的写上内容的图片是需要保存的,因为我希望下次打开笔记本的时候,我在那一页页上写下的小心情还是存在的。当然我没法再把它们存在drawable中了,我选择把这些记录保存到本地。

本篇的内容并不涉及我如何从本地获取图片来初始化我笔记本的页,也不涉及我如何从内存中把我的图片写到本地。这里提到这些是因为打开笔记本的时候,取出写过的页和从drawable中去会有些不同。而在之前写过的页上继续我写字是很正常的事,我应该有这样的功能。(你做为程序员不应阻止我勤俭节约的本质吧。这里顺便给你们举个例子吧:我有个大学舍友,考研那会,他在草稿上演算的时候都是先用黑色的笔写满整张草稿,然后再换红色的笔写满,然后才扔掉。怎样,节约吧?其实那时这么做的他大概是想通过这样的方式向自己展示努力的成果吧。毕竟不是每次演算都是有结果的,在那段迷茫空虚的日子,这些努力的痕迹却可以证明未辜负青春正好)。当然,初始化书写过的内容,保存书写的内容到本地,会单独写一篇,请移步这里,《初始化本子上已经记录的文字》
下面开始吧:

获取bitmap对象的方法

  • getResources().getDrawable.getBimap
  • 通过BitmapFactory功能累来获取

我们需要在一个图片上画图,过程是:获取该图的bitmap对象->建立画布->在对应的像素处作画。
在项目的过程中,我发现使用BitmapFactory来操作bitmap对象十分的方便,

BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inMutable = true;
baseBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.page_background, opts);

BitmapFactory.Options是BitmapFactory一个功能参数设置,指示该如何去处理对象,如果处理实际上也决定了我们得到什么(虽然生活比这个复杂,但也是这个理)。如这里的设置inMutable为true,mutable是可变的意思,即为获取的图片我们是可以

canvas = new Canvas(baseBitmap);

这样建立一个画布作画的。默认获取得的bitmap是不支持改变bitmap的内容的,即使你把这块2进制的数据取到内存中,这里这么做的原因不知道是不是为了保护数据完整性(我感觉不是很必要,毕竟你去new canvas的时候就是表示要对其操作了,在VC++中就是未保护的)。注意这里不是不是mutable的时候建立画布会报错。如果采取是第一种方法获取的bitmap的话,当然也是非mutable的了,这里的办法就是

//其中copy还需要传进参数
baseBitmap =((BitmapDrawable)getResources().getDrawable(R.drawable.page_background))
    .getBitmap().copy();

由于第二种要简洁明了,我推荐第二种方式啦,所以copy中的参数我就不说了,有兴趣的自己看去api的文档吧。

按比例缩放图片

这介绍两种缩放方式

  • BitmapFactory按比例缩放
  • CreateBitmap按长宽比例缩放
BitmapFactory按比例缩放

这里我们设定一个情景,假定我们需要获取一个bitmap对象其长宽为原来图片的大小,即一个800*800(px)的图片,我希望获取的bitmap对象就是800*800。可能有点了解android的朋友会知道,在drawable文件夹中的图片会根据他文件夹的后缀(drawable-ldpi、-mdpi、-hdpi、-xhdpi、xxhdpi)去自动的进行一些拉伸,android这么的机制呢是为了保证不同显示分辨率上显示效果的一致性。这到底是个什么情况呢,我会在文章的最后来解释一下。这也就是我在之前说的,从本地取出写过的页图片和从drawable中去会有些不同,因为drawable会自动去做图片的缩放啊,从drawable中获取的可能已经是缩放过的了,所以我们这里要想取得原始图片了。是不是感觉有点乱,其实没有啊,我去取得原始图片是因为我提前知道drawable会缩放图片啊(其实哪有什么提前,之前并没有人告诉我,我也是先掉到坑里从这个坑中爬出来的)。
至于我为什么要获得原始图,因为我只有一个原始图的像素矩阵我才能“随心所欲”地去到对应像素点位置作画。如800*800的图,我希望把它分成8*8的格子,然后我把左上角的格子涂黑:

canvas.drawRect(0, 0, 100, 100);

好了,我们还是来看看如果使用BitmapFactory来进行按长宽原始比例缩放:

//firstly
BitmapFactory.Options opts = new BitmapFactory.Options();
options.inJustDecodeBounds = true;//此参数意思是不建立bitmap对象,只取图片原来大小,这么做的好处就是节约内存
BitmapFactory.decodeResource(getResources(), R.drawable.page_background, opts);//这里无需返回bitmap
int be = (int)(options.outHeight / (float)200);//200为希望的高缩放到200,只要设置这个就好,因为宽和高是等比例的嘛
//then
options.inJustDecodeBounds = false;//默认为false。意思建立bitmap
option.inSampleSize = be ;//注意上面计算be时int那个强制转换,因为inSample只接受int
baseBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.page_background
, opts);

上面就是把图片从800*800缩放到200*200的例子了,我们做个简单的计算,相当于把option.inSampleSize赋值为4,则获取到的图为原来1/4。inSampleSize只能为整数,所以我们获得的图片只能为1,1/2,1/3,…(这就有问题啦,2/3这样的是不允许存在的,哪怕你算术运算算出的结果是整数,这个在上面说的最后部分给个例子)。其实知道BitmapFactory的inSampleSize设置发明出来是为了快速的获取缩略图用的,你也就释然了。
按照上面的来,我要获取原图,那我是不是应该

 be = (int)(options.outHeight / (float)options.outHeight);//be = 1

设option.inSampleSize=1,遗憾的是获取的图还是被缩放了。就是说我没有用BitmapFactory从drawable中取出未做缩放的图了。其实如果你这样就可以取得未做缩放图片了:

BitmapFactory.decodeFile("/sdcard/test.jpg", options);//注意为本地读取,这就是和drawable的区别了。options不做任何设置,就是取原图
CreateBitmap按长宽比例缩放
matrix.postScale(scaleWidth, scaleHeight);
resizeBitmap = Bitmap.createBitmap(bitmap, 0, 0, bmpWith, bmpHeight, matrix, false);

scaleWidth和scaleHeight的计算是类似这样 scaleWidth=200/800=1/4其中200为其想要缩放到的尺寸,当然这里的这里的scaleWidth和scaleHeight是可以分别指定到不同的尺寸的,也没有必须int型的限制。
Bitmap.createBitmap函数中的参数bitmap为指定的缩放对象;后面的0, 0, bmpWith, bmpHeight即为所取的bitmap对象的区域,我们这里bmpwith,bmpHeight为bitmap这个指定对象的对应值啦,意思取整个图进行缩放。matrix就是给个缩放的比例尺了。

认识Drawable(hdpi,mdpi,ldpi等等)作用

首先系统是根据发布的目标设备的PPI(pixels per inch)去对应的文夹中搜索所添加资源图片的,搜索到即取出来。dpi和ppi的区别 搜索顺序是:对应文件夹->有标识文件夹(hdpi,mdpi,ldpi等,就近原则,至于是先搜大的还是小的,深究这个没有意义,毕竟如果你取实现该功能实现者就随你了)->drawable(默认文件夹)。而这个dpi每个设备是不同的,android并不可能为每种dpi设置一个drawable,所以这些dpi都是有个就近取特定值的策略。
各个文件夹对应的dpi

文件夹种类dpi值
drawable160dpi
drawable-ldpi120dpi
drawable-mdpi160dpi
drawable-hdpi240dpi
drawable-xhdpi320dpi
drawable-xxhdpi480dpi

其实最终我想获取图片原始尺寸并没有采用第二种方式(第一种直接取不是原始尺寸啦,前面说了),毕竟那种拉伸是可以能引起图片的变形的(height和width可以按不同的比例拉伸啊)。主要还是我喜欢BitmapFactory那样的操作啦,封装得很好,要是我们自己开发的过程中可以把工具类做成这个样子那就更好啦,想的时候看看源码吧(其实我也没看呢,哈哈)。那么我是怎么做的呢?

BitmapFactory.Options opts = new BitmapFactory.Options();
baseBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.page_background
    , opts);
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
float density = dm.density;
nNibSize = (int)(100 * (160 * density / 240));//hdpi represent for 240dp;density depend on 160;

前面一段我们知道是怎么回事了,我想取一个原始尺寸的图片嘛(当然通过url到本地取就是原始尺寸了),它给我一个拉伸之后的嘛。然后我过DisplayMetrics取获取目标设备的PPI给我画笔的像素宽度做一个相应的放大。即我之前800*800的图片,画一个点是100*100的,每个100*100的框都是一个点。现在800*800的图片放大了,那么我相应的点的大小也要放大(缩小也缩小嘛,我这么做也是为了兼容不是PPI设备嘛,我也很牛逼)。其中,nNibSize就是我所谓一个点的像素(100*100)。举个例子会更清晰点:

我的设备为5.7inch,分辨率为1920*1080(px),计算该设备PPI

《点读笔写字App(1)——从Drawable中获取图片画图》

这就是求得我设备PPI了。而我的设备就近归档到那个drawable呢?我debug我的代码,我发现800*800图片放到了-hdpi下,获得的图的大小为1467*1467,为什么会是这个值。 在上述的代码中,我获得了一个就近取的像素密度,发现dm.density取得的值为2.75,也就是我们386的像素密度就近取了440=2.75*160,结果表明440并没有在我们上表所列的文件夹中的,说明android有自己一套归档策略,至于为什么会有440这档,没必要去深究,那只是实现者想去处理事情的方式而已。

而1467=800*(2.75*160/240)这个值就是根据这个来缩放的。首先我们的图片在-hdpi中,这个像素密度是比我们设备的对应像素密度要低的。也就是说在hdpi对应的像素密度中,我们的图应该显示成原图,而到高的PPI中时如果再显示原图的话,那么图片相对于屏幕要小了,要达到相同的显示效果就是要把图片按照这个PPI的比例放大,这个比例就是2.75*160/240。所以nNibSize = (int)(100 * (160 * density / 240));这行代码我只是按照他的一个比例把我100的像素变大以适应变大后的图而已。效果是可以在如下图中,不管PPI是多少,总是可以一次只涂黑一个格子,不会出现涂到别的像素或者涂不满的情况。

《点读笔写字App(1)——从Drawable中获取图片画图》 page_background.png

    原文作者:Crawl_W
    原文地址: https://www.jianshu.com/p/1ecff65e0c22
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞