Android JPEG 压缩那些事

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

  • 质量

这个参数会影响 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 个最近像素的加权平均值的结果。

除了以上两种,还有以下几种效果的更好的算法:

  • Bicubic(双立方采样)

重新采样的栅格中的每个像素都是原始栅格中 4x4 16 个最近像素值的加权值的结果,更接近的像素会有更高的权重。

  • Lanczos

高阶插值算法,它考虑了更多周围像素,并保留了最多的图像信息。

  • Magic Kernel

快速又高效,却能产生惊人的清晰和锐利的结果,更详细的介绍:www.johncostella.com/magic/。

Spectrum


Spectrum 是 Facebook 开源的跨平台图像转码依赖库,与 Android 系统默认自带的 jpeg-turbo 相对,它有以下优势:

  • JPEG 编码基于 mozjpeg,相对于 jpeg-turbo,它提高了压缩率,但也增加了压缩处理时间。

  • 支持 Bicubic(双立方采样)和 Magic Kernel 采样算法。

  • 核心使用 CPP 实现,可以同时在 Android 和 iOS 平台实现一致的压缩效果。

  • 支持更多自定义配置,包括色度采样模式等等。

基准测试


基于 google/butteraugli 来比较原图像和压缩图像之间的质量差异,这个数值越小越好。

设备信息:华为 P20 Pro,Android 10

A 压缩质量 80

| 核心 | 压缩质量 | 色度采样模式 | 质量差异 | 文件大小 | 耗时 | 压缩率 |

| — | — | — | — | — | — | — |

| 原图 | - | S444 | - | 8.7MB | - | - |

| jpeg-turbo | 80 | S444 | 2.943352 | 2.5MB | 2255ms | 71% |

| mozjpeg | 80 | S444 | 2.486266 | 2.8MB | 3567ms | 67% |

| mozjpeg | 80 | S420 | 2.493475(-15%) | 2.3MB | 2703ms | 73%(+2%) |

B 压缩质量 75

| 核心 | 压缩质量 | 色度采样模式 | 质量差异 | 文件大小 | 耗时 | 压缩率 |
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后如何才能让我们在面试中对答如流呢?

答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?有没有免费资料可以借鉴?为此我整理了一份Android学习资料路线:

这里是一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套BAT大厂面试资料专题包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家。

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

最后,祝愿即将跳槽和已经开始求职的大家都能找到一份好的工作!

这些只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试Android岗位的门槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢再关注一下

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

确的学习,有方向的学习呢?有没有免费资料可以借鉴?为此我整理了一份Android学习资料路线:

[外链图片转存中…(img-4c7B4gHG-1712506442198)]

这里是一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套BAT大厂面试资料专题包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家。

[外链图片转存中…(img-OXPkTZdY-1712506442198)]

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

最后,祝愿即将跳槽和已经开始求职的大家都能找到一份好的工作!

这些只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试Android岗位的门槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢再关注一下

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值