Android中图片压缩方案详解及源码下载
Android中图片压缩方案详解及源码下载
图片的展示可以说在我们任何一个应用中都避免不了,可是大量的图片就会出现很多的问题,比如加载大图片或者多图时的OOM问题,可以移步到Android高效加载大图及多图避免程序OOM.还有一个问题就是图片的上传下载问题,往往我们都喜欢图片既清楚又占的内存小,也就是尽可能少的耗费我们的流量,这就是我今天所要讲述的问题:图片的压缩方案的详解。
1、质量压缩法
设置bitmapoptions属性,降低图片的质量,像素不会减少
第一个参数为需要压缩的bitmap图片对象,第二个参数为压缩后图片保存的位置
设置options属性0-100,来实现压缩。
privateBitmapcompressImage(Bitmapimage){ ByteArrayOutputStreambaos=newByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.JPEG,100,baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 intoptions=100; while(baos.toByteArray().length/1024>100){//循环判断如果压缩后图片是否大于100kb,大于继续压缩 baos.reset();//重置baos即清空baos image.compress(Bitmap.CompressFormat.JPEG,options,baos);//这里压缩options%,把压缩后的数据存放到baos中 options-=10;//每次都减少10 } ByteArrayInputStreamisBm=newByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中 Bitmapbitmap=BitmapFactory.decodeStream(isBm,null,null);//把ByteArrayInputStream数据生成图片 returnbitmap; }
质量压缩不会减少图片的像素。它是在保持像素不变的前提下改变图片的位深及透明度等,来达到压缩图片的目的。进过它压缩的图片文件大小会有改变,但是导入成bitmap后占得内存是不变的。因为要保持像素不变,所以它就无法无限压缩,到达一个值之后就不会继续变小了。显然这个方法并不适用于缩略图,其实也不适用于想通过压缩图片减少内存的适用,仅仅适用于想在保证图片质量的同时减少文件大小的情况而已。
2、采样率压缩法
privateBitmapgetimage(StringsrcPath){ BitmapFactory.OptionsnewOpts=newBitmapFactory.Options(); //开始读入图片,此时把options.inJustDecodeBounds设回true了 newOpts.inJustDecodeBounds=true; Bitmapbitmap=BitmapFactory.decodeFile(srcPath,newOpts);//此时返回bm为空 newOpts.inJustDecodeBounds=false; intw=newOpts.outWidth; inth=newOpts.outHeight; //现在主流手机比较多是1280*720分辨率,所以高和宽我们设置为 floathh=1280f;//这里设置高度为1280f floatww=720f;//这里设置宽度为720f //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 intbe=1;//be=1表示不缩放 if(w>h&&w>ww){//如果宽度大的话根据宽度固定大小缩放 be=(int)(newOpts.outWidth/ww); }elseif(whh){//如果高度高的话根据宽度固定大小缩放 be=(int)(newOpts.outHeight/hh); } if(be<=0) be=1; newOpts.inSampleSize=be;//设置缩放比例 //重新读入图片,注意此时已经把options.inJustDecodeBounds设回false了 bitmap=BitmapFactory.decodeFile(srcPath,newOpts); returncompressImage(bitmap);//压缩好比例大小后再进行质量压缩 }
这个方法的好处是大大的缩小了内存的使用,在读存储器上的图片时,如果不需要高清的效果,可以先只读取图片的边,通过宽和高设定好取样率后再加载图片,这样就不会过多的占用内存。
3、缩放法
通过缩放图片像素来减少图片占用内存大小。
方式一
publicstaticvoidcompressBitmapToFile(Bitmapbmp,Filefile){ //尺寸压缩倍数,值越大,图片尺寸越小 intratio=2; //压缩Bitmap到对应尺寸 Bitmapresult=Bitmap.createBitmap(bmp.getWidth()/ratio,bmp.getHeight()/ratio,Config.ARGB_8888); Canvascanvas=newCanvas(result); Rectrect=newRect(0,0,bmp.getWidth()/ratio,bmp.getHeight()/ratio); canvas.drawBitmap(bmp,null,rect,null); ByteArrayOutputStreambaos=newByteArrayOutputStream(); //把压缩后的数据存放到baos中 result.compress(Bitmap.CompressFormat.JPEG,100,baos); try{ FileOutputStreamfos=newFileOutputStream(file); fos.write(baos.toByteArray()); fos.flush(); fos.close(); }catch(Exceptione){ e.printStackTrace(); } }
方式二
ByteArrayOutputStreamout=newByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.JPEG,85,out); floatzoom=(float)Math.sqrt(size*1024/(float)out.toByteArray().length); Matrixmatrix=newMatrix(); matrix.setScale(zoom,zoom); Bitmapresult=Bitmap.createBitmap(image,0,0,image.getWidth(),image.getHeight(),matrix,true); out.reset(); result.compress(Bitmap.CompressFormat.JPEG,85,out); while(out.toByteArray().length>size*1024){ System.out.println(out.toByteArray().length); matrix.setScale(0.9f,0.9f); result=Bitmap.createBitmap(result,0,0,result.getWidth(),result.getHeight(),matrix,true); out.reset(); result.compress(Bitmap.CompressFormat.JPEG,85,out); }
缩放法其实很简单,设定好matrix,在createBitmap就可以了。但是我们并不知道缩放比例,而是要求了图片的最终大小。直接用大小的比例来做的话肯定是有问题的,用大小比例的开方来做会比较接近,但是还是有差距。但是只要再做一下微调应该就可以了,微调的话就是修改过的图片大小比最终大小还大的话,就进行0.8的压缩再比较,循环直到大小合适。这样就能得到合适大小的图片,而且也能比较保证质量。
4、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倍。
jstringJava_net_bither_util_NativeUtil_compressBitmap(JNIEnv*env, jobjectthiz,jobjectbitmapcolor,intw,inth,intquality, jbyteArrayfileNameStr,jbooleanoptimize){ AndroidBitmapInfoinfocolor; BYTE*pixelscolor; intret; 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); } BYTEr,g,b; data=NULL; data=malloc(w*h*3); tmpdata=data; intj=0,i=0; intcolor; for(i=0;i>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); intresultCode=generateJPEG(tmpdata,w,h,quality,fileName,optimize); free(tmpdata); if(resultCode==0){ jstringresult=(*env)->NewStringUTF(env,error); error=NULL; returnresult; } return(*env)->NewStringUTF(env,"1");//success }
5、质量压缩+采样率压缩+JNI调用libjpeg库压缩结合使用
首先通过尺寸压缩,压缩到手机常用的一个分辨率(1280*960微信好像是压缩到这个分辨率),然后我们要把图片压缩到一定大小以内(比如说200k),然后通过循环进行质量压缩来计算options需要设置为多少,最后调用JNI压缩。
计算缩放比
/** *计算缩放比 *@parambitWidth当前图片宽度 *@parambitHeight当前图片高度 *@returnint缩放比 */ publicstaticintgetRatioSize(intbitWidth,intbitHeight){ //图片最大分辨率 intimageHeight=1280; intimageWidth=960; //缩放比 intratio=1; //缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 if(bitWidth>bitHeight&&bitWidth>imageWidth){ //如果图片宽度比高度大,以宽度为基准 ratio=bitWidth/imageWidth; }elseif(bitWidthimageHeight){ //如果图片高度比宽度大,以高度为基准 ratio=bitHeight/imageHeight; } //最小比率为1 if(ratio<=0) ratio=1; returnratio; }
质量压缩+JNI压缩
/** *@Description:通过JNI图片压缩把Bitmap保存到指定目录 *@paramcurFilePath *当前图片文件地址 *@paramtargetFilePath *要保存的图片文件地址 */ publicstaticvoidcompressBitmap(StringcurFilePath,StringtargetFilePath){ //最大图片大小500KB intmaxSize=500; //根据地址获取bitmap Bitmapresult=getBitmapFromFile(curFilePath); ByteArrayOutputStreambaos=newByteArrayOutputStream(); //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 intquality=100; result.compress(Bitmap.CompressFormat.JPEG,quality,baos); //循环判断如果压缩后图片是否大于500kb,大于继续压缩 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(); } }
JNI图片压缩工具类
packagenet.bither.util; importandroid.graphics.Bitmap; importandroid.graphics.Bitmap.Config; importandroid.graphics.BitmapFactory; importandroid.graphics.Canvas; importandroid.graphics.Matrix; importandroid.graphics.Rect; importandroid.media.ExifInterface; importjava.io.ByteArrayOutputStream; importjava.io.File; importjava.io.FileInputStream; importjava.io.FileNotFoundException; importjava.io.IOException; /** *JNI图片压缩工具类 * *@DescriptionTODO *@Packagenet.bither.util *@ClassNativeUtil */ publicclassNativeUtil{ privatestaticintDEFAULT_QUALITY=95; /** *@Description:JNI基本压缩 *@parambit *bitmap对象 *@paramfileName *指定保存目录名 *@paramoptimize *是否采用哈弗曼表数据计算品质相差5-10倍 */ publicstaticvoidcompressBitmap(Bitmapbit,StringfileName,booleanoptimize){ saveBitmap(bit,DEFAULT_QUALITY,fileName,optimize); } /** *@Description:通过JNI图片压缩把Bitmap保存到指定目录 *@paramimage *bitmap对象 *@paramfilePath *要保存的指定目录 */ publicstaticvoidcompressBitmap(Bitmapimage,StringfilePath){ //最大图片大小150KB intmaxSize=150; //获取尺寸压缩倍数 intratio=NativeUtil.getRatioSize(image.getWidth(),image.getHeight()); //压缩Bitmap到对应尺寸 Bitmapresult=Bitmap.createBitmap(image.getWidth()/ratio,image.getHeight()/ratio,Config.ARGB_8888); Canvascanvas=newCanvas(result); Rectrect=newRect(0,0,image.getWidth()/ratio,image.getHeight()/ratio); canvas.drawBitmap(image,null,rect,null); ByteArrayOutputStreambaos=newByteArrayOutputStream(); //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 intoptions=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保存到指定目录 *@paramcurFilePath *当前图片文件地址 *@paramtargetFilePath *要保存的图片文件地址 */ publicstaticvoidcompressBitmap(StringcurFilePath,StringtargetFilePath){ //最大图片大小500KB intmaxSize=500; //根据地址获取bitmap Bitmapresult=getBitmapFromFile(curFilePath); ByteArrayOutputStreambaos=newByteArrayOutputStream(); //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 intquality=100; result.compress(Bitmap.CompressFormat.JPEG,quality,baos); //循环判断如果压缩后图片是否大于500kb,大于继续压缩 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(); } } /** *计算缩放比 *@parambitWidth当前图片宽度 *@parambitHeight当前图片高度 *@returnint缩放比 */ publicstaticintgetRatioSize(intbitWidth,intbitHeight){ //图片最大分辨率 intimageHeight=1280; intimageWidth=960; //缩放比 intratio=1; //缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 if(bitWidth>bitHeight&&bitWidth>imageWidth){ //如果图片宽度比高度大,以宽度为基准 ratio=bitWidth/imageWidth; }elseif(bitWidthimageHeight){ //如果图片高度比宽度大,以高度为基准 ratio=bitHeight/imageHeight; } //最小比率为1 if(ratio<=0) ratio=1; returnratio; } /** *通过文件路径读获取Bitmap防止OOM以及解决图片旋转问题 *@paramfilePath *@return */ publicstaticBitmapgetBitmapFromFile(StringfilePath){ BitmapFactory.OptionsnewOpts=newBitmapFactory.Options(); newOpts.inJustDecodeBounds=true;//只读边,不读内容 BitmapFactory.decodeFile(filePath,newOpts); intw=newOpts.outWidth; inth=newOpts.outHeight; //获取尺寸压缩倍数 newOpts.inSampleSize=NativeUtil.getRatioSize(w,h); newOpts.inJustDecodeBounds=false;//读取所有内容 newOpts.inDither=false; newOpts.inPurgeable=true; newOpts.inInputShareable=true; newOpts.inTempStorage=newbyte[32*1024]; Bitmapbitmap=null; Filefile=newFile(filePath); FileInputStreamfs=null; try{ fs=newFileInputStream(file); }catch(FileNotFoundExceptione){ e.printStackTrace(); } try{ if(fs!=null){ bitmap=BitmapFactory.decodeFileDescriptor(fs.getFD(),null,newOpts); //旋转图片 intphotoDegree=readPictureDegree(filePath); if(photoDegree!=0){ Matrixmatrix=newMatrix(); matrix.postRotate(photoDegree); //创建新的图片 bitmap=Bitmap.createBitmap(bitmap,0,0, bitmap.getWidth(),bitmap.getHeight(),matrix,true); } } }catch(IOExceptione){ e.printStackTrace(); }finally{ if(fs!=null){ try{ fs.close(); }catch(IOExceptione){ e.printStackTrace(); } } } returnbitmap; } /** * *读取图片属性:旋转的角度 *@parampath图片绝对路径 *@returndegree旋转的角度 */ publicstaticintreadPictureDegree(Stringpath){ intdegree=0; try{ ExifInterfaceexifInterface=newExifInterface(path); intorientation=exifInterface.getAttributeInt( ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); switch(orientation){ caseExifInterface.ORIENTATION_ROTATE_90: degree=90; break; caseExifInterface.ORIENTATION_ROTATE_180: degree=180; break; caseExifInterface.ORIENTATION_ROTATE_270: degree=270; break; } }catch(IOExceptione){ e.printStackTrace(); } returndegree; } /** *调用native方法 *@Description:函数描述 *@parambit *@paramquality *@paramfileName *@paramoptimize */ privatestaticvoidsaveBitmap(Bitmapbit,intquality,StringfileName,booleanoptimize){ compressBitmap(bit,bit.getWidth(),bit.getHeight(),quality,fileName.getBytes(),optimize); } /** *调用底层bitherlibjni.c中的方法 *@Description:函数描述 *@parambit *@paramw *@paramh *@paramquality *@paramfileNameBytes *@paramoptimize *@return */ privatestaticnativeStringcompressBitmap(Bitmapbit,intw,inth,intquality,byte[]fileNameBytes, booleanoptimize); /** *加载lib下两个so文件 */ static{ System.loadLibrary("jpegbither"); System.loadLibrary("bitherjni"); } }
图片压缩处理中可能遇到的问题:
请求系统相册有三个Action
注意:图库(缩略图)和图片(原图)
ACTION_OPEN_DOCUMENT仅限4.4或以上使用默认打开原图
从图片获取到的uri格式为:content://com.android.providers.media.documents/document/image%666>>>
ACTION_GET_CONTENT4.4以下默认打开缩略图。以上打开文件管理器供选择,选择图库打开为缩略图页面,选择图片打开为原图浏览。
从图库获取到的uri格式为:content://media/external/images/media/666666
ACTION_PICK都可用,打开默认是缩略图界面,还需要进一步点开查看。
参考代码:
publicvoidpickFromGallery(){ if(Build.VERSION.SDK_INT根据URI获取对应的文件路径
在我们从图库中选择图片后回调给我们的data.getData()可能是URI,我们平时对文件的操作基本上都是基于路径然后进行各种操作与转换,如今我们需要将URI对应的文件路径找出来,具体参考代码如下:
publicstaticStringgetPathByUri(Contextcontext,Uridata){ if(Build.VERSION.SDK_INT=Build.VERSION_CODES.KITKAT; //DocumentProvider if(isKitKat&&DocumentsContract.isDocumentUri(context,uri)){ if(isExternalStorageDocument(uri)){//ExternalStorageProvider finalStringdocId=DocumentsContract.getDocumentId(uri); finalString[]split=docId.split(":"); finalStringtype=split[0]; if("primary".equalsIgnoreCase(type)){ returnEnvironment.getExternalStorageDirectory()+"/"+split[1]; } }elseif(isDownloadsDocument(uri)){//DownloadsProvider finalStringid=DocumentsContract.getDocumentId(uri); finalUricontentUri=ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); returngetDataColumn(context,contentUri,null,null); }elseif(isMediaDocument(uri)){//MediaProvider finalStringdocId=DocumentsContract.getDocumentId(uri); finalString[]split=docId.split(":"); finalStringtype=split[0]; UricontentUri=null; if("image".equals(type)){ contentUri=MediaStore.Images.Media.EXTERNAL_CONTENT_URI; }elseif("video".equals(type)){ contentUri=MediaStore.Video.Media.EXTERNAL_CONTENT_URI; }elseif("audio".equals(type)){ contentUri=MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } finalStringselection="_id=?"; finalString[]selectionArgs=newString[]{split[1]}; returngetDataColumn(context,contentUri,selection,selectionArgs); } }elseif("content".equalsIgnoreCase(uri.getScheme())){//MediaStore //(and //general) returngetDataColumn(context,uri,null,null); }elseif("file".equalsIgnoreCase(uri.getScheme())){//File returnuri.getPath(); } returnnull; } /** *GetthevalueofthedatacolumnforthisUri.Thisisusefulfor *MediaStoreUris,andotherfile-basedContentProviders. * *@paramcontext *Thecontext. *@paramuri *TheUritoquery. *@paramselection *(Optional)Filterusedinthequery. *@paramselectionArgs *(Optional)Selectionargumentsusedinthequery. *@returnThevalueofthe_datacolumn,whichistypicallyafilepath. */ publicstaticStringgetDataColumn(Contextcontext,Uriuri,Stringselection,String[]selectionArgs){ Cursorcursor=null; finalStringcolumn="_data"; finalString[]projection={column}; try{ cursor=context.getContentResolver().query(uri,projection,selection,selectionArgs,null); if(cursor!=null&&cursor.moveToFirst()){ finalintcolumn_index=cursor.getColumnIndexOrThrow(column); returncursor.getString(column_index); } }finally{ if(cursor!=null) cursor.close(); } returnnull; } /** *@paramuri *TheUritocheck. *@returnWhethertheUriauthorityisExternalStorageProvider. */ publicstaticbooleanisExternalStorageDocument(Uriuri){ return"com.android.externalstorage.documents".equals(uri.getAuthority()); } /** *@paramuri *TheUritocheck. *@returnWhethertheUriauthorityisDownloadsProvider. */ publicstaticbooleanisDownloadsDocument(Uriuri){ return"com.android.providers.downloads.documents".equals(uri.getAuthority()); } /** *@paramuri *TheUritocheck. *@returnWhethertheUriauthorityisMediaProvider. */ publicstaticbooleanisMediaDocument(Uriuri){ return"com.android.providers.media.documents".equals(uri.getAuthority()); } 源码奉上,自行参考:源码下载