百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

android程序员高效压缩图片(libjpeg)几种方案

bigegpt 2024-08-07 17:52 12 浏览

用过ios手机的同学应该很明显感觉到,ios拍照1M的图片要比安卓拍照排出来的5M的图片还要清晰。这是为什么呢?

这得了解android底层是如何对图片进行处理的

当时谷歌开发Android的时候,考虑了大部分手机的配置并没有那么高,所以对图片处理是使用的Skia这个开源库。当然这个库的底层还是是用的jpeg对图片进行压缩处理。但是为了能够适配低端的手机(这里的低端是指以前的硬件配置不高的手机,CPU和内存在手机上都非常吃紧 性能差),由于哈夫曼算法非常吃CPU,被迫用了其他的算法。所以Skia在进行图片处理并没有去使用压缩图像过程中基于图像数据计算哈弗曼表(关于图片压缩中的哈弗曼表,请自行查阅相关资料),但是解码还是保留了哈夫曼算法。这就导致了图片处理后文件变大了。

我们使用微信发图片的时候,会发现发出去的图片明显比原图小很大,但是效果好像差不多,那又是为什么呢,经过了怎样的压缩呢,其实他们是用了哈夫曼算法进行图片压缩;接下来我们就用这算法进行图片压缩:

仿微信终级压缩

1.下载JPEG引擎使用的库---libjpeg库

libjpeg是一个被广泛使用的JPEG解码、JPEG编码和其他的JPEG功能的实现库.说它使用广泛,是因为它跨了很多平台。比如Linux平台、JDK、Android和其他库如tess-two等等。libjpeg库下载地址;

2.编译android中libjpeg库使用库

编译android中libjpeg库使用库,这里我暂时先不做详细的请解,后期我会做一个ndk开发专题进行详细请解,编译后生成的libjpegbither.so和.h头文件,看如下图:

3.导入libjpeg库libjpegbither.so及头文件

在项目文件夹新建jni文件夹,把刚才生成的libjpegbither.so及头文件放在此文件夹下;

4.新建BitmapCompressUtils

  • 写一个native方法,调用c方法
 /**
 * 调用底层 bitherlibjni.c中的方法
 *
 * @param bit
 * @param w
 * @param h
 * @param quality
 * @param fileNameBytes
 * @param optimize
 * @return
 * @Description:函数描述
 */
public static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
 boolean optimize);
  • 引入库lib下二个so文件
 /**
 * 加载lib下两个so文件
 */
static {
 System.loadLibrary("jpegbither");
 System.loadLibrary("bitherjni");
}
  • 写一个方法,调用native方法,便于java层调用
 /**
 * @param image bitmap对象
 * @param filePath 要保存的指定目录
 * @Description: 通过JNI图片压缩把Bitmap保存到指定目录
 */
public static void compressBitmap(Bitmap image, String filePath) {
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
 int options = 20;
 // JNI调用保存图片到SD卡 这个关键
 NativeUtil.saveBitmap(image, options, filePath, true);
}

4.编写bitherjni.cpp

#include "bitherlibjni.h"
#include <string.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <stdio.h>
#include <setjmp.h>
#include <math.h>
#include <stdint.h>
#include <time.h>
//统一编译方式
extern "C" {
#include "jpeg/jpeglib.h"
#include "jpeg/cdjpeg.h" /* Common decls for cjpeg/djpeg applications */
#include "jpeg/jversion.h" /* for version message */
#include "jpeg/android/config.h"
}
#define LOG_TAG "jni"
#define LOGW(...) __android_log_write(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define true 1
#define false 0
typedef uint8_t BYTE;
char *error;
struct my_error_mgr {
 struct jpeg_error_mgr pub;
 jmp_buf setjmp_buffer;
};
typedef struct my_error_mgr * my_error_ptr;
METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
 my_error_ptr myerr = (my_error_ptr) cinfo->err;
 (*cinfo->err->output_message) (cinfo);
 error=(char*)myerr->pub.jpeg_message_table[myerr- >pub.msg_code];
 LOGE("jpeg_message_table[%d]:%s", myerr- >pub.msg_code,myerr->pub.jpeg_message_table[myerr- >pub.msg_code]);
 // LOGE("addon_message_table:%s", myerr->pub.addon_message_table);
// LOGE("SIZEOF:%d",myerr->pub.msg_parm.i[0]);
// LOGE("sizeof:%d",myerr->pub.msg_parm.i[1]);
 longjmp(myerr->setjmp_buffer, 1);
}
int generateJPEG(BYTE* data, int w, int h, int quality,
 const char* outfilename, jboolean optimize) {
//jpeg的结构体,保存的比如宽、高、位深、图片格式等信息,相当于java的类
struct jpeg_compress_struct jcs;
//当读完整个文件的时候就会回调my_error_exit这个退出方法。setjmp是一个系统级函数,是一个回调。
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;
}
//初始化jsc结构体
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;
// if (optimize) {
// LOGI("optimize==ture");
// } else {
// LOGI("optimize==false");
// }
//看源码注释,设置哈夫曼编码:/* TRUE=arithmetic coding, FALSE=Huffman */
jcs.arith_code = false;
int nComponent = 3;
/* 颜色的组成 rgb,三个 # of color components in input image */
jcs.input_components = nComponent;
//设置结构体的颜色空间为rgb
jcs.in_color_space = JCS_RGB;
// if (nComponent == 1)
// jcs.in_color_space = JCS_GRAYSCALE;
// else
// jcs.in_color_space = JCS_RGB;
//全部设置默认参数/* Default parameter setup for compression */
jpeg_set_defaults(&jcs);
//是否采用哈弗曼表数据计算 品质相差5-10倍
jcs.optimize_coding = optimize;
//设置质量
jpeg_set_quality(&jcs, quality, true);
//开始压缩,(是否写入全部像素)
jpeg_start_compress(&jcs, TRUE);
JSAMPROW row_pointer[1];
int row_stride;
//一行的rgb数量
row_stride = jcs.image_width * nComponent;
//一行一行遍历
while (jcs.next_scanline < jcs.image_height) {
 //得到一行的首地址
 row_pointer[0] = &data[jcs.next_scanline * row_stride];
 //此方法会将jcs.next_scanline加1
 jpeg_write_scanlines(&jcs, row_pointer, 1);//row_pointer就是一行的首地址,1:写入的行数
}
jpeg_finish_compress(&jcs);//结束
jpeg_destroy_compress(&jcs);//销毁 回收内存
fclose(f);//关闭文件
return 1;
}
/**
 * byte数组转C的字符串
 */
char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
char* rtn = NULL;
jsize alen = env->GetArrayLength( barr);
jbyte* ba = env->GetByteArrayElements( barr, 0);
if (alen > 0) {
 rtn = (char*) malloc(alen + 1);
 memcpy(rtn, ba, alen);
 rtn[alen] = 0;
}
env->ReleaseByteArrayElements( barr, ba, 0);
return rtn;
}
jstring Java_net_bither_util_BitmapCompressUtils_compressBitmap(JNIEnv* env,
 jclass thiz, jobject bitmapcolor, int w, int h, int quality,
 jbyteArray fileNameStr, jboolean optimize) {
BYTE *pixelscolor;
//1.将bitmap里面的所有像素信息读取出来,并转换成RGB数据,保存到二维byte数组里面
//处理bitmap图形信息方法1 锁定画布
AndroidBitmap_lockPixels(env,bitmapcolor,(void**)&pixelscolor);
//2.解析每一个像素点里面的rgb值(去掉alpha值),保存到一维数组data里面
BYTE *data;
BYTE r,g,b;
data = (BYTE*)malloc(w*h*3);//每一个像素都有三个信息RGB
BYTE *tmpdata;
tmpdata = data;//临时保存data的首地址
int i=0,j=0;
int color;
for (i = 0; i < h; ++i) {
 for (j = 0; j < w; ++j) {
 //解决掉alpha
 //获取二维数组的每一个像素信息(四个部分a/r/g/b)的首地址
 color = *((int *)pixelscolor);//通过地址取值
 //0~255:
// a = ((color & 0xFF000000) >> 24);
 r = ((color & 0x00FF0000) >> 16);
 g = ((color & 0x0000FF00) >> 8);
 b = ((color & 0x000000FF));
 //改值!!!----保存到data数据里面
 *data = b;
 *(data+1) = g;
 *(data+2) = r;
 data = data + 3;
 //一个像素包括argb四个值,每+4就是取下一个像素点
 pixelscolor += 4;
 }
}
//处理bitmap图形信息方法2 解锁
AndroidBitmap_unlockPixels(env,bitmapcolor);
char* fileName = jstrinTostring(env,fileNameStr);
//调用libjpeg核心方法实现压缩
int resultCode = generateJPEG(tmpdata,w,h,quality,fileName,optimize);
if(resultCode ==0){
 jstring result = env->NewStringUTF("-1");
 return result;
}
return env->NewStringUTF("1");
}

Java

net

bither

util

BitmapCompressUtils_compressBitmap就是java调用的方法,通过此方法就可以进行图片压缩,c代码我就不再讲解,注释写得很清楚了;接下我再讲一下另外几个方案进行图片压缩;

质量压缩

质量压缩,这个只是降低了图片的质量,但是像素是不会减小的

/** 质量压缩,
 * @param bitmap 要压缩的图片
 * @param file //压缩的图片保存地址
 * Hint to the compressor, 0-100. 0 meaning compress for small size, 100 meaning compress for max quality. Some
 * formats, like PNG which is lossless, will ignore the quality setting
 * quality (0-100) 100是不压缩,值越小,压缩得越厉害
 */
public static void qualityCompressBitmap(Bitmap bitmap,File file){
 //字节数组输出流
 ByteArrayOutputStream stream =new ByteArrayOutputStream();
 int quality=20;
 //图片压缩后把数据放在stream中
 bitmap.compress(Bitmap.CompressFormat.JPEG,quality, stream);
 try {
 FileOutputStream fileOutputStream=new FileOutputStream(file);
 //不断把stream的数据写文件输出流中去
 fileOutputStream.write(stream.toByteArray());
 fileOutputStream.flush();
 fileOutputStream.close();
 } catch (Exception e) {
 e.printStackTrace();
 }
}

尺寸压缩

尺寸压缩,通过缩放图片的像素,减小图片占用内存大小,这个比如用于缩略图,今日头条文章上显示的小图就是这样实现的;

 /**尺寸压缩
 * @param bitmap 要压缩的图片
 * @param ratio 压缩比例,值越大,图片的尺寸就越小
 * @param file 压缩的图片保存地址
 */
public static void sizeCompressBitmap(Bitmap bitmap,int ratio,File file){
 if (ratio<=0){
 return;
 }
 Bitmap result=Bitmap.createBitmap(bitmap.getWidth()/ratio,bitmap.getHeight()/ratio, Bitmap.Config.ARGB_8888);
 Canvas canvas =new Canvas();
 Rect rect=new Rect(0,0,bitmap.getWidth()/ratio,bitmap.getHeight()/ratio);
 canvas.drawBitmap(bitmap,null,rect,null);
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 // 把压缩后的数据存放到baos中
 result.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
 try {
 FileOutputStream fos = new FileOutputStream(file);
 fos.write(baos.toByteArray());
 fos.flush();
 fos.close();
 } catch (Exception e) {
 e.printStackTrace();
 }
}

采样率压缩

 /** 采样率压缩
 * @param filePath 压缩图
 * @param file 压缩的图片保存地址
 */
public static void pixeCompressBitmap(String filePath, File file){
 //采样率,数值越高,图片像素越低
 int inSampleSize=8;
 BitmapFactory.Options osts=new BitmapFactory.Options();
 osts.inSampleSize=inSampleSize;
 //inJustDecodeBounds设为True时,不会真正加载图片,而是得到图片的宽高信息。
 osts.inJustDecodeBounds=false;
 Bitmap bitmap= BitmapFactory.decodeFile(filePath,osts);
 ByteArrayOutputStream stream =new ByteArrayOutputStream();
 bitmap.compress(Bitmap.CompressFormat.JPEG,100,stream);
 try {
 if (file.exists()){
 file.delete();
 }else{
 file.createNewFile();
 }
 FileOutputStream fileOutputStream=new FileOutputStream(file);
 fileOutputStream.write(stream.toByteArray());
 fileOutputStream.flush();
 fileOutputStream.close();
 } catch (Exception e) {
 e.printStackTrace();
 }
}

总结:以上几种方案对图片进行压缩,各有优缺点,根据实际场景来选择方案来进行图片压缩.

相关推荐

方差分析简介(方差分析通俗理解)

介绍方差分析(ANOVA,AnalysisofVariance)是一种广泛使用的统计方法,用于比较两个或多个组之间的均值。单因素方差分析是方差分析的一种变体,旨在检测三个或更多分类组的均值是否存在...

正如404页面所预示,猴子正成为断网元凶--吧嗒吧嗒真好吃

吧嗒吧嗒,绘图:MakiNaro你可以通过加热、冰冻、水淹、模塑、甚至压溃压力来使网络光缆硬化。但用猴子显然是不行的。光缆那新挤压成型的塑料外皮太尼玛诱人了,无法阻挡一场试吃盛宴的举行。印度政府正...

Python数据可视化:箱线图多种库画法

概念箱线图通过数据的四分位数来展示数据的分布情况。例如:数据的中心位置,数据间的离散程度,是否有异常值等。把数据从小到大进行排列并等分成四份,第一分位数(Q1),第二分位数(Q2)和第三分位数(Q3)...

多组独立(完全随机设计)样本秩和检验的SPSS操作教程及结果解读

作者/风仕在上一期,我们已经讲完了两组独立样本秩和检验的SPSS操作教程及结果解读,这期开始讲多组独立样本秩和检验,我们主要从多组独立样本秩和检验介绍、两组独立样本秩和检验使用条件及案例的SPSS操作...

方差分析 in R语言 and Excel(方差分析r语言例题)

今天来写一篇实际中比较实用的分析方法,方差分析。通过方差分析,我们可以确定组别之间的差异是否超出了由于随机因素引起的差异范围。方差分析分为单因素方差分析和多因素方差分析,这一篇先介绍一下单因素方差分析...

可视化:前端数据可视化插件大盘点 图表/图谱/地图/关系图

前端数据可视化插件大盘点图表/图谱/地图/关系图全有在大数据时代,很多时候我们需要在网页中显示数据统计报表,从而能很直观地了解数据的走向,开发人员很多时候需要使用图表来表现一些数据。随着Web技术的...

matplotlib 必知的 15 个图(matplotlib各种图)

施工专题,我已完成20篇,施工系列几乎覆盖Python完整技术栈,目标只总结实践中最实用的东西,直击问题本质,快速帮助读者们入门和进阶:1我的施工计划2数字专题3字符串专题4列表专题5流程控制专题6编...

R ggplot2常用图表绘制指南(ggplot2绘制折线图)

ggplot2是R语言中强大的数据可视化包,基于“图形语法”(GrammarofGraphics),通过分层方式构建图表。以下是常用图表命令的详细指南,涵盖基本语法、常见图表类型及示例,适合...

Python数据可视化:从Pandas基础到Seaborn高级应用

数据可视化是数据分析中不可或缺的一环,它能帮助我们直观理解数据模式和趋势。本文将全面介绍Python中最常用的三种可视化方法。Pandas内置绘图功能Pandas基于Matplotlib提供了简洁的绘...

Python 数据可视化常用命令备忘录

本文提供了一个全面的Python数据可视化备忘单,适用于探索性数据分析(EDA)。该备忘单涵盖了单变量分析、双变量分析、多变量分析、时间序列分析、文本数据分析、可视化定制以及保存与显示等内容。所...

统计图的种类(统计图的种类及特点图片)

统计图是利用几何图形或具体事物的形象和地图等形式来表现社会经济现象数量特征和数量关系的图形。以下是几种常见的统计图类型及其适用场景:1.条形图(BarChart)条形图是用矩形条的高度或长度来表示...

实测,大模型谁更懂数据可视化?(数据可视化和可视化分析的主要模型)

大家好,我是Ai学习的老章看论文时,经常看到漂亮的图表,很多不知道是用什么工具绘制的,或者很想复刻类似图表。实测,大模型LaTeX公式识别,出乎预料前文,我用Kimi、Qwen-3-235B...

通过AI提示词让Deepseek快速生成各种类型的图表制作

在数据分析和可视化领域,图表是传达信息的重要工具。然而,传统图表制作往往需要专业的软件和一定的技术知识。本文将介绍如何通过AI提示词,利用Deepseek快速生成各种类型的图表,包括柱状图、折线图、饼...

数据可视化:解析箱线图(box plot)

箱线图/盒须图(boxplot)是数据分布的图形表示,由五个摘要组成:最小值、第一四分位数(25th百分位数)、中位数、第三四分位数(75th百分位数)和最大值。箱子代表四分位距(IQR)。IQR是...

[seaborn] seaborn学习笔记1-箱形图Boxplot

1箱形图Boxplot(代码下载)Boxplot可能是最常见的图形类型之一。它能够很好表示数据中的分布规律。箱型图方框的末尾显示了上下四分位数。极线显示最高和最低值,不包括异常值。seaborn中...