Android JPEG 压缩那些事

只保留 Cr 分量

Cr

利用这个特性,可以对 Y’CbCr 颜色空间做进一步的下采样,即降低 CbCr 分量的空间分辨率。

下采样率为 “4:4:4” 表示不进行下采样;

下采样率为 “4:2:2” 表示水平方向上减少 2 倍

下采样率为 “4:2:0” 表示水平和垂直方向上减少 2 倍(最常用)

image-20210411164013275

下采样率通常表示为三部分比率 j:a:b,如果存在透明度则为四部分,这描述了 j个像素宽和 2 个像素高的概念区域中亮度和色度样本的数量。

  • j 表示水平方向采样率参考(概念区域的宽度)
  • a 表示第一行的色差采样(Cr,Cb)
  • b 表示第二行和第一行的色差采样(Cr,Cb)变化
  • 块分割

在下采样之后,每个通道必须被分割为 8x8 的像素块,最小编码单位(MCU) 取决于使用的下采样。

如果下采样率为 “4:4:4”,则最小编码单位块的大小为 8x8;

如果下采样率为 “4:2:2”,则最小编码单位块的大小为 16x8;

如果下采样率为 “4:2:0”,则最小编码单位块的大小为 16x16;

image-20210509221734480

如果通道数据不能被切割为整数倍的块,则通常会使用纯色去填充,例如黑色。

  • 离散余弦变换

  • 量化

人眼善于在相对较大的区域看到较小的亮度差异,但不能很好地区分高频亮度变化的确切强度。这使得人们可以大大减少高频分量中的信息量。只需将频域中的每个分量除以该分量的常量,然后四舍五入(有损运算)为最接近的整数即可。

这个步骤是不可逆的

  • 使用无损算法(霍夫曼编码的一种变体)进一步压缩所有 8×8 块的数据。

JEPG 压缩效果

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

| — | — | — | — |

| JPEG example JPG RIP 100.jpg | 最高质量(100) | 81447 | 2.7:1 |

| JPEG example JPG RIP 050.jpg | 高质量(50) | 14679 | 15:1 |

| JPEG example JPG RIP 025.jpg | 中等质量(25) | 9407 | 23:1 |

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

| JPEG example JPG RIP 001.jpg | 最低质量(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 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。

image-20210418224749597

四. 分配像素内存

调用 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 是核心实现。

image-20210419142507664

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

最后

其实要轻松掌握很简单,要点就两个:

  1. 找到一套好的视频资料,紧跟大牛梳理好的知识框架进行学习。
  2. 多练。 (视频优势是互动感强,容易集中注意力)

你不需要是天才,也不需要具备强悍的天赋,只要做到这两点,短期内成功的概率是非常高的。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

阿里P7Android高级教程

下面资料部分截图,诚意满满:特别适合有3-5年开发经验的Android程序员们学习。

附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、近期面试跳槽、自身职业规划迷茫的朋友们。

Android核心高级技术PDF资料,BAT大厂面试真题解析;

mpression at the cost of

// slower encode performance.

fCInfo.optimize_coding = TRUE;

最后

其实要轻松掌握很简单,要点就两个:

  1. 找到一套好的视频资料,紧跟大牛梳理好的知识框架进行学习。
  2. 多练。 (视频优势是互动感强,容易集中注意力)

你不需要是天才,也不需要具备强悍的天赋,只要做到这两点,短期内成功的概率是非常高的。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

阿里P7Android高级教程

下面资料部分截图,诚意满满:特别适合有3-5年开发经验的Android程序员们学习。

[外链图片转存中…(img-gjhckoJA-1725877740567)]

附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、近期面试跳槽、自身职业规划迷茫的朋友们。

Android核心高级技术PDF资料,BAT大厂面试真题解析;
[外链图片转存中…(img-oqDNtwBk-1725877740568)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值