Glide 4.9 源碼分析(二) —— 採樣壓縮的實現

前言

從 Glide 的一次加載流程中可知, Glide 拿到數據流以後, 使用 Downsampler 進行採樣處理而且反回了一個 Bitmapweb

public class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {

  private final Downsampler downsampler;
  
  public Resource<Bitmap> decode(@NonNull InputStream source, int width, int height,
      @NonNull Options options)
      throws IOException {
    ......
    try {
      // 根據請求配置的數據, 對數據流進行採樣壓縮
      return downsampler.decode(invalidatingStream, width, height, options, callbacks);
    } finally {
      ......
    }
  }
    
}
複製代碼

本次就着重的分析它對數據流的處理數組

一. 處理數據流

public final class Downsampler {
    
  public Resource<Bitmap> decode(InputStream is, int outWidth, int outHeight,
      Options options) throws IOException {
    return decode(is, outWidth, outHeight, options, EMPTY_CALLBACKS);
  }
  
  @SuppressWarnings({"resource", "deprecation"})
  public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
      Options options, DecodeCallbacks callbacks) throws IOException { 
    // 從緩存複用池中獲取 byte 數據組
    byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    // 獲取 Bitmap.Options 併爲其 BitmapFactory.Options.inTempStorage 分配緩衝區
    BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
    bitmapFactoryOptions.inTempStorage = bytesForOptions;
    // 獲取解碼的類型, ARGB_8888, RGB_565...
    DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
    // 獲取採用壓縮的策略
    DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
    // 是否須要將 Bitmap 的寬高固定爲請求的尺寸
    boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
    // 用於判斷 Bitmap 尺寸是不是可變的
    boolean isHardwareConfigAllowed = options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);
    try {
      // 調用 decodeFromWrappedStreams 獲取 Bitmap 數據
      Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
          downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
          requestedHeight, fixBitmapToRequestedDimensions, callbacks);
      return BitmapResource.obtain(result, bitmapPool);
    } finally {
      .......
      // 回收數組數據
      byteArrayPool.put(bytesForOptions);
    }
  }

  private Bitmap decodeFromWrappedStreams(InputStream is,
      BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
      DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
      int requestedHeight, boolean fixBitmapToRequestedDimensions,
      DecodeCallbacks callbacks) throws IOException {
    long startTime = LogTime.getLogTime();
    // 1. 經過數據流解析圖片的尺寸
    int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
    int sourceWidth = sourceDimensions[0];
    int sourceHeight = sourceDimensions[1];
    ......
    // 2. 獲取圖形的旋轉角度等信息
    int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
    int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
    boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);
    
    // 3. 獲取目標的寬高
    int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth;
    int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight;
    
    // 4. 解析圖片封裝格式, JPEG, PNG, WEBP, GIF...
    ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);
    
    // 5. 計算 Bitmap 的採樣率存放到 options.inSampleSize 中
    calculateScaling(......);
    
    // 6. 計算 Bitmap 所需顏色通道, 保存到 options.inPreferredConfig 中
    calculateConfig(.......); 
    
    // 7. 根據採樣率計算指望的尺寸, 
    boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 
    if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
      int expectedWidth;
      int expectedHeight;
      if (sourceWidth >= 0 && sourceHeight >= 0 && fixBitmapToRequestedDimensions && isKitKatOrGreater) {
        expectedWidth = targetWidth;
        expectedHeight = targetHeight;
      } else {
        // 計算 density 的比例
        float densityMultiplier = isScaling(options) ? (float) options.inTargetDensity / options.inDensity : 1f;
        int sampleSize = options.inSampleSize;
        // 計算採樣的寬高
        int downsampledWidth = (int) Math.ceil(sourceWidth / (float) sampleSize);
        int downsampledHeight = (int) Math.ceil(sourceHeight / (float) sampleSize);
        // 根據像素比求出指望的寬高
        expectedWidth = Math.round(downsampledWidth * densityMultiplier);
        expectedHeight = Math.round(downsampledHeight * densityMultiplier);
      }
      // 7.1 根據指望的寬高從 BitmapPool 中取能夠複用的對象, 存入 Options.inBitmap 中, 減小內存消耗
      if (expectedWidth > 0 && expectedHeight > 0) {
        setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
      }
    }
    
    // 8. 根據配置好的 options 解析數據流
    Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
    callbacks.onDecodeComplete(bitmapPool, downsampled);
    
    // 9. 嘗試對圖片進行角度矯正
    Bitmap rotated = null;
    if (downsampled != null) { 
      // 嘗試對圖片進行旋轉操做
      downsampled.setDensity(displayMetrics.densityDpi); 
      rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation);
      // 若返回了一個新的 Bitmap, 則將以前的 Bitmap 添加進享元複用池
      if (!downsampled.equals(rotated)) {
        bitmapPool.put(downsampled);
      }
    }
    return rotated;
    
  }
    
}
複製代碼

好的, Downsampler.decode 解析數據流獲取 Bitmap 對象一共有以下幾個步驟緩存

  • 經過數據流解析出圖形的原始寬高
  • 獲取圖形的旋轉角度等信息
  • 獲取此次圖片請求的目標寬高
  • 獲取圖像的封裝格式
    • JPEG, PNG, WEBP, GIF...
  • 計算 Bitmap 縮放方式
  • 計算 Bitmap 顏色通道
  • 根據採樣率計算指望的尺寸
    • 根據指望的寬高從 BitmapPool 中取能夠複用的對象, 存入 Options.inBitmap 中, 減小內存消耗
  • 根據配置好的 options 解析數據流
    • 與獲取圖像原始寬高的操做一致
  • 對圖像進行角度矯正

好的, 可見 Glide 解析一次數據流作了不少的操做, 咱們對重點的操做進行逐一分析bash

二. 經過數據流獲取圖像寬高

public final class Downsampler {

 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 };
  }
  
  private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options,
      DecodeCallbacks callbacks, BitmapPool bitmapPool) throws IOException {
    if (options.inJustDecodeBounds) {
      is.mark(MARK_POSITION);
    } else {
      ......
      callbacks.onObtainBounds();
    }
    ......
    final Bitmap result;
    TransformationUtils.getBitmapDrawableLock().lock();
    try {
      // 1. 經過 BitmapFactory 來解析 InputStream 將數據保存在 options 中
      result = BitmapFactory.decodeStream(is, null, options);
    } catch (IllegalArgumentException e) {
      ......
      // 2. 如果由於 BitmapFactory 沒法重用 options.inBitmap 這個位圖, 則會進入下面分支
      if (options.inBitmap != null) {
        try {
          is.reset();// 重置 InputStream 的位置
          bitmapPool.put(options.inBitmap);// 將 inBitmap 添加到緩存池中
          // 2.1 將 options.inBitmap 置空後從新解析 
          options.inBitmap = null;
          return decodeStream(is, options, callbacks, bitmapPool);
        } catch (IOException resetException) {
          ......
        }
      }
      ......
    } finally {
      TransformationUtils.getBitmapDrawableLock().unlock();
    }
    // 3. 重置 InputStream 流, 供後續使用
    if (options.inJustDecodeBounds) {
      is.reset();
    }
    // 4. 返回解析到的數據
    return result;
  }
  
}
複製代碼

具體的流程如上所示, 其中仍是有不少細節值得咱們參考和學習app

  • 在解析 Bitmap 的時候, 經過給 Options 中的 inBitmap 賦值, 讓新解析的 Bitmap 複用這個對象以此來減小內存的消耗
  • 若沒法複用則會在異常處理中, 使用無 inBitmap 的方式再次解析

三. 獲取圖像封裝格式

public final class ImageHeaderParserUtils {
    
  public static ImageType getType(@NonNull List<ImageHeaderParser> parsers,
      @Nullable InputStream is, @NonNull ArrayPool byteArrayPool) throws IOException {
    ......
    is.mark(MARK_POSITION);
    for (int i = 0, size = parsers.size(); i < size; i++) {
      // 1. 獲取解析器
      ImageHeaderParser parser = parsers.get(i);
      try {
        // 2. 使用解析器解析輸入流獲取圖片類型
        ImageType type = parser.getType(is);
        if (type != ImageType.UNKNOWN) {
          return type;
        }
      } finally {
        is.reset();
      }
    }

    return ImageType.UNKNOWN;
  }  
    
}
複製代碼

好的, 首先是獲取解析器, 這個解析器是 Glide 對象建立時註冊的ide

public class Glide implements ComponentCallbacks2 {
  
  Glide(...) {
    ......
    registry = new Registry();
    registry.register(new DefaultImageHeaderParser());
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
      registry.register(new ExifInterfaceImageHeaderParser());
    }
    ......
  }
  
}
複製代碼

Glide 中提供了兩個解析器, 分別爲 DefaultImageHeaderParser 和 ExifInterfaceImageHeaderParser, 咱們主要關注一下 DefaultImageHeaderParser 這個解析器性能

public final class DefaultImageHeaderParser implements ImageHeaderParser {
    
  @Override
  public ImageType getType(@NonNull InputStream is) throws IOException {
    return getType(new StreamReader(Preconditions.checkNotNull(is)));
  }    
  
  private static final int GIF_HEADER = 0x474946;
  private static final int PNG_HEADER = 0x89504E47;
  static final int EXIF_MAGIC_NUMBER = 0xFFD8;
  // "RIFF"
  private static final int RIFF_HEADER = 0x52494646;
  // "WEBP"
  private static final int WEBP_HEADER = 0x57454250;
  // "VP8" null.
  private static final int VP8_HEADER = 0x56503800;
  private static final int VP8_HEADER_MASK = 0xFFFFFF00;
  private static final int VP8_HEADER_TYPE_MASK = 0x000000FF;
  // 'X'
  private static final int VP8_HEADER_TYPE_EXTENDED = 0x00000058;
  // 'L'
  private static final int VP8_HEADER_TYPE_LOSSLESS = 0x0000004C;
  private static final int WEBP_EXTENDED_ALPHA_FLAG = 1 << 4;
  private static final int WEBP_LOSSLESS_ALPHA_FLAG = 1 << 3;
  
  private ImageType getType(Reader reader) throws IOException {
    final int firstTwoBytes = reader.getUInt16();

    // 1. 獲取 InputStream 的前兩個 Byte, 若爲 0xFFD8 則說明爲 JPEG 封裝格式
    if (firstTwoBytes == EXIF_MAGIC_NUMBER) {
      return JPEG;
    }
    // 2. 獲取 InputStream 前四個 Byte, 若爲 0x89504E47, 則說明爲 PNG 封裝格式
    final int firstFourBytes = (firstTwoBytes << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF);
    if (firstFourBytes == PNG_HEADER) {
      // 2.1 判斷是否爲帶 Alpha 通道的 png 圖片
      reader.skip(25 - 4);
      int alpha = reader.getByte();
      return alpha >= 3 ? PNG_A : PNG;
    }
    // 3. 獲取前三個 Byte, 若爲 0x474946, 則說明爲 GIF 封裝格式
    if (firstFourBytes >> 8 == GIF_HEADER) {
      return GIF;
    }
    // 4. 判斷是否爲 Webp 封裝類型
    if (firstFourBytes != RIFF_HEADER) {
      return UNKNOWN;
    }
    reader.skip(4);// Bytes [4 - 7] 包含的是長度信息, 跳過
    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) {
      reader.skip(4);
      return (reader.getByte() & WEBP_LOSSLESS_ALPHA_FLAG) != 0 ? ImageType.WEBP_A : ImageType.WEBP;
    }
    return ImageType.WEBP;
  }
    
}
複製代碼

好的, 能夠看到它是經過圖片封裝格式中的字節數來判斷圖片的類型的學習

  • JPEG 的前兩個 Byte 爲 0xFFD8
  • PNG 的前 4 個 Byte 爲 0x89504E47
  • GIF 的前 3 個 Byte 爲 0x474946
  • WEBP 的斷定較爲複雜 能夠對照代碼自行查看

咱們知道平時獲取圖片封裝格式是使用如下的方式ui

val ops = BitmapFactory.Options()
ops.inJustDecodeBounds = true
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.wallpaper, ops)
Log.e("TAG", ops.outMimeType)
複製代碼

Glide 經過直接解析流的方式獲取圖片的封裝格式, 不須要關注其餘信息, 無疑比經過 BitmapFactory 來的更加高效spa

四. 計算 Bitmap 縮放方式

Glid 對於 Bitmap 縮放的計算過程比較複雜, 分別有以下幾步

  • 計算採樣率
  • 計算採樣後圖片的尺寸
  • 將採樣後圖片的尺寸調整爲目標尺寸

一) 計算採樣率

public final class Downsampler {
    
  private static void calculateScaling(
      ImageType imageType,
      InputStream is,
      DecodeCallbacks decodeCallbacks,
      BitmapPool bitmapPool,
      DownsampleStrategy downsampleStrategy,
      int degreesToRotate,
      int sourceWidth,
      int sourceHeight,
      int targetWidth,
      int targetHeight,
      BitmapFactory.Options options) throws IOException {
    ......
    // 1. 計算採樣率 
    // 1.1 獲取源圖片尺寸與目標尺寸的精確縮放比
    // downsampleStrategy 在構建 Request 時傳入
    final float exactScaleFactor;
    if (degreesToRotate == 90 || degreesToRotate == 270) {
      // 1.1.1 將寬高倒置計算縮放因子
      exactScaleFactor = downsampleStrategy.getScaleFactor(sourceHeight, sourceWidth,
          targetWidth, targetHeight);
    } else {
      // 1.1.2 正常計算縮放因子
      exactScaleFactor = downsampleStrategy.getScaleFactor(sourceWidth, sourceHeight, 
          targetWidth, targetHeight);
    }

    // 1.2 獲取採樣的類型: MEMORY(節省內存), QUALITY(更高質量)
    SampleSizeRounding rounding = downsampleStrategy.getSampleSizeRounding(sourceWidth,
        sourceHeight, targetWidth, targetHeight);
    ......
    
    // 1,3 計算縮放因子
    // 1.3.1 計算整型的尺寸(round 操做在原來值的基礎上 + 0.5), 參考 Android 源碼
    int outWidth = round(exactScaleFactor * sourceWidth);
    int outHeight = round(exactScaleFactor * sourceHeight);
    
    // 1.3.2 計算寬高方向上的整型縮放因子
    int widthScaleFactor = sourceWidth / outWidth;
    int heightScaleFactor = sourceHeight / outHeight;
     
     // 1.3.3 根據採樣類型, 肯定整型縮放因子 scaleFactor
     // 若爲 MEMORY, 則爲寬高的最大值
     // 若爲 QUALITY, 則爲寬高的最小值
    int scaleFactor = rounding == SampleSizeRounding.MEMORY
        ? Math.max(widthScaleFactor, heightScaleFactor)
        : Math.min(widthScaleFactor, heightScaleFactor);
    
    // 1.4 根據整型縮放因子, 計算採樣率(即將 scaleFactor 轉爲 2 的冪次)
    int powerOfTwoSampleSize;
    // 1.4.1 Android 7.0 如下不支持縮放 webp, 縮放因子置爲 1 
    if (Build.VERSION.SDK_INT <= 23
        && NO_DOWNSAMPLE_PRE_N_MIME_TYPES.contains(options.outMimeType)) {
      powerOfTwoSampleSize = 1;
    } else {
      // 1.4.2 將 scaleFactor 轉爲 2 的冪次, 若爲省內存模式, 則嘗試近一步增長採樣率
      powerOfTwoSampleSize = Math.max(1, Integer.highestOneBit(scaleFactor));
      if (rounding == SampleSizeRounding.MEMORY && powerOfTwoSampleSize < (1.f / exactScaleFactor)) {
        powerOfTwoSampleSize = powerOfTwoSampleSize << 1;
      }
    }
    ......
  }    
    
}
複製代碼

計算採樣率的過程主要有以下幾步

  • 計算精確的縮放因子
  • 獲取採樣的類型
    • MEMORY: 省內存
    • QUALITY: 高質量
  • 計算整型的縮放因子
  • 將整型縮放因子轉爲 2 的冪次
    • 即轉爲 BitmapFactory 可用的採樣率

二) 計算採樣後圖片尺寸

public final class Downsampler {
    
  private static void calculateScaling(...) throws IOException {
    ......
    // 2. 根據採樣率, 計算採樣後圖片的尺寸
    options.inSampleSize = powerOfTwoSampleSize;
    int powerOfTwoWidth;
    int powerOfTwoHeight;
    // 2.1 處理 JPEG
    if (imageType == ImageType.JPEG) { 
      // Libjpeg 最高支持單次 8 位的降採樣, 超過 8 次則分步計算
      int nativeScaling = Math.min(powerOfTwoSampleSize, 8);
      powerOfTwoWidth = (int) Math.ceil(sourceWidth / (float) nativeScaling);  // 對 float 向上取整
      powerOfTwoHeight = (int) Math.ceil(sourceHeight / (float) nativeScaling);
      // 若 powerOfTwoSampleSize 比 8 大, 則再進行一次採樣, 用於計算出最終的目標值
      int secondaryScaling = powerOfTwoSampleSize / 8;
      if (secondaryScaling > 0) {
        powerOfTwoWidth = powerOfTwoWidth / secondaryScaling;
        powerOfTwoHeight = powerOfTwoHeight / secondaryScaling;
      }
    //2.2 處理 PNG
    } else if (imageType == ImageType.PNG || imageType == ImageType.PNG_A) {
      // 對採樣結果向下取整
      powerOfTwoWidth = (int) Math.floor(sourceWidth / (float) powerOfTwoSampleSize);
      powerOfTwoHeight = (int) Math.floor(sourceHeight / (float) powerOfTwoSampleSize);
    // 2.3 處理 WEBP
    } else if (imageType == ImageType.WEBP || imageType == ImageType.WEBP_A) {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        // 7.0 以上對採樣結果進行四捨五入
        powerOfTwoWidth = Math.round(sourceWidth / (float) powerOfTwoSampleSize);
        powerOfTwoHeight = Math.round(sourceHeight / (float) powerOfTwoSampleSize);
      } else {
        // 7.0 如下, 對採樣結果向下取整
        powerOfTwoWidth = (int) Math.floor(sourceWidth / (float) powerOfTwoSampleSize);
        powerOfTwoHeight = (int) Math.floor(sourceHeight / (float) powerOfTwoSampleSize);
      }
    // 2.4 處理其餘圖片類型, 而且須要降採樣
    } else if (
        sourceWidth % powerOfTwoSampleSize != 0 || sourceHeight % powerOfTwoSampleSize != 0) {
      // 經過 Android 的 BitmapFactory 去獲取尺寸
      int[] dimensions = getDimensions(is, options, decodeCallbacks, bitmapPool); 
      powerOfTwoWidth = dimensions[0];
      powerOfTwoHeight = dimensions[1];
    // 2.5 處理其餘圖片類型, 而且不須要降採樣
    } else {
      // 若爲其餘圖片類型, 而且不須要降採樣
      powerOfTwoWidth = sourceWidth / powerOfTwoSampleSize;
      powerOfTwoHeight = sourceHeight / powerOfTwoSampleSize;
    }
    ......
  }    
    
}
複製代碼

計算採樣尺寸, Glide 並無直接將採樣率放入 options.inSampleSize 而是根據規則自行進行了運算, 下降了使用 BitmapFactory 調用 native 方法帶來的性能損耗

三) 將採樣後圖片的尺寸調整爲目標尺寸

public final class Downsampler {
    
  private static void calculateScaling(...) throws IOException {
    ......
    // 3. 將採樣尺寸調整成爲目標尺寸
    // 3.1 計算採樣尺寸與目標尺寸的縮放因子
    double adjustedScaleFactor = downsampleStrategy.getScaleFactor(
        powerOfTwoWidth, powerOfTwoHeight, targetWidth, targetHeight);

    // 3.2 經過調整 inTargetDensity 和 inDensity 來完成目標的顯示效果
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      // 調整目標的屏幕密度
      options.inTargetDensity = adjustTargetDensityForError(adjustedScaleFactor);
      // 調整圖片的像素密度
      options.inDensity = getDensityMultiplier(adjustedScaleFactor);
    }
    if (isScaling(options)) {
      options.inScaled = true;
    } else {
      options.inDensity = options.inTargetDensity = 0;
    }
  }    
    
}
複製代碼

能夠看到將採樣尺寸調整成爲目標尺寸是經過調整 options 中 inTargetDensity 和 inDensity 的值, 來讓圖片縮放到目標顯示效果尺寸的

好的, 到這裏 Glide 計算 Bitmap 縮放的部分就解析完畢了, 咱們光知道 Glide 默認會將圖片加載的尺寸置爲 ImageView 的大小, 殊不知道它爲了還原的精度, 內部作了如何之多的細節處理, 其縝密性可見一斑

五. 選擇顏色通道

public final class Downsampler {
    
  private void calculateConfig(
      InputStream is,
      DecodeFormat format,
      boolean isHardwareConfigAllowed,
      boolean isExifOrientationRequired,
      BitmapFactory.Options optionsWithScaling,
      int targetWidth,
      int targetHeight) {

    ......
    // 判斷是否有 Alpha 通道
    boolean hasAlpha = false;
    try {
      hasAlpha = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool).hasAlpha();
    } catch (IOException e) {
      ......
    }
    // 若存在 Alpha 通道則使用 RGB_8888, 反之使用 565
    optionsWithScaling.inPreferredConfig =
        hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
    if (optionsWithScaling.inPreferredConfig == Config.RGB_565) {
      optionsWithScaling.inDither = true;
    }
  }
    
}
複製代碼

好的, Bitmap 顏色通道的選取方式仍是很是簡單的

  • 對於存在透明通道的圖片, 使用 ARGB_8888 保證圖片不會丟失透明通道
  • 對於無透明通道圖片, 使用 RGB_565 保證圖片內存佔用量最低

總結

到這裏 Glide 將數據流解析成爲 Bitmap 的流程就完成了, 其中提供了很是優秀的將圖片採樣壓縮的實現顏色通道的選取策略, 這都很是值得咱們學習和借鑑

相關文章
相關標籤/搜索