前言
在一次偶然的情况下,在上看到 一句代码搞定 Android 图片压缩
真的是打瞌睡碰到了枕头啊~
因为最近项目开发中要新增一个模块,主要是用于上传照片的。现在的手机,像素越做越高,分辨率越来越牛逼,但是特么的体积特越来越大了,懂不懂就是4~5M 的大小。在天朝的网络环境下,上行跟下行的带宽极度的不对称,一天20M 的光纤上行却只有200k/s 左右,所以对于需要上传照片的需求来说,图片压缩是一个不可避免的议题。
一开始我对于图片的处理都是仅限于裁剪,降低分辨率,采样等常规的方法,体积是降了一些,但是图片的清晰度也跟着下来了。虽说压缩是必须的,但是让用户看清楚照片内容同样也是刚需。往往平衡两者之后,体积就没有减少的那么的明显了。
Luban 却做到了兼顾两者的同时将体积降到了很小。
github 地址:https://github.com/Curzibn/Luban
具体的使用方法可以看看原作者的 或者 GitHub
但是这个库让我有点小小遗憾的是:使用的 Rxjava 1.0+的版本
Rxjava是一个很优秀的开源库,Rxjava2.0+更是一个更更优秀的开源库,我之前就已经在项目中使用了。但是 Luban却只适合 Rxjava1.0+的版本,原作者也没有进行相关的升级适配。本着自己动手也要用上 Luban的想法(真的蛮喜欢 Luban,谢谢原作者的分享),于是自己进行了相关的改造
让 Luban 适用 Rxjava2.0+
在 github上我们可以看到,Luban 的核心文件其实就只有三个
既然要对 Luban 进行修改,那么自己 compile 就显然不合适了,而且核心文件其实也就三个,所以我们可以先把这三个文件下载下来放到自己的项目中去。然后仔细看一下最核心的文件 Luan.java ,我们发现,其实 Luban 使用 Rxjava 主要是用来控制线程,所以我们要改动的就是把这几个地方用 Rxjava2.0+的方式重写一遍就好了
我们把这几个地方用 Rxjava2.0+的方式重写一遍
当然这样子,也是有不足之处的,就是后面原作者对算法进行升级之类的,我们很难快速的升级。但是如果原作者对 Luban 进行升级,很可能就直接支持 Rxjava2.0+了。
哈哈,我也在原作者的下提了适配 Rxjava2.0+,希望作者能采纳哈`
如果这篇文章又帮到你的话,请点一下‘喜欢’,我会更努力的创作的
以下还是改写之后整个 Luban.java 文件,有需要的直接替换旧的 Luban.java就好。
public classLuban {
private static final intFIRST_GEAR=1;
public static final intTHIRD_GEAR=3;
private static finalStringTAG="Luban";
private staticStringDEFAULT_DISK_CACHE_DIR="luban_disk_cache";
private static volatileLubanINSTANCE;
private finalFilemCacheDir;
privateOnCompressListenercompressListener;
privateFilemFile;
private intgear=THIRD_GEAR;
privateStringfilename;
privateLuban(File cacheDir) {
mCacheDir= cacheDir;
}
/**
* Returns a directory with a default name in the private cache directory of the application to use to store
* retrieved media and thumbnails.
*
*@paramcontextA context.
*@see#getPhotoCacheDir(android.content.Context, String)
*/
private static synchronizedFilegetPhotoCacheDir(Context context) {
returngetPhotoCacheDir(context,Luban.DEFAULT_DISK_CACHE_DIR);
}
/**
* Returns a directory with the given name in the private cache directory of the application to use to store
* retrieved media and thumbnails.
*
*@paramcontextA context.
*@paramcacheNameThe name of the subdirectory in which to store the cache.
*@see#getPhotoCacheDir(android.content.Context)
*/
private staticFilegetPhotoCacheDir(Context context,String cacheName) {
File cacheDir = context.getCacheDir();
if(cacheDir !=null) {
File result =newFile(cacheDir,cacheName);
if(!result.mkdirs() && (!result.exists() || !result.isDirectory())) {
// File wasn't able to create a directory, or the result exists but not a directory
return null;
}
File noMedia =newFile(cacheDir +"/.nomedia");
if(!noMedia.mkdirs() && (!noMedia.exists() || !noMedia.isDirectory())) {
return null;
}
returnresult;
}
if(Log.isLoggable(TAG,Log.ERROR)) {
Log.e(TAG,"default disk cache dir is null");
}
return null;
}
public staticLubanget(Context context) {
if(INSTANCE==null)INSTANCE=newLuban(Luban.getPhotoCacheDir(context));
returnINSTANCE;
}
publicLubanlaunch() {
checkNotNull(mFile,"the image file cannot be null, please call .load() before this method!");
if(compressListener!=null)compressListener.onStart();
if(gear== Luban.FIRST_GEAR)
Observable.just(mFile)
.map(newFunction() {
@Override
publicFileapply(@io.reactivex.annotations.NonNullFile file)throwsException {
returnfirstCompress(file);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(newConsumer() {
@Override
public voidaccept(@io.reactivex.annotations.NonNullThrowable throwable)throwsException {
if(compressListener!=null)compressListener.onError(throwable);
}
})
.onErrorResumeNext(Observable.empty())
.subscribe(newConsumer() {
@Override
public voidaccept(@io.reactivex.annotations.NonNullFile file)throwsException {
if(compressListener!=null)compressListener.onSuccess(file);
}
});
else if(gear== Luban.THIRD_GEAR)
Observable.just(mFile)
.map(newFunction() {
@Override
publicFileapply(@io.reactivex.annotations.NonNullFile file)throwsException {
returnthirdCompress(file);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(newConsumer() {
@Override
public voidaccept(@io.reactivex.annotations.NonNullThrowable throwable)throwsException {
if(compressListener!=null)compressListener.onError(throwable);
}
})
.onErrorResumeNext(Observable.empty())
.subscribe(newConsumer() {
@Override
public voidaccept(@io.reactivex.annotations.NonNullFile file)throwsException {
if(compressListener!=null)compressListener.onSuccess(file);
}
})
;
return this;
}
publicLubanload(File file) {
mFile= file;
return this;
}
publicLubansetCompressListener(OnCompressListener listener) {
compressListener= listener;
return this;
}
publicLubanputGear(intgear) {
this.gear= gear;
return this;
}
/**
*@deprecated
*/
publicLubansetFilename(String filename) {
this.filename= filename;
return this;
}
publicObservableasObservable() {
if(gear==FIRST_GEAR)
returnObservable.just(mFile).map(newFunction() {
@Override
publicFileapply(@io.reactivex.annotations.NonNullFile file)throwsException {
returnfirstCompress(file);
}
});
else if(gear==THIRD_GEAR)
returnObservable.just(mFile).map(newFunction() {
@Override
publicFileapply(@io.reactivex.annotations.NonNullFile file)throwsException {
returnthirdCompress(file);
}
});
else returnObservable.empty();
}
privateFilethirdCompress(@NonNullFile file) {
String thumb =mCacheDir.getAbsolutePath() + File.separator+
(TextUtils.isEmpty(filename) ? System.currentTimeMillis() :filename) +".jpg";
doublesize;
String filePath = file.getAbsolutePath();
intangle = getImageSpinAngle(filePath);
intwidth = getImageSize(filePath)[0];
intheight = getImageSize(filePath)[1];
intthumbW = width %2==1? width +1: width;
intthumbH = height %2==1? height +1: height;
width = thumbW > thumbH ? thumbH : thumbW;
height = thumbW > thumbH ? thumbW : thumbH;
doublescale = ((double) width / height);
if(scale <=1&& scale >0.5625) {
if(height <1664) {
if(file.length() /1024<150)returnfile;
size = (width * height) / Math.pow(1664,2) *150;
size = size <60?60: size;
}else if(height >=1664&& height <4990) {
thumbW = width /2;
thumbH = height /2;
size = (thumbW * thumbH) / Math.pow(2495,2) *300;
size = size <60?60: size;
}else if(height >=4990&& height <10240) {
thumbW = width /4;
thumbH = height /4;
size = (thumbW * thumbH) / Math.pow(2560,2) *300;
size = size <100?100: size;
}else{
intmultiple = height /1280==0?1: height /1280;
thumbW = width / multiple;
thumbH = height / multiple;
size = (thumbW * thumbH) / Math.pow(2560,2) *300;
size = size <100?100: size;
}
}else if(scale <=0.5625&& scale >0.5) {
if(height <1280&& file.length() /1024<200)returnfile;
intmultiple = height /1280==0?1: height /1280;
thumbW = width / multiple;
thumbH = height / multiple;
size = (thumbW * thumbH) / (1440.0*2560.0) *400;
size = size <100?100: size;
}else{
intmultiple = (int) Math.ceil(height / (1280.0/ scale));
thumbW = width / multiple;
thumbH = height / multiple;
size = ((thumbW * thumbH) / (1280.0* (1280/ scale))) *500;
size = size <100?100: size;
}
returncompress(filePath,thumb,thumbW,thumbH,angle,(long) size);
}
privateFilefirstCompress(@NonNullFile file) {
intminSize =60;
intlongSide =720;
intshortSide =1280;
String filePath = file.getAbsolutePath();
String thumbFilePath =mCacheDir.getAbsolutePath() + File.separator+
(TextUtils.isEmpty(filename) ? System.currentTimeMillis() :filename) +".jpg";
longsize =0;
longmaxSize = file.length() /5;
intangle = getImageSpinAngle(filePath);
int[] imgSize = getImageSize(filePath);
intwidth =0,height =0;
if(imgSize[0] <= imgSize[1]) {
doublescale = (double) imgSize[0] / (double) imgSize[1];
if(scale <=1.0&& scale >0.5625) {
width = imgSize[0] > shortSide ? shortSide : imgSize[0];
height = width * imgSize[1] / imgSize[0];
size = minSize;
}else if(scale <=0.5625) {
height = imgSize[1] > longSide ? longSide : imgSize[1];
width = height * imgSize[0] / imgSize[1];
size = maxSize;
}
}else{
doublescale = (double) imgSize[1] / (double) imgSize[0];
if(scale <=1.0&& scale >0.5625) {
height = imgSize[1] > shortSide ? shortSide : imgSize[1];
width = height * imgSize[0] / imgSize[1];
size = minSize;
}else if(scale <=0.5625) {
width = imgSize[0] > longSide ? longSide : imgSize[0];
height = width * imgSize[1] / imgSize[0];
size = maxSize;
}
}
returncompress(filePath,thumbFilePath,width,height,angle,size);
}
/**
* obtain the image's width and height
*
*@paramimagePaththe path of image
*/
public int[]getImageSize(String imagePath) {
int[] res =new int[2];
BitmapFactory.Options options =newBitmapFactory.Options();
options.inJustDecodeBounds=true;
options.inSampleSize=1;
BitmapFactory.decodeFile(imagePath,options);
res[0] = options.outWidth;
res[1] = options.outHeight;
returnres;
}
/**
* obtain the thumbnail that specify the size
*
*@paramimagePaththe target image path
*@paramwidththe width of thumbnail
*@paramheightthe height of thumbnail
*@return{@linkBitmap}
*/
privateBitmapcompress(String imagePath, intwidth, intheight) {
BitmapFactory.Options options =newBitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeFile(imagePath,options);
intoutH = options.outHeight;
intoutW = options.outWidth;
intinSampleSize =1;
if(outH > height || outW > width) {
inthalfH = outH /2;
inthalfW = outW /2;
while((halfH / inSampleSize) > height && (halfW / inSampleSize) > width) {
inSampleSize *=2;
}
}
options.inSampleSize= inSampleSize;
options.inJustDecodeBounds=false;
intheightRatio = (int) Math.ceil(options.outHeight/ (float) height);
intwidthRatio = (int) Math.ceil(options.outWidth/ (float) width);
if(heightRatio >1|| widthRatio >1) {
if(heightRatio > widthRatio) {
options.inSampleSize= heightRatio;
}else{
options.inSampleSize= widthRatio;
}
}
options.inJustDecodeBounds=false;
returnBitmapFactory.decodeFile(imagePath,options);
}
/**
* obtain the image rotation angle
*
*@parampathpath of target image
*/
private intgetImageSpinAngle(String path) {
intdegree =0;
try{
ExifInterface exifInterface =newExifInterface(path);
intorientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL);
switch(orientation) {
caseExifInterface.ORIENTATION_ROTATE_90:
degree =90;
break;
caseExifInterface.ORIENTATION_ROTATE_180:
degree =180;
break;
caseExifInterface.ORIENTATION_ROTATE_270:
degree =270;
break;
}
}catch(IOException e) {
e.printStackTrace();
}
returndegree;
}
/**
* 指定参数压缩图片
* create the thumbnail with the true rotate angle
*
*@paramlargeImagePaththe big image path
*@paramthumbFilePaththe thumbnail path
*@paramwidthwidth of thumbnail
*@paramheightheight of thumbnail
*@paramanglerotation angle of thumbnail
*@paramsizethe file size of image
*/
privateFilecompress(String largeImagePath,String thumbFilePath, intwidth, intheight, intangle, longsize) {
Bitmap thbBitmap = compress(largeImagePath,width,height);
thbBitmap =rotatingImage(angle,thbBitmap);
returnsaveImage(thumbFilePath,thbBitmap,size);
}
/**
* 旋转图片
* rotate the image with specified angle
*
*@paramanglethe angle will be rotating 旋转的角度
*@parambitmaptarget image 目标图片
*/
private staticBitmaprotatingImage(intangle,Bitmap bitmap) {
//rotate image
Matrix matrix =newMatrix();
matrix.postRotate(angle);
//create a new image
returnBitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix, true);
}
/**
* 保存图片到指定路径
* Save image with specified size
*
*@paramfilePaththe image file save path 储存路径
*@parambitmapthe image what be save 目标图片
*@paramsizethe file size of image 期望大小
*/
privateFilesaveImage(String filePath,Bitmap bitmap, longsize) {
checkNotNull(bitmap,TAG+"bitmap cannot be null");
File result =newFile(filePath.substring(0,filePath.lastIndexOf("/")));
if(!result.exists() && !result.mkdirs())return null;
ByteArrayOutputStream stream =newByteArrayOutputStream();
intoptions =100;
bitmap.compress(Bitmap.CompressFormat.JPEG,options,stream);
while(stream.toByteArray().length/1024> size && options >6) {
stream.reset();
options -=6;
bitmap.compress(Bitmap.CompressFormat.JPEG,options,stream);
}
bitmap.recycle();
try{
FileOutputStream fos =newFileOutputStream(filePath);
fos.write(stream.toByteArray());
fos.flush();
fos.close();
stream.close();
}catch(IOException e) {
e.printStackTrace();
}
return newFile(filePath);
}
}