Luban 鲁班 图片压缩

介绍

Luban(鲁班)—Image compression with efficiency very close to WeChat Moments/可能是最接近微信朋友圈的图片压缩算法
implementation 'top.zibin:Luban:1.1.8'
1
1
 
1
implementation 'top.zibin:Luban:1.1.8'

目前做App开发总绕不开图片这个元素。但是随着手机拍照分辨率的提升,图片的压缩成为一个很重要的问题。单纯对图片进行裁切,压缩已经有很多文章介绍。但是裁切成多少,压缩成多少却很难控制好,裁切过头图片太小,质量压缩过头则显示效果太差。
于是自然想到App巨头“微信”会是怎么处理,Luban就是通过在微信朋友圈发送近100张不同分辨率图片,对比原图与微信压缩后的图片逆向推算出来的压缩算法。
因为有其他语言也想要实现Luban,所以描述了一遍 算法步骤
因为是逆向推算,效果还没法跟微信一模一样,但是已经很接近微信朋友圈压缩后的效果,具体看以下对比!

内容 原图 Luban Wechat
截屏 720P 720*1280,390k 720*1280,87k 720*1280,56k
截屏 1080P 1080*1920,2.21M 1080*1920,104k 1080*1920,112k
拍照 13M(4:3) 3096*4128,3.12M 1548*2064,141k 1548*2064,147k
拍照 9.6M(16:9) 4128*2322,4.64M 1032*581,97k 1032*581,74k
滚动截屏 1080*6433,1.56M 1080*6433,351k 1080*6433,482k

使用方式

方法列表
  • load    传入原图
  • filter    设置不压缩的条件
  • ignoreBy    设置不压缩的阈值,当原始图片大小小于此值时不压缩,单位为K,默认为 100K
  • setFocusAlpha    设置是否保留透明通道,设为 false 速度更快,但是可能有一个黑色的背景,默认为true
  • setTargetDir    设置压缩后保存压缩图片的路径,不指定时存放在【/storage/emulated/0/Android/data/包名/cache/luban_disk_cache/保存文件的时间戳.**】
  • setCompressListener    压缩回调接口,实际发现,只有异步调用时才会回调,同步调用时设置此回调无意义
  • setRenameListener    压缩前重命名接口,注意,参数 filePath 表示传入文件路径,返回值表示用来重命名后的文件名,使用时要留意这两者的区别

load 可以穿入的值
  • String:路径
  • File:文件
  • Uri:统一资源识别符
  • InputStreamProvider:通过此接口获取输入流,以兼容文件、FileProvider方式获取到的图片
  • List:集合,支持上面String、File、Uri这三种类型的集合,如果集合中某一文件不存在并不影响其他文件的压缩

注意:对于 get 返回值,如果图片被压缩了,返回的是新生成的压缩图片的路径,否则返回的是原始图片的路径(文件不存在情况下也是如此)。

案例

public class LubanActivity extends ListActivity {
	private static final String PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/pics/";
	private static final String SAVE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/pics/tem/";
	private List<String> paths = Arrays.asList(PATH + "pic.jpg", PATH + "icon.jpg", PATH + "icon.png", PATH + "flower.jpg");
	
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		String[] array = {
				"异步调用",
				"同步调用:批处理",
				"同步调用:只处理一个",
				"和 rxJava 一起使用",
				"常用 API 的使用演示",
		};
		setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
		
		File filePath = new File(SAVE_PATH);
		if (!filePath.exists()) {
			filePath.mkdirs();
		}
	}
	
	@Override
	protected void onListItemClick(ListView l, View v, int position, long id) {
		switch (position) {
			case 0:
				Luban.with(this)
						.load(paths) //传入原图
						.setCompressListener(getListener())
						.launch();
				break;
			case 1:
				try {
					List<File> fileList = Luban.with(this).load(paths).get();
					for (File file : fileList) {
						Log.i("bqt", file.length() / 1024 + "," + file.getAbsolutePath());
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
				break;
			case 2:
				try {
					String path = paths.get(0);
					File file1 = Luban.with(this).load(paths).get(path);//压缩了很多个,但只取指定其中一个压缩后的结果
					File file2 = Luban.with(this).load(path).get(path);//压缩了一个,取压缩后的结果
					Log.i("bqt", file1.length() / 1024 + "," + file1.getAbsolutePath());
					Log.i("bqt", file2.length() / 1024 + "," + file2.getAbsolutePath());
				} catch (IOException e) {
					e.printStackTrace();
				}
				break;
			case 3:
				Flowable.just(paths)
						.observeOn(Schedulers.io()) //在子线程压缩
						.map(paths -> Luban.with(this).load(paths).get())//直接返回压缩后的文件
						.flatMap((Function<List<File>, Flowable<File>>) Flowable::fromIterable) //扁平化
						.subscribeOn(AndroidSchedulers.mainThread()) //回到主线程
						.subscribe(file -> Log.i("bqt", file.length() / 1024 + "," + file.getAbsolutePath()));
				break;
			case 4:
				try {
					List<File> fileList = Luban.with(this)
							.load(paths)
							.ignoreBy(1)//设置不压缩的阈值,当原始图片大小小于此值时不压缩,单位为K,默认为 100K
							.setFocusAlpha(true) //设置是否保留透明通道,设为 false 速度更快,但是可能有一个黑色的背景,默认为true
							.setTargetDir(SAVE_PATH) //设置压缩后保存压缩图片的路径
							.filter(path -> !(TextUtils.isEmpty(path) || path.endsWith("flower.jpg"))) //设置不压缩的条件
							.setCompressListener(getListener())
							.get();
					for (File file : fileList) {
						//如果图片被压缩了,返回的是新生成的压缩图片的路径,否则返回的是原始图片的路径
						Log.i("bqt", file.length() / 1024 + "," + file.getAbsolutePath());
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
				break;
		}
	}
	
	@NonNull
	private OnCompressListener getListener() {
		return new OnCompressListener() { //内部采用IO线程进行图片压缩,外部调用只需设置好结果监听即可
			@Override
			public void onStart() {
				Log.i("bqt", "【onStart】" + isMainThread());//true
			}
			
			@Override
			public void onSuccess(File file) {
				Log.i("bqt", "【onSuccess】" + isMainThread() + "," + file.length() / 1024 + "," + file.getAbsolutePath());//true
			}
			
			@Override
			public void onError(Throwable e) {
				e.printStackTrace();
				Log.i("bqt", "【onError】" + e.getMessage());
			}
		};
	}
	
	private boolean isMainThread() {
		return Looper.myLooper() == Looper.getMainLooper();
	}
}
106
 
1
public class LubanActivity extends ListActivity {
2
    private static final String PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/pics/";
3
    private static final String SAVE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/pics/tem/";
4
    private List<String> paths = Arrays.asList(PATH + "pic.jpg", PATH + "icon.jpg", PATH + "icon.png", PATH + "flower.jpg");
5
    
6
    protected void onCreate(Bundle savedInstanceState) {
7
        super.onCreate(savedInstanceState);
8
        String[] array = {
9
                "异步调用",
10
                "同步调用:批处理",
11
                "同步调用:只处理一个",
12
                "和 rxJava 一起使用",
13
                "常用 API 的使用演示",
14
        };
15
        setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
16
        
17
        File filePath = new File(SAVE_PATH);
18
        if (!filePath.exists()) {
19
            filePath.mkdirs();
20
        }
21
    }
22
    
23
    @Override
24
    protected void onListItemClick(ListView l, View v, int position, long id) {
25
        switch (position) {
26
            case 0:
27
                Luban.with(this)
28
                        .load(paths) //传入原图
29
                        .setCompressListener(getListener())
30
                        .launch();
31
                break;
32
            case 1:
33
                try {
34
                    List<File> fileList = Luban.with(this).load(paths).get();
35
                    for (File file : fileList) {
36
                        Log.i("bqt", file.length() / 1024 + "," + file.getAbsolutePath());
37
                    }
38
                } catch (IOException e) {
39
                    e.printStackTrace();
40
                }
41
                break;
42
            case 2:
43
                try {
44
                    String path = paths.get(0);
45
                    File file1 = Luban.with(this).load(paths).get(path);//压缩了很多个,但只取指定其中一个压缩后的结果
46
                    File file2 = Luban.with(this).load(path).get(path);//压缩了一个,取压缩后的结果
47
                    Log.i("bqt", file1.length() / 1024 + "," + file1.getAbsolutePath());
48
                    Log.i("bqt", file2.length() / 1024 + "," + file2.getAbsolutePath());
49
                } catch (IOException e) {
50
                    e.printStackTrace();
51
                }
52
                break;
53
            case 3:
54
                Flowable.just(paths)
55
                        .observeOn(Schedulers.io()) //在子线程压缩
56
                        .map(paths -> Luban.with(this).load(paths).get())//直接返回压缩后的文件
57
                        .flatMap((Function<List<File>, Flowable<File>>) Flowable::fromIterable) //扁平化
58
                        .subscribeOn(AndroidSchedulers.mainThread()) //回到主线程
59
                        .subscribe(file -> Log.i("bqt", file.length() / 1024 + "," + file.getAbsolutePath()));
60
                break;
61
            case 4:
62
                try {
63
                    List<File> fileList = Luban.with(this)
64
                            .load(paths)
65
                            .ignoreBy(1)//设置不压缩的阈值,当原始图片大小小于此值时不压缩,单位为K,默认为 100K
66
                            .setFocusAlpha(true) //设置是否保留透明通道,设为 false 速度更快,但是可能有一个黑色的背景,默认为true
67
                            .setTargetDir(SAVE_PATH) //设置压缩后保存压缩图片的路径
68
                            .filter(path -> !(TextUtils.isEmpty(path) || path.endsWith("flower.jpg"))) //设置不压缩的条件
69
                            .setCompressListener(getListener())
70
                            .get();
71
                    for (File file : fileList) {
72
                        //如果图片被压缩了,返回的是新生成的压缩图片的路径,否则返回的是原始图片的路径
73
                        Log.i("bqt", file.length() / 1024 + "," + file.getAbsolutePath());
74
                    }
75
                } catch (IOException e) {
76
                    e.printStackTrace();
77
                }
78
                break;
79
        }
80
    }
81
    
82
    @NonNull
83
    private OnCompressListener getListener() {
84
        return new OnCompressListener() { //内部采用IO线程进行图片压缩,外部调用只需设置好结果监听即可
85
            @Override
86
            public void onStart() {
87
                Log.i("bqt", "【onStart】" + isMainThread());//true
88
            }
89
            
90
            @Override
91
            public void onSuccess(File file) {
92
                Log.i("bqt", "【onSuccess】" + isMainThread() + "," + file.length() / 1024 + "," + file.getAbsolutePath());//true
93
            }
94
            
95
            @Override
96
            public void onError(Throwable e) {
97
                e.printStackTrace();
98
                Log.i("bqt", "【onError】" + e.getMessage());
99
            }
100
        };
101
    }
102
    
103
    private boolean isMainThread() {
104
        return Looper.myLooper() == Looper.getMainLooper();
105
    }
106
}

2018-8-28




猜你喜欢

转载自www.cnblogs.com/baiqiantao/p/9550744.html