Android性能优化——图片压缩的终极选择libjpeg-turbo

概述

之前在网络上搜索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

各种压缩方式

  1. 质量压缩

  2. 采样率压缩

  3. 按比例大小压缩

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

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ddssingsong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值