如题,多种压缩方式常用的有尺寸压缩、质量压缩以及通过JNI调用libjpeg库来进行压缩,三种方式结合使用实现指定图片内存大小,清晰度达到最优,下面就先分别介绍下这几种压缩方式。
1. 质量压缩
设置bitmap options属性,降低图片的质量,像素不会减少</br>
第一个参数为需要压缩的bitmap图片对象,第二个参数为压缩后图片保存的位置</br>
设置options 属性0-100,来实现压缩</br>
public static void compressImageToFile(Bitmap bmp,File file) {
// 0-100 100为不压缩
int options = 100;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 把压缩后的数据存放到baos中
bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
2. 尺寸压缩
通过缩放图片像素来减少图片占用内存大小
public static void compressBitmapToFile(Bitmap bmp, File file){
// 尺寸压缩倍数,值越大,图片尺寸越小
int ratio = 2;
// 压缩Bitmap到对应尺寸
Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Config.ARGB_8888);
Canvas canvas = new Canvas(result);
Rect rect = new Rect(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio);
canvas.drawBitmap(bmp, 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();
}
}
设置图片的采样率,降低图片像素
public static void compressBitmap(String filePath, File file){
// 数值越高,图片像素越低
int inSampleSize = 2;
BitmapFactory.Options options = new BitmapFactory.Options();
//采样率
options.inSampleSize = inSampleSize;
Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 把压缩后的数据存放到baos中
bitmap.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();
}
}
3. JNI调用libjpeg库压缩
JNI静态调用 bitherlibjni.c 中的方法来实现压缩
Java_net_bither_util_NativeUtil_compressBitmap
net_bither_util为包名,NativeUtil为类名,compressBitmap为native方法名,后面我会把整个类分享出来
我们只需要调用saveBitmap()方法就可以,bmp 需要压缩的Bitmap对象, quality压缩质量0-100, fileName 压缩后要保存的文件地址, optimize 是否采用哈弗曼表数据计算 品质相差5-10倍
jstring Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv* env,
jobject thiz, jobject bitmapcolor, int w, int h, int quality,
jbyteArray fileNameStr, jboolean optimize) {
AndroidBitmapInfo infocolor;
BYTE* pixelscolor;
int ret;
BYTE * data;
BYTE *tmpdata;
char * fileName = jstrinTostring(env, fileNameStr);
if ((ret = AndroidBitmap_getInfo(env, bitmapcolor, &infocolor)) < 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
return (*env)->NewStringUTF(env, "0");;
}
if ((ret = AndroidBitmap_lockPixels(env, bitmapcolor, &pixelscolor)) < 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
}
BYTE r, g, b;
data = NULL;
data = malloc(w * h * 3);
tmpdata = data;
int j = 0, i = 0;
int color;
for (i = 0; i < h; i++) {
for (j = 0; j < w; j++) {
color = *((int *) pixelscolor);
r = ((color & 0x00FF0000) >> 16);
g = ((color & 0x0000FF00) >> 8);
b = color & 0x000000FF;
*data = b;
*(data + 1) = g;
*(data + 2) = r;
data = data + 3;
pixelscolor += 4;
}
}
AndroidBitmap_unlockPixels(env, bitmapcolor);
int resultCode= generateJPEG(tmpdata, w, h, quality, fileName, optimize);
free(tmpdata);
if(resultCode==0){
jstring result=(*env)->NewStringUTF(env, error);
error=NULL;
return result;
}
return (*env)->NewStringUTF(env, "1"); //success
}
compressBitmap()为native关联方法,saveBitmap() 压缩调用方法
private static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
boolean optimize);
private static void saveBitmap(Bitmap bmp, int quality, String fileName, boolean optimize) {
compressBitmap(bit, bit.getWidth(), bit.getHeight(), quality, fileName.getBytes(), optimize);
}
4. 结合三种方式的终极压缩
首先通过尺寸压缩,压缩到手机常用的一个分辨率(1280*960 微信好像是压缩到这个分辨率),然后我们要把图片压缩到100KB以内,通过质量压缩来计算options需要设置为多少,最后调用JNI压缩,这边我测试了下,压缩出来的清晰度和原图几乎差不多,压缩时间大概1秒钟左右
public static int getRatioSize(int bitWidth, int bitHeight) {
// 图片最大分辨率
int imageHeight = 1280;
int imageWidth = 960;
// 缩放比
int ratio = 1;
// 缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
if (bitWidth > bitHeight && bitWidth > imageWidth) {
// 如果图片宽度比高度大,以宽度为基准
ratio = bitWidth / imageWidth;
} else if (bitWidth < bitHeight && bitHeight > imageHeight) {
// 如果图片高度比宽度大,以高度为基准
ratio = bitHeight / imageHeight;
}
// 最小比率为1
if (ratio <= 0)
ratio = 1;
return ratio;
}
public static void compressBitmap(Bitmap image, String filePath) {
// 最大图片大小 100KB
int maxSize = 100;
// 获取尺寸压缩倍数
int ratio = NativeUtil.getRatioSize(image.getWidth(), image.getHeight());
// 压缩Bitmap到对应尺寸
Bitmap result = Bitmap.createBitmap(image.getWidth() / ratio, image.getHeight() / ratio, Config.ARGB_8888);
Canvas canvas = new Canvas(result);
Rect rect = new Rect(0, 0, image.getWidth() / ratio, image.getHeight() / ratio);
canvas.drawBitmap(image, null, rect, null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
int options = 100;
result.compress(Bitmap.CompressFormat.JPEG, options, baos);
// 循环判断如果压缩后图片是否大于100kb,大于继续压缩
while (baos.toByteArray().length / 1024 > maxSize) {
// 重置baos即清空baos
baos.reset();
// 每次都减少10
options -= 10;
// 这里压缩options%,把压缩后的数据存放到baos中
result.compress(Bitmap.CompressFormat.JPEG, options, baos);
}
// JNI调用保存图片到SD卡 这个关键
NativeUtil.saveBitmap(result, options, filePath, true);
// 释放Bitmap
if (result != null && !result.isRecycled()) {
result.recycle();
result = null;
}
}
五. NativeUtil类的源码
16.9.29更新
1、添加getBitmapFromFile()方法,解决OOM和图片旋转的问题
2、添加compressBitmap()方法,传递当前图片本地路径和解压后图片保存路径两个参数,即可,实现压缩
package net.bither.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.media.ExifInterface;
/**
* JNI图片压缩工具类
*
* @Description TODO
* @Package net.bither.util
* @Class NativeUtil
* @Copyright: Copyright (c) 2015
* @author XiaoSai
* @date 2016年3月21日 下午2:13:55
* @version V1.0.0
*/
public class NativeUtil {
private static int DEFAULT_QUALITY = 95;
/**
* @Description: JNI基本压缩
* @param bit
* bitmap对象
* @param fileName
* 指定保存目录名
* @param optimize
* 是否采用哈弗曼表数据计算 品质相差5-10倍
* @author XiaoSai
* @date 2016年3月23日 下午6:32:49
* @version V1.0.0
*/
public static void compressBitmap(Bitmap bit, String fileName, boolean optimize) {
saveBitmap(bit, DEFAULT_QUALITY, fileName, optimize);
}
/**
* @Description: 通过JNI图片压缩把Bitmap保存到指定目录
* @param image
* bitmap对象
* @param filePath
* 要保存的指定目录
* @author XiaoSai
* @date 2016年3月23日 下午6:28:15
* @version V1.0.0
*/
public static void compressBitmap(Bitmap image, String filePath) {
// 最大图片大小 150KB
int maxSize = 150;
// 获取尺寸压缩倍数
int ratio = NativeUtil.getRatioSize(image.getWidth(),image.getHeight());
// 压缩Bitmap到对应尺寸
Bitmap result = Bitmap.createBitmap(image.getWidth() / ratio,image.getHeight() / ratio,Config.ARGB_8888);
Canvas canvas = new Canvas(result);
Rect rect = new Rect(0, 0, image.getWidth() / ratio, image.getHeight() / ratio);
canvas.drawBitmap(image,null,rect,null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
int options = 100;
result.compress(Bitmap.CompressFormat.JPEG, options, baos);
// 循环判断如果压缩后图片是否大于100kb,大于继续压缩
while (baos.toByteArray().length / 1024 > maxSize) {
// 重置baos即清空baos
baos.reset();
// 每次都减少10
options -= 10;
// 这里压缩options%,把压缩后的数据存放到baos中
result.compress(Bitmap.CompressFormat.JPEG, options, baos);
}
// JNI保存图片到SD卡 这个关键
NativeUtil.saveBitmap(result, options, filePath, true);
// 释放Bitmap
if (!result.isRecycled()) {
result.recycle();
}
}
/**
* @Description: 通过JNI图片压缩把Bitmap保存到指定目录
* @param curFilePath
* 当前图片文件地址
* @param targetFilePath
* 要保存的图片文件地址
* @author XiaoSai
* @date 2016年9月28日 下午17:43:15
* @version V1.0.0
*/
public static void compressBitmap(String curFilePath, String targetFilePath) {
// 最大图片大小 150KB
int maxSize = 150;
//根据地址获取bitmap
Bitmap result = getBitmapFromFile(curFilePath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
int quality = 100;
result.compress(Bitmap.CompressFormat.JPEG, quality, baos);
// 循环判断如果压缩后图片是否大于100kb,大于继续压缩
while (baos.toByteArray().length / 1024 > maxSize) {
// 重置baos即清空baos
baos.reset();
// 每次都减少10
quality -= 10;
// 这里压缩quality,把压缩后的数据存放到baos中
result.compress(Bitmap.CompressFormat.JPEG, quality, baos);
}
// JNI保存图片到SD卡 这个关键
NativeUtil.saveBitmap(result, quality, targetFilePath, true);
// 释放Bitmap
if (!result.isRecycled()) {
result.recycle();
}
}
/**
* 计算缩放比
* @param bitWidth 当前图片宽度
* @param bitHeight 当前图片高度
* @return int 缩放比
* @author XiaoSai
* @date 2016年3月21日 下午3:03:38
* @version V1.0.0
*/
public static int getRatioSize(int bitWidth, int bitHeight) {
// 图片最大分辨率
int imageHeight = 1280;
int imageWidth = 960;
// 缩放比
int ratio = 1;
// 缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
if (bitWidth > bitHeight && bitWidth > imageWidth) {
// 如果图片宽度比高度大,以宽度为基准
ratio = bitWidth / imageWidth;
} else if (bitWidth < bitHeight && bitHeight > imageHeight) {
// 如果图片高度比宽度大,以高度为基准
ratio = bitHeight / imageHeight;
}
// 最小比率为1
if (ratio <= 0)
ratio = 1;
return ratio;
}
/**
* 通过文件路径读获取Bitmap防止OOM以及解决图片旋转问题
* @param filePath
* @return
*/
public static Bitmap getBitmapFromFile(String filePath){
BitmapFactory.Options newOpts = new BitmapFactory.Options();
newOpts.inJustDecodeBounds = true;//只读边,不读内容
BitmapFactory.decodeFile(filePath, newOpts);
int w = newOpts.outWidth;
int h = newOpts.outHeight;
// 获取尺寸压缩倍数
newOpts.inSampleSize = NativeUtil.getRatioSize(w,h);
newOpts.inJustDecodeBounds = false;//读取所有内容
newOpts.inDither = false;
newOpts.inPurgeable=true;
newOpts.inInputShareable=true;
newOpts.inTempStorage = new byte[32 * 1024];
Bitmap bitmap = null;
File file = new File(filePath);
FileInputStream fs = null;
try {
fs = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
if(fs!=null){
bitmap = BitmapFactory.decodeFileDescriptor(fs.getFD(),null,newOpts);
//旋转图片
int photoDegree = readPictureDegree(filePath);
if(photoDegree != 0){
Matrix matrix = new Matrix();
matrix.postRotate(photoDegree);
// 创建新的图片
bitmap = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally{
if(fs!=null) {
try {
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}
/**
*
* 读取图片属性:旋转的角度
* @param path 图片绝对路径
* @return degree旋转的角度
*/
public static int readPictureDegree(String path) {
int degree = 0;
try {
ExifInterface exifInterface = new ExifInterface(path);
int orientation = exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
} catch (IOException e) {
e.printStackTrace();
}
return degree;
}
/**
* 调用native方法
* @Description:函数描述
* @param bit
* @param quality
* @param fileName
* @param optimize
* @author XiaoSai
* @date 2016年3月23日 下午6:36:46
* @version V1.0.0
*/
private static void saveBitmap(Bitmap bit, int quality, String fileName, boolean optimize) {
compressBitmap(bit, bit.getWidth(), bit.getHeight(), quality, fileName.getBytes(), optimize);
}
/**
* 调用底层 bitherlibjni.c中的方法
* @Description:函数描述
* @param bit
* @param w
* @param h
* @param quality
* @param fileNameBytes
* @param optimize
* @return
* @author XiaoSai
* @date 2016年3月23日 下午6:35:53
* @version V1.0.0
*/
private static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
boolean optimize);
/**
* 加载lib下两个so文件
*/
static {
System.loadLibrary("jpegbither");
System.loadLibrary("bitherjni");
}
}
六. ThumbnailUtils系统工具类的使用
纯属为了增加篇幅,大家别介意哈,咳咳, 其实也是为了记录一下,以后用到可以直接过来看
创建一张视频的缩略图。如果视频已损坏或者格式不支持可能返回null。</br>
filePath:视频文件路径</br>
kind:文件种类,可以是 MINI_KIND 或 MICRO_KIND</br>
Bitmap createVideoThumbnail(String filePath, int kind)
创建所需尺寸居中缩放的位图。</br>
source: 需要被创造缩略图的源位图对象</br>
width: 生成目标的宽度</br>
height: 生成目标的高度</br>
options:在缩略图抽取时提供的选项</br>
Bitmap extractThumbnail(Bitmap source, int width, int height, int options)
创建所需尺寸居中缩放的位图。</br>
source: 需要被创造缩略图的源位图对象</br>
width: 生成目标的宽度</br>
height: 生成目标的高度</br>
Bitmap extractThumbnail(Bitmap source, int width, int height)
最后当然要奉上源码了,源码中封装了参考网上的拍照和选取图片工具类,有问题可以指出,共同进步!
<b>2016.11.08更新:</b>
很多朋友说在AndroidStudio里面编译有问题,就抽了个时间重新写了一个DEMO给大家参考,要注意的地方就是要在build.gradle里面添加下面代码,否则就会报找不到so文件的错误。
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
网友评论
在studio 3.0 以上的版本 我构建项目有问题
/Users/conan/AndroidStudioProjects/studyOpenSource/JniEx/app/src/main/cpp/bitherlibjni.cpp
Error:(75) undefined reference to 'jpeg_std_error'
Error:(82) undefined reference to 'jpeg_CreateCompress'
Error:(89) undefined reference to 'jpeg_stdio_dest'
Error:(111) undefined reference to 'jpeg_set_defaults'
Error:(115) undefined reference to 'jpeg_set_quality'
Error:(129) undefined reference to 'jpeg_write_scanlines'
Error:(131) undefined reference to 'jpeg_finish_compress'
Error:(132) undefined reference to 'jpeg_destroy_compress'
Error:(60) undefined reference to '__android_log_print'
Error:(160) undefined reference to 'AndroidBitmap_lockPixels'
Error:(190) undefined reference to 'AndroidBitmap_unlockPixels'
Error:error: linker command failed with exit code 1 (use -v to see invocation)
不知道你遇见过没
12-14 17:04:45.713 4296-8380/com.libjpegcompress W/ExifInterface: Skip the tag entry since tag number is not defined: 2
12-14 17:04:45.851 4296-4296/com.libjpegcompress W/System.err: java.lang.RuntimeException
12-14 17:04:45.851 4296-4296/com.libjpegcompress W/System.err: at me.xiaosai.imagecompress.ImageCompress$1.run(ImageCompress.java:54)
12-14 17:04:45.851 4296-4296/com.libjpegcompress W/System.err: at java.lang.Thread.run(Thread.java:764)
12-14 17:04:45.851 4296-4296/com.libjpegcompress E/compress: onError
这是为什么?
内存溢出,如何解决
Process: com.bjzyhealth.user, PID: 11526
java.lang.UnsatisfiedLinkError: ********
couldn't find "libjpegbither.so"
一样是导入demo是可以运行的,加入自己的项目就是有问题的,能帮忙提供下解决问题的思路吗
感谢 肖赛Soaic 的分享
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.graphics.Bitmap.compress(android.graphics.Bitmap$CompressFormat, int, java.io.OutputStream)' on a null object reference
at net.bither.util.NativeUtil.compressBitmap(NativeUtil.java:107)
at com.libjpegcompress.activity.MainActivity$2.run(MainActivity.java:70)
运行demo时报这个错,知道什么原因吗
大小:48.000KB-------101.000KB
哈哈哈,和我以前犯得错一样
1)E/BitmapFactory: Unable to decode stream: java.io.FileNotFoundException: /storage/emulated/0/picture_old.jpg: open failed: EACCES (Permission denied)
2) E/AndroidRuntime: FATAL EXCEPTION: Thread-313
Process: com.kingiscl.picturescaling, PID: 6126
Theme: themes:{}
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.graphics.Bitmap.compress(android.graphics.Bitmap$CompressFormat, int, java.io.OutputStream)' on a null object reference
at com.kingiscl.util.NativeUtil.compressBitmap(NativeUtil.java:97)
at com.kingiscl.picturescaling.MainActivity$2.run(MainActivity.java:59)
(1)build.gradle我加了你说的语句
(2)AndroidManifest,我加了如下的语句
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
希望你帮我解答下
**** Build of configuration Default for project SelectPhoto ****
C:\Users\qunqun\AppData\Local\Android\Sdk\ndk-bundle\ndk-build.cmd all
Android NDK: WARNING: APP_PLATFORM android-19 is larger than android:minSdkVersion 8 in ./AndroidManifest.xml
Android NDK: ERROR:jni/Android.mk:jpegbither: LOCAL_SRC_FILES points to a missing file
Android NDK: Check that jni/libjpegbither.so exists or that its path is correct
C:/Users/qunqun/AppData/Local/Android/Sdk/ndk-bundle/build//../build/core/prebuilt-library.mk:45: *** Android NDK: Aborting . Stop.
**** Build Finished ****
我导入eclipse一builde就失败