Android 图片压缩 Luban 的 RxJava2.0 版本

前言

在一次偶然的情况下,在简书上看到 一句代码搞定 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+的方式重写一遍就好了

原Luban.java 使用的 Rxjava1.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);

}

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容