preface

Image compression in Android technology has been a bad street, last week I looked at two open source libraries and compared my own project compression, found some new things, record with this.

Why compress

  • If your image is going to be uploaded, a few megabytes won’t do, and the image resolution is higher than the device resolution.
  • If an image is to be displayed on an Android device, the ImageView will eventually load a Bitmap object, so we need to consider the memory usage of a single Bitmap object. How to calculate the memory usage of an image? Bitmap memory size = image length x image width x number of bytes per pixel Determines the last parameter. Bitmap’ is usually encoded in two ways: ARGB_8888 and RGB_565, ARGB_8888 each pixel contains 4 bytes. RGB_565 contains 2 bytes. ARGB_8888 is generally used. The typical 1080 x 1920 image memory footprint is 1920 x 1080 x 4 = 7.9m

The compression principle

It can be concluded from the above that image compression should be done in two ways at the same time: first reduce the resolution, and then reduce the quality of each pixel (memory footprint).

Resolution compression

Let’s say I have an original picture of 3840×2400, and I want to compress it to 1920×1080, but it’s impossible to compress it 100%. Because image compression is required to maintain an aspect ratio, can an 800×100 landscape image be compressed into a 20×200 vertical image? It’s impossible. The common algorithm here is to keep short edges in the 1920×1080 range and then compress the whole graph proportionally:

Here the aspect ratio of the original image is 3840/2400 = 1.6, the aspect ratio of the target image is 1920/1080 = 1.78>1.6, and the shorter side is high. So it should be compressed in a high proportion. 2400/1080=2.22, so the real target value is: 1728×1080, the compression ratio is rounded to: 2, then compress with the following code:

  private Bitmap compressPixel(String filePath){
    Bitmap bmp = null;
    BitmapFactory.Options options = new BitmapFactory.Options();
    //setting inSampleSize value allows to load a scaled down version of the original image
    options.inSampleSize = 2;

    //inJustDecodeBounds set to false to load the actual bitmap
    options.inJustDecodeBounds = false;
    options.inTempStorage = new byte[16 * 1024];
    try {
      //load the bitmap from its path
      bmp = BitmapFactory.decodeFile(filePath, options);
      if (bmp == null) {

        InputStream inputStream = null;
        try {
          inputStream = new FileInputStream(filePath);
          BitmapFactory.decodeStream(inputStream, null, options);
          inputStream.close();
        } catch (FileNotFoundException exception) {
          exception.printStackTrace();
        } catch(IOException exception) { exception.printStackTrace(); }}}catch (OutOfMemoryError exception) {
      exception.printStackTrace();
    }finally {
      returnbmp; }}Copy the code

It seems that there is no problem. Take a look at the measured results. The original picture is 3840*2400 with a size of 2.2m.

It can be seen that none of the 4 images after compression has reached the target value, and the deviation is large. The reason is that the property options.inSampleSize can only be 2 to the power of N. If it is calculated to be 7, Android will take the approximate value 8, and so on, so that the value cannot be compressed to the target value. Compressor is an open source library. It redraws the compressed image on the Canvas according to the target size, resulting in a new bitmap.

Core code:

Matrix scaleMatrix = new Matrix();
    scaleMatrix.setScale(ratioX, ratioY, 0.0);

    Canvas canvas = new Canvas(scaledBitmap);
    canvas.setMatrix(scaleMatrix);
    canvas.drawBitmap(bmp, 0.0.new Paint(Paint.FILTER_BITMAP_FLAG));
Copy the code

Using the open source Compressor library, the following images are compared:

And you can see that it gets compressed down to the real target value every time. (Note that it is not the target value, and distinguish between the target value and the real target value)

The quality of compressed

Bitmap has a method compress(CompressFormat format, int quality, OutputStream). Quality means that the compression quality is between 0 and 100. The smaller the value, the more intense the compression is. However, we usually do not set this value directly. Instead, we define a customized compressed size, such as 300KB, and dynamically calculate the quality. Core code:

ByteArrayOutputStream baos = new ByteArrayOutputStream(); int options_ = 100; actualOutBitmap.compress(Bitmap.CompressFormat.JPEG, options_, baos); Int baosLength = baos.tobyteArray ().length; While (baosLength / 1024 > maxFileSize) {// loop to determine if the compressed image is larger than maxMemmorrySize, baos.reset(); // Reset the baos so that the next write overwrites the previous content options_ = math.max (0, options_ -10); / / picture quality every time reduce 10 actualOutBitmap.com press (Bitmap.Com pressFormat. JPEG, options_, baos); BaosLength = baos.tobyteArray ().length; If (options_ == 0)// If the quality of the image has been reduced to the lowest, no compression break; }
Copy the code

Compression practice

  • Now mature open source libraries Luban:https://github.com/Curzibn/Luban is more complex, the open source library algorithm according to the contrast before and after rendering the reverse calculation WeChat circle of friends of compression, the last effect and WeChat about, if you can use the high compression requirements. However, method calls are asynchronous and feedback results in the form of callbacks, which is not good.
  • Compressor:https://github.com/zetbaitsu/Compressor on the open source library is in ordinary compression algorithm has been optimized to improve, the source code is easy to understand, recommended! The following is a compression test of the three large diagrams with different target values using Compressor (BV is the compression of our project, just ignore it). The quality parameter is set to 80%