Very comprehensive knowledge combing of Android Bitmap

Keywords: Android Attribute Mobile

In daily development, it can be said that Bitmap can not be seen, basically every application will be used directly or indirectly, which involves a lot of relevant knowledge.
So here I comb Bitmap's common knowledge, limited to experience and ability, not too in-depth analysis.

1. Distinguish between decodeResource() and decodeFile()

The difference here is not the difference between method names and parameters, but the difference in processing the size of the decoded image.

DecdeFile () is used to read the graph on the SD card and get the original size of the picture.
DecdeResource () is used to read Res, Raw and other resources, and the original size of the image * scaling coefficient is obtained.

As you can see, decodeResource() has one more zoom factor than decodeFile(). The calculation of zoom factor depends on the screen density. Of course, this parameter can also be adjusted.

// Scaling coefficients can be adjusted by these parameters of BitmapFactory.Options
public class BitmapFactory {
    public static class Options {
        public boolean inScaled;     // Default true
        public int inDensity;        // Default 160 for folders without dpi
        public int inTargetDensity;  // It depends on the screen.
    }
}

We have a 720x720 picture now.

1.1 inScaled attribute

If inScaled is set to false, no zooming is performed, and the decoded image size is 720x720; otherwise, look down.
If inScaled is set to true or not, the scaling coefficients are calculated based on inDensity and inTarget Density.

1.2 Default

Put this picture in the drawable directory by default:
Taking 720p Red Rice 3 as an example, the scaling coefficient = in Target Density (specific 320 / in Density (default 160) = 2 = density, and the decoded image size is 1440x1440.
Take MX4 of 1080p as an example, the scaling coefficient = in Target Density (specific 480 / in Density (default 160) = 3 = density, and the decoded image size is 2160x 2160.

1.3 *dpi folder impact

Putting pictures in folders like drawable or raw without dpi will be calculated according to the above algorithm.
What happens if you put it in xhdpi? On MX4, put it into xhdpi, and the decoded image size is 1080x 1080.
Because placing it in a folder with dpi affects the default value of in Density, placing it in xhdpi is 160 x 2 = 320; so the zoom factor is 480 (screen) / 320 (xhdpi) = 1.5; so the resulting image size is 1080 x 1080.

1.4 Manual setting of zoom factor

If you don't want to rely on the density of the system itself, you can manually set in Density and in Target Density to control the scaling factor:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = 1;
options.inDensity = 160;
options.inTargetDensity = 160;
bitmap = BitmapFactory.decodeResource(getResources(),
        R.drawable.origin, options);
// On MX4, although density = 3
// But by setting inTarget Density / inDensity = 160/160 = 1
// The decoded image size is 720x720
System.out.println("w:" + bitmap.getWidth()
        + ", h:" + bitmap.getHeight());

2. recycle() method

2.1 Official statement

First, Android's allocation area for Bitmap memory (pixel data) is differentiated in different versions:

As of Android 3.0 (API level 11), the pixel data is stored on the Dalvik heap along with the associated bitmap.

Beginning with 3.0, Bitmap pixel data and Bitmap objects are stored in the Dalvik heap, while before 3.0, Bitmap pixel data is stored in Native memory.
So before 3.0, the release of Bitmap pixel data in Nativie memory was uncertain, and Crash was prone to memory overflow. Officials strongly recommend calling recycle(), of course, when it was determined that it was not needed; after 3.0, it was not required.
Reference link: Managing Bitmap Memory

2.2 Point of discussion

After 3.0, there is no official recycle() suggestion. Is it really unnecessary to recycle()?
In the doctor's article: Bitmap.recycle-triggered murder Finally, it points out that "in the case of incompatibility with Android 2.3, don't use recycle method to manage Bitmap, that's the matter of GC!" At the beginning of the article, it is pointed out that the reason lies in the annotation of the recycle() method:

/**
 * ... This is an advanced call, and normally need not be called,
 * since the normal GC process will free up this memory when
 * there are no more references to this bitmap.
 */
public void recycle() {}

In fact, this statement is inaccurate and cannot be used as a basis for not calling the recycle() method.
Because in commit history, this line of comment was available as early as 2008 to initialize the code, but the earlier code did not need the recycle() method.

If active recycle() is not needed after 3.0, the latest AOSP source code should be reflected accordingly. I checked the code of System UI and Gallery 2, and did not ban Bitmap's recycle() method.
So, personally, if Bitmap really doesn't work, what's wrong with recycle?
PS: As for the bug mentioned by the doctor, it is obviously an optimization strategy. In the development of APP, we can add two different bitmap s to judge the condition.

3. How much memory does Bitmap occupy?

This excellent article, which has been written by bugly, makes it very clear:
Android development can't get around the pit: How much memory does your Bitmap actually occupy?

4. inBitmap

BitmapFactory.Options.inBitmap is a new property in Android 3.0. If set, it will reuse the memory of the Bitmap to improve performance.
However, this reuse is conditional. Before Android 4.4, only Bitmap of the same size can be reused, while Android 4.4 + can only use Bitmap of smaller proportion.
Detailed information is provided on the official website. Here are two ways to learn about the sample code:

private static void addInBitmapOptions(BitmapFactory.Options options,
        ImageCache cache) {
    // inBitmap only works with mutable bitmaps, so force the decoder to
    // return mutable bitmaps.
    options.inMutable = true;

    if (cache != null) {
        // Try to find a bitmap to use for inBitmap.
        Bitmap inBitmap = cache.getBitmapFromReusableSet(options);

        if (inBitmap != null) {
            // If a suitable bitmap has been found,
            // set it as the value of inBitmap.
            options.inBitmap = inBitmap;
        }
    }
}

static boolean canUseForInBitmap(
        Bitmap candidate, BitmapFactory.Options targetOptions) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        // From Android 4.4 (KitKat) onward we can re-use
        // if the byte size of the new bitmap is smaller than
        // the reusable bitmap candidate
        // allocation byte count.
        int width = targetOptions.outWidth / targetOptions.inSampleSize;
        int height =
            targetOptions.outHeight / targetOptions.inSampleSize;
        int byteCount = width * height
            * getBytesPerPixel(candidate.getConfig());
        return byteCount <= candidate.getAllocationByteCount();
    }

    // On earlier versions,
    // the dimensions must match exactly and the inSampleSize must be 1
    return candidate.getWidth() == targetOptions.outWidth
        && candidate.getHeight() == targetOptions.outHeight
        && targetOptions.inSampleSize == 1;
}

Reference link:
Managing Bitmap Memory
Reuse of Bitmap Objects

5. LRU cache algorithm

LRU,Least Recently Used,Discards the least recently used items first.

In the most recently used data, the least used data is discarded. In contrast, there is an MRU that discards the most used data.
This is the famous principle of locality.

5.1 Implementation Ideas

1. New data is inserted into the head of the list.
2. Whenever the cache hits (that is, the cache data is accessed), the data is moved to the head of the linked list.
3. When the list is full, discard the data at the end of the list.

5.2 LruCache

The implementation class LruCache of Lru algorithm is provided in Android 3.1 and support v4.
LinkedHashMap is used internally.

5.3 DiskLruCache

All objects and data of LruCache are in memory (or LinkedHashMap), while DiskLruCache is a disk cache, but its implementation is slightly more complex.
With DiskLruCache, you don't have to worry about too many files or pictures taking up too much disk space. It can automatically clean up the unused pictures.
DiskLruCache system is not officially available and needs to be downloaded separately: DiskLruCache

6. Computing inSampleSize

The most important technique to save memory with Bitmap is to load Bitmap of the right size, because many pictures are big in the camera pixels nowadays. These big pictures are loaded directly into memory, which is the easiest to OOM.
Loading the appropriate Bitmap requires reading the original size of the Bitmap and loading it with the size of the appropriate multiple reduced.
So, the calculation of this reduced multiple is the calculation of inSampleSize.

// According to maxWidth, maxHeight calculates the most suitable in SampleSize
public static int $sampleSize(BitmapFactory.Options options,
        int maxWidth, int maxHeight) {
    // raw height and width of image
    int rawWidth = options.outWidth;
    int rawHeight = options.outHeight;

    // calculate best sample size
    int inSampleSize = 0;
    if (rawHeight > maxHeight || rawWidth > maxWidth) {
        float ratioWidth = (float) rawWidth / maxWidth;
        float ratioHeight = (float) rawHeight / maxHeight;
        inSampleSize = (int) Math.min(ratioHeight, ratioWidth);
    }
    inSampleSize = Math.max(1, inSampleSize);

    return inSampleSize;
}

As for inSampleSize, it should be noted that it can only be the second power of 2, otherwise it will take the value closest to the second power of 2.

7. thumbnails

In order to save memory, we need to set the inJustDecodeBounds of BitmapFactory.Options to true, so that Bitmap can use decodeFile method to store the height and width in Bitmap.Options, but the memory footprint is empty (no real loading picture).
With Options with high and wide information, combined with the inSampleSize algorithm above to calculate the reduction factor, we can load a thumbnail of the appropriate size of the local map.

/**
 * Get thumbnails
 * Support automatic rotation
 * Some types of mobile phone camera pictures are reversed and can be automatically corrected based on exif information.
 * @return
 */
public static Bitmap $thumbnail(String path,
        int maxWidth, int maxHeight, boolean autoRotate) {

    int angle = 0;
    if (autoRotate) {
        angle = ImageLess.$exifRotateAngle(path);
    }

    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    // Get the width and height information of the image into options, and then return to bm empty
    Bitmap bitmap = BitmapFactory.decodeFile(path, options);
    options.inJustDecodeBounds = false;
    // Calculating scaling ratio
    int sampleSize = $sampleSize(options, maxWidth, maxHeight);
    options.inSampleSize = sampleSize;
    options.inPreferredConfig = Bitmap.Config.RGB_565;
    options.inPurgeable = true;
    options.inInputShareable = true;

    if (bitmap != null && !bitmap.isRecycled()) {
        bitmap.recycle();
    }
    bitmap = BitmapFactory.decodeFile(path, options);

    if (autoRotate && angle != 0) {
        bitmap = $rotate(bitmap, angle);
    }

    return bitmap;
}

A Thumbnail Utils built into the system can also generate thumbnails. The details are different, but the principle is the same.

8. Matrix deformation

Students who have studied linear algebra or image processing must be well aware of the power of Matrix. Many common image transformations can be accomplished by one Matrix, even more complex ones.

// Matrix matrix = new Matrix();
// Each change includes set, pre and post, which are set, matrix multiplication and matrix multiplication.
Translation: matrix.setTranslate()
Zoom: matrix.setScale()
Rotation: matrix.setRotate()
Bevel: matrix.setSkew()

Let me give you two examples to illustrate.

8.1 rotation

Rotate a certain angle with Matrix's postRotate method.

Matrix matrix = new Matrix();
// Angle is the angle of rotation.
matrix.postRotate(angle);
Bitmap rotatedBitmap = Bitmap.createBitmap(originBitmap,
        0,
        0,
        originBitmap.getWidth(),
        originBitmap.getHeight(),
        matrix,
        true);

8.2 zoom

Rotate an angle with Matrix's postScale method.

Matrix matrix = new Matrix();
// scaleX and scaleY are scaled in horizontal and vertical directions, respectively.
matrix.postScale(scaleX, scaleY);
Bitmap scaledBitmap = Bitmap.createBitmap(originBitmap,
        0,
        0,
        originBitmap.getWidth(),
        originBitmap.getHeight(),
        matrix,
        true);

Bitmap itself has a zoom method, but it is to zoom bitmap to the target size. The principle is also to use Matrix. Let's encapsulate it.

// Scale the horizontal and width to the specified size. Note that in this case, the picture is easily deformed.
Bitmap scaledBitmap = Bitmap.createScaledBitmap(originBitmap,
        dstWidth,
        dstHeight,
        true);

More results can be achieved through combination.

9. tailoring

There are still many application scenarios for image clipping: Avatar clipping, photo clipping, rounded corners, circles and so on.

9.1 rectangle

Matrix shape clipping is relatively simple. createBitmap method can be used directly:

Canvas canvas = new Canvas(originBitmap);
draw(canvas);
// Determine the location and size of the tailoring
Bitmap clipBitmap = Bitmap.createBitmap(originBitmap,
        left, top,
        clipWidth, clipHeight);

9.2 fillet

For rounded corners, we need to use Xfermode and Porter Duff Xfermode to intersect the rounded matrix on the original Bitmap to get the rounded corner Bitmap.

// Preparing brushes
Paint paint = new Paint();
paint.setAntiAlias(true);

// Matrix for tailoring
Rect rect = new Rect(0, 0,
        originBitmap.getWidth(), originBitmap.getHeight());
RectF rectF = new RectF(new Rect(0, 0,
        originBitmap.getWidth(), originBitmap.getHeight()));

Bitmap roundBitmap = Bitmap.createBitmap(originBitmap.getWidth(),
        originBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(roundBitmap);
// Round corner matrix, radius is rounded corner size
canvas.drawRoundRect(rectF, radius, radius, paint);

// Key code for Xfermode and SRC_IN
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(originBitmap, rect, rect, paint);

9.3 circle

The principle is the same as that of the corner cutting above, but the circle is painted on it.
To cut the circle from the middle, we need to calculate the left and top values for drawing the original Bitmap.

int min = originBitmap.getWidth() > originBitmap.getHeight() ?
originBitmap.getHeight() : originBitmap.getWidth();
Paint paint = new Paint();
paint.setAntiAlias(true);
Bitmap circleBitmap = Bitmap.createBitmap(min, min,
    Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(circleBitmap);
// circular
canvas.drawCircle(min / 2, min / 2, min / 2, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

// Centered display
int left = - (originBitmap.getWidth() - min) / 2;
int top = - (originBitmap.getHeight() - min) / 2;
canvas.drawBitmap(originBitmap, left, top, paint);

From the treatment of rounded corners and circles, we should be able to see that drawing any polygon is possible.

10. Save Bitmap

Many image applications support clipping, filtering, and so on. Ultimately, the processed Bitmap needs to be saved locally. Otherwise, no matter how powerful the function is, it will be in vain.

public static String $save(Bitmap bitmap,
        Bitmap.CompressFormat format, int quality, File destFile) {
    try {
        FileOutputStream out = new FileOutputStream(destFile);
        if (bitmap.compress(format, quality, out)) {
            out.flush();
            out.close();
        }

        if (bitmap != null && !bitmap.isRecycled()) {
            bitmap.recycle();
        }

        return destFile.getAbsolutePath();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

If you want to be more stable or easier to save to SDCard's package name path, you can encapsulate it again:

// Save it locally, default path / mnt/sdcard/[package]/save /, name the file with random UUID
public static String $save(Bitmap bitmap,
        Bitmap.CompressFormat format, int quality, Context context) {
    if (!Environment.getExternalStorageState()
            .equals(Environment.MEDIA_MOUNTED)) {
        return null;
    }

    File dir = new File(Environment.getExternalStorageDirectory()
            + "/" + context.getPackageName() + "/save/");
    if (!dir.exists()) {
        dir.mkdirs();
    }
    File destFile = new File(dir, UUID.randomUUID().toString());
    return $save(bitmap, format, quality, destFile);
}

11. Giant Graph Loading

Giant graphics loading, of course, can not use conventional methods, must be OOM.
The principle is relatively simple. There is a class BitmapRegionDecoder in the system.

public static BitmapRegionDecoder newInstance(byte[] data, int offset,
        int length, boolean isShareable) throws IOException {
}
public static BitmapRegionDecoder newInstance(
        FileDescriptor fd, boolean isShareable) throws IOException {
}
public static BitmapRegionDecoder newInstance(InputStream is,
        boolean isShareable) throws IOException {
}
public static BitmapRegionDecoder newInstance(String pathName,
        boolean isShareable) throws IOException {
}

It can be loaded by region:

public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
}

The Bitmap RegionDecoder is also used to browse the maps of Weibo, which can be consulted by oneself.

12. Color Matrix

Image processing is actually a very esoteric subject. Fortunately, Android provides the color matrix ColorMatrix class, which can achieve many simple special effects. Take the gray scale effect as an example.

Bitmap grayBitmap = Bitmap.createBitmap(originBitmap.getWidth(),
        originBitmap.getHeight(), Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(grayBitmap);
Paint paint = new Paint();
ColorMatrix colorMatrix = new ColorMatrix();
// Setting saturation to 0 realizes gray scale effect
colorMatrix.setSaturation(0);
ColorMatrixColorFilter colorMatrixColorFilter =
        new ColorMatrixColorFilter(colorMatrix);
paint.setColorFilter(colorMatrixColorFilter);
canvas.drawBitmap(originBitmap, 0, 0, paint);

In addition to saturation, we can also adjust the contrast, color change and so on.

13. Thumbnail Utils Analysis

Thumbnail Utils is a special method of generating thumbnails provided by the system. I have written a special article on thumbnails analysis. There are many contents, please move on: Understanding Thumbnail Utils

14. summary

Since Bitmap often deals with it, it is necessary to make it clear.
Inevitably, there will be omissions. Welcome to leave a message, I will add as appropriate.

Links to the original text: http://jayfeng.com/2016/03/22/Android-Bitmap%E9%9D%A2%E9%9D%A2%E8%A7%82/

Posted by jasonX on Mon, 15 Apr 2019 10:09:32 -0700