概述
之前在网络上搜索Android图片压缩,能看到各种各样的关于图片压缩的文章,本没有必要再写一篇,但是最近的一个需求真是折腾了我很久,本来以为很简单的事情,愣是搞了好几天。这里记录下我完成的整个过程,作为一个笔记供以后查阅。如果读友觉得有帮助,就?下
需求分析
需求(额呵,这个肯定不是客户的需求):在即时通讯发消息的场景中,需要发送的图片保持清晰度的同时压缩一定的比例,最好不要超过1M。
衍生需求(一看就是后台小哥的臆想):上传到服务器的图片要适当的压缩,不然服务器撑不住。
附加需求:(无力吐槽)最好能做个sdk供调用
分析下:就是个图片压缩嘛,简单
private Bitmap compressImage(Bitmap image) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
int options = 100;
//循环判断如果压缩后图片是否大于100kb,大于继续压缩
while (baos.toByteArray().length / 1024 > 100) {
baos.reset();
image.compress(Bitmap.CompressFormat.JPEG, options, baos);
//每次都减少10
options -= 10;
}
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
return BitmapFactory.decodeStream(isBm, null, null);
}
试了试,好像不行,图片压缩了之后会失真,而且用起来不是那么顺手
上述代码中Bitmap.CompressFormat.JPEG可以更换为CompressFormat.PNG和CompressFormat.WEBP
PNG 格式是无损的,它无法再进行质量压缩,options 这个参数就没有作用了,会被忽略,图片并不会缩小
CompressFormat.WEBP ,这个格式是 google 推出的图片格式,它会比 JPEG 更加省空间,但是IOS好像兼容有些问题,还是JPEG靠谱
到网上搜搜,我的天,各种图片压缩的文章,看得的我眼花缭乱。
这里放个原图,然后继续分析
原图大小:116 KB
各种压缩方式
-
质量压缩
-
采样率压缩
-
按比例大小压缩
libjepg-turbo+jnigraphics
介绍
翻译一下官网的描述
libjpeg-turbo是一种jpeg图像编解码器,它使用simd指令(mmx、sse2、avx2、neon、altivec)加速x86、x86-64、ARM和PowerPC系统上的Baseline JPEG(标准型)压缩和解压缩,以及x86和x86-64系统上的渐进式progressive JPEG(渐进式)压缩。在这些系统中,libjpeg-turbo的速度通常是libjpeg的2-6倍,其他的都是相同的。在其他类型的系统上,libjpeg-turbo由于其高度优化的哈夫曼编码例程,仍然可以在很大程度上超过libjpeg 。在许多情况下,libjpeg-turbo的性能与专有的高速JPEG编解码器的性能相当。
libjpeg-turbo最初基于libjpeg/simd,这是 Miyasaka Masaru 开发的libjpeg v6b的MMX(多媒体扩展)加速衍生产品。
2009年,在TigerVnc和VirtualGL项目中对编解码器进行了大量改进,2010年初,libjpeg turbo被拆分成了一个独立的项目,其目标是为更广泛的用户和开发人员提供高速的jpeg压缩/解压缩技术。
编译
github编译教程:https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/BUILDING.md
刚开始我也是用linux编译,使用像下面的编译脚本可以编译成功
#!/bin/bash
NDK_PATH=/home/shuai/Android/android-ndk-r13
TOOLCHAIN=gcc
ANDROID_VERSION=17
cd libjpeg-turbo-2.0.2
cmake -G "Unix Makefiles" \
-DANDROID_ABI=armeabi-v7a \
-DANDROID_ARM_MODE=arm \
-DANDROID_PLATFORM=android-${ANDROID_VERSION} \
-DANDROID_TOOLCHAIN=${TOOLCHAIN} \
-DCMAKE_ASM_FLAGS="--target=arm-linux-androideabi${ANDROID_VERSION}" \
-DCMAKE_TOOLCHAIN_FILE=${NDK_PATH}/build/cmake/android.toolchain.cmake \
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=android/arm-v7a \
-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=android/arm-v7a \
make
后来发现,可以直接在android studio中直接编译通过,真是开心的像廋了10斤一样
新建带jni的Android项目,将libjpeg-turbo整个项目直接拷贝到jni目录下,然后在CmakeLists.txt中加入这个
cmake_minimum_required(VERSION 3.4.1)
add_library(
native-jpeg
SHARED
native-jpeg.cpp)
# 编译子项目(源码路径)
ADD_SUBDIRECTORY(libjpeg-turbo-2.0.2)
find_library(
log-lib
log)
target_link_libraries(
native-jpeg
${log-lib})
build之后妥妥可以拿到动态和静态库
同样的方法,我又编译了libpng的代码
具体请参考:https://github.com/ddssingsong/NdkImage,下载项目直接运行,里面还有使用demo
使用
打造一个图片压缩框架
// 压缩bitmap
extern "C"
JNIEXPORT void JNICALL
Java_com_dds_ndkimage_NativeImageUtils_compressBitmap(JNIEnv *env, jclass type, jobject bitmap,jint q,jstring p_) {
if (bitmap == NULL) {
LOGE("bitmap is null");
return;
}
const char *path = env->GetStringUTFChars(p_, 0);
AndroidBitmapInfo info;
// 获取bitmap中信息
AndroidBitmap_getInfo(env, bitmap, &info);
// 获取图片中的像素信息
uint8_t *pixels;
AndroidBitmap_lockPixels(env, bitmap, (void **) (&pixels));
int w = info.width;
int h = info.height;
int color;
//data中可以存放图片的所有内容
uint8_t *data = (uint8_t *) malloc((size_t) (w * h * 3));
uint8_t *temp = data;
uint8_t r, g, b;//byte
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
color = *(int *) pixels;
//取出rgb
r = (uint8_t) ((color >> 16) & 0xFF);// #00rrggbb 16 0000rr 8 00rrgg
g = (uint8_t) ((color >> 8) & 0xFF);
b = (uint8_t) (color & 0xFF);
*data = b;
*(data + 1) = g;
*(data + 2) = r;
data += 3;
//指针跳过4个字节
pixels += 4;
}
}
//把得到的新的图片的信息存入一个新文件 中
jpegcompress::write_JPEG_file(temp, w, h, q, path);
//释放内存
free(temp);
AndroidBitmap_unlockPixels(env, bitmap);
env->ReleaseStringUTFChars(p_, path);
}
void jpegcompress::write_JPEG_file(uint8_t *data, int w, int h, jint q, const char *path) {
//3.1、创建jpeg压缩对象
jpeg_compress_struct jcs;
//错误回调
jpeg_error_mgr error;
jcs.err = jpeg_std_error(&error);
//创建压缩对象
jpeg_create_compress(&jcs);
//3.2、指定存储文件 write binary
FILE *f = fopen(path, "wb");
jpeg_stdio_dest(&jcs, f);
//3.3、设置压缩参数
jcs.image_width = (JDIMENSION) (w);
jcs.image_height = (JDIMENSION) h;
//bgr
jcs.input_components = 3;
jcs.in_color_space = JCS_RGB;
jpeg_set_defaults(&jcs);
//开启哈夫曼功能
jcs.optimize_coding = true;
jpeg_set_quality(&jcs, q, 1);
//4 开始压缩
jpeg_start_compress(&jcs, 1);
// 5 循环写入每一行数据
int row_stride = w * 3;//一行的字节数
JSAMPROW row[1];
while (jcs.next_scanline < jcs.image_height) {
//取一行数据
uint8_t *pixels = data + jcs.next_scanline * row_stride;
row[0] = pixels;
jpeg_write_scanlines(&jcs, row, 1);
}
//6 压缩完成
jpeg_finish_compress(&jcs);
//7 释放jpeg对象
fclose(f);
jpeg_destroy_compress(&jcs);
}
详见:https://github.com/ddssingsong/NdkImage
引用文章
Android 中图片压缩分析https://www.cnblogs.com/qcloud1001/p/7827133.html)https://www.cnblogs.com/qcloud1001/p/7827133.html