# Curzibn/Luban: Android image compression that imitates WeChat Moments

> Luban 2 is a Kotlin rewrite of an Android image compression library whose sizing and bitrate rules were reverse engineered from WeChat Moments. It is small, opinionated, and only useful if you are compressing photos on Android.

**Curzibn/Luban** — Luban 2（鲁班 2） —— 高效简洁的 Android 图片压缩工具库，像素级还原微信朋友圈压缩策略。(An efficient and concise Android image compression library that closely replicates the compression strategy of WeChat Moments.)

- Repository: https://github.com/Curzibn/Luban
- Stars: 13,768 · Forks: 2,256
- Language: C
- License: Apache-2.0
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/curzibn-luban

## What Luban 2 is actually for

The README opens with the problem it was built around: phone cameras now produce very large images, and deciding how far to crop or compress is hard to get right. Crop too aggressively and the picture looks small; compress too aggressively and it looks bad. Luban's answer is to stop guessing and copy a reference point. The author sent roughly 100 photos of different resolutions through WeChat Moments, compared the originals with WeChat's output, and reverse engineered the compression rule from the differences. The README is explicit that this is reverse engineering, so the result is close to WeChat Moments rather than identical to it.

That framing tells you who the library is for. It is for Android developers who upload user photos and want a defensible default instead of a hand-tuned quality number. It is not a general image processing toolkit. There is no crop API, no rotation API, no watermarking, no format conversion to WebP or AVIF. The input is a File or a Uri, the output is a File, and the interesting decisions happen inside the library.

## How the adaptive compression algorithm decides

The README calls the approach Adaptive Unified Image Compression and describes it as a set of decisions driven by the resolution of the source image. The first is a 1440px short-side baseline, chosen so the output still looks sharp on 2K and 4K screens. Around that baseline sit several special cases.

Panoramas with a long edge above 10800px are handled differently: the long edge is locked to 1440px so the full field of view survives. Images above roughly 41 megapixels are downsampled by a factor of four. Very long screenshots get a 10.24MP pixel ceiling and are scaled proportionally, which the README frames as out-of-memory protection rather than a quality choice.

Bitrate control is a second axis, applied on top of the resolution decision. Images under 0.5MP are barely touched, to avoid visible compression artifacts. Images between 0.5MP and 1MP get a higher encoding quality to compensate for the resolution loss. Standard images between 1MP and 3MP use a balanced coefficient aimed at matching mainstream social apps. Anything above 3MP is compressed harder.

The robustness rules are the part worth reading closely. If compression produces a file larger than the input, Luban passes the original through, so the README can claim it never makes a file bigger. Small PNGs keep their alpha channel; large PNGs are transcoded to JPEG, which means transparency is lost on those. Inputs with degenerate dimensions such as 0, negative values or 1px are handled rather than crashing.

The comparison table in the README is the clearest illustration of where this lands. A 3024x4032, 5.10MB photo comes out at 1440x1920 and 305KB against WeChat's 303KB. A 1440x3200 screenshot at 2.10MB comes out at 148KB, noticeably smaller than WeChat's 256KB. A 1242x22080 long record at 6.10MB comes out at 758x13490 and 290KB, close to WeChat's 744x13129 and 256KB. Those are the project's own numbers, not an independent benchmark.

## Installing Luban 2 and compressing your first image

Luban 2 is published to Maven Central under the group top.zibin. The README asks you to confirm the repository is available in your build, then add the dependency. The version string in the README is 2.0.2, and the README itself notes that you should check Maven Central for the latest version number.

```kotlin
repositories {
    mavenCentral()
}

dependencies {
    implementation("top.zibin:luban:2.0.2")
}
```

The Groovy form is the same coordinate written the other way, for projects still on build.gradle rather than build.gradle.kts.

```groovy
dependencies {
    implementation 'top.zibin:luban:2.0.2'
}
```

The README states API 21+ is required. For the first real call, the recommended Kotlin path is the DSL API, which takes a list of inputs and returns one Result per input. The outputDir is where compressed files are written. For Uri inputs the README says outputDir defaults to context.cacheDir; for File inputs you must set outputDir explicitly.

```kotlin
lifecycleScope.launch {
    val results = luban(context) {
        outputDir = File(context.cacheDir, "compressed")
        compress(imageUri1)
        compress(imageFile1)
    }

    results.forEach { result ->
        result.getOrNull()?.let { file ->
            Log.d("Luban", "压缩成功: ${file.absolutePath}")
        } ?: Log.e("Luban", "压缩失败: ${result.exceptionOrNull()?.message}")
    }
}
```

What you should see is one entry in results per input, each either a compressed File or an exception. The README notes the DSL is declarative, so calling compress() before setting outputDir produces the same result as the reverse order. If you prefer a single-image call, the extension function form is shorter: imageUri.compressTo(context) returns a Result<File>, and inputFile.compressTo(outputDir) does the same for files. Java projects use Luban.with(context).load(...).setTargetDir(...).launch() with an OnCompressListener that fires onStart, onSuccess and onError, and the README mentions an optional bindLifecycle(lifecycleOwner) that cancels work when the page is destroyed.

## Where Luban 2 is the wrong choice

The largest limitation is scope. This is an Android library. There is no server-side component, no CLI, no desktop build. If your pipeline compresses images on a backend after upload, Luban does not help, and porting the heuristics means reimplementing them from the README's prose rather than calling a shared library.

The second limitation is format. The README describes PNG handling as pass-through for small files and transcoding to JPEG for large ones. There is no mention of WebP, AVIF or HEIC output, and no quality parameter exposed to callers. You get the library's judgement, not a dial. If your product needs a specific output format or a user-facing quality slider, this is the wrong tool.

The third is that the algorithm is a reverse-engineered approximation of one specific consumer app. WeChat Moments is the target, and the README says the result is close but not identical. If your reference point is a different service, or a CDN with its own transcoding, matching WeChat's output is not obviously the right goal. The README's own table shows the library sometimes compresses harder than WeChat (the 2K screenshot case) and sometimes less (the 2K screenshot is 148KB against WeChat's 256KB, while the design original is 263KB against 279KB). It is a heuristic tuned toward a target, not a correctness guarantee.

Finally, the README does not document rollback, cancellation semantics beyond bindLifecycle, or what happens when the output directory is not writable. Those are questions to answer from the source before shipping.

## Luban 2 against plain Bitmap.compress and AndroidX

The obvious alternative is doing nothing: call BitmapFactory to decode, scale with Bitmap.createScaledBitmap, and write out with Bitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream). That path is fully under your control and has no dependency. The difference is that you own every decision. You pick the target resolution, you pick the quality number, and you handle the case where the source is a 12000x5000 panorama or a 1242x22080 long screenshot. Luban's value is precisely that it has opinions about those cases, including the 10800px panorama threshold, the 41MP downsample and the 10.24MP long-image ceiling, plus the pass-through rule when compression makes the file bigger.

A second alternative is AndroidX's own image APIs, which cover decoding and scaling but do not ship a compression policy. You would still be writing the resolution and bitrate logic yourself. The trade-off is the same: fewer dependencies and full control, against reimplementing heuristics that someone else already tuned and documented.

A third option worth naming is to compress on the server after upload. That removes the client-side CPU cost and lets you change policy without an app release, but it means uploading the original, which is exactly the bandwidth and storage cost Luban exists to avoid on the client side.

## Maintenance, releases and the Apache-2.0 licence

The repository is not archived and the last push was on 2026-09-20, so the codebase is being touched recently. That is a different signal from the release history. The most recent release listed is turbo-1.0.0 from 2018-07-23, followed by 1.1.8 on 2018-07-17 and 1.1.7 on 2018-06-22. There is a wide gap between the last tagged release and the last commit, which matters if your team pins versions by release rather than by commit. It also means the 2.0.2 version string in the README may not correspond to a tag in the releases list, so verify the artifact on Maven Central before depending on it.

The licence is Apache-2.0, and the README carries a copyright line for 2025. Apache-2.0 permits commercial use and modification and includes a patent grant. It also requires that you retain the licence and notice files and state significant changes. The repository has both a README.md and a README_EN.md, so the documentation is maintained in two languages, which is a real cost for a small project. Upgrade cost is low in the sense that the public surface is small (a handful of static methods, extension functions and a DSL block), but the compression output is the product, so a version bump can change file sizes even when the API does not move.

## Conclusion

Adopt Luban 2 if you ship an Android app that uploads user photos and you want a single call that returns a compressed File with sane resolution and bitrate rules already applied. Do not adopt it if you need PNG or WebP output, a server-side or command-line compressor, or any image work outside Android. Before wiring it into a release build, confirm the current top.zibin:luban version on Maven Central rather than trusting the 2.0.2 shown in the README, and check the README_EN.md translation against README.md if your team reads English, because the English file is not the one the maintainer edits first.

## FAQ

### How do I install Luban 2 in an Android project?

Add mavenCentral() to your repositories, then add the dependency top.zibin:luban:2.0.2 in either Kotlin DSL or Groovy form. The README notes you should check Maven Central for the latest version number rather than assuming 2.0.2 is current. API 21+ is required.

### How do I use Luban to compress an image?

The README's recommended Kotlin path is the DSL API: call luban(context) inside a coroutine, set outputDir, call compress() with a Uri or File, and read the returned Result list. For a single image, imageUri.compressTo(context) or inputFile.compressTo(outputDir) returns a Result<File>. Java projects use Luban.with(context).load(...).setTargetDir(...).launch() with an OnCompressListener.

### What is Luban 2?

It is an Android image compression library, described in the README as a Kotlin rewrite of the original Luban that uses Kotlin Coroutines and TurboJPEG. Its compression strategy was reverse engineered by comparing roughly 100 photos sent through WeChat Moments against the originals. It is licensed Apache-2.0.

## Sources

- [Curzibn/Luban on GitHub](https://github.com/Curzibn/Luban)
- [Issues](https://github.com/Curzibn/Luban/issues)
- [License: Apache-2.0](https://github.com/Curzibn/Luban/blob/master/LICENSE)
- [README](https://github.com/Curzibn/Luban/blob/master/README.md)
- [Releases](https://github.com/Curzibn/Luban/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/curzibn-luban
