在Android项目中常常需要调整原始图片的尺寸大小以适应存储、传输和图片处理等需求。在Android API中提供了一些缩放图片的方法,在项目中发现,使用Android API中的Canvas、BitmapFactory和ThumbnailUtils等类的相关方法缩放图片,锯齿感明显,图像质量不高;另外还有一些第三方的开源库专门用于在Android平台缩放图片;在FFmpeg中也提供了缩放图片和视频的方法,可以编译FFmpeg在Android平台调用相关方法。本文将总结在项目中使用上述方法的操作和实现的效果。
首先创建一个Canvas对象,使用Canvas的drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)方法,根据Rect dst指定bitmap绘制在canvas上的位置,从而改变bitmap的大小。需要注意的是,使用这种方法时,为了得到更好效果的输出,要添加抗锯齿处理。
/**
* 使用Canvas
* @param bitmap 原始的Bitmap
* @param rect Bitmap被缩放放置的Rect
* @return 缩放后的Bitmap
*/
public static Bitmap scaleCanvas(Bitmap bitmap, Rect rect) {
Bitmap newBitmap = Bitmap.createBitmap(rect.width(), rect.height(), Bitmap.Config.ARGB_8888);//创建和目标相同大小的空Bitmap
Canvas canvas = new Canvas(newBitmap);
Paint paint = new Paint();
Bitmap temp = bitmap;
//针对绘制bitmap添加抗锯齿
PaintFlagsDrawFilter pfd= new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);
paint.setFilterBitmap(true); //对Bitmap进行滤波处理
paint.setAntiAlias(true);//设置抗锯齿
canvas.setDrawFilter(pfd);
canvas.drawBitmap(temp, null, rect, paint);
return newBitmap;
}
在我们的项目中,需要将一个尺寸较大的图片缩小后合并到另外一个图片上,那么使用这种方法缩小原始图片和原始图片的对比如下。
Bitmap实际上就是由像素点组成的矩阵,在Android中的Matrix主要用于对图像缩放、平移和旋转处理操作,Matrix对象调用postScale(float sx, float sy)方法设置缩放,在Bitmap的createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter)方法中将Matrix对象传入,即可根据Matrix规则生成新的Bitmap。
/**
* 使用Matrix
* @param bitmap 原始的Bitmap
* @param width 目标宽度
* @param height 目标高度
* @return 缩放后的Bitmap
*/
public static Bitmap scaleMatrix(Bitmap bitmap, int width, int height){
int w = bitmap.getWidth();
int h = bitmap.getHeight();
float scaleW = width/w;
float scaleH = height/h;
Matrix matrix = new Matrix();
matrix.postScale(scaleW, scaleH); // 长和宽放大缩小的比例
return Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true);
}
ThumbnailUtils是Android API提供的获取缩略图的工具类,所以可以很容易地获得缩放后的Bitmap,使用该类中extractThumbnail(Bitmap source, int width, int height)方法返回获得缩放的Bitmap,该方法的参数中,source指输入待缩放的原始Bitmap,width和height分别指目标宽度和高度。
上面分别是使用Canvas、Matrix和ThumbnailUtils得到的缩小后的图片。
使用上述方法得到的缩放图片的质量并不太好,失真比较严重。例如在我们的项目中,是将一个较大尺寸的图片缩小后贴到一个较小尺寸的图片上,合并后的图片只要稍微放大就会看到明显的锯齿感,与原始图片存在着明显的差异。
在Android中使用的图像缩放的算法都是比较简单高效的,所以输出的质量就不太好。比如使用Matrix缩小图片采用的是双线性采样(Bilinear Resampling)的算法,产生的图片斜率不太准确、细节会出现退化。对于同样的图片,使用Matrix方法缩小尺寸,和使用Photoshop缩小到相同的尺寸,可以明显分辨出差别。所以,使用Android中采用的缩放图片的算法处理效果不好,失真也更严重。
有一些开源的Android平台的第三方库可以用来调整图片的尺寸,它们采用一些算法可以产生不同质量的缩放图。比如ComPressHelper、Compressor、Luban等压缩图片的Android库,也提供了缩放图片的方法,具体的使用方法可以参考链接页面。在实际使用中,发现其输出质量并不比使用Android API的高。使用CompressHelper缩小图片尺寸和原始图片的对比如下。
FFmpeg的libswscale类库提供了将输入的图片缩放到指定的大小,使用scale过滤器指定缩放后的宽高大小,也可以只设置宽和高中的一个,另一个置为-1,保持输出的宽高比。使用-sws_flags方法可以指定缩放所采用的算法,下面是一个指定缩放宽高和缩放算法的命令示例:ffmpeg -i test.tif -vf scale=504:376 -sws_flags bilinear out.bmp
。
之前的博文已经介绍过编译在Android以命令调用FFmpeg的动态库,可以通过指令调用具体的FFmpeg的处理操作。指定输入、输出路径、目标的宽高和采用的缩放算法,生成缩放图片命令的函数如下。
/**
* FFmpeg缩放图片
* @param input 输入路径
* @param output 输出路径
* @param width 目标宽度
* @param height 目标高度
* @param algorithm 缩放算法
* @return FFmpeg缩放命令
*/
public static String[] scaleImage(String input, String output, int width, int height, String algorithm) {
final int count = 8;
String[] commands = new String[count];
int index = 0;
commands[index++] = "ffmpeg";
commands[index++] = "-i";
commands[index++] = input;
commands[index++] = "-vf";
commands[index++] = "scale="+width+":"+height;
commands[index++] = "-sws_flags";
commands[index++] = algorithm;
commands[index++] = output;
return commands;
}
FFmpeg中提供的各种缩放算法,采用其中bicubic、spline、sinc等许多算法产生的缩放后的图片质量要明显好于使用Android API生成的缩放图片。
在Android项目中需要缩放图片的尺寸,一般使用Android API中提供的方法就可以满足需求,而且这些方法调用简单,处理效率高。如果仅仅缩放图片,并不提倡使用CompressHelper等第三方库,这些库主要侧重于图片压缩效率,缩放图片的效果并不比Android API好。如果需要得到更高质量的缩放图片,则应该使用其它的缩放算法处理,FFmpeg中不光提供了简单的缩放图片的方法,还提供了十几种缩放算法选项,另外,根据具体的图片特征选择合适的算法,可以更好地提高缩放图片的质量。