README_EN.md
Luban 2 β An efficient and concise Android image compression library that closely replicates the compression strategy of WeChat Moments.
Images are an essential part of app development. With the increasing resolution of mobile cameras, image compression has become a critical issue. While there are many articles on simple cropping and compression, choosing the right crop and compression levels is trickyβcropping too much results in tiny images, while over-compressing leads to poor display quality.
Naturally, one wonders how the industry giant "WeChat" handles this. Luban was derived by reverse-engineering WeChat Momentsβ behavior: we sent nearly 100 images with different resolutions and compared the originals with WeChatβs outputs to infer the compression strategy.
Since this behavior is inferred from observation, the results may not match WeChat exactly, but they are very close. See the comparison below!
This library is the Kotlin refactored version of Luban. While upgrading the core algorithm, it is optimized with Kotlin Coroutines and TurboJPEG for faster processing and better output quality. The new algorithm is more robust and efficient than the original, providing more efficient asynchronous processing and superior compression quality.
| Image Type | Original | Luban | |
|---|---|---|---|
| Standard Photo | 3024Γ4032, 5.10MB | 1440Γ1920, 305KB | 1440Γ1920, 303KB |
| High-Res Photo | 4000Γ6000, 12.10MB | 1440Γ2160, 318KB | 1440Γ2160, 305KB |
| 2K Screenshot | 1440Γ3200, 2.10MB | 1440Γ3200, 148KB | 1440Γ3200, 256KB |
| Long Screenshot | 1242Γ22080, 6.10MB | 758Γ13490, 290KB | 744Γ13129, 256KB |
| Panorama | 12000Γ5000, 8.10MB | 1440Γ600, 126KB | 1440Γ600, 123KB |
| Design Draft | 6000Γ6000, 6.90MB | 1440Γ1440, 263KB | 1440Γ1440, 279KB |
This library uses an Adaptive Unified Image Compression algorithm that dynamically applies differentiated strategies based on the original image's resolution characteristics to achieve optimal balance between quality and file size.
Make sure mavenCentral() is included in your repositories.
Add the dependency to your module's build.gradle.kts file:
dependencies {
implementation("top.zibin:luban:2.0.1")
}
Luban provides three ways to use the library in Kotlin, from most idiomatic to traditional:
The most Kotlin-idiomatic way is using the DSL API:
lifecycleScope.launch {
val results = luban(context) {
outputDir = File(context.cacheDir, "compressed")
compress(imageUri1)
compress(imageUri2)
compress(imageFile1)
compress(listOf(imageFile2, imageFile3))
compress(listOf(imageUri3, imageUri4))
}
results.forEach { result ->
result.getOrNull()?.let { file ->
Log.d("Luban", "Compressed: ${file.absolutePath}")
} ?: run {
val error = result.exceptionOrNull()
Log.e("Luban", "Error: ${error?.message}")
}
}
}
Note:
Uri compression, if outputDir is not specified, it defaults to context.cacheDir. For File compression, outputDir must be explicitly set.outputDir before calling compress(), or call compress() before setting outputDir.You can also use extension functions for a more fluent API:
lifecycleScope.launch {
val result = imageUri.compressTo(context)
result.getOrElse { error ->
Log.e("Luban", "Error: ${error.message}")
return@launch
}.let { file ->
Log.d("Luban", "Compressed: ${file.absolutePath}")
}
}
Compress a single file:
val result = inputFile.compressTo(outputDir)
Compress to a specific output file:
val result = inputFile.compressToFile(outputFile)
Compress multiple files:
val results = fileList.compressTo(outputDir)
val results = uriList.compressTo(context)
The traditional way using static methods:
lifecycleScope.launch {
val result = Luban.compress(context, inputUri, outputDir)
result.getOrElse { error ->
Log.e("Luban", "Error: ${error.message}")
return@launch
}.let { file ->
Log.d("Luban", "Compressed: ${file.absolutePath}")
}
}
Available methods:
Luban.compress(context, input: Uri, outputDir: File = context.cacheDir): Result<File>Luban.compress(input: File, outputDir: File): Result<File>Luban.compressToFile(input: File, output: File): Result<File>Luban.compress(context, inputs: List<Uri>, outputDir: File = context.cacheDir): List<Result<File>>Luban.compress(inputs: List<File>, outputDir: File): List<Result<File>>Use in a custom CoroutineScope:
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
scope.launch {
val result = Luban.compress(context, inputUri)
result.getOrElse { error ->
// Handle error
return@launch
}.let { file ->
// Handle success
}
}
For Java projects or if you prefer a callback-based approach, use the Luban.with() API which is compatible with the original library style.
Luban.with(context)
.load(imageFile) // Can be File, Uri, or String path
.setTargetDir(context.getCacheDir())
.bindLifecycle(lifecycleOwner) // Optional: Auto-cancel on destroy
.setCompressListener(new OnCompressListener() {
@Override
public void onStart() {
// Compression started
}
@Override
public void onSuccess(File file) {
// Compression finished successfully
}
@Override
public void onError(Throwable e) {
// An error occurred
}
})
.launch();
List<String> imagePaths = ...; // List of image paths
Luban.with(context)
.load(imagePaths) // Load a list of images
.setTargetDir(context.getCacheDir())
.setCompressListener(new OnCompressListener() {
@Override
public void onStart() {
// Compression started
}
@Override
public void onSuccess(File file) {
// Called for EACH successfully compressed image
Log.d("Luban", "Compressed: " + file.getAbsolutePath());
}
@Override
public void onError(Throwable e) {
// An error occurred
}
})
.launch();
If this project has been helpful to you, please consider supporting my work through the following methods. Your support is the motivation for me to continue improving and maintaining this project.
<div align="center"> <table> <tr> <td align="center"> </td> <td width="50"></td> <td align="center"> </td> </tr> </table> </div>Thank you for your support! π
Copyright 2025 Zibin
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.