文章目录
- 一、 图片质量压缩方法
- 二、 Skia 二维图形库
- 三、 libjpeg、libpng 函数库引入
在博客 【Android 内存优化】图片文件压缩 ( Android 原生 API 提供的图片压缩功能能 | 图片质量压缩 | 图片尺寸压缩 ) 简要介绍了 图片文件压缩格式 , 以及 Android 提供的图片质量 , 尺寸压缩原生 API ;
在博客 【Android 内存优化】Android 原生 API 图片压缩代码示例 ( PNG 格式压缩 | JPEG 格式压缩 | WEBP 格式压缩 | 动态权限申请 | Android10 存储策略 ) 主要使用了上述 Android 原生 API 压缩图片功能进行图片压缩 ;
在博客 【Android 内存优化】Android 原生 API 图片压缩原理 ( 图片质量压缩方法 | 查找 Java 源码中的 native 方法对应的 C 源码 ) 中主要查找 Bitmap.java 对应的 Native 层的 C 类 Bitmap.cpp 源码文件 , 并分析了其动态注册 Native 方法的过程 ;
本博客中将分析 Bitmap.cpp 中的源码 ;
一、 图片质量压缩方法
Java 对应方法 :
参数分析 :
- long nativeBitmap 参数 : Native 层的 Bitmap 指针 ;
- int format 参数 : 压缩格式格式 ;
- int quality 参数 : 压缩质量 ;
- OutputStream stream 参数 : 输出流 ;
- byte[] tempStorage 参数 : 暂时的存储区 ;
private static native boolean nativeCompress(long nativeBitmap, int format,
int quality, OutputStream stream,
byte[] tempStorage);
C 对应方法 :
参数分析 :
- jlong bitmapHandle 参数 : Native 层的 Bitmap 指针 ;
- jint format 参数 : 压缩格式格式 ;
- jint quality 参数 : 压缩质量 ;
- jobject jstream 参数 : 输出流 ;
- jbyteArray jstorage 参数 : 暂时的存储区 ;
static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle,
jint format, jint quality,
jobject jstream, jbyteArray jstorage) {
// 获取 Bitmap 指针
SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
SkImageEncoder::Type fm;
// 判断图片压缩格式 , 给 SkImageEncoder::Type fm 局部变量赋值
switch (format) {
// JPEG 格式
case kJPEG_JavaEncodeFormat:
fm = SkImageEncoder::kJPEG_Type;
break;
// PNG 格式
case kPNG_JavaEncodeFormat:
fm = SkImageEncoder::kPNG_Type;
break;
// WEBP 格式
case kWEBP_JavaEncodeFormat:
fm = SkImageEncoder::kWEBP_Type;
break;
// 如果传入未知格式 , 直接返回 错误信息
default:
return JNI_FALSE;
}
bool success = false;
if (NULL != bitmap) {
SkAutoLockPixels alp(*bitmap);
if (NULL == bitmap->getPixels()) {
return JNI_FALSE;
}
SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
if (NULL == strm) {
return JNI_FALSE;
}
// 创建图片编码器 , 需要根据传入的 fm 编码类型创建
SkImageEncoder* encoder = SkImageEncoder::Create(fm);
if (NULL != encoder) {
// ★ 这是图片压缩的核心方法
success = encoder->encodeStream(strm, *bitmap, quality);
delete encoder;
}
delete strm;
}
return success ? JNI_TRUE : JNI_FALSE;
}
源码位置 frameworksbasecorejniandroidgraphicsBitmap.cpp
上述 Bitmap.cpp 中的 Bitmap_compress 方法中 , 最终调用的 SkImageEncoder 的 encodeStream 方法 ;
SkImageEncoder 不是最终调用的类 , 而是根据不同的图片压缩格式 , 调用对应的类 , 如果最终压缩格式是 JPEG 格式 , 那么就会调用 SkJPEGImageEncoder 方法 ,
在下面的 SkImageEncoder.h 中声明了 SkImageEncoder 类 , 特别注意下面定义的 virtual bool onEncode 方法 , 是虚函数 , 需要在子类中实现该函数 ;
代码语言:javascript复制#ifndef SkImageEncoder_DEFINED
#define SkImageEncoder_DEFINED
#include "SkTypes.h"
#include "SkTRegistry.h"
class SkBitmap;
class SkData;
class SkWStream;
class SkImageEncoder {
public:
enum Type {
kUnknown_Type,
kBMP_Type,
kGIF_Type,
kICO_Type,
kJPEG_Type,
kPNG_Type,
kWBMP_Type,
kWEBP_Type,
kKTX_Type,
};
static SkImageEncoder* Create(Type);
virtual ~SkImageEncoder();
/* Quality ranges from 0..100 */
enum {
kDefaultQuality = 80
};
SkData* encodeData(const SkBitmap&, int quality);
bool encodeFile(const char file[], const SkBitmap& bm, int quality);
bool encodeStream(SkWStream* stream, const SkBitmap& bm, int quality);
static SkData* EncodeData(const SkBitmap&, Type, int quality);
static bool EncodeFile(const char file[], const SkBitmap&, Type,
int quality);
static bool EncodeStream(SkWStream*, const SkBitmap&, Type,
int quality);
protected:
// 特别注意 : 该函数是个虚函数 , 需要在子类实现中实现该方法
virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) = 0;
};
源码位置 externalskiaincludecoreSkImageEncoder.h
在 SkImageEncoder.cpp 中实现了上述方法 , 其中压缩文件的方法 SkImageEncoder::encodeStream , 在该方法中调用了 onEncode 方法 , 该函数是虚函数 , 需要在子类冲实现 ;
代码语言:javascript复制#include "SkImageEncoder.h"
#include "SkBitmap.h"
#include "SkStream.h"
#include "SkTemplates.h"
SkImageEncoder::~SkImageEncoder() {}
// 在该方法中调用了 onEncode 虚函数方法 , 该方法需要在子类中实现
bool SkImageEncoder::encodeStream(SkWStream* stream, const SkBitmap& bm,
int quality) {
quality = SkMin32(100, SkMax32(0, quality));
return this->onEncode(stream, bm, quality);
}
// ... 省略部分代码
源码位置 externalskiasrcimagesSkImageEncoder.cpp
下面的 SkJPEGImageEncoder 类是 SkImageEncoder 子类 , 该类主要处理 JPEG 格式编码操作 ; 在重写的 onEncode 方法中 , 主要使用 libjpeg 函数库实现 JPEG 图像编码 ;
代码语言:javascript复制// SkJPEGImageEncoder 是 SkImageEncoder 子类 , 共有继承
class SkJPEGImageEncoder : public SkImageEncoder {
protected:
// 该方法中调用了大量 libjpeg 库的函数
virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) {
#ifdef TIME_ENCODE
SkAutoTime atm("JPEG Encode");
#endif
SkAutoLockPixels alp(bm);
if (NULL == bm.getPixels()) {
return false;
}
jpeg_compress_struct cinfo;
skjpeg_error_mgr sk_err;
skjpeg_destination_mgr sk_wstream(stream);
// allocate these before set call setjmp
SkAutoMalloc oneRow;
SkAutoLockColors ctLocker;
cinfo.err = jpeg_std_error(&sk_err);
sk_err.error_exit = skjpeg_error_exit;
if (setjmp(sk_err.fJmpBuf)) {
return false;
}
// Keep after setjmp or mark volatile.
const WriteScanline writer = ChooseWriter(bm);
if (NULL == writer) {
return false;
}
jpeg_create_compress(&cinfo);
cinfo.dest = &sk_wstream;
cinfo.image_width = bm.width();
cinfo.image_height = bm.height();
cinfo.input_components = 3;
#ifdef WE_CONVERT_TO_YUV
cinfo.in_color_space = JCS_YCbCr;
#else
cinfo.in_color_space = JCS_RGB;
#endif
cinfo.input_gamma = 1;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
#ifdef DCT_IFAST_SUPPORTED
cinfo.dct_method = JDCT_IFAST;
#endif
jpeg_start_compress(&cinfo, TRUE);
const int width = bm.width();
uint8_t* oneRowP = (uint8_t*)oneRow.reset(width * 3);
const SkPMColor* colors = ctLocker.lockColors(bm);
const void* srcRow = bm.getPixels();
while (cinfo.next_scanline < cinfo.image_height) {
JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
writer(oneRowP, srcRow, width, colors);
row_pointer[0] = oneRowP;
(void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
srcRow = (const void*)((const char*)srcRow bm.rowBytes());
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
return true;
}
};
源码位置 externalskiasrcimagesSkImageDecoder_libjpeg.cpp
二、 Skia 二维图形库
Skia 是 C 开源二维图形库 , 用于操作二维图形 , 提供一系列 2D 图形处理 API , 在 Chrom 浏览器 , 安卓手机 , 狐火浏览器中使用该图形库作为二维图形引擎 ;
Skia 相关网址 :
- 官方网站 , 国内无法访问 ;
- 源码地址 , 国内无法访问 ;
- GitHub 源码镜像
三、 libjpeg、libpng 函数库引入
libjpeg、libpng 函数库引入 : Android 中的 Bitmap 就使用到了 Skia 引擎 , Android 中的 Skia 功能不全 , 经过删减了 ;
- 处理 JPEG 格式图像基于 libjpeg 函数库 ;
- 处理 PNG 格式图形基于 libpng 函数库 ;