Glide图片格式设置RGB565不生效?

一、前言

项目是一个图片类应用,内存占用一直居高不下。尤其在一些低端机型上,内存问题尤为突出。项目中使用Glide图片加载器,基于4.9.0 版本,默认图片加载格式是ARGB_8888。对于ARGB_8888的Bitmap,一个像素点存放了Alpha、Red、Green、Blue四种信息,总共4个字节。对于一些低端机型,为了降低内存占用,提升应用可用性,设置图片加载格式为RGB_565,其占用2个字节,故对比起来可以节省一半的内存。代价是会损失一定的三色彩,还好这在可接受的范围内。

二、添加图片格式设置

通过自定义GlideModule,我们可以对图片加载器进行一些全局配置,在这里,可以设置加载的图片格式。如下代码所示,设置当为低端机型时,图片格式为RGB_565;高端机型时,设置为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));
		 }       
    }

    //    针对V4用户可以提升速度
    @Override
    public boolean isManifestParsingEnabled() {
        return false;
    }
}
复制代码

打印加载图片的格式,发现有的是RGB_565,有的是ARGB_8888。排除图片加载重新设置图片格式的情况,理论上所有图片应该都是RGB_565的格式,但是从实际结果来看,和预期是不相符的。看下一下源码注释:

/**
 * 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;
复制代码

PREFER_RGB_565的注释,有这么一段内容。翻译过来大致意思:支持alpha通道的返回ARGB_8888,不支持alpha通道的返回RGB_565。

为什么会这样子,接下来我们从Glide的图片创建流程来分析看看。

三、Glide图片创建流程

最终查看的是Glide获取的bitmap的图片格式,所以分析看下Glide的创建bitmap的主要流程。这个流程,主要在com.bumptech.glide.load.resource.bitmap.Downsampler.java类中完成。在这个类中,主要流程在DownSample.decodeFromWrappedStreams()方法中,主要包含如下流程:

  1. 读取原始图片信息:获取图片尺寸、类型等相关信息
  2. 计算目标图片尺寸和解码配置
  3. 设置inBitmap
  4. 从原始图片输入流获取目标bitmap

3.1 读取原始图片信息

在getDimensions方法中,通过设置options属性 options.inJustDecodeBounds = true,只读取图片信息,并不真的创建bitmap。此时outXxx 字段会被设置对应的图片属性值 ,
如 : outWidth 输出图像的 宽度 , outHeight 输出高度 , outMimeType 输出类型 ,
outConfig 像素格式 , outColorSpace 输出颜色空间。

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 };
}
复制代码

3.2 计算目标图片尺寸和解码配置

尺寸计算这部分本篇内容并不关心,重点看下解码配置部分。

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;
  }
}
复制代码

总的看来,主要包含以下几点逻辑:

  • 如果设置支持硬件解码,则无需关注图片格式的设置
  • 如果设置DecodeFormat为PREFER_ARGB_8888或者SDK版本为16,则使用PREFER_ARGB_8888
  • 如果上述条件都不成立(设置为PREFER_RGB_565),判断图片是否支持alpha通道。若不支持alpha,设置为RGB_565;否则,设置为ARGB_8888。

那么,Glide是如何知道当前图片是否支持alpha通道的呢?

答案就是EXIF。EXIF,英文Exchangeable image file format的简称,翻译为可交换图像文件格式。是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据。Glide正是基于EXIF,从而获取不同的图片格式,如PNG、JPEG等。具体逻辑在DefaultImageHeaderParser.java中实现。

@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;
}
复制代码

返回的图片类型,是一个枚举类型。定义如下:

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;
  }
}
复制代码

类型中包含是否有alpha通道信息。因此,当图片类型确定之后,是否有alpha通道也就确定了。

3.3 设置inBitmap

主要逻辑在 setInBitmap(BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height)中,设置option的inBitmap字段。此部分逻辑不影响最终获取bitmap的图片格式,暂不深入分析。

3.4 解码获取目标bitmap

接下来,decodeFromWrappedStreams方法中调用如下:

Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
复制代码

在decodeStream方法中,调用了:

result = BitmapFactory.decodeStream(is, null, options);
复制代码

此时,options中的inPreferredConfig字段数据,为在第二步计算配置赋值所得。将基于inPreferredConfig配置,解码获取目标bitmap。

四、总结

通过上述分析,可以发现当设置图片格式为RGB_565的时候,并不是所有图片都会按照这个格式进行输出。在Glide内部,会读取原始图片的EXIF头信息,获取当前图片的格式。若当前格式的图片支持alpha通道,则还是会设置为ARGB_8888的格式。

猜你喜欢

转载自juejin.im/post/7035637493059813412
今日推荐