One, foreword
The project is a graphics application with a high memory footprint. Memory issues are particularly acute on some low-end models. Glide image loader is used in the project. Based on version 4.9.0, the default image loading format is ARGB_8888. For ARGB_8888’s Bitmap, a pixel contains Alpha, Red, Green, and Blue information, a total of 4 bytes. For some low-end models, in order to reduce memory consumption and improve application availability, the image loading format is set to RGB_565, which occupies 2 bytes, so it can save half of the memory by comparison. The price is the loss of a certain tricolor, fortunately this is within the acceptable range.
Second, add picture format Settings
By customizing GlideModule, we can do some global configuration for the image loader, where we can format the images to load. As shown in the following code, set the image format to RGB_565 for low-end models; For high-end models, set this parameter to ARGB_8888.
@GlideModule public class CustomGlideModule extends AppGlideModule { private final RequestOptions requestOptions = new RequestOptions(); @Override public void applyOptions(Context context, GlideBuilder builder) { if(DeviceUtils.isLowLevelDevice()){ builder.setDefaultRequestOptions((RequestOptions)this.requestOptions.format(DecodeFormat.PREFER_RGB_565)); } else { builder.setDefaultRequestOptions((RequestOptions)this.requestOptions.format(DecodeFormat.PREFER_ARGB_8888)); Override public Boolean isManifestParsingEnabled() {return manifestParsingEnabled (); }}Copy the code
Print the format of the loaded image and find either RGB_565 or ARGB_8888. In theory, all images should be RGB_565 format, but the actual result is not consistent with the expected. Take a look at the source code comment:
/**
* Bitmaps decoded from image formats that support and/or use alpha (some types of PNGs, GIFs etc)
* should return {@link android.graphics.Bitmap.Config#ARGB_8888} for
* {@link android.graphics.Bitmap#getConfig()}. Bitmaps decoded from formats that don't support or
* use alpha should return {@link android.graphics.Bitmap.Config#RGB_565} for
* {@link android.graphics.Bitmap#getConfig()}.
*
* <p>On Android O+, this format will will use ARGB_8888 only when it's not possible to use
* {@link android.graphics.Bitmap.Config#HARDWARE}.
*/
PREFER_RGB_565;
Copy the code
PREFER_RGB_565 comment, which contains the text. Alpha channel returns ARGB_8888, alpha channel returns RGB_565.
Why is it like this? Next, let’s analyze the picture creation process of Glide.
Glide picture creation process
The final view is the picture format of the bitmap obtained by Glide, so the analysis of the main process of creating bitmap Glide. The process, mainly in com. Bumptech. Glide. The load. The resource. The bitmap. Downsampler. Java classes. In this class, the main process in the DownSample. DecodeFromWrappedStreams () method, mainly includes the following processes:
- Read the original picture information: obtain the picture size, type and other related information
- Calculates the target picture size and decoding configuration
- Set the inBitmap
- Gets the target bitmap from the original image input stream
3.1 Reading the original picture information
In getDimensions approach, by setting the attribute options. The options inJustDecodeBounds = true, only read the image information, doesn’t really create a bitmap. At this point, the outXxx field will be set to the corresponding image property value, such as: OutWidth Output image width, outHeight output height, outMimeType output type, outConfig pixel format, outColorSpace output color space.
private static int[] getDimensions(InputStream is, BitmapFactory.Options options,
DecodeCallbacks decodeCallbacks, BitmapPool bitmapPool) throws IOException {
options.inJustDecodeBounds = true;
decodeStream(is, options, decodeCallbacks, bitmapPool);
options.inJustDecodeBounds = false;
return new int[] { options.outWidth, options.outHeight };
}
Copy the code
3.2 Calculate the target image size and decoding configuration
Size calculation is not concerned with this part of the content, focus on the decoding configuration part.
private void calculateConfig( InputStream is, DecodeFormat format, boolean isHardwareConfigAllowed, boolean isExifOrientationRequired, BitmapFactory.Options optionsWithScaling, int targetWidth, int targetHeight) { if (hardwareConfigState.setHardwareConfigIfAllowed( targetWidth, targetHeight, optionsWithScaling, format, isHardwareConfigAllowed, isExifOrientationRequired)) { return; } // Changing configs can cause skewing on 4.1, see issue #128. if (format == DecodeFormat.PREFER_ARGB_8888 || Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) { optionsWithScaling.inPreferredConfig = Bitmap.Config.ARGB_8888; return; } boolean hasAlpha = false; try { hasAlpha = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool).hasAlpha(); } catch (IOException e) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Cannot determine whether the image has alpha or not from header" + ", format " + format, e); } } optionsWithScaling.inPreferredConfig = hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; if (optionsWithScaling.inPreferredConfig == Config.RGB_565) { optionsWithScaling.inDither = true; }}Copy the code
In general, it mainly contains the following logic:
- If the Settings support hardware decoding, you do not need to worry about the image format Settings
- If DecodeFormat is PREFER_ARGB_8888 or SDK version 16, use PREFER_ARGB_8888
- If neither of the above conditions is true (PREFER_RGB_565), check whether the image supports the alpha channel. If alpha is not supported, set this parameter to RGB_565. Otherwise, set to ARGB_8888.
So how does Glide know if the current image supports the Alpha channel?
The answer is EXIF. EXIF, short for Exchangeable image file format, translated into Exchangeable image file format. It is specially set for digital camera photos, which can record digital photo attribute information and shooting data. Glide is based on EXIF to capture different image formats such as PNG, JPEG, etc. Specific logic in DefaultImageHeaderParser. In Java implementation.
@NonNull private ImageType getType(Reader reader) throws IOException { final int firstTwoBytes = reader.getUInt16(); // JPEG. if (firstTwoBytes == EXIF_MAGIC_NUMBER) { return JPEG; } final int firstFourBytes = (firstTwoBytes << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF); // PNG. if (firstFourBytes == PNG_HEADER) { // See: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha // -color-type reader.skip(25 - 4); int alpha = reader.getByte(); // A RGB indexed PNG can also have transparency. Better safe than sorry! return alpha >= 3 ? PNG_A : PNG; } // GIF from first 3 bytes. if (firstFourBytes >> 8 == GIF_HEADER) { return GIF; } // WebP (reads up to 21 bytes). See https://developers.google.com/speed/webp/docs/riff_container // for details. if (firstFourBytes ! = RIFF_HEADER) { return UNKNOWN; } // Bytes 4 - 7 contain length information. Skip these. reader.skip(4); final int thirdFourBytes = (reader.getUInt16() << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF); if (thirdFourBytes ! = WEBP_HEADER) { return UNKNOWN; } final int fourthFourBytes = (reader.getUInt16() << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF); if ((fourthFourBytes & VP8_HEADER_MASK) ! = VP8_HEADER) { return UNKNOWN; } if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_EXTENDED) { // Skip some more length bytes and check for transparency/alpha flag. reader.skip(4); return (reader.getByte() & WEBP_EXTENDED_ALPHA_FLAG) ! = 0? ImageType.WEBP_A : ImageType.WEBP; } if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_LOSSLESS) { // See chromium.googlesource.com/webm/libwebp/+/master/doc/webp-lossless-bitstream-spec.txt // for more info. reader.skip(4); return (reader.getByte() & WEBP_LOSSLESS_ALPHA_FLAG) ! = 0? ImageType.WEBP_A : ImageType.WEBP; } return ImageType.WEBP; }Copy the code
The image type returned is an enumerated type. The definition is as follows:
enum ImageType { GIF(true), JPEG(false), RAW(false), /** PNG type with alpha. */ PNG_A(true), /** PNG type without alpha. */ PNG(false), /** WebP type with alpha. */ WEBP_A(true), /** WebP type without alpha. */ WEBP(false), /** Unrecognized type. */ UNKNOWN(false); private final boolean hasAlpha; ImageType(boolean hasAlpha) { this.hasAlpha = hasAlpha; } public boolean hasAlpha() { return hasAlpha; }}Copy the code
The type contains information about whether there is an alpha channel. Therefore, when the image type is determined, whether there is an alpha channel is determined.
3.3 set inBitmap
SetInBitmap (bitmapFactory. Options Options, BitmapPool BitmapPool, int width, int height) is used to set the inBitmap field of option. This part of logic does not affect the image format of the final bitmap, so it will not be analyzed in depth.
3.4 Decoding to obtain the target bitmap
Next, the decodeFromWrappedStreams method calls as follows:
Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
Copy the code
In the decodeStream method, we call:
result = BitmapFactory.decodeStream(is, null, options);
Copy the code
In this case, the inPreferredConfig field in options is the value assigned in the second step. Decodes the target bitmap based on the inPreferredConfig configuration.
Four,
From the above analysis, it can be found that when the image format is set to RGB_565, not all images will be output in this format. Inside Glide, the EXIF header of the original image is read to get the format of the current image. If the images in the current format support alpha channel, the format will still be ARGB_8888.