前言
回顾了下以前写的调用相机和相册的功能,准备把它们整合下,想起曾经用魅族在获取大图时OOM的问题,决定重看一遍当初的解决方式。在获取缩略图步骤上发现了系统已经提供了工具类ThumbnailUtils,当然减少内存消耗不只有这一步。
先前获取缩略图的方法
public static Bitmap getThumbnail(Uri uri,int size, Context context) throws Exception {
InputStream input = context.getContentResolver().openInputStream(uri);
//配置BitmapFactory.Options,inJustDecodeBounds设为true,以获取图片的宽高
BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
onlyBoundsOptions.inJustDecodeBounds = true;
onlyBoundsOptions.inDither=true;//optional
onlyBoundsOptions.inPreferredConfig=Bitmap.Config.ARGB_8888;//optional
//计算inSampleSize缩放比例
BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
input.close();
if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1))
return null;
int originalSize = (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) ? onlyBoundsOptions.outHeight : onlyBoundsOptions.outWidth;
double ratio = (originalSize > size) ? (originalSize / size) : 1.0;
//获取到缩放比例后,再次设置BitmapFactory.Options,获取图片缩略图
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
bitmapOptions.inDither=true;//optional
bitmapOptions.inPreferredConfig=Bitmap.Config.ARGB_8888;//optional
input = context.getContentResolver().openInputStream(uri);
Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
input.close();
return bitmap;
}
/**
* 将double的比例采用近似值的方式转为int
* @param ratio
* @return
*/
private static int getPowerOfTwoForSampleRatio(double ratio){
int k = Integer.highestOneBit((int)Math.floor(ratio));
if(k==0) return 1;
else return k;
}
总体思想是通过设置BitmapFactory.Options.inJustDecodeBounds设为true,先获取到图片的宽高而并不会生产Bitmap;再通过所需图片的最长边size来获取缩放比例inSampleSize的值;
然后获所需尺寸的图片。
获取缩放比例inSampleSize值的算法可以单独拉出一个方法,根据需求进行设置:
/**
* 根据图片的Options和期望的图片大小来获取图片的缩小比例、
* 如果图片的宽或高有一个大于目标值,就做处理;否则不做处理。
* 关于inSampleSize需要注意,它只能是2的次方,否则它会取最接近2的次方的值。
* @param options 目标图片的BitmapFactory.Options
* @param expectationWidth 期望图片的宽
* @param expectationHeight 期望图片的高
* @return
*/
public static int sampleSize(BitmapFactory.Options options, int expectationWidth, int expectationHeight) {
//首先获取图片的宽
int rawWidth = options.outWidth;
int rawHeight = options.outHeight;
//在计算图片的sampleSize
int inSampleSize = 0;
if (rawHeight > expectationHeight || rawWidth > expectationWidth) {
float ratioHeight = rawHeight / expectationHeight;
float rationWidth = rawWidth / expectationWidth;
inSampleSize = (int) Math.min(ratioHeight, rationWidth);
}
inSampleSize = Math.max(inSampleSize, 1);
return inSampleSize ;
}
这种缩放是考虑短的边进行缩放控制,如果短边长度小于期望长度,不进行缩放。
通过上述方法成功获取到图片缩略图,但系统已经给我们提供获取方法,而且算法上考虑的情况更优秀,下面来看看。
ThumbnailUtils
获取图片缩略图,我们使用的方法是extractThumbnail,但主要实现方法是transform.我的是API23的源码。
/**
* Transform source Bitmap to targeted width and height.
*/
private static Bitmap transform(Matrix scaler,
Bitmap source,
int targetWidth,
int targetHeight,
int options) {
//是否可以进行图片放大操作
boolean scaleUp = (options & OPTIONS_SCALE_UP) != 0;
//是否可以进行原图资源回收操作
boolean recycle = (options & OPTIONS_RECYCLE_INPUT) != 0;
int deltaX = source.getWidth() - targetWidth;
int deltaY = source.getHeight() - targetHeight;
/*
*图片如果小于目标值,进行放大处理
*/
if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
/*
* In this case the bitmap is smaller, at least in one dimension,
* than the target. Transform it by placing as much of the image
* as possible into the target and leaving the top/bottom or
* left/right (or both) black.
*/
Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b2);
int deltaXHalf = Math.max(0, deltaX / 2);
int deltaYHalf = Math.max(0, deltaY / 2);
Rect src = new Rect(
deltaXHalf,
deltaYHalf,
deltaXHalf + Math.min(targetWidth, source.getWidth()),
deltaYHalf + Math.min(targetHeight, source.getHeight()));
int dstX = (targetWidth - src.width()) / 2;
int dstY = (targetHeight - src.height()) / 2;
Rect dst = new Rect(
dstX,
dstY,
targetWidth - dstX,
targetHeight - dstY);
c.drawBitmap(source, src, dst, null);
if (recycle) {
source.recycle();
}
c.setBitmap(null);
return b2;
}
/*
*图片如果大于目标值,进行缩小处理
*/
float bitmapWidthF = source.getWidth();
float bitmapHeightF = source.getHeight();
float bitmapAspect = bitmapWidthF / bitmapHeightF;
float viewAspect = (float) targetWidth / targetHeight;
//获取缩放比例,如果原图宽高比大于目标宽高比,也就是原图变得更“窄”了
//就用高度比例进行缩放,否则用宽度比例进行缩放。
//效果上看就是将图片完全展示。
if (bitmapAspect > viewAspect) {
float scale = targetHeight / bitmapHeightF;
if (scale < .9F || scale > 1F) {
scaler.setScale(scale, scale);
} else {
scaler = null;
}
} else {
float scale = targetWidth / bitmapWidthF;
if (scale < .9F || scale > 1F) {
scaler.setScale(scale, scale);
} else {
scaler = null;
}
}
//根据缩放比例创建缩略图
Bitmap b1;
if (scaler != null) {
// this is used for minithumb and crop, so we want to filter here.
b1 = Bitmap.createBitmap(source, 0, 0,
source.getWidth(), source.getHeight(), scaler, true);
} else {
b1 = source;
}
if (recycle && b1 != source) {
source.recycle();
}
int dx1 = Math.max(0, b1.getWidth() - targetWidth);
int dy1 = Math.max(0, b1.getHeight() - targetHeight);
Bitmap b2 = Bitmap.createBitmap(
b1,
dx1 / 2,
dy1 / 2,
targetWidth,
targetHeight);
if (b2 != b1) {
if (recycle || b1 != source) {
b1.recycle();
}
}
return b2;
}
可以看出,系统在效果上看就是将图片完全展示,算法上考虑的情况也更为丰富。原图的回收,是否放大,目标图片的宽高等考虑进去了。
总结
获取缩略图总的步骤就是:
graph TB
A{开始}-->B(获取原始图片宽高)
B --> C[根据算法获取缩放比例]
C --> D[根据缩放比例创建缩略图]
在ThumbnailUtils的行数只有521行,还有些方法并有被使用到,感兴趣的可以去看看。
虽然ThumbnailUtils的代码不长,但学到了不同的实现方式和写作方式,了解了自己的不足。多看源码对自己的提升还是很有用的。