Compressor is an open source image compression library on the Android platform. Using this library, you can easily compress local images. At the same time, the library also provides various compression parameters.

use

val compressedImageFile = Compressor.compress(context, actualImageFile) {
    resolution(1280.720)
    quality(80)
    format(Bitmap.CompressFormat.WEBP)
    size(2 _097_152) // 2 MB
}
Copy the code

Input:

  • An image file
  • Compression quality, as well as formatting type, maximum compression quality.

Output:

  • Compressed image file (this file is stored in the sandbox directory of the application by default)

The core

Let’s look at the core of the warehouse.

Compression implementation

The function of this library is to compress images through the compress method provided by Bitmap itself.

bitmap.compress(format, quality, fileOutputStream)
Copy the code

Combination of compression parameters

The compression operation is completed using the compress method entry of the singleton class Compressor. You need to specify the target compression file and compression parameters first.

And compression parameters control is through the fourth compressionPatch control, it has a default implementation DefaultConstraint. The default (), so if you do not specify other Settings, the default Settings will take effect.

In addition, when you set custom Compression parameters, these parameters are stored in the Compression Constraints set, which is an abstract interface set of image Compression parameters. Then you iterate over the parameters set and invoke different Compression parameter implementations, as shown below. Here is a chained invocation effect:

compression.constraints.forEach { constraint ->
    // Whether the policy meets the condition
    while (constraint.isSatisfied(result).not()) {
      	// If not, process it
        result = constraint.satisfy(result)
    }
}
Copy the code

In this way, the result of each compression parameter is used as the input of the next compression parameter, thus achieving the effect of a chain call, step by step, so that all parameter Settings take effect in a single image source file.

Compression parameter interface

The Constraint interface is the core of the library and is a clever design.

Generally speaking, for image compression, we can according to the process oriented thought, only need to define a method, then the method in the quality of image compression, compression format, processing according to the location of the output, the final is compressed, such code logic will be concentrated in a such a design on subsequent code maintenance is not good, but do not have modularity, It’s all one big chunk, and it doesn’t look very elegant.

The library elegantly solves this problem through the Constraint interface.

Different compression parameters, to implement their own compression scheme, this interface provides two methods:

 interface Constraint {
    fun isSatisfied(imageFile: File): Boolean
    fun satisfy(imageFile: File): File
}
Copy the code

The first method is isSatisfied, which determines whether the current image file has satisfied the parameter setting criteria. If so, the satisfy method is not satisfied, otherwise the satisfy method is executed, which performs the specific compression setting.

For example, the implementation of FormatConstraint, which is the implementation class that specifies the compression format, is processed if the current image is already in the specified format, and not otherwise.

class FormatConstraint(private val format: Bitmap.CompressFormat) : Constraint {
    override fun isSatisfied(imageFile: File): Boolean {
        return format == imageFile.compressFormat()
    }

    override fun satisfy(imageFile: File): File {
        return overWrite(imageFile, loadBitmap(imageFile), format)
    }
}
Copy the code

In this case, if the current image format is detected not to be the specified format, it will execute satisfy method, and specific compression will be performed within SATISFY method. Throughout the implementation of several other parameter strategies, most of them use overWriter to set specific image parameters.

OverWrite the implementation of the

  • Check whether the image format is consistent with the specified format, otherwise change the image name suffix
  • Delete temporary local image files
  • The Bitmap is compressed, processed, saved to a new temporary file, and returned with the new parameters

The code looks like this:

fun overWrite(imageFile: File, bitmap: Bitmap, format: Bitmap.CompressFormat = imageFile.compressFormat(), quality: Int = 100): File {
    val result = if (format == imageFile.compressFormat()) {
        imageFile
    } else {
        File("${imageFile.absolutePath.substringBeforeLast(".")}.${format.extension()}")
    }
    imageFile.delete()
    saveBitmap(bitmap, result, format, quality)
    return result
}
Copy the code

The saveBitmap method is used to compress Bitmap.

Break up

  • The abstract interface for Constraint compression parameter Settings that must be implemented by each compression policy
  • Compressor’s compression front class, the entry class, provides only a method for the caller to set different compression options and start compression.
    • Compression is a collection used to hold different constraints
  • Implementation classes for different compression strategies
    • DefaultConstraint Implementation of the default compression parameter
    • DestinationConstraint specifies the file location of the compressed file output
    • FormatConstraint specifies the compression format for the final output of the file
    • QualityConstraint specifies the compression quality
    • ResolutionConstraint Specifies the image width and height value
    • SizeConstraint Specifies the final compression size of the image

details

  • The compression quality setting is not valid for PNG images.

This is due to Bitmap’s compression limitations. The COMPRESS method, even with compression quality set, does not work with PNG.

From Bitmap#compress parameter description

  • How to implement specified size compression #SizeConstraint

Set maximum quality files, if the current file size is greater than the maximum quality, continues to be compressed, concrete by setting the sampling rate is compressed images, and set the minimum sampling rate is 10, and set up a number of compression, if more than the specified number of compression, haven’t reached the size, are no longer compressed, the skill quality even if the images have not reached the target.

insufficient

As you can see from the implementation of the overWrite method above, each compression parameter takes effect with the deletion of the previous cache file and the generation of the next temporary file, which may result in high I/O overhead.

But this is a kind of game, such benefits, is to put the different compression parameters to realize class split into different modules, to make the code structure clear, and in my development plump cloud (a mobile bed), found no IO overhead cause what problem, so, compared to this design code from the concise and maintainability, Such IO overhead is negligible.

Splash DeepSource 2020/12/03