写点什么

Android JPEG 压缩那些事,深入解析 Android-AutoLayout

用户头像
Android架构
关注
发布于: 刚刚

| 图片 | 质量([1,100]) | 大小(bytes) | 压缩比例 |


| --- | --- | --- | --- |


|



| 最高质量(100) | 81447 | 2.7:1 |


|



| 高质量(50) | 14679 | 15:1 |


|



| 中等质量(25) | 9407 | 23:1 |


|



| 低质量(10) | 4787 | 46:1 |


|



| 最低质量(1) | 1523 | 144:1 |

JEPG 编码实现


广泛使用的 C 库,用于读取和写入 JPEG 图像文件。



高性能的 JEPG 图像解编码器,使用 SIMD 指令来加速在 x86、x86-64、Arm 和 PowerPC 系统上的 JEPG 文件压缩和解压缩,以及在 x86、x86-64 系统上的渐进式压缩。


在 x86 和 x86-64 系统上,libjpeg-turbo 的速度是 libjpeg 的 2-6 倍,在其他系统上,也能大大优于 libjpeg。


Android 图像解码




Android 上展示一张图像,都需要将图像解码成 Bitmap 对象,Bitmap 表示图像像素的集合,像素占用的内存大小取决 Bitmap 配置,目前 Android 支持的配置有如下:


  • ALPHA_8


只存储透明度通道


  • ARGB_4444


每个像素使用 2 字节存储


  • ARGB_8888


每个像素使用 4 字节存储(默认)


  • HARDWARE


特殊配置,Bitmap 数据存储在专门的图形内存(Native)


  • RGBA_F16


每个像素使用 8 字节存储


  • RGB_565


每个像素使用 2 字节存储,只有 RGB 通道。

源码解析

通常我们可以调用 BitmapFactory.decodeStream 方法从图像流中解码,Java 层只是个简单的入口,相关实现都在 Native 层的 BitmapFactory.doDecode 方法中。


// frameworks/base/libs/hwui/jni/BitmapFactory.cpp


static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,jobject padding, jobject options, jlong inBitmapHandle,jlong colorSpaceHandle) {


// ...


}

一. 初始化相关参数

  • sampleSize


采样率


  • onlyDecodeSize


是否只解码尺寸


  • prefCodeType


优先使用的颜色类型


  • isHardware


是否存储在专门的图像内存


  • isMutable


是否可变


  • scale


缩放系数


  • requireUnpremultiplied


颜色通道是否不需要"预乘"透明通道


  • javaBitmap


可复用的 Bitmap

二. 创建解码器

根据解码的图像格式,创建不同的解码器 SkCodec。


| 图像格式 | SkCodec |


| --- | --- |


| JPEG | SkJpegCodec |


| WebP | SkWebpCodec |


| Gif | SkGifCodec |


| PNG | SkPngCodec |


SkCodec 负责核心实现,SkAndroidCodec 则是 SkCodec 的包装类,用于提供一些 Android 特有的 API。同样的,SkAndroidCodec 也是根据图像格式,创建不同的 SkAndroidCodec。


| 图像格式 | SkAndroidCodec |


| --- | --- |


| JPEG,PNG,Gif | SkSampledCodec |


| WebP | SkAndroidCodecadapter |

三. 创建内存分配器

根据是否存在可复用的 Bitmap,和是否需要缩放,使用不同的内存分配器 Allocator。


四. 分配像素内存

调用 SkBitmap.tryAllocPixels 方法尝试分配所需的像素内存,存在以下情况,可能会导致分配失败。


  • Java Heap OOM

  • Native Heap OOM

  • 使用的可复用 Bitmap 太小

五. 执行解码

调用 SkAndroidCodec.getAndroidPixels 方法开始执行编码操作。


SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& requestInfo,


void* requestPixels, size_t requestRowBytes, const AndroidOptions* options) {


// ...


return this->onGetAndroidPixels(requestInfo,requestPixels,requestRowBytes,*options);


}


SkAndroid.onGetAndroidPixels 方法有两个实现,分别是 SkSampledCodec 和 SkAndroidCodecadapter。


这里我们以 JPEG 图像解码为例,从上文可知,它使用的是 SkSampledCodec 和 SkJpegCodec,SkJpegCodec 是核心实现。



Android 除了支持使用 BitmapFactory 进行完整的解码,也支持使用 BitmapRegionDecoder 进行局部解码,这个在处理特大的图像时特别有用。


Android JPEG 压缩




Android 在图像压缩上一直有个令人诟病的问题,同等大小的图像文件,iOS 显示上总是更加细腻,也就是压缩效果更好,关于这个问题更详细的讨论,可以看这篇文章:github.com/bither/bith…


总的来说,就是 Android 底层使用的自家维护的一个开源 2D 渲染引擎 Skia,Skia 在 JPEG 图像文件的解编码上依赖的是 libjpeg 库,libjpeg 压缩参数叫:optimize_coding,这个参数为 TRUE,可以带来更好的压缩效果,同时也会消耗更多的 时间。


在 7.0 以下,Google 为了兼容性能较差的设备,而将这个值设置为 FALSE,7.0 及其以上,已经设置为 TRUE。


关于 optimize_coding 为 FALSE,更多的讨论可以看 groups.google.com/g/skia-disc…



7.0 以下:androidxref.com/6.0.1_r10/x…



7.0 及其以上:androidxref.com/7.0.0_r1/xr…


所以,现在比较主流的做法是,在 7.0 以下版本,可以基于 libjpeg-turbo 实现 JPEG 图像文件的压缩。

源码解析

可以通过调用 Bitmap.compress 方法来进行图像压缩,可选配置有:


  • format


压缩图像格式,有 JPEG、PNG、WEBP。


  • quality


压缩质量,可选值有 0-100。


同样的,Java 层只是提供 API 入口,实现还是在 Native 层的 Bitmap.Bitmap_comperss() 方法。


// framework/base/libs/hwui/jni/Bitmap.cpp


static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle,jint format, jint quality,jobject jstream, jbyteArray jstorage) {


}

一. 创建编码器

根据图像格式创建不同的编码器。


| 图像格式 | 编码器 |


| --- | --- |


| JPEG | SkJpegEncoder |


| PNG | SkPngEnccoder |


| WebP | SkWebpEncoder |

二. 设置编码参数

Android JPEG 解码是依赖于 libjpeglibjpeg-turbo


在开始压缩编码之前,会先设置一系列参数。


  • 图像尺寸

  • 颜色类型


常用的颜色类型有:


JCS_EXT_BGRA, /* blue/green/red/alpha */


JCS_EXT_


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


BGRA, /* blue/green/red/alpha */


  • 下采样率


目前 Android 支持 “4:2:0”(默认),“4:2:2” 和 “4:4:4”。


  • 最佳霍夫曼编码表


默认为 true,表示使用最佳霍夫曼编码表,虽然会降低压缩性能,但提高了压缩效率。


// Tells libjpeg-turbo to compute optimal Huffman coding tables


// for the image. This improves compression at the cost of


// slower encode performance.


fCInfo.optimize_coding = TRUE;


  • 质量


这个参数会影响 JPEG 编码中 “量化” 这个步骤

三. 执行编码

// external/skia/src/imagess/SkImageEncoder.cpp


bool SkEncoder::encodeRows(int numRows) {


// ...


this->onEncodeRows(numRows);


}


JPEG 图像编码由 SkJpegEncoder 实现。


// txternal/skia/src/images/SkJpegEncoder.cpp


bool SkJpegEncoder::onEncodeRows(int numRows) {


// ...


for (int i = 0; i < numRows; i++) {


// 执行 libjpeg-turbo 编码操作


jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1);


}


}


采样算法




当调整图像的尺寸时,就需要对原始图像像素数据进行重新处理,这称为图像的采样处理。


目前 Android 默认支持 Nearest neighbor(邻近采样)Bilinear(双线性采样) 这两种采样算法。


  • Nearest neighbor(邻近采样)


重新采样的栅格中每个像素获取与原始栅格中的最近像素相同的值,这个处理时间是最快的,但也会导致图像产生锯齿。


  • Bilinear(双线性采样)


重新采样的栅格中的每个像素都是原始栅格中 2x2 4 个最近像素的加权平均值的结果。

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android JPEG 压缩那些事,深入解析Android-AutoLayout