标签(空格分隔): Android
图像数字化
在现实生活中,我们通过眼睛看见的景象是连续不断的自然景象,我们想要通过计算机得到或处理这些真实的图像的话,我们就必须对这些图像进行数字化,图像数字化是用离散来表示连续,图像数字化分为三个步骤:
- 采样:真实的图像行和列是连续的,我们每行和每列按照一定的控件间隔采样尽量多的色块,这每个色块就是像素。
- 量化:用一个数字去表示一个色块对应的颜色,如果这个数字的位数越多,那么我们能表示的颜色信息就越详细
- 编码:通常意思就是压缩编码,通过对之前得到的数字矩阵通过各种变换,达到压缩数据的目的。
图像在文件中的存储
我们都知道存储图像实际上就是存储它的像素值,将一张图像放的很大的话,我们会发现一个个排列有序的色块,用不同的二进制序列表示不同的颜色,这样来看的话,一幅图像就是一个由数字组成的颜色矩阵,通过一定规则存储这些颜色矩阵,我们就存储了这个颜色矩阵对应的图像。当然,通过一定的方式对这个颜色矩阵进行变换,我们就可以对这幅图像进行变换(伸缩、旋转、压缩等等)以达到我们想要的图片效果。
基于Android操作图像
- 加载图片
Android中提供了BitmapFactory这个类支持对图片的操作,这个类提供了四个方法:decodeFile(),decodeResource(),decodeSream()和decodeByteArray(),分别分别用于支持从文件系统、资源、输入流和字节数组中加载出一个Bitmap对象,这样我们就拿到了图片的对象,我们可以通过ImageView提供的方法setImageBitmap()将图片设置给该控件完成图片的加载。下面举个栗子:
//从文件中加载bitmap
private void loadBitmap (){
Bitmap test = BitmapFactory.decodeFile("/storage/131B-1A02/IMG_20191011_135824.jpg");
//Rect rect = new Rect(0,0,test.getWidth(),test.getHeight());
showBitmap(test);
}
private void showBitmap(Bitmap bitmap){
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams)ivTest.getLayoutParams();
params.width = bitmap.getWidth();
params.height = bitmap.getHeight();
ivTest.setImageBitmap(bitmap);
}
//从网上通过url加载bitmap
private void loadBitmapFromHttp(final String url){
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
try {
URL path = new URL(url);
connection = (HttpURLConnection)path.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(6000);
connection.setReadTimeout(6000);
InputStream in = connection.getInputStream();
final Bitmap gank = BitmapFactory.decodeStream(in);
Log.d("loadBitmap:",""+(gank==null));
mHandler.post(new Runnable() {
@Override
public void run() {
if(gank!=null)
showBitmap(gank);
}
});
} catch (Exception e) {
e.printStackTrace();
}finally {
if(connection!=null)
connection.disconnect();
}
}
}).start();
}
2 . 高效的加载图片
我们都知道,在Android值用于显示图像的控件是ImageView,把资源图像设置后,图像会根据ImageView的宽高伸缩。如果过资源图像分辨率很大,而ImageView的宽高很小,这时候我们就可以对图像进行压缩去更加高效的展示图像。我们可以通过Android中提供的BitmapFactory.Options这个类,这个类中有一个inSampleSize参数,这个参数决定了图像的压缩比,他有一下属性:
- 这个参数只能是2的幂,不规范的设置都会向下取整转化为其相近的2的幂
- 一个图像是1024 * 1024 * 8(8M)的,设置inSampleSize为2的话,宽和高的像素才都会少一半,最后压缩为512 * 512 * 8(2M)
- 可以通过设置Option中另外一个标志位inJustDecodeBounds,预加载一次图片(设置了inJustDecodeBounds标志位的预加载是轻量级的操作),计算出需要的inSampleSize,然后在正常加载图像。
下面举个例子:
private Bitmap resizeBitmapFromFile(String path , int scaleFactor){
final BitmapFactory.Options options= new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path,options);
int width = options.outWidth;
int height = options.outHeight;
int requireWidth = width >> scaleFactor;
int requireHeight = height >> scaleFactor;
int inSampleSize = 1;
if (height>requireHeight || width> requireWidth){
final int halfHeight=height/2;
final int halfWidth=width/2;
//计算最大的采样率,采样率为2的指数
while ((halfWidth/inSampleSize)>=requireWidth && (halfHeight/inSampleSize)>=requireHeight){
inSampleSize = inSampleSize << 1;
}
}
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path,options);
}
3 . 图像的变换
前面讲了如何加载Bitmap,但是有些时候我们拿到的Bitmap并不适合直接拿来使用,我们要通过一些图像变换或者其他处理之后才能满足使用的需求。
最常见的栗子就是圆角图像——用户想要设置圆形头像,但是我们从他指定的位置读出的图像都是矩形的,我们就需要进行一些操作去满足矩形图像转化成为圆形图像的应用,下面提供一个简单的思路:
private Bitmap roundBitmap(Bitmap bitmap,float rx,float ry){
//首先创建一个可变的位图对象,颜色空间是RGB,
Bitmap result = Bitmap.createBitmap(requireWidth,requireHeight,
Bitmap.Config.ARGB_8888);
final Paint paint = new Paint();
//创建一个画布,并指定该画布能绘制之前我们创建的位图
Canvas canvas = new Canvas(result);
paint.setAntiAlias(true);
RectF rectF = new RectF(0,0,requireWidth,requireHeight);
//先画一个圆角矩形的图层
canvas.drawRoundRect(rectF,rx,ry,paint);
//然后指定图层的叠加模式为,形状取下层,图像取上层
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap,0,0,paint);
return result;
}
这里的重点在于13行的图层叠加模式的使用,Android提供了多种不同的模式,通过这个方法设置不同的模式可以完成很多意向不到的变化。
再举一个栗子:用户觉得只有圆形头像太单调,他想要一个笑脸的头像。刚听到用户的需求,某底层程序小王只有暗自叹气,但是冷静过后,想了想之前圆角图像的实现,发现好像并不是很难,撸着袖子开干:
private Bitmap smileBitmap(Bitmap bitmap){
Bitmap result = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(),
Bitmap.Config.ARGB_8888);
final Paint paint = new Paint();
Canvas canvas = new Canvas(result);
paint.setAntiAlias(true);
RectF rectF = new RectF(0,0,bitmap.getWidth()/2,bitmap.getHeight()/2);
canvas.drawArc(rectF,180,360,false,paint);
RectF rectF1 = new RectF(bitmap.getWidth()/2,0,bitmap.getWidth(),bitmap.getHeight()/2);
canvas.drawArc(rectF1,180,360,false,paint);
RectF rectF2 = new RectF(0,0,bitmap.getWidth(),bitmap.getHeight());
canvas.drawArc(rectF2,0,180,false,paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap,0,0,paint);
return result;
}
做完这个需求之后,他老是不安心,总觉得“贪得无厌”的用户还有更多的需求,他准备把这个问题抽象出来,用户无非是想要各种类型的头像,那么他可以准备各种图片框供用户选择,什么圆角图片框,三角图片框,笑脸图片框,花型图片框,心形图片框等等。用户选择完图片后,他可以直接把这图片套进这个图片框中就行了。
过了几天这边又发现了几个问题,用户下线了之后,头像还是彩色的,让人觉得体验不好,需要下线后把图像转化成灰色的提醒一下别人。 接受这个需求的码农挠了挠脑袋,决定从图像的底层入手,把RGB的混合相加彩色空间,平均一下实现单通道的灰度图像:
private Bitmap bitmapToGray(Bitmap bitmap){
Bitmap result = bitmap.copy(Bitmap.Config.ARGB_8888,true);
int width = result.getWidth();
int height = result.getHeight();
// 保存所有的像素的数组,图片宽×高
int[] pixels = new int[width * height];
result.getPixels(pixels,0,width,0,0,width,height);
for(int i =0;i < pixels.length;i++){
int a = (pixels[i] & 0xff000000)>>24; //透明通道
int red = (pixels[i] & 0x00ff0000) >> 16; //红色通道
int green = (pixels[i] & 0x0000ff00) >> 8; //绿色通道
int blue = pixels[i] & 0x000000ff; //蓝色通道
int average = (red + green + blue )/3; // 转换成为灰度图像需要平均三个通道
pixels[i] = (a << 24) | (average << 16) | (average << 8) | average;
}
result.setPixels(pixels,0,width,0,0,width,height);
return result;
}
作者(小白)水平有限,如有问题还请大佬指点。
网友评论