当前位置:网站首页>Detailed explanation of bitmap optimization

Detailed explanation of bitmap optimization

2022-06-23 22:22:00 Great inventor

Why? Bitmap It can lead to OOM?

1. Each model is compiling ROM An application heap memory has been set VM Value limit dalvik.vm.heapgrowthlimit, Used to limit the maximum memory available for each application , Exceeding this maximum value will result in OOM. This threshold , Generally according to the mobile phone screen dpi Increasing size ,dpi Smaller phones , The lower the maximum memory available per application . So when the number of loaded images is large , It is easy to exceed this threshold , cause OOM.

2. The higher the resolution of the image , The more memory consumed , When loading high resolution pictures , It will take up a lot of memory , If it is not handled properly, it will OOM. for example , The resolution of one is :1920x1080 Pictures of the . If Bitmap Use ARGB_8888 32 Bits to tile , Occupied memory is 1920x1080x4 Bytes , Take up nearly 8M Memory , As one can imagine , If the picture is not processed , will OOM.

Bitmap Basic knowledge of

A picture Bitmap Memory used = Picture length x Image width x The number of bytes a pixel occupies undefined and Bitmap.Config, It is an important parameter to specify the number of bytes per pixel .

 among ,A On behalf of transparency ;R For red ;G Representing green ;B Representing blue .
ALPHA_8
 Express 8 position Alpha Bitmap , namely A=8, One pixel occupied 1 Bytes , It has no color , Transparency only 
ARGB_4444
 Express 16 position ARGB Bitmap , namely A=4,R=4,G=4,B=4, One pixel takes up 4+4+4+4=16 position ,2 Bytes 
ARGB_8888
 Express 32 position ARGB Bitmap , namely A=8,R=8,G=8,B=8, One pixel takes up 8+8+8+8=32 position ,4 Bytes 
RGB_565
 Express 16 position RGB Bitmap , namely R=5,G=6,B=5, It has no transparency , One pixel takes up 5+6+5=16 position ,2 Bytes 

A picture Bitmap Memory used = Picture length x Image width x The number of bytes a pixel occupies

According to the above algorithm , You can calculate the memory occupied by the picture , With 100*100 Pixel picture as an example

image.png

Now let's start learning Bitmap Optimization scheme

One 、Bitmap Mass compression

adopt Bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos); Way to reduce picture quality

     public static Bitmap compressImage(Bitmap bitmap){  
            ByteArrayOutputStream baos = new ByteArrayOutputStream();  
            // Mass compression method , here 100 Means no compression , Store the compressed data in baos in   
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);  
            int options = 100;  
            // Loop to determine if the compressed image is larger than 50kb, Greater than continue to compress   
            while ( baos.toByteArray().length / 1024>50) {  
                // Empty baos  
                baos.reset();  
                bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);  
                options -= 10;// Every time it's reduced 10  
            }  
            // Put the compressed data baos Store in ByteArrayInputStream in   
            ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());  
            // hold ByteArrayInputStream Data generated images   
            Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null);  
            return newBitmap;  
        }

Two 、 Zoom compression

        int ratio = 8;
        // Create a new bitmap based on parameters 
        Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(result);
        Rect rect = new Rect(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio);
        canvas.drawBitmap(bmp, null, rect, null);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        result.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(baos.toByteArray());
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

3、 ... and 、 Sample rate compression ( Size compression )

Bitmap The core idea of optimized loading is to use BitmapFactory.Options To load images of the required size .

Such as through ImageView To show pictures , A lot of times ImageView It's not as big as the original size of the picture , If you load the whole picture , Then set to ImageView,ImageView Is unable to display the original picture . adopt BitmapFactory.Options You can load the reduced image at a certain sampling rate , Put the reduced picture in ImageView It shows that , This will reduce the memory consumption and avoid OOM, Improved Bitmap Load time performance .BitmapFactory The four class methods provided for loading images support BitmapFactory.Options Parameters , It is very convenient to sample and zoom an image .

for fear of OOM abnormal , It's best to parse each picture , First check the size of the picture , Then you can decide whether to load the whole picture into memory or compress the picture and load it into memory . The following factors need to be considered :

1. Estimate the amount of memory required to load the entire image

2. How much memory would you like to provide to load an image

3. The actual size of the control used to display this picture

4. Current device screen size and resolution

adopt BitmapFactory.Options To zoom the image , Mainly used its inSampleSize Parameters , I.e. sampling rate . When inSampleSize by 1 when , The size of the sampled image is the original size of the image ; When inSampleSize Greater than 1 when , such as 2, Then the width and height of the sampled image are all the same as the original image 1/2, The number of pixels is... Of the original image 1/4, The memory size occupied by it is the same as that of the original picture 1/4.

The sampling rate must be greater than 1 The integer of , The picture will have the effect of shrinking , And the sampling rate affects both width and height , The scale is 1/(inSampleSize Of 2 Power ), such as inSampleSize by 4, So the zoom ratio is 1/16. Official documents point out that ,inSampleSize The values for 2 The index of :1、2、4、8、16 wait .

How to obtain the sampling rate ?

1. take BitmapFactory.Options Of inJustDecodeBounds The parameter is set to true And load the picture ;undefined 2. from BitmapFactory.Options Take out the original width and height information of the picture , They correspond to outWidth and outHeight Parameters ;undefined 3. According to the rules of sampling rate and the target View To calculate the sample rate inSampleSize;undefined 4. take BitmapFactory.Options Of inJustDecodeBounds The parameter is set to false, Then reload the picture .

 /**
     *  Compress by picture size   Parameter is bitmap
     * @param bitmap
     * @param pixelW
     * @param pixelH
     * @return
     */
    public static Bitmap compressImageFromBitmap(Bitmap bitmap, int pixelW, int pixelH) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
        if( os.toByteArray().length / 1024>512) {// If the picture is larger than 0.5M, Compression to avoid generating images (BitmapFactory.decodeStream) Time overflows 
            os.reset();
            bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);// Here compression 50%, Store the compressed data in baos in 
        }
        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
        BitmapFactory.Options options = new BitmapFactory.Options();
// First sampling 
        options.inJustDecodeBounds = true;// Load only bitmap The border , Take up part of the memory 
        options.inPreferredConfig = Bitmap.Config.RGB_565;// Set color mode 
        BitmapFactory.decodeStream(is, null, options);// Configure preferences 
// Second sampling 
        options.inJustDecodeBounds = false;
        options.inSampleSize = computeSampleSize(options , pixelH > pixelW ? pixelW : pixelH ,pixelW * pixelH );
        is = new ByteArrayInputStream(os.toByteArray());
// Configure the final preferences for the new bitmap object 
        Bitmap newBitmap = BitmapFactory.decodeStream(is, null, options);
        return newBitmap;
    }
    /**
     *  Dynamically calculate the... Of the picture inSampleSize
     * @param options
     * @param minSideLength
     * @param maxNumOfPixels
     * @return
     */
    public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
        int roundedSize;
        if (initialSize <= 8) {
            roundedSize = 1;
            while (roundedSize < initialSize) {
                roundedSize <<= 1;
            }
        } else {
            roundedSize = (initialSize + 7) / 8 * 8;
        }
        return roundedSize;
    }
    private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        double w = options.outWidth;
        double h = options.outHeight;
        int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
        int upperBound = (minSideLength == -1) ? 128 :(int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
        if (upperBound < lowerBound) {
            return lowerBound;
        }
        if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
            return 1;
        } else if (minSideLength == -1) {
            return lowerBound;
        } else {
            return upperBound;
        }
    }
}

Four 、Bitmap Color mode compression

Android The default is to use ARGB8888 Configure to handle color , Occupy 4 byte , change to the use of sth. RGB565, Will only occupy 2 byte , The price is that there will be relatively few colors , It is suitable for scenes with low requirements for color richness .

 BitmapFactory.Options options = new BitmapFactory.Options();
 options.inPreferredConfig = Bitmap.Config.RGB_565;// Set color mode 

5、 ... and 、libjpeg.so Library compression

libjpeg Is a widely used open source JPEG Image library , Android also relies on libjpeg To compress pictures . But Android is not directly encapsulated libjpeg, It is based on another name Skia As an image processing engine .Skia It is a large and comprehensive engine maintained by Google itself , Various image processing functions are realized in it , And it is widely used in the products of Google itself and other companies ( Such as :Chrome、Firefox、 Android etc. ).Skia Yes libjpeg It's well packaged , Based on this engine, it can be very convenient for the operating system 、 Browser and other development image processing functions .

**Java The local method of is as follows :

public static native String compressBitmap(Bitmap bit, int w, int h, int

quality, byte[] fileNameBytes, boolean optimize);**

following C The code steps are as follows :

1、 take Android Of bitmap Decode and convert to RGB data undefined 2、 by JPEG Object allocates space and initializes undefined 3、 Specify the compressed data source undefined 4、 Get file information undefined 5、 Set parameters for compression , Including image size , Color space undefined 6、 Start compressing undefined 7、 Compression complete undefined 8、 Release resources

#include <string.h>
#include <bitmap.h>
#include <log.h>
#include "jni.h"
#include <stdio.h>
#include <setjmp.h>
#include <math.h>
#include <stdint.h>
#include <time.h>
#include "jpeg/android/config.h"
#include "jpeg/jpeglib.h"
#include "jpeg/cdjpeg.h"        /* Common decls for cjpeg/djpeg applications */
#define LOG_TAG "jni"
//#define LOGW(...)  __android_log_write(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
//#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define true 1
#define false 0
typedef uint8_t BYTE;
char *error;
struct my_error_mgr {
    struct jpeg_error_mgr pub;
    jmp_buf setjmp_buffer;
};
typedef struct my_error_mgr * my_error_ptr;
METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
my_error_ptr myerr = (my_error_ptr) cinfo->err;
(*cinfo->err->output_message) (cinfo);
error=(char*)myerr->pub.jpeg_message_table[myerr->pub.msg_code];
LOGE("jpeg_message_table[%d]:%s", myerr->pub.msg_code,myerr->pub.jpeg_message_table[myerr->pub.msg_code]);
// LOGE("addon_message_table:%s", myerr->pub.addon_message_table);
//  LOGE("SIZEOF:%d",myerr->pub.msg_parm.i[0]);
//  LOGE("sizeof:%d",myerr->pub.msg_parm.i[1]);
longjmp(myerr->setjmp_buffer, 1);
}
int generateJPEG(BYTE* data, int w, int h, int quality,
                 const char* outfilename, jboolean optimize) {
    int nComponent = 3;
    // jpeg The structure of the body , Keep it wide, for example 、 high 、 A deep 、 Picture format and other information 
    struct jpeg_compress_struct jcs;
    struct my_error_mgr jem;
    jcs.err = jpeg_std_error(&jem.pub);
    jem.pub.error_exit = my_error_exit;
    if (setjmp(jem.setjmp_buffer)) {
        return 0;
    }
    jpeg_create_compress(&jcs);
    //  Open the output file  wb: Can write byte
    FILE* f = fopen(outfilename, "wb");
    if (f == NULL) {
        return 0;
    }
    //  Set the file path of the structure 
    jpeg_stdio_dest(&jcs, f);
    jcs.image_width = w;
    jcs.image_height = h;
    //  Set Huffman code 
    jcs.arith_code = false;
    jcs.input_components = nComponent;
    if (nComponent == 1)
        jcs.in_color_space = JCS_GRAYSCALE;
    else
        jcs.in_color_space = JCS_RGB;
    jpeg_set_defaults(&jcs);
    jcs.optimize_coding = optimize;
    jpeg_set_quality(&jcs, quality, true);
    //  Start compressing , Write all pixels 
    jpeg_start_compress(&jcs, TRUE);
    JSAMPROW row_pointer[1];
    int row_stride;
    row_stride = jcs.image_width * nComponent;
    while (jcs.next_scanline < jcs.image_height) {
        row_pointer[0] = &data[jcs.next_scanline * row_stride];
        jpeg_write_scanlines(&jcs, row_pointer, 1);
    }
    jpeg_finish_compress(&jcs);
    jpeg_destroy_compress(&jcs);
    fclose(f);
    return 1;
}
typedef struct {
    uint8_t r;
    uint8_t g;
    uint8_t b;
} rgb;
char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
    char* rtn = NULL;
    jsize alen = (*env)->GetArrayLength(env, barr);
    jbyte* ba = (*env)->GetByteArrayElements(env, barr, 0);
    if (alen > 0) {
        rtn = (char*) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    (*env)->ReleaseByteArrayElements(env, barr, ba, 0);
    return rtn;
}
jstring Java_com_effective_bitmap_utils_EffectiveBitmapUtils_compressBitmap(JNIEnv* env,
                                                       jobject thiz, jobject bitmapcolor, int w, int h, int quality,
                                                       jbyteArray fileNameStr, jboolean optimize) {
    AndroidBitmapInfo infocolor;
    BYTE* pixelscolor;
    int ret;
    BYTE * data;
    BYTE *tmpdata;
    char * fileName = jstrinTostring(env, fileNameStr);
    if ((ret = AndroidBitmap_getInfo(env, bitmapcolor, &infocolor)) < 0) {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return (*env)->NewStringUTF(env, "0");;
    }
    if ((ret = AndroidBitmap_lockPixels(env, bitmapcolor, (void**)&pixelscolor)) < 0) {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
    }
    BYTE r, g, b;
    data = NULL;
    data = malloc(w * h * 3);
    tmpdata = data;
    int j = 0, i = 0;
    int color;
    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            color = *((int *) pixelscolor);
            r = ((color & 0x00FF0000) >> 16);
            g = ((color & 0x0000FF00) >> 8);
            b = color & 0x000000FF;
            *data = b;
            *(data + 1) = g;
            *(data + 2) = r;
            data = data + 3;
            pixelscolor += 4;
        }
    }
    AndroidBitmap_unlockPixels(env, bitmapcolor);
    int resultCode= generateJPEG(tmpdata, w, h, quality, fileName, optimize);
    free(tmpdata);
    if(resultCode==0){
        jstring result=(*env)->NewStringUTF(env, error);
        error=NULL;
        return result;
    }
    return (*env)->NewStringUTF(env, "1"); //success
}

6、 ... and 、 Three level cache (LruCache and DiskLruCache Realization )

After first loading pictures from the network , Cache pictures in memory and sd In the card . such , We don't have to load pictures on the Internet frequently , For very good memory control , Will consider using LruCache As Bitmap A storage container in memory , stay sd Card is used DiskLruCache To uniformly manage the image cache on the disk .

SoftReference and inBitmap Combination of parameters

Store in this way as LruCache Obsolete reuse pool

Adopt LruCache As storage Bitmap The container of , And in the LruCache There is a method worth noting in , That's it entryRemoved, According to the statement given in the document , stay LruCache This method will be called when the container is full and it is necessary to eliminate the stored objects to make room ( Be careful . It's just that the object is eliminated LruCache Containers , But it doesn't mean that the memory of the object will be Dalvik The virtual machine is recycled ), At this point, it is possible to Bitmap Use SoftReference Wrap it up , And use a prepared one HashSet Containers to hold these things that are going to be recycled Bitmap. Someone will ask. . What's the point of storing like this ? The reason for this storage , It must be mentioned again inBitmap Parameters ( stay Android3.0 Just started , Please refer to API Medium BitmapFactory.Options Parameter information ). This parameter is mainly provided for us to reuse the memory Bitmap.

When the above conditions are met . The system carries out decoder It will check if there is any reusable in memory Bitmap. Avoid us going to SD The card is loaded with pictures and the system performance is degraded , After all, reusing directly from memory is better than reusing directly from memory SD On the card IO The efficiency of operation should be improved dozens of times .

原网站

版权声明
本文为[Great inventor]所创,转载请带上原文链接,感谢
https://yzsam.com/2021/12/202112151541452556.html