抱歉,你查看的文章不存在

一种JNI方法实现图片压缩,压缩率极高

直接使用项目或直接复制libs中的so库到项目中即可(当前只构建了armeabi),需要其他ABI可检下项目另外使用CMake构建即可。

结果预览:

原图大小5.99M~~ 我们把所有经过压缩的图片放到同等大小的情况后,很明显,采样压缩跟尺寸压缩都不是我们想要的结果,而质量压缩跟JNI压缩我设置的质量压缩值都是30,JNI压缩出来只有278KB,直接质量压缩出来的有484KB,综合之后,JNI才是综合最优的方式,当然,如果只是头像,我们设置可以把配置值设置得更小,图片就更小。

为什么iPhone手机图片的质量比Android的好?

首先了解两个图像处理库:libjpeg、Skia。

  • Skia:图像处理引擎,Google在Android系统上就是采用Skia,它是基于libjpeg的二次封装,Google在很多其它产品也使用了这个库,比如Chorme,Firefox等等。
  • libjpeg:早期的图像处理引擎,用于PC端。

官方文档可以看到libjpeg.doc这样一段话:

boolean optimize_coding TRUE causes the compressor to compute optimal Huffman coding tables for the image. This requires an extra pass over the data and therefore costs a good deal of space and time. The default is FALSE, which tells the compressor to use the supplied or default Huffman tables. In most cases optimal tables save only a few percent of file size compared to the default tables. Note that when this is TRUE, you need not supply Huffman tables at all, and any you do supply will be overwritten. boolean optimize_coding:

参数为TRUE时,图片压缩算法使用最优的哈夫曼编码表,它需要额外传递数据,因此会耗费CPU运算时间,以及开辟很多临时内存空间。参数为FALSE时,使用默认的哈夫曼编码表。在大多数情况,使用最优哈夫曼编码表相比默认哈夫曼编码表,能节省图像文件很大比例的大小。为什么使用最优哈夫曼编码表可以节省图像文件很大的比例大小呢?可以先了解下什么是哈夫曼树和哈夫曼编码

其次了解下libjpeg使用哈夫曼编码是对图片上的每个像素(ARGB)进行编码,比如 ARGB(每个颜色通道取值范围0-255)的编码分别是: A:001 R:010 G:011 B:100 如果采用定长,那一个图片下来一个人像素就是001010011100… 如果我们使用可变长编码方式,遍历、再嵌套遍历,再嵌套遍历每一个像素来获取前缀编码(没错,这个运算过程很大,而且临时变量内存也需要很大,但相对于今天的手机CPU来说,so easy),我们大致可以得到这样的编码: A:01 R:10 G:11 B:100 那一个图片下来一个人像素就是011011100…

编码长度的优化后,接下来干的事就是计算权重以及每个颜色通道对应的编码的出现频次构建哈夫曼树了,这里就参考博客里头的图片了哈。

哈夫曼树

看完博客之后,可以知道最优哈夫曼编码其实是使用了可变长编码方式,而默认的哈夫曼编码使用了定长编码方式,因此需要更多的存储空间,呈现出来的手机图片自然会大很大。

libjpeg把optimize_coding参数默认设置为FALSE是因为10多年前的Android手机CPU跟内存都非常吃紧,所以当年没有设置为TRUE。如今的手机CPU跟内存都“起飞了”,Goolge的Skia图像处理引擎却还是使用optimize_coding的默认值FALSE。我们无法修改系统Skia的这个参数值,所以只能默默忍受size很大的图像文件。

经过大量图像压缩测试结果,得到两个结论:

  • 1.图片压缩到相同的质量,FALSE所产出的图像文件大小是TRUE的5-10倍。
  • 2.图片压缩到相同的质量,Android所产出的图像文件大小比iOS也是大5-10倍。

所以,通过使用libjpeg编译自己的native library修改optimize_coding参数的值,达图像质量相同,所产出的图像却能节省5-10倍空间大小的效果。

实现的步骤:

  • 1.构建libjpeg的so库 到官方下载对应自己电脑系统类型的压缩包,创建Android项目导入压缩包里头的xx.h、xx.c文件构建so库。bither/bither-android-lib已经做了这个工作,因此我们只需直接拿他的libjpegbither.so即可。
  • 2.导入libjpeg的声明头文件,因为步骤1的libjpegbither.so是对这些头文件的实现,因此需要导入这些头文件。
  • 3.创建CMake脚本
cmake_minimum_required(VERSION 3.4.1)

add_library( effective-bitmap
             SHARED
             src/main/cpp/effective-bitmap.c )


include_directories( src/main/cpp/jpeg/)

add_library(jpegbither SHARED IMPORTED)
set_target_properties(jpegbither
  PROPERTIES IMPORTED_LOCATION
  ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libjpegbither.so)


find_library( log-lib
              log )

find_library( jnigraphics-lib jnigraphics )

target_link_libraries( effective-bitmap
                       jpegbither
                       ${log-lib}
                       ${jnigraphics-lib})

4.配置gradle关联CMakeLists.txt构建脚本,以及指定产出的ABI的类型

android {
    // ...
    defaultConfig {
        // ...
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }

        ndk {
            abiFilters 'armeabi'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

5.编写C/C++代码

int generateJPEG(BYTE* data, int w, int h, int quality,
                 const char* outfilename, jboolean optimize) {
    int nComponent = 3;
    // jpeg的结构体,保存的比如宽、高、位深、图片格式等信息
    struct jpeg_compress_struct jcs;

    struct my_error_mgr jem;

    jcs.err = jpeg_std_error(&jem.pub);
    jem.pub.error_exit = my_error_exit;
    if (setjmp(jem.setjmp_buffer)) {
        return 0;
    }
    jpeg_create_compress(&jcs);
    // 打开输出文件 wb:可写byte
    FILE* f = fopen(outfilename, "wb");
    if (f == NULL) {
        return 0;
    }
    // 设置结构体的文件路径
    jpeg_stdio_dest(&jcs, f);
    jcs.image_width = w;
    jcs.image_height = h;

    // 设置哈夫曼编码
    jcs.arith_code = false;
    jcs.input_components = nComponent;
    if (nComponent == 1)
        jcs.in_color_space = JCS_GRAYSCALE;
    else
        jcs.in_color_space = JCS_RGB;

    jpeg_set_defaults(&jcs);
    jcs.optimize_coding = optimize;
    jpeg_set_quality(&jcs, quality, true);
    // 开始压缩,写入全部像素
    jpeg_start_compress(&jcs, TRUE);

    JSAMPROW row_pointer[1];
    int row_stride;
    row_stride = jcs.image_width * nComponent;
    while (jcs.next_scanline < jcs.image_height) {
        row_pointer[0] = &data[jcs.next_scanline * row_stride];
        jpeg_write_scanlines(&jcs, row_pointer, 1);
    }

    jpeg_finish_compress(&jcs);
    jpeg_destroy_compress(&jcs);
    fclose(f);

    return 1;
}

6.构建so库

项目链接:https://github.com/zengfw/EffectiveBitmap

参考链接:

  • Why the image quality of iPhone is much better than Android?

原创作者:Ricky,原文链接:https://www.jianshu.com/p/f20f9bcdd6a4

原文发布于微信公众号 - 何俊林(DriodDeveloper)

原文发表时间:2018-11-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

码农突围

0 篇文章93 人订阅

相关文章

来自专栏小樱的经验随笔

CTF---隐写术入门第三题 打不开的文件

打不开的文件分值:10 来源: 实验吧 难度:中 参与人数:2718人 Get Flag:1222人 答题人数:1276人 解题通过率:96% 咦!这个文件怎么...

578120
来自专栏Python中文社区

Python量子力学计算模拟以及数据可视化

專 欄 ❈Pytlab,Python 中文社区专栏作者。主要从事科学计算与高性能计算领域的应用,主要语言为Python,C,C++。熟悉数值算法(最优化方法,...

1K90
来自专栏逸鹏说道

Toxy新手教程

Toxy新手教程 官方网站:http://toxy.codeplex.com Toxy是干嘛用的?它是.NET平台上的文件抽取框架,主要解决各种格式的内容抽取问...

31260
来自专栏技术专栏

Spark SQL/Hive调优

任务进度长时间维持在99%(或100%),查看任务监控页面,发现只有少量(1个或几个)reduce子任务未完成。因为其处理的数据量和其他reduce差异过大。 ...

80620
来自专栏吉浦迅科技

DAY31:阅读global memory

11420
来自专栏章鱼的慢慢技术路

在Unity场景中控制日夜的轮转

15460
来自专栏拂晓风起

cocos2d-js 越来越慢的定时器schedule 制作不变慢的定时器

14240
来自专栏点滴积累

geotrellis使用(三)geotrellis数据处理过程分析

之前简单介绍了geotrellis的工作过程以及一个简单的demo,最近在此demo的基础上实现了SRTM DEM数据的实时分析以及高程实时处理,下面我就以我实...

69760
来自专栏颇忒脱的技术博客

面向程序员的网络基本知识 - 子网分割

本系列文章旨在向程序员分享一些网络基本知识,让程序员具备基本的网络常识,以便与网络工程师沟通。本系列文章不会涉及如何组建网络、如何配置交换机/路由器等硬件相关的...

13430
来自专栏大数据智能实战

使用word2vec训练wiki中文语料

实验环境:Ubuntu + eclipse + python3.5 首先(1)下载最新中文wiki语料库: wget https://dumps.wikime...

941100

扫码关注云+社区

领取腾讯云代金券