美文网首页Android技术知识Android开发Android开发积累
Glide 4.9 源码分析(二) —— 采样压缩的实现

Glide 4.9 源码分析(二) —— 采样压缩的实现

作者: SharryChoo | 来源:发表于2019-04-04 16:57 被阅读0次

    前言

    从 Glide 的一次加载流程中可知, Glide 拿到数据流之后, 使用 Downsampler 进行采样处理并且反回了一个 Bitmap

    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 解析一次数据流做了很多的操作, 我们对重点的操作进行逐一分析

    二. 通过数据流获取图像宽高

    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;
      }
      
    }
    

    具体的流程如上所示, 其中还是有很多细节值得我们参考和学习

    • 在解析 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 对象创建时注册的

    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 的判定较为复杂 可以对照代码自行查看

    我们知道平时获取图片封装格式是使用以下的方式

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

    Glide 通过直接解析流的方式获取图片的封装格式, 不需要关注其他信息, 无疑比通过 BitmapFactory 来的更加高效

    四. 计算 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 的流程就完成了, 其中提供了非常优秀的将图片采样压缩的实现颜色通道的选取策略, 这都非常值得我们学习和借鉴

    相关文章

      网友评论

        本文标题:Glide 4.9 源码分析(二) —— 采样压缩的实现

        本文链接:https://www.haomeiwen.com/subject/sxsqiqtx.html