Luban 2

repository·master·Indexed 12 days ago

https://github.com/curzibn/luban

A high-efficiency Android image compression library that mimics WeChat's compression strategies. It uses an Adaptive Unified Image Compression algorithm to balance visual quality and file size based on image resolution and characteristics. Luban 2 provides a Kotlin DSL, extension functions, and a Java-compatible Builder API via LubanCompat, supporting single and batch compression of Files and Uris with lifecycle binding to prevent memory leaks.

Tokens
5.2K
Snippets
15
Records
18
Agent score
95%

What's inside Luban

  1. How Luban 2's compression algorithm works

    master

    Luban 2 uses an Adaptive Unified Image Compression algorithm that mimics WeChat Moments' behavior. It adjusts strategies based on image characteristics:

    Resolution Decisions

    • 1440p Baseline: Uses 1440px as the default short-side baseline.
    • Panorama Strategy: For ultra-wide panoramas (long side > 10800px), it locks the long side to 1440px.
    • Mega-Pixel Protection: Images > 41MP receive 1/4 downsampling. Ultra-long screenshots are capped at 10.24MP to prevent OOM errors.

    Bitrate Control

    • < 0.5MP: Minimal lossy compression.
    • 0.5-1MP: Enhanced encoding quality.
    • 1-3MP: Balanced coefficients.
    • > 3MP: High compression ratios.

    Robustness

    • Inflation Fallback: If the compressed file is larger than the original, the original is returned.
    • Format Handling: Preserves transparency for small PNGs; converts large PNGs to JPEG.
    • Input Defense: Safely handles extreme resolutions (0, negative, 1px).
  2. How Luban 2's Adaptive Unified Image Compression works

    master

    Luban 2 uses an Adaptive Unified Image Compression algorithm that dynamically applies different strategies based on the input image's resolution and characteristics to balance quality and file size.

    Resolution Decision Logic

    • 1440p Baseline: Uses 1440px as the short-side baseline for modern screens.
    • Panorama Strategy: For ultra-wide panoramas (long side > 10800px), it locks the long side to 1440px.
    • High-Pixel Protection: Images exceeding 40 million pixels undergo 1/4 downsampling.
    • Long Image Protection: For extremely long screenshots, it enforces a 10.24MP pixel limit via proportional scaling to prevent OOM.

    Bitrate Control

    • Small images (<0.5MP): Minimal lossy compression to prevent artifacts.
    • High-frequency images (0.5-1MP): Higher encoding quality to compensate for resolution loss.
    • Standard images (1-3MP): Balanced coefficient targeting social media standards.
    • Large/Long images (>3MP): High compression rate to significantly reduce size.

    Robustness Features

    • Expansion Fallback: If the compressed file is larger than the original, the original is returned instead.
    • Smart Format Passthrough: Retains transparency for small PNGs; converts large PNGs to JPEG automatically.
    • Input Defense: Handles extreme resolutions (0, negative, 1px) to prevent crashes.
  3. Install Luban 2 via Maven Central

    master

    To use Luban 2 in your Android project, ensure mavenCentral() is included in your repositories block. Then, add the dependency to your module's build.gradle or build.gradle.kts file.

    Note: Check Maven Central for the latest version number.

    // Kotlin DSL (build.gradle.kts)
    dependencies {
        implementation("top.zibin:luban:2.0.1")
    }
    // Groovy (build.gradle)
    dependencies {
        implementation 'top.zibin:luban:2.0.1'
    }
  4. Install Luban 2 via Gradle

    master

    To use Luban 2 in your Android project, ensure mavenCentral() is included in your repositories, then add the following dependency to your module's build.gradle.kts file:

    dependencies {
        implementation("top.zibin:luban:2.0.1")
    }
  5. Use the Luban DSL for batch image compression

    master

    For complex batching requirements, use the luban DSL. This allows you to queue multiple single-file compression tasks and multiple batch-file compression tasks into a single execution block.

    1. Call luban(context) { ... } or luban { ... }.
    2. Inside the block, use compress(input) for single files/URIs or compress(inputs) for lists.
    3. Set the outputDir once for the entire block.
    4. The execute() function (called automatically by the luban helper) runs all tasks and returns a List<Result<File>>.

    Note: If using luban { ... } without a context, you must provide a Uri via a context or ensure all inputs are File objects.

    val results = luban(context) {
        outputDir = File(context.cacheDir, "compressed")
        
        // Add single files
        compress(file1)
        compress(uri1)
        
        // Add batches
        compress(listOf(file2, file3))
        compress(listOf(uri2, uri3))
    }
    
    // results is List<Result<File>>
  6. Manage compression lifecycle with bindLifecycle

    master

    To prevent memory leaks or unnecessary processing when an Android component (like an Activity or Fragment) is destroyed, use bindLifecycle(lifecycleOwner: LifecycleOwner) in your LubanCompat.Builder.

    When the provided LifecycleOwner reaches the ON_DESTROY state, the compression task is automatically cancelled.

    LubanCompat.with(context)
        .load(myFile)
        .bindLifecycle(this) // 'this' being an Activity or Fragment
        .launch()
  7. Compress images using Luban static methods (Kotlin)

    master

    If you prefer traditional static methods, use the Luban.compress family of functions. These return a Result<File> or List<Result<File>>.

    // Single Uri
    Luban.compress(context, inputUri, outputDir)
    
    // Single File
    Luban.compress(inputFile, outputDir)
    
    // Compress to a specific file
    Luban.compressToFile(inputFile, outputFile)
    
    // Multiple inputs
    Luban.compress(context, inputUris, outputDir)
    Luban.compress(inputFiles, outputDir)
  8. Compress images using Kotlin DSL (Recommended)

    master

    The most idiomatic way to use Luban 2 in Kotlin is via the DSL API. This allows you to declaratively configure the outputDir and queue multiple compression tasks (supporting Uri, File, or lists of both).

    Important Notes:

    • If compressing Uri objects, outputDir defaults to context.cacheDir if not specified.
    • If compressing File objects, outputDir must be explicitly set.
    • The order of configuration does not matter; you can set outputDir before or after calling compress().
    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}")
            }
        }
    }
  9. Compress images using Kotlin Extension Functions

    master

    For a more fluent API, Luban 2 provides extension functions for single or multiple files/URIs.

    // Compress a single Uri to context.cacheDir
    val result = imageUri.compressTo(context)
    
    // Compress a single File to a specific directory
    val result = inputFile.compressTo(outputDir)
    
    // Compress a single File to a specific output file
    val result = inputFile.compressToFile(outputFile)
    
    // Compress multiple files
    val results = fileList.compressTo(outputDir)
    val results = uriList.compressTo(context)
  10. Use Luban 2 with Kotlin Coroutines (DSL Style)

    master

    The recommended way to use Luban 2 in Kotlin is via the DSL API. This allows for declarative configuration and supports multiple inputs (Uris or Files) in a single block.

    Key Rules:

    • If compressing Uri types and no outputDir is specified, it defaults to context.cacheDir.
    • If compressing File types, you must explicitly set outputDir.
    • Configuration is declarative; the order of outputDir and compress() calls does not matter.
    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", "压缩成功: ${file.absolutePath}")
            } ?: run {
                val error = result.exceptionOrNull()
                Log.e("Luban", "压缩失败: ${error?.message}")
            }
        }
    }
  11. Compress images using Java (Builder Pattern)

    master

    For Java projects, use the Luban.with(context) builder pattern. You can load a single File, Uri, or a String path, and optionally bind the operation to a LifecycleOwner to automatically cancel compression when the lifecycle is destroyed.

    // Single File
    Luban.with(context)
        .load(imageFile) // Can be File, Uri, or String path
        .setTargetDir(context.getCacheDir())
        .bindLifecycle(lifecycleOwner) // Optional
        .setCompressListener(new OnCompressListener() {
            @Override
            public void onStart() {}
    
            @Override
            public void onSuccess(File file) {}
    
            @Override
            public void onError(Throwable e) {}
        })
        .launch();
    
    // Multiple Files
    Luban.with(context)
        .load(imagePaths) // List of Strings
        .setTargetDir(context.getCacheDir())
        .setCompressListener(new OnCompressListener() {
            @Override
            public void onStart() {}
    
            @Override
            public void onSuccess(File file) {
                // Called for EACH successfully compressed image
            }
    
            @Override
            public void onError(Throwable e) {}
        })
        .launch();
  12. Use Luban 2 with Java (Builder Pattern)

    master

    For Java projects, use the Luban.with(context) builder API. You can load single files, Uris, or paths (as Strings) and provide an OnCompressListener to handle lifecycle events.

    Features:

    • .bindLifecycle(lifecycleOwner): Automatically cancels compression when the lifecycle is destroyed.
    • .setCompressListener(...): Provides callbacks for onStart, onSuccess, and onError.
    // Single image compression
    Luban.with(context)
        .load(imageFile) // Supports File, Uri, or String path
        .setTargetDir(context.getCacheDir())
        .bindLifecycle(lifecycleOwner) // Optional
        .setCompressListener(new OnCompressListener() {
            @Override
            public void onStart() {}
    
            @Override
            public void onSuccess(File file) {}
    
            @Override
            public void onError(Throwable e) {}
        })
        .launch();
    
    // Multiple image compression
    List<String> imagePaths = ...;
    Luban.with(context)
        .load(imagePaths)
        .setTargetDir(context.getCacheDir())
        .setCompressListener(new OnCompressListener() {
            @Override
            public void onStart() {}
    
            @Override
            public void onSuccess(File file) {
                // Called once for each successfully compressed image
                Log.d("Luban", "Compressed: " + file.getAbsolutePath());
            }
    
            @Override
            public void onError(Throwable e) {}
        })
        .launch();